From 0993f3180ba6b81a6d41864daae0eed684e40969 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sun, 14 Nov 2021 14:18:48 +0800 Subject: [PATCH] show icon image under system topbar, fix bottom sheet UI after rotation --- .../card_locker/LoyaltyCardViewActivity.java | 161 +++++++++--------- .../res/layout/loyalty_card_view_layout.xml | 153 ++++++++--------- .../LoyaltyCardViewActivityTest.java | 9 +- 3 files changed, 159 insertions(+), 164 deletions(-) diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java index 058d183eb..b85af6b9b 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java @@ -5,11 +5,11 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.Outline; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; -import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.GestureDetector; @@ -18,13 +18,13 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; @@ -46,8 +46,10 @@ import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.Guideline; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.widget.NestedScrollView; import androidx.core.widget.TextViewCompat; import protect.card_locker.async.TaskHandler; import protect.card_locker.preferences.Settings; @@ -59,8 +61,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements TextView cardIdFieldView; BottomSheetBehavior behavior; - View bottomSheet; - View bottomSheetContentWrapper; + LinearLayout bottomSheet; + NestedScrollView bottomSheetContentWrapper; ImageView bottomSheetButton; TextView noteView; TextView groupsView; @@ -74,7 +76,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements View collapsingToolbarLayout; AppBarLayout appBarLayout; ImageView iconImage; - RelativeLayout relativeLayout; Toolbar landscapeToolbar; int loyaltyCardId; @@ -104,10 +105,13 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements private ImageView[] dots; boolean isBarcodeSupported = true; + int bottomSheetState; + static final String STATE_IMAGEINDEX = "imageIndex"; static final String STATE_FULLSCREEN = "isFullscreen"; + static final String STATE_BOTTOMSHEET = "bottomSheetState"; - private final int HEADER_FILTER_ALPHA = 70; + private final int HEADER_FILTER_ALPHA = 127; final private TaskHandler mTasks = new TaskHandler(); @@ -241,6 +245,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements if (savedInstanceState != null) { mainImageIndex = savedInstanceState.getInt(STATE_IMAGEINDEX); isFullscreen = savedInstanceState.getBoolean(STATE_FULLSCREEN); + bottomSheetState = savedInstanceState.getInt(STATE_BOTTOMSHEET); } settings = new Settings(this); @@ -268,7 +273,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout); appBarLayout = findViewById(R.id.app_bar_layout); iconImage = findViewById(R.id.icon_image); - relativeLayout = findViewById(R.id.relative_layout); landscapeToolbar = findViewById(R.id.toolbar_landscape); centerGuideline = findViewById(R.id.centerGuideline); @@ -329,20 +333,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { - if (newState == BottomSheetBehavior.STATE_DRAGGING) { - editButton.hide(); - } else if (newState == BottomSheetBehavior.STATE_EXPANDED) { - bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_down_24); - editButton.hide(); - } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) { - bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24); - if (!isFullscreen) { - editButton.show(); - } - - // Scroll bottomsheet content back to top - bottomSheetContentWrapper.setScrollY(0); - } + changeUiToBottomSheetState(newState); } @Override @@ -358,56 +349,18 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements } }); - // Fix bottom sheet content sizing - ViewTreeObserver viewTreeObserver = bottomSheet.getViewTreeObserver(); - viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + appBarLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override - public void onGlobalLayout() { - Log.d("onGlobalLayout", "checking if bottom sheet size has to be changed"); - DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); - int height = displayMetrics.heightPixels; - int maxHeight = height - appBarLayout.getHeight() - bottomSheetButton.getHeight(); - Log.d(TAG, "Button sheet should be " + maxHeight + " pixels high"); - // setting it to the same value triggers this callback again on android 11, but it has to stay for when the activity starts fullscreen - // so check for if changes are needed instead of throwing the callback away - ViewGroup.LayoutParams params = bottomSheetContentWrapper.getLayoutParams(); - if (params.height != maxHeight || params.width != LinearLayout.LayoutParams.MATCH_PARENT) { - Log.d("onGlobalLayout", "setting max height: " + maxHeight); - bottomSheetContentWrapper.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - maxHeight - ) - ); - } + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + adjustLayoutHeights(); } }); - // synchronizing iconImage size with it's layer above - collapsingToolbarLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + appBarLayout.setOutlineProvider(new ViewOutlineProvider() { @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - Log.d("onLayoutChange", "collapsing toolbar change, height: " + v.getHeight()); - // on android 11, setAdjustViewBounds on the imageView triggers this callback again to my horror, so adding an if to block it - if(iconImage.getHeight() != v.getHeight()) { - // setAdjustViewBounds and scale type breaks when an image is set, then the following has to be execute in this order - iconImage.setAdjustViewBounds(true); - iconImage.setScaleType(ImageView.ScaleType.CENTER_CROP); - iconImage.setMaxHeight(v.getHeight()); - iconImage.setMaxWidth(v.getWidth()); - } - } - }); - landscapeToolbar.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - Log.d("onLayoutChange", "landscape toolbar change, height: " + v.getHeight()); - if(iconImage.getHeight() != v.getHeight()) { - iconImage.setAdjustViewBounds(true); - iconImage.setScaleType(ImageView.ScaleType.CENTER_CROP); - iconImage.setMaxHeight(v.getHeight()); - iconImage.setMaxWidth(v.getWidth()); - } + public void getOutline(View view, Outline outline) { + ViewOutlineProvider.BACKGROUND.getOutline(view, outline); + outline.setAlpha(0f); } }); @@ -416,6 +369,57 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements mainImage.setOnTouchListener(gestureTouchListener); } + private void changeUiToBottomSheetState(int newState){ + if (newState == BottomSheetBehavior.STATE_DRAGGING) { + editButton.hide(); + } else if (newState == BottomSheetBehavior.STATE_EXPANDED) { + bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_down_24); + editButton.hide(); + } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) { + bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24); + if (!isFullscreen) { + editButton.show(); + } + + // Scroll bottomsheet content back to top + bottomSheetContentWrapper.setScrollY(0); + } + bottomSheetState = newState; + } + + private void adjustLayoutHeights(){ + // use getLayoutParams instead of getHeight when heights are pre-determined in xml! getHeight could return 0 if a View is not inflated + if(iconImage.getLayoutParams().height != appBarLayout.getHeight()) { + Log.d("adjustLayoutHeights", "setting imageIcon height from: " + iconImage.getLayoutParams().height + " to: " + appBarLayout.getHeight()); + iconImage.setLayoutParams(new CoordinatorLayout.LayoutParams( + CoordinatorLayout.LayoutParams.MATCH_PARENT, appBarLayout.getHeight()) + ); + } + int bottomSheetHeight = getResources().getDisplayMetrics().heightPixels - appBarLayout.getHeight() - bottomSheetButton.getLayoutParams().height; + ViewGroup.LayoutParams params = bottomSheetContentWrapper.getLayoutParams(); + if (params.height != bottomSheetHeight || params.width != LinearLayout.LayoutParams.MATCH_PARENT) { + // XXX android 5 - 9 has so much quirks with setting bottomSheetContent height + // just invalidate the wrapper works on 10 onward + // bottomSheetContentWrapper.invalidate(); + // The below worked on android 5 but not 6, reloading the card then it breaks again on 6, entirely random :( + // for (int i = 0; i < bottomSheetContentWrapper.getChildCount(); i++) { + // bottomSheetContentWrapper.getChildAt(i).invalidate(); + // } + // since it's basically allergic to getting enlarged then shrunk again, and setting it at all when fullscreen makes no sense + if(!isFullscreen){ + Log.d("adjustLayoutHeights", "setting bottomSheet height from: " + params.height + " to: " + bottomSheetHeight); + bottomSheetContentWrapper.setLayoutParams( + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, bottomSheetHeight) + ); + } + } + // XXX + // from android 5 - 9 a padding magically grows on top of bottomSheet after leaving fullscreen + // I suspect that it's a bug with going into immersive mode then out, along with coordinator layout bottom sheet + Log.d("adjustLayoutHeights", "padding top: " + bottomSheet.getPaddingTop() + " padding bottom: " + bottomSheet.getPaddingBottom()); + } + + @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -428,6 +432,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putInt(STATE_IMAGEINDEX, mainImageIndex); savedInstanceState.putBoolean(STATE_FULLSCREEN, isFullscreen); + savedInstanceState.putInt(STATE_BOTTOMSHEET, bottomSheetState); super.onSaveInstanceState(savedInstanceState); } @@ -546,8 +551,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements backgroundHeaderColor = LetterBitmap.getDefaultColor(this, loyaltyCard.store); } - appBarLayout.setBackgroundColor(backgroundHeaderColor); - int textColor; if (Utils.needsDarkForeground(backgroundHeaderColor)) { textColor = Color.BLACK; @@ -555,7 +558,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements textColor = Color.WHITE; } storeName.setTextColor(textColor); - ((Toolbar) findViewById(R.id.toolbar_landscape)).setTitleTextColor(textColor); + landscapeToolbar.setTitleTextColor(textColor); Bitmap icon = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.icon); if (icon != null){ @@ -563,16 +566,12 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements Log.d("onResume", "setting icon image"); iconImage.setImageBitmap(icon); int backgroundWithAlpha = Color.argb(HEADER_FILTER_ALPHA, Color.red(backgroundAlphaColor), Color.green(backgroundAlphaColor), Color.blue(backgroundAlphaColor)); - collapsingToolbarLayout.setBackgroundColor(backgroundWithAlpha); - landscapeToolbar.setBackgroundColor(backgroundWithAlpha); // for images that has alpha - iconImage.setBackgroundColor(backgroundWithAlpha); + appBarLayout.setBackgroundColor(backgroundWithAlpha); }else{ Bitmap plain = Bitmap.createBitmap(new int[]{backgroundHeaderColor}, 1, 1, Bitmap.Config.ARGB_8888); iconImage.setImageBitmap(plain); - collapsingToolbarLayout.setBackgroundColor(Color.TRANSPARENT); - landscapeToolbar.setBackgroundColor(Color.TRANSPARENT); - iconImage.setBackgroundColor(Color.TRANSPARENT); + appBarLayout.setBackgroundColor(Color.TRANSPARENT); } // If the background is very bright, we should use dark icons @@ -635,6 +634,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements setFullscreen(isFullscreen); + // restore bottomSheet UI states from changing orientation + changeUiToBottomSheetState(bottomSheetState); + db.updateLoyaltyCardLastUsed(loyaltyCard.id); } @@ -721,7 +723,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements private void setupOrientation() { Toolbar portraitToolbar = findViewById(R.id.toolbar); - Toolbar landscapeToolbar = findViewById(R.id.toolbar_landscape); int orientation = getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { @@ -888,7 +889,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements // Or the barcode will be centered instead of on top of the screen // Don't ask me why... appBarLayout.setVisibility(View.INVISIBLE); - relativeLayout.setVisibility(View.GONE); + collapsingToolbarLayout.setVisibility(View.GONE); + landscapeToolbar.setVisibility(View.GONE); // Hide other UI elements cardIdFieldView.setVisibility(View.GONE); @@ -896,6 +898,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements behavior.setState(BottomSheetBehavior.STATE_COLLAPSED); editButton.hide(); + // Set Android to fullscreen mode getWindow().getDecorView().setSystemUiVisibility( getWindow().getDecorView().getSystemUiVisibility() @@ -924,9 +927,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements // Show appropriate toolbar // And restore 24dp paddingTop for appBarLayout appBarLayout.setVisibility(View.VISIBLE); - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - relativeLayout.setVisibility(View.VISIBLE); + setupOrientation(); // Show other UI elements cardIdFieldView.setVisibility(View.VISIBLE); diff --git a/app/src/main/res/layout/loyalty_card_view_layout.xml b/app/src/main/res/layout/loyalty_card_view_layout.xml index 368cefcb3..5f8e06754 100644 --- a/app/src/main/res/layout/loyalty_card_view_layout.xml +++ b/app/src/main/res/layout/loyalty_card_view_layout.xml @@ -155,8 +155,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/inputBackground" - android:fitsSystemWindows="false" android:orientation="vertical" + android:paddingTop="0px" android:visibility="gone" app:behavior_hideable="false" app:behavior_peekHeight="80dp" @@ -168,10 +168,10 @@ android:layout_width="match_parent" android:layout_height="80dp" android:layout_gravity="top|start" - android:scaleType="fitCenter" android:contentDescription="@string/toggleMoreInfo" - app:tint="#ffffff" - app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24" /> + android:scaleType="fitCenter" + app:srcCompat="@drawable/ic_baseline_arrow_drop_up_24" + app:tint="#ffffff" /> + + > - + + + android:clipChildren="false" + android:clipToPadding="false" + android:minHeight="56.0dip" + app:contentScrim="?colorPrimary" + app:expandedTitleGravity="top" + app:expandedTitleMarginEnd="64dp" + app:expandedTitleMarginStart="48dp"> - + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginTop="?actionBarSize" + android:layout_marginBottom="?actionBarSize" + android:ellipsize="end" + android:maxLines="1" + android:textAlignment="center" + android:textColor="@android:color/white" + android:textSize="40sp" + app:layout_collapseMode="parallax" /> - - - - - - - - - + + + + + diff --git a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java index 0c1c93f67..76fcbc8f4 100644 --- a/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java +++ b/app/src/test/java/protect/card_locker/LoyaltyCardViewActivityTest.java @@ -1274,7 +1274,6 @@ public class LoyaltyCardViewActivityTest ImageView mainImage = activity.findViewById(R.id.mainImage); View collapsingToolbarLayout = activity.findViewById(R.id.collapsingToolbarLayout); - View relativeLayout = activity.findViewById(R.id.relative_layout); View bottomSheet = activity.findViewById(R.id.bottom_sheet); ImageButton maximizeButton = activity.findViewById(R.id.maximizeButton); ImageButton minimizeButton = activity.findViewById(R.id.minimizeButton); @@ -1306,7 +1305,7 @@ public class LoyaltyCardViewActivityTest assertEquals(uiOptions | View.SYSTEM_UI_FLAG_FULLSCREEN, uiOptions); // Elements should not be visible (except minimize button and scaler) - assertEquals(View.GONE, relativeLayout.getVisibility()); + assertEquals(View.GONE, collapsingToolbarLayout.getVisibility()); assertEquals(View.GONE, bottomSheet.getVisibility()); assertEquals(View.GONE, maximizeButton.getVisibility()); assertEquals(View.VISIBLE, minimizeButton.getVisibility()); @@ -1320,7 +1319,7 @@ public class LoyaltyCardViewActivityTest uiOptions = activity.getWindow().getDecorView().getSystemUiVisibility(); assertNotEquals(uiOptions | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY, uiOptions); assertNotEquals(uiOptions | View.SYSTEM_UI_FLAG_FULLSCREEN, uiOptions); - assertEquals(View.VISIBLE, relativeLayout.getVisibility()); + assertEquals(View.VISIBLE, collapsingToolbarLayout.getVisibility()); assertEquals(View.VISIBLE, bottomSheet.getVisibility()); assertEquals(View.VISIBLE, maximizeButton.getVisibility()); assertEquals(View.GONE, minimizeButton.getVisibility()); @@ -1334,7 +1333,7 @@ public class LoyaltyCardViewActivityTest uiOptions = activity.getWindow().getDecorView().getSystemUiVisibility(); assertEquals(uiOptions | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY, uiOptions); assertEquals(uiOptions | View.SYSTEM_UI_FLAG_FULLSCREEN, uiOptions); - assertEquals(View.GONE, relativeLayout.getVisibility()); + assertEquals(View.GONE, collapsingToolbarLayout.getVisibility()); assertEquals(View.GONE, bottomSheet.getVisibility()); assertEquals(View.GONE, maximizeButton.getVisibility()); assertEquals(View.VISIBLE, minimizeButton.getVisibility()); @@ -1348,7 +1347,7 @@ public class LoyaltyCardViewActivityTest uiOptions = activity.getWindow().getDecorView().getSystemUiVisibility(); assertNotEquals(uiOptions | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY, uiOptions); assertNotEquals(uiOptions | View.SYSTEM_UI_FLAG_FULLSCREEN, uiOptions); - assertEquals(View.VISIBLE, relativeLayout.getVisibility()); + assertEquals(View.VISIBLE, collapsingToolbarLayout.getVisibility()); assertEquals(View.VISIBLE, bottomSheet.getVisibility()); assertEquals(View.VISIBLE, maximizeButton.getVisibility()); assertEquals(View.GONE, minimizeButton.getVisibility());