package protect.card_locker; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.text.util.Linkify; import android.util.Log; import android.util.TypedValue; import android.view.GestureDetector; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; 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.SeekBar; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Guideline; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.graphics.BlendModeColorFilterCompat; import androidx.core.graphics.BlendModeCompat; import androidx.core.graphics.ColorUtils; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.widget.TextViewCompat; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.bottomappbar.BottomAppBar; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import protect.card_locker.async.TaskHandler; import protect.card_locker.preferences.Settings; public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements GestureDetector.OnGestureListener { private static final String TAG = "Catima"; private GestureDetector mGestureDetector; CoordinatorLayout coordinatorLayout; ConstraintLayout mainLayout; TextView cardIdFieldView; BottomAppBar bottomAppBar; ImageButton bottomAppBarInfoButton; ImageButton bottomAppBarPreviousButton; ImageButton bottomAppBarNextButton; AppCompatTextView storeName; ImageButton maximizeButton; ImageView mainImage; LinearLayout dotIndicator; ImageButton minimizeButton; View collapsingToolbarLayout; AppBarLayout appBarLayout; ImageView iconImage; Toolbar portraitToolbar; Toolbar landscapeToolbar; int loyaltyCardId; ArrayList cardList; LoyaltyCard loyaltyCard; List loyaltyCardGroups; boolean rotationEnabled; SQLiteDatabase database; ImportURIHelper importURIHelper; Settings settings; String cardIdString; String barcodeIdString; CatimaBarcode format; FloatingActionButton editButton; Guideline centerGuideline; SeekBar barcodeScaler; Bitmap frontImageBitmap; Bitmap backImageBitmap; boolean starred; boolean backgroundNeedsDarkIcons; boolean isFullscreen = false; int mainImageIndex = 0; List imageTypes; private ImageView[] dots; boolean isBarcodeSupported = true; static final String STATE_IMAGEINDEX = "imageIndex"; static final String STATE_FULLSCREEN = "isFullscreen"; private final int HEADER_FILTER_ALPHA = 127; final private TaskHandler mTasks = new TaskHandler(); Runnable barcodeImageGenerationFinishedCallback; @Override public boolean onDown(MotionEvent e) { return true; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { if (imageTypes.size() > 1) { Toast.makeText(this, getString(R.string.swipeToSwitchImages), Toast.LENGTH_SHORT).show(); } return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onLongPress(MotionEvent e) { // Also switch on long-press for accessibility setMainImage(true, true); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { Log.d(TAG, "On fling"); if (Math.abs(velocityY) > (0.75 * Math.abs(velocityX))) { // Vertical swipe // Swipe up if (velocityY < -150) { if (!isFullscreen) { setFullscreen(true); } return false; } // Swipe down if (velocityY > 150) { if (isFullscreen) { setFullscreen(false); } return false; } } else if (Math.abs(velocityX) > (0.75 * Math.abs(velocityY))) { // Horizontal swipe // Swipe right if (velocityX < -150) { setMainImage(true, false); return false; } // Swipe left if (velocityX > 150) { setMainImage(false, false); return false; } } if (imageTypes.size() > 1) { Toast.makeText(this, getString(R.string.swipeToSwitchImages), Toast.LENGTH_SHORT).show(); } return true; } enum ImageType { NONE, BARCODE, IMAGE_FRONT, IMAGE_BACK } private void extractIntentFields(Intent intent) { final Bundle b = intent.getExtras(); loyaltyCardId = b != null ? b.getInt("id") : 0; cardList = b != null ? b.getIntegerArrayList("cardList") : null; Log.d(TAG, "View activity: id=" + loyaltyCardId); } private Drawable getDotIcon(boolean active, boolean darkMode) { Drawable unwrappedIcon = AppCompatResources.getDrawable(this, active ? R.drawable.active_dot : R.drawable.inactive_dot); assert unwrappedIcon != null; Drawable wrappedIcon = DrawableCompat.wrap(unwrappedIcon); if (darkMode) { DrawableCompat.setTint(wrappedIcon, Color.WHITE); } else { DrawableCompat.setTint(wrappedIcon, Color.BLACK); } return wrappedIcon; } private Drawable getIcon(int icon, boolean dark) { Drawable unwrappedIcon = AppCompatResources.getDrawable(this, icon); assert unwrappedIcon != null; Drawable wrappedIcon = DrawableCompat.wrap(unwrappedIcon); wrappedIcon.mutate(); if (dark) { DrawableCompat.setTint(wrappedIcon, Color.BLACK); } else { DrawableCompat.setTintList(wrappedIcon, null); } return wrappedIcon; } private void setCenterGuideline(int zoomLevel) { float scale = zoomLevel / 100f; if (format != null && format.isSquare()) { centerGuideline.setGuidelinePercent(0.75f * scale); } else { centerGuideline.setGuidelinePercent(0.5f * scale); } } @Override protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState == null) { Bundle incomingIntentExtras = getIntent().getExtras(); if (incomingIntentExtras == null) { Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show(); finish(); return; } int transitionRight = incomingIntentExtras.getInt("transition_right", -1); if (transitionRight == 1) { // right side transition overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left); } else if (transitionRight == 0) { // left side transition overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right); } } super.onCreate(savedInstanceState); settings = new Settings(this); String cardOrientation = settings.getCardViewOrientation(); if (cardOrientation.equals(getString(R.string.settings_key_lock_on_opening_orientation))) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); } else if (cardOrientation.equals(getString(R.string.settings_key_portrait_orientation))) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } else if (cardOrientation.equals(getString(R.string.settings_key_landscape_orientation))) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } if (savedInstanceState != null) { mainImageIndex = savedInstanceState.getInt(STATE_IMAGEINDEX); isFullscreen = savedInstanceState.getBoolean(STATE_FULLSCREEN); } extractIntentFields(getIntent()); setContentView(R.layout.loyalty_card_view_layout); database = new DBHelper(this).getWritableDatabase(); importURIHelper = new ImportURIHelper(this); coordinatorLayout = findViewById(R.id.coordinator_layout); mainLayout = findViewById(R.id.mainLayout); cardIdFieldView = findViewById(R.id.cardIdView); storeName = findViewById(R.id.storeName); maximizeButton = findViewById(R.id.maximizeButton); mainImage = findViewById(R.id.mainImage); mainImage.setClipToOutline(true); dotIndicator = findViewById(R.id.dotIndicator); minimizeButton = findViewById(R.id.minimizeButton); collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout); appBarLayout = findViewById(R.id.app_bar_layout); bottomAppBar = findViewById(R.id.bottom_app_bar); iconImage = findViewById(R.id.icon_image); portraitToolbar = findViewById(R.id.toolbar); landscapeToolbar = findViewById(R.id.toolbar_landscape); bottomAppBarInfoButton = findViewById(R.id.button_show_info); bottomAppBarPreviousButton = findViewById(R.id.button_previous); bottomAppBarNextButton = findViewById(R.id.button_next); barcodeImageGenerationFinishedCallback = () -> { if (!(boolean) mainImage.getTag()) { mainImage.setVisibility(View.GONE); imageTypes.remove(ImageType.BARCODE); // Redraw UI setDotIndicator(Utils.isDarkModeEnabled(LoyaltyCardViewActivity.this)); setFullscreen(isFullscreen); Toast.makeText(LoyaltyCardViewActivity.this, getString(R.string.wrongValueForBarcodeType), Toast.LENGTH_LONG).show(); } }; centerGuideline = findViewById(R.id.centerGuideline); barcodeScaler = findViewById(R.id.barcodeScaler); barcodeScaler.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (!fromUser) { Log.d(TAG, "non user triggered onProgressChanged, ignoring, progress is " + progress); return; } Log.d(TAG, "Progress is " + progress); Log.d(TAG, "Max is " + barcodeScaler.getMax()); float scale = (float) progress / (float) barcodeScaler.getMax(); Log.d(TAG, "Scaling to " + scale); loyaltyCard.zoomLevel = progress; DBHelper.updateLoyaltyCardZoomLevel(database, loyaltyCardId, loyaltyCard.zoomLevel); setCenterGuideline(loyaltyCard.zoomLevel); drawMainImage(mainImageIndex, true, isFullscreen); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); rotationEnabled = true; // Allow making barcode fullscreen on tap maximizeButton.setOnClickListener(v -> setFullscreen(true)); minimizeButton.setOnClickListener(v -> setFullscreen(false)); editButton = findViewById(R.id.fabEdit); editButton.setOnClickListener(v -> { Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class); Bundle bundle = new Bundle(); bundle.putInt("id", loyaltyCardId); bundle.putBoolean("update", true); intent.putExtras(bundle); startActivity(intent); finish(); }); editButton.bringToFront(); appBarLayout.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { ViewOutlineProvider.BACKGROUND.getOutline(view, outline); outline.setAlpha(0f); } }); bottomAppBarInfoButton.setOnClickListener(view -> showInfoDialog()); bottomAppBarPreviousButton.setOnClickListener(view -> prevNextCard(false)); bottomAppBarNextButton.setOnClickListener(view -> prevNextCard(true)); mGestureDetector = new GestureDetector(this, this); View.OnTouchListener gestureTouchListener = (v, event) -> mGestureDetector.onTouchEvent(event); mainImage.setOnTouchListener(gestureTouchListener); appBarLayout.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) { iconImage.setLayoutParams(new CoordinatorLayout.LayoutParams( CoordinatorLayout.LayoutParams.MATCH_PARENT, appBarLayout.getHeight()) ); iconImage.setClipBounds(new Rect(left, top, right, bottom)); } }); } private SpannableStringBuilder padSpannableString(SpannableStringBuilder spannableStringBuilder) { if (spannableStringBuilder.length() > 0) { spannableStringBuilder.append("\n\n"); } return spannableStringBuilder; } private boolean hasBalance(LoyaltyCard loyaltyCard) { return !loyaltyCard.balance.equals(new BigDecimal(0)); } private void showInfoDialog() { AlertDialog.Builder infoDialog = new AlertDialog.Builder(this); TextView infoTitleView = new TextView(this); infoTitleView.setPadding(20, 20, 20, 20); infoTitleView.setTextSize(settings.getFontSizeMax(settings.getMediumFont())); infoTitleView.setText(loyaltyCard.store); infoDialog.setCustomTitle(infoTitleView); infoDialog.setTitle(loyaltyCard.store); TextView infoTextview = new TextView(this); infoTextview.setPadding(20, 0, 20, 0); infoTextview.setAutoLinkMask(Linkify.ALL); infoTextview.setTextIsSelectable(true); SpannableStringBuilder infoText = new SpannableStringBuilder(); if (!loyaltyCard.note.isEmpty()) { infoText.append(loyaltyCard.note); } if (loyaltyCardGroups.size() > 0) { List groupNames = new ArrayList<>(); for (Group group : loyaltyCardGroups) { groupNames.add(group._id); } padSpannableString(infoText); infoText.append(getString(R.string.groupsList, TextUtils.join(", ", groupNames))); } if (hasBalance(loyaltyCard)) { padSpannableString(infoText); infoText.append(getString(R.string.balanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType))); } if (loyaltyCard.expiry != null) { String formattedExpiry = DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry); padSpannableString(infoText); if (Utils.hasExpired(loyaltyCard.expiry)) { int start = infoText.length(); infoText.append(getString(R.string.expiryStateSentenceExpired, formattedExpiry)); infoText.setSpan(new ForegroundColorSpan(Color.RED), start, infoText.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); } else { infoText.append(getString(R.string.expiryStateSentence, formattedExpiry)); } } infoTextview.setText(infoText); infoDialog.setView(infoTextview); infoDialog.setPositiveButton(R.string.ok, (dialogInterface, i) -> dialogInterface.dismiss()); infoDialog.create().show(); } private void setBottomAppBarButtonState() { if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.expiry != null) { bottomAppBarInfoButton.setVisibility(View.VISIBLE); } else { bottomAppBarInfoButton.setVisibility(View.GONE); } if (cardList == null || cardList.size() == 1) { bottomAppBarPreviousButton.setVisibility(View.GONE); bottomAppBarNextButton.setVisibility(View.GONE); } else { bottomAppBarPreviousButton.setVisibility(View.VISIBLE); bottomAppBarNextButton.setVisibility(View.VISIBLE); } } private void prevNextCard(boolean next) { // If we're in RTL layout, we want the "left" button to be "next" instead of "previous" // So we swap next around boolean transitionRight = next; if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { next = !next; } int cardListPosition = cardList.indexOf(loyaltyCardId); if (next) { if (cardListPosition == cardList.size() - 1) { cardListPosition = 0; } else { cardListPosition = cardListPosition + 1; } } else { if (cardListPosition == 0) { cardListPosition = cardList.size() - 1; } else { cardListPosition = cardListPosition - 1; } } loyaltyCardId = cardList.get(cardListPosition); // Restart activity with new card id and index Intent intent = getIntent(); Bundle b = intent.getExtras(); b.putInt("id", loyaltyCardId); b.putInt("transition_right", transitionRight ? 1 : 0); intent.putExtras(b); intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.i(TAG, "Received new intent"); extractIntentFields(intent); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putInt(STATE_IMAGEINDEX, mainImageIndex); savedInstanceState.putBoolean(STATE_FULLSCREEN, isFullscreen); super.onSaveInstanceState(savedInstanceState); } @Override public void onResume() { super.onResume(); Log.i(TAG, "To view card: " + loyaltyCardId); // The brightness value is on a scale from [0, ..., 1], where // '1' is the brightest. We attempt to maximize the brightness // to help barcode readers scan the barcode. Window window = getWindow(); if (window != null) { WindowManager.LayoutParams attributes = window.getAttributes(); if (settings.useMaxBrightnessDisplayingBarcode()) { attributes.screenBrightness = 1F; } if (settings.getKeepScreenOn()) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } if (settings.getDisableLockscreenWhileViewingCard()) { window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); } window.setAttributes(attributes); } loyaltyCard = DBHelper.getLoyaltyCard(database, loyaltyCardId); if (loyaltyCard == null) { Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId); Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show(); finish(); return; } loyaltyCardGroups = DBHelper.getLoyaltyCardGroups(database, loyaltyCardId); setupOrientation(); format = loyaltyCard.barcodeType; cardIdString = loyaltyCard.cardId; barcodeIdString = loyaltyCard.barcodeId; cardIdFieldView.setText(loyaltyCard.cardId); TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(cardIdFieldView, settings.getFontSizeMin(settings.getLargeFont()), settings.getFontSizeMax(settings.getLargeFont()), 1, TypedValue.COMPLEX_UNIT_SP); storeName.setText(loyaltyCard.store); storeName.setTextSize(settings.getFontSizeMax(settings.getLargeFont())); TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration( storeName, settings.getFontSizeMin(settings.getLargeFont()), settings.getFontSizeMax(settings.getLargeFont()), 1, TypedValue.COMPLEX_UNIT_DIP); int backgroundHeaderColor; if (loyaltyCard.headerColor != null) { backgroundHeaderColor = loyaltyCard.headerColor; } else { backgroundHeaderColor = LetterBitmap.getDefaultColor(this, loyaltyCard.store); } int textColor; if (Utils.needsDarkForeground(backgroundHeaderColor)) { textColor = Color.BLACK; } else { textColor = Color.WHITE; } storeName.setTextColor(textColor); landscapeToolbar.setTitleTextColor(textColor); // Also apply colours to UI elements int darkenedColor = ColorUtils.blendARGB(backgroundHeaderColor, Color.BLACK, 0.1f); barcodeScaler.setProgressTintList(ColorStateList.valueOf(darkenedColor)); barcodeScaler.setThumbTintList(ColorStateList.valueOf(darkenedColor)); maximizeButton.setBackgroundColor(darkenedColor); minimizeButton.setBackgroundColor(darkenedColor); bottomAppBar.setBackgroundColor(darkenedColor); maximizeButton.setColorFilter(textColor); minimizeButton.setColorFilter(textColor); int complementaryColor = Utils.getComplementaryColor(darkenedColor); editButton.setBackgroundTintList(ColorStateList.valueOf(complementaryColor)); Drawable editButtonIcon = editButton.getDrawable(); editButtonIcon.mutate(); editButtonIcon.setTint(Utils.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE); editButton.setImageDrawable(editButtonIcon); Bitmap icon = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.icon); if (icon != null) { int backgroundAlphaColor = Utils.needsDarkForeground(backgroundHeaderColor) ? Color.WHITE : Color.BLACK; 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)); // for images that has alpha appBarLayout.setBackgroundColor(backgroundWithAlpha); } else { Bitmap plain = Bitmap.createBitmap(new int[]{backgroundHeaderColor}, 1, 1, Bitmap.Config.ARGB_8888); iconImage.setImageBitmap(plain); appBarLayout.setBackgroundColor(Color.TRANSPARENT); } // If the background is very bright, we should use dark icons backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setHomeAsUpIndicator(getIcon(R.drawable.home_arrow_back_white, backgroundNeedsDarkIcons)); } fixImageButtonColor(bottomAppBarInfoButton); fixImageButtonColor(bottomAppBarPreviousButton); fixImageButtonColor(bottomAppBarNextButton); setBottomAppBarButtonState(); // Make notification area light if dark icons are needed if (Build.VERSION.SDK_INT >= 23) { window.getDecorView().setSystemUiVisibility(backgroundNeedsDarkIcons ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0); window.setStatusBarColor(Color.TRANSPARENT); } else { // Darken statusbar if icons won't be visible otherwise window.setStatusBarColor(backgroundNeedsDarkIcons ? ColorUtils.blendARGB(backgroundHeaderColor, Color.BLACK, 0.15f) : Color.TRANSPARENT); } // Set shadow colour of store text so even same color on same color would be readable storeName.setShadowLayer(1, 1, 1, backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE); if (format != null && !format.isSupported()) { isBarcodeSupported = false; Toast.makeText(this, getString(R.string.unsupportedBarcodeType), Toast.LENGTH_LONG).show(); } else if (format == null) { isBarcodeSupported = false; } imageTypes = new ArrayList<>(); if (isBarcodeSupported) { imageTypes.add(ImageType.BARCODE); } frontImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.front); if (frontImageBitmap != null) { imageTypes.add(ImageType.IMAGE_FRONT); } backImageBitmap = Utils.retrieveCardImage(this, loyaltyCard.id, ImageLocationType.back); if (backImageBitmap != null) { imageTypes.add(ImageType.IMAGE_BACK); } setDotIndicator(Utils.isDarkModeEnabled(this)); setFullscreen(isFullscreen); DBHelper.updateLoyaltyCardLastUsed(database, loyaltyCard.id); } private void fixImageButtonColor(ImageButton imageButton) { imageButton.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE, BlendModeCompat.SRC_ATOP)); } @Override public void onBackPressed() { if (isFullscreen) { setFullscreen(false); return; } super.onBackPressed(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.card_view_menu, menu); starred = loyaltyCard.starStatus != 0; if (loyaltyCard.archiveStatus != 0) { menu.findItem(R.id.action_unarchive).setVisible(true); menu.findItem(R.id.action_archive).setVisible(false); } else { menu.findItem(R.id.action_unarchive).setVisible(false); menu.findItem(R.id.action_archive).setVisible(true); } menu.findItem(R.id.action_overflow).setIcon(getIcon(R.drawable.ic_overflow_menu, backgroundNeedsDarkIcons)); menu.findItem(R.id.action_share).setIcon(getIcon(R.drawable.ic_share_white, backgroundNeedsDarkIcons)); return super.onCreateOptionsMenu(menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); if (starred) { menu.findItem(R.id.action_star_unstar).setIcon(getIcon(R.drawable.ic_starred_white, backgroundNeedsDarkIcons)); menu.findItem(R.id.action_star_unstar).setTitle(R.string.unstar); } else { menu.findItem(R.id.action_star_unstar).setIcon(getIcon(R.drawable.ic_unstarred_white, backgroundNeedsDarkIcons)); menu.findItem(R.id.action_star_unstar).setTitle(R.string.star); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { finish(); } else if (id == R.id.action_share) { try { importURIHelper.startShareIntent(Arrays.asList(loyaltyCard)); } catch (UnsupportedEncodingException e) { Toast.makeText(LoyaltyCardViewActivity.this, R.string.failedGeneratingShareURL, Toast.LENGTH_LONG).show(); e.printStackTrace(); } return true; } else if (id == R.id.action_duplicate) { Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class); Bundle bundle = new Bundle(); bundle.putInt("id", loyaltyCardId); bundle.putBoolean("duplicateId", true); intent.putExtras(bundle); startActivity(intent); return true; } else if (id == R.id.action_star_unstar) { starred = !starred; DBHelper.updateLoyaltyCardStarStatus(database, loyaltyCardId, starred ? 1 : 0); // Re-init loyaltyCard with new data from DB onResume(); return true; } else if (id == R.id.action_archive) { DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1); Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show(); // Re-init loyaltyCard with new data from DB onResume(); return true; } else if (id == R.id.action_unarchive) { DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 0); Toast.makeText(LoyaltyCardViewActivity.this, R.string.unarchived, Toast.LENGTH_LONG).show(); // Re-init loyaltyCard with new data from DB onResume(); return true; } else if (id == R.id.action_delete) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.deleteTitle); builder.setMessage(R.string.deleteConfirmation); builder.setPositiveButton(R.string.confirm, (dialog, which) -> { Log.e(TAG, "Deleting card: " + loyaltyCardId); DBHelper.deleteLoyaltyCard(database, LoyaltyCardViewActivity.this, loyaltyCardId); ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, loyaltyCardId); finish(); dialog.dismiss(); }); builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); AlertDialog dialog = builder.create(); dialog.show(); return true; } return super.onOptionsItemSelected(item); } private void setupOrientation() { int orientation = getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { Log.d(TAG, "Detected landscape mode"); setTitle(loyaltyCard.store); collapsingToolbarLayout.setVisibility(View.GONE); portraitToolbar.setVisibility(View.GONE); landscapeToolbar.setVisibility(View.VISIBLE); setSupportActionBar(landscapeToolbar); } else { Log.d(TAG, "Detected portrait mode"); setTitle(""); collapsingToolbarLayout.setVisibility(View.VISIBLE); portraitToolbar.setVisibility(View.VISIBLE); landscapeToolbar.setVisibility(View.GONE); setSupportActionBar(portraitToolbar); } ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } } private void drawBarcode(boolean addPadding) { mTasks.flushTaskList(TaskHandler.TYPE.BARCODE, true, false, false); if (format != null) { BarcodeImageWriterTask barcodeWriter = new BarcodeImageWriterTask( getApplicationContext(), mainImage, barcodeIdString != null ? barcodeIdString : cardIdString, format, null, false, barcodeImageGenerationFinishedCallback, addPadding); mTasks.executeTask(TaskHandler.TYPE.BARCODE, barcodeWriter); } } private void redrawBarcodeAfterResize(boolean addPadding) { if (format != null) { mainImage.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mainImage.getViewTreeObserver().removeOnGlobalLayoutListener(this); Log.d(TAG, "ImageView size now known"); drawBarcode(addPadding); } }); } } private void drawMainImage(int index, boolean waitForResize, boolean isFullscreen) { if (imageTypes.isEmpty()) { mainImage.setVisibility(View.GONE); return; } if (dots != null) { boolean darkMode = Utils.isDarkModeEnabled(getApplicationContext()); for (int i = 0; i < dots.length; i++) { dots[i].setImageDrawable(getDotIcon(i == index, darkMode)); } } ImageType wantedImageType = imageTypes.get(index); if (wantedImageType == ImageType.BARCODE) { // Use border in non-fullscreen mode if (!isFullscreen) { mainImage.setBackground(AppCompatResources.getDrawable(this, R.drawable.round_outline)); } else { mainImage.setBackgroundColor(Color.WHITE); } if (waitForResize) { redrawBarcodeAfterResize(!isFullscreen); } else { drawBarcode(!isFullscreen); } mainImage.setContentDescription(getString(R.string.barcodeImageDescriptionWithType, format.prettyName())); } else if (wantedImageType == ImageType.IMAGE_FRONT) { mainImage.setImageBitmap(frontImageBitmap); mainImage.setBackgroundColor(Color.TRANSPARENT); mainImage.setContentDescription(getString(R.string.frontImageDescription)); } else if (wantedImageType == ImageType.IMAGE_BACK) { mainImage.setImageBitmap(backImageBitmap); mainImage.setBackgroundColor(Color.TRANSPARENT); mainImage.setContentDescription(getString(R.string.backImageDescription)); } else { throw new IllegalArgumentException("Unknown image type: " + wantedImageType); } mainImage.setVisibility(View.VISIBLE); } private void setMainImage(boolean next, boolean overflow) { int newIndex = mainImageIndex + (next ? 1 : -1); if (newIndex >= imageTypes.size() && overflow) { newIndex = 0; } if (newIndex == -1 || newIndex >= imageTypes.size()) { return; } mainImageIndex = newIndex; drawMainImage(newIndex, false, isFullscreen); } private void setDotIndicator(boolean darkMode) { dotIndicator.removeAllViews(); if (imageTypes.size() >= 2) { dots = new ImageView[imageTypes.size()]; for (int i = 0; i < imageTypes.size(); i++) { dots[i] = new ImageView(this); dots[i].setImageDrawable(getDotIcon(false, darkMode)); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); params.setMargins(8, 0, 8, 0); dotIndicator.addView(dots[i], params); } dotIndicator.setVisibility(View.VISIBLE); } } /** * When enabled, hides the status bar and moves the barcode to the top of the screen. *

* The purpose of this function is to make sure the barcode can be scanned from the phone * by machines which offer no space to insert the complete device. */ private void setFullscreen(boolean enabled) { isFullscreen = enabled; ActionBar actionBar = getSupportActionBar(); if (enabled && !imageTypes.isEmpty()) { Log.d(TAG, "Move into fullscreen"); drawMainImage(mainImageIndex, true, isFullscreen); barcodeScaler.setProgress(loyaltyCard.zoomLevel); setCenterGuideline(loyaltyCard.zoomLevel); // Hide maximize and show minimize button and scaler maximizeButton.setVisibility(View.GONE); minimizeButton.setVisibility(View.VISIBLE); barcodeScaler.setVisibility(View.VISIBLE); // Hide actionbar if (actionBar != null) { actionBar.hide(); } // Hide toolbars appBarLayout.setVisibility(View.INVISIBLE); iconImage.setVisibility(View.INVISIBLE); collapsingToolbarLayout.setVisibility(View.GONE); landscapeToolbar.setVisibility(View.GONE); // Hide other UI elements cardIdFieldView.setVisibility(View.GONE); bottomAppBar.setVisibility(View.GONE); editButton.setVisibility(View.GONE); // Set Android to fullscreen mode getWindow().getDecorView().setSystemUiVisibility( getWindow().getDecorView().getSystemUiVisibility() | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN ); } else { Log.d(TAG, "Move out of fullscreen"); // Reset center guideline setCenterGuideline(100); drawMainImage(mainImageIndex, true, isFullscreen); // Show maximize and hide minimize button and scaler maximizeButton.setVisibility(imageTypes.isEmpty() ? View.GONE : View.VISIBLE); minimizeButton.setVisibility(View.GONE); barcodeScaler.setVisibility(View.GONE); // Show actionbar if (actionBar != null) { actionBar.show(); } // Show appropriate toolbar appBarLayout.setVisibility(View.VISIBLE); setupOrientation(); iconImage.setVisibility(View.VISIBLE); // Show other UI elements cardIdFieldView.setVisibility(View.VISIBLE); editButton.setVisibility(View.VISIBLE); bottomAppBar.setVisibility(View.VISIBLE); // Unset fullscreen mode getWindow().getDecorView().setSystemUiVisibility( getWindow().getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY & ~View.SYSTEM_UI_FLAG_FULLSCREEN ); } Log.d("setFullScreen", "Is full screen enabled? " + enabled + " Zoom Level = " + barcodeScaler.getProgress()); } }