Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
android:name=".cardview.LoyaltyCardViewActivity"
android:exported="true"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".cardview.LoyaltyCardImageActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".LoyaltyCardEditActivity"
android:exported="true"
Expand Down

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this into it's own cardimageview package and rename it to LoyaltyCardImageViewActivity.kt, given how stand-alone it is and to make clear this is specifically for viewing images, nothing else?

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package protect.card_locker.cardview

import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.compose.setContent
import protect.card_locker.CatimaComponentActivity
import protect.card_locker.ImageLocationType
import protect.card_locker.R
import protect.card_locker.Utils
import protect.card_locker.compose.LoyaltyCardImageScreen
import protect.card_locker.compose.theme.CatimaTheme
import protect.card_locker.preferences.Settings

class LoyaltyCardImageActivity : CatimaComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

fixedEdgeToEdge()
applyWindowPreferences()

val imageLocationType = intent.imageLocationType()
val loyaltyCardId = intent.getIntExtra(BUNDLE_ID, 0)
val bitmap = imageLocationType?.let {
Utils.retrieveCardImage(this, loyaltyCardId, it)
}

if (loyaltyCardId == 0 || imageLocationType == null || bitmap == null) {
Toast.makeText(this, R.string.failedToRetrieveImageFile, Toast.LENGTH_SHORT).show()
finish()
return
}

title = getString(imageLocationType.descriptionRes())

setContent {
CatimaTheme {
LoyaltyCardImageScreen(
bitmap = bitmap,
contentDescriptionRes = imageLocationType.descriptionRes(),
onBack = { onBackPressedDispatcher.onBackPressed() }
)
}
}
}

private fun applyWindowPreferences() {
val settings = Settings(this)
val currentWindow = window ?: return

currentWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
Comment on lines +53 to +54

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not doing any input so this is unneeded

Suggested change
currentWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)


val attributes = currentWindow.attributes
if (settings.useMaxBrightnessDisplayingBarcode()) {
attributes.screenBrightness = 1F
}

if (settings.keepScreenOn) {
currentWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}

currentWindow.attributes = attributes
}

private fun Intent.imageLocationType(): ImageLocationType? {
val name = getStringExtra(BUNDLE_IMAGE_LOCATION_TYPE) ?: return null
return runCatching { ImageLocationType.valueOf(name) }.getOrNull()
}

private fun ImageLocationType.descriptionRes(): Int {
return when (this) {
ImageLocationType.icon -> R.string.thumbnailDescription
ImageLocationType.front -> R.string.frontImageDescription
ImageLocationType.back -> R.string.backImageDescription
}
}

companion object {
const val BUNDLE_ID = "id"
const val BUNDLE_IMAGE_LOCATION_TYPE = "imageLocationType"

@JvmStatic
fun createIntent(
context: Context,
loyaltyCardId: Int,
imageLocationType: ImageLocationType
): Intent {
return Intent(context, LoyaltyCardImageActivity::class.java)
.putExtra(BUNDLE_ID, loyaltyCardId)
.putExtra(BUNDLE_IMAGE_LOCATION_TYPE, imageLocationType.name)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package protect.card_locker.cardview;

import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
Expand Down Expand Up @@ -34,7 +33,6 @@
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.FileProvider;
import androidx.core.graphics.BlendModeColorFilterCompat;
import androidx.core.graphics.BlendModeCompat;
import androidx.core.graphics.ColorUtils;
Expand All @@ -44,7 +42,6 @@
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.charset.Charset;
Expand Down Expand Up @@ -136,46 +133,28 @@ public void onMainImageTap() {
return;
}

// If this is an image, open it in the gallery.
openImageInGallery(imageType);
openImage(imageType);
}

private void openImageInGallery(LoyaltyCardImageType imageType) {
File file = null;

private void openImage(LoyaltyCardImageType imageType) {
switch (imageType) {
case NONE:
case BARCODE:
return;
case ICON:
file = Utils.retrieveCardImageAsFile(this, loyaltyCardId, ImageLocationType.icon);
break;
startImageActivity(ImageLocationType.icon);
return;
case IMAGE_FRONT:
file = Utils.retrieveCardImageAsFile(this, loyaltyCardId, ImageLocationType.front);
break;
startImageActivity(ImageLocationType.front);
return;
case IMAGE_BACK:
file = Utils.retrieveCardImageAsFile(this, loyaltyCardId, ImageLocationType.back);
break;
case BARCODE:
Toast.makeText(this, R.string.barcodeLongPressMessage, Toast.LENGTH_SHORT).show();
startImageActivity(ImageLocationType.back);
return;
}
Comment on lines 140 to 153

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Android Studio complains about the unnecessary return. How about this? Then we even have logging in case something stupid gets done in the future :)

    private void openImage(LoyaltyCardImageType imageType) {
        switch (imageType) {
            case ICON:
                startImageActivity(ImageLocationType.icon);
                break;
            case IMAGE_FRONT:
                startImageActivity(ImageLocationType.front);
                break;
            case IMAGE_BACK:
                startImageActivity(ImageLocationType.back);
                break;
            default:
                Log.w(TAG, "openImage called with unsupported image type");
        }
    }

}

// Do nothing if there is no file
if (file == null) {
Toast.makeText(this, R.string.failedToRetrieveImageFile, Toast.LENGTH_SHORT).show();
return;
}

try {
Intent intent = new Intent(Intent.ACTION_VIEW)
.setDataAndType(FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file), "image/*")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
} catch (ActivityNotFoundException e) {
// Display a toast message if an image viewer is not installed on device
Toast.makeText(this, R.string.failedLaunchingPhotoPicker, Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
private void startImageActivity(ImageLocationType imageLocationType) {
startActivity(LoyaltyCardImageActivity.createIntent(this, loyaltyCardId, imageLocationType));
}

@Override
Expand Down Expand Up @@ -291,7 +270,7 @@ protected void onCreate(Bundle savedInstanceState) {

binding.iconContainer.setOnClickListener(view -> {
if (loyaltyCard.getImageThumbnail(this) != null) {
openImageInGallery(LoyaltyCardImageType.ICON);
openImage(LoyaltyCardImageType.ICON);
} else {
Toast.makeText(LoyaltyCardViewActivity.this, R.string.icon_header_click_text, Toast.LENGTH_LONG).show();
}
Expand Down Expand Up @@ -887,9 +866,9 @@ private void updateMainImageAccessibility() {
int accessibilityClickAction;
LoyaltyCardImageType currentImageType = cardNavigator.getCurrent();
if (currentImageType == LoyaltyCardImageType.IMAGE_FRONT) {
accessibilityClickAction = R.string.openFrontImageInGalleryApp;
accessibilityClickAction = R.string.zoomFrontImage;
} else if (currentImageType == LoyaltyCardImageType.IMAGE_BACK) {
accessibilityClickAction = R.string.openBackImageInGalleryApp;
accessibilityClickAction = R.string.zoomBackImage;
} else {
accessibilityClickAction = R.string.moveBarcodeToTopOfScreen;
}
Expand Down

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I asked you already to move this to compose, but I think it may be best for this to use the same package as the activity. So maybe move this to compose/cardimageview/LoyaltyCardImageViewActivity.kt?

Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package protect.card_locker.compose

import android.graphics.Bitmap
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import protect.card_locker.R

@Composable
fun LoyaltyCardImageScreen(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given with the other renames, maybe LoyaltyCardImageViewScreen?

bitmap: Bitmap,
@StringRes contentDescriptionRes: Int,
onBack: () -> Unit,
) {
var scale by remember { mutableStateOf(1F) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Android Studio suggestion:

Prefer mutableFloatStateOf instead of mutableStateOf

var offset by remember { mutableStateOf(Offset.Zero) }
var size by remember { mutableStateOf(IntSize.Zero) }
val zoomState = rememberTransformableState { _, zoomChange, panChange, _ ->
val nextScale = (scale * zoomChange).coerceIn(1F, MAX_IMAGE_SCALE)
scale = nextScale
offset = if (nextScale == 1F) {
Offset.Zero
} else {
(offset + panChange).clampTo(size, nextScale)
}
}

Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.testTag("card_image_zoom")
) {
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = stringResource(contentDescriptionRes),
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize()
.onSizeChanged { size = it }
.pointerInput(bitmap) {
detectTapGestures(
onDoubleTap = {
scale = 1F
offset = Offset.Zero
}
)
}
.transformable(zoomState)
.graphicsLayer {
scaleX = scale
scaleY = scale
translationX = offset.x
translationY = offset.y
}
.testTag("card_image")
)

IconButton(
onClick = onBack,
modifier = Modifier
.align(Alignment.TopStart)
.statusBarsPadding()
.padding(8.dp)
.testTag("card_image_back")
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back)
)
Comment on lines +96 to +99

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The back icon remains black in dark mode. Maybe using the CatimaTopAppBar like in the AboutActivity makes sense? Also standardizes the UI more, which should help future maintenance.

}
}
}

private fun Offset.clampTo(size: IntSize, scale: Float): Offset {
if (size.width == 0 || size.height == 0) {
return this
}

val maxX = size.width * (scale - 1F) / 2F
val maxY = size.height * (scale - 1F) / 2F
return Offset(
x = x.coerceIn(-maxX, maxX),
y = y.coerceIn(-maxY, maxY)
)
}

private const val MAX_IMAGE_SCALE = 5F
3 changes: 0 additions & 3 deletions app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@
<string name="previousCard">السابق</string>
<string name="nextCard">التالي</string>
<string name="failedToRetrieveImageFile">فشل في استخراج ملف الصورة</string>
<string name="barcodeLongPressMessage">يمكن فتح صور فقط في تطبيق معرض الصور</string>
<string name="failedToOpenUrl">ثبت متصفح ويب أولاً</string>
<string name="welcome">مرحبًا بك في كاتيما</string>
<string name="updateBalanceTitle">كم أنفقت أو استلمت؟</string>
Expand All @@ -243,7 +242,6 @@
<string name="permissionReadCardsLabel">اقرأ بطاقات كاتيما</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">حقوق النشر © 2019–<xliff:g>%d</xliff:g> Sylvia van Os والمساهمون</string>
<string name="app_copyright_short">حقوق النشر © Sylvia van Os والمساهمون</string>
<string name="openFrontImageInGalleryApp">افتح الصورة الأمامية في تطبيق المعرض</string>
<string name="show_name_below_image_thumbnail">إظهار الاسم أسفل الصورة المصغرة</string>
<string name="show_validity">إظهار الصلاحية</string>
<string name="view_online">مشاهدة حية</string>
Expand All @@ -259,7 +257,6 @@
<string name="switchToFrontImage">التبديل إلى الصورة الأمامية</string>
<string name="settings_allow_content_provider_read_summary">سيظل يتعين على التطبيقات طلب الإذن لمنحها حق الوصول</string>
<string name="setBarcodeHeight">ضبط ارتفاع الباركود</string>
<string name="openBackImageInGalleryApp">فتح الصورة الخلفية في تطبيق المعرض</string>
<string name="settings_allow_content_provider_read_title">السماح للتطبيقات الأخرى بالوصول إلى بياناتي</string>
<string name="donate">تبرّع</string>
<string name="show_archived_cards">عرض البطاقات المؤرشفة</string>
Expand Down
3 changes: 0 additions & 3 deletions app/src/main/res/values-be/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@
<string name="showMoreInfo">Паказаць інфармацыю</string>
<string name="updateBalance">Абнавіць баланс</string>
<string name="failedToRetrieveImageFile">Не ўдалося атрымаць файл відарысу</string>
<string name="barcodeLongPressMessage">У галерэі можна адкрываць толькі відарысы</string>
<string name="sort_by_name">Назва</string>
<string name="sort_by_most_recently_used">Частата выкарыстання</string>
<string name="sort_by_valid_from">Дзейнічае ад</string>
Expand Down Expand Up @@ -203,7 +202,6 @@
<string name="validFromSentence">Дзейнічае з: <xliff:g>%s</xliff:g></string>
<string name="height">Вышыня</string>
<string name="switchToFrontImage">Пераключыцца на пярэдні відарыс</string>
<string name="openBackImageInGalleryApp">Адкрыць задні відарыс у галерэі</string>
<string name="setBarcodeHeight">Задаць вышыню штрыхкода</string>
<string name="donate">Ахвяраваць</string>
<string name="icon_header_click_text">Доўгі націск для рэдагавання мініяцюры</string>
Expand Down Expand Up @@ -288,7 +286,6 @@
<string name="report_error">Паведаміць пра памылку</string>
<string name="failedLaunchingPhotoPicker">Не атрымалася знайсці праграму для галерэі, якая падтрымліваецца</string>
<string name="unsupportedFile">Гэты файл не падтрымліваецца</string>
<string name="openFrontImageInGalleryApp">Адкрыць пярэдні відарыс у галерэі</string>
<string name="field_must_not_be_empty">Поле не павінна быць пустым</string>
<string name="add_manually_warning_title">Рэкамендуецца сканаванне</string>
<string name="errorReadingFile">Не атрымалася прачытаць файл</string>
Expand Down
3 changes: 0 additions & 3 deletions app/src/main/res/values-bg/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@
<string name="nextCard">Следваща</string>
<string name="failedToOpenUrl">Първо инсталирайте уеб браузър</string>
<string name="welcome">Добре дошли при Катима</string>
<string name="barcodeLongPressMessage">В приложението галерия могат да бъдат отваряни само изображения</string>
<string name="failedToRetrieveImageFile">Не е възможно извличане на изображение</string>
<string name="noCameraPermissionDirectToSystemSetting">За да сканирате щрихкодове с Catima е необходим достъп до камерата. За да промените разрешението докоснете тук.</string>
<string name="updateBalanceTitle">Колко е похарчено или получено?</string>
Expand All @@ -218,8 +217,6 @@
<string name="switchToFrontImage">Показване на предната страна</string>
<string name="switchToBackImage">Показване на задната страна</string>
<string name="switchToBarcode">Показване на щрихкода</string>
<string name="openFrontImageInGalleryApp">Отваряне на изображението на предната страна в приложение за преглед за изображения</string>
<string name="openBackImageInGalleryApp">Отваряне на изображението на задната страна в приложение за преглед за изображения</string>
<string name="setBarcodeHeight">Задаване на височина на щрихкода</string>
<string name="donate">Даряване</string>
<string name="icon_header_click_text">Задръжте, за да промените миниатюрата</string>
Expand Down
Loading