Migrate WearOS app to Material3

Also always display sync status on screen
This commit is contained in:
Sylvia van Os committed 2026-08-11 22:06:01 +02:00
1 parent b326a7148d
commit 9459c78e7c
10 files changed
+76 -49

No files matched your search

@@ -14,6 +14,7 @@ import android.widget.RemoteViews
import androidx.core.widget.RemoteViewsCompat
import protect.card_locker.DBHelper.LoyaltyCardArchiveFilter
import protect.card_locker.cardview.LoyaltyCardViewActivity
import protect.card_locker.shared.ForegroundColorHelper
class ListWidget : AppWidgetProvider() {
fun updateAll(context: Context) {
@@ -100,7 +101,7 @@ class ListWidget : AppWidgetProvider() {
// Note: Android 5 will not use bitmaps
val remoteViews = RemoteViews(context.packageName, R.layout.list_widget_item).apply {
val headerColor = Utils.getHeaderColor(context, loyaltyCard)
val foreground = if (Utils.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
val foreground = if (ForegroundColorHelper.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE
setInt(R.id.item_container_foreground, "setBackgroundColor", headerColor)
val icon = loyaltyCard.getImageThumbnail(context)
// FIXME: The icon flow causes a crash up to Android 12L, so force anything below 33 down this path
@@ -91,6 +91,7 @@ import java.util.concurrent.Callable;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.LayoutChipChoiceBinding;
import protect.card_locker.databinding.LoyaltyCardEditActivityBinding;
import protect.card_locker.shared.ForegroundColorHelper;
import protect.card_locker.viewmodels.LoyaltyCardEditActivityViewModel;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback, ColorPickerDialogListener {
@@ -896,8 +897,8 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
if (headerColor != null) {
thumbnail.setOnClickListener(new ChooseCardImage());
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
}
onResuming = false;
@@ -919,18 +920,18 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity implements
setLoyaltyCardHeaderColor(headerColor);
thumbnail.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnail.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
} else {
generateIcon(storeFieldEdit.getText().toString().trim());
Integer headerColor = viewModel.getLoyaltyCard().headerColor;
if (headerColor != null) {
thumbnailEditIcon.setBackgroundColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(Utils.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
thumbnailEditIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
thumbnailEditIcon.setColorFilter(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.WHITE : Color.BLACK);
}
}
}
@@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import protect.card_locker.cardview.LoyaltyCardViewActivity;
import protect.card_locker.shared.ForegroundColorHelper;
public class ShortcutHelper {
/**
@@ -102,7 +103,7 @@ public class ShortcutHelper {
if (iconBitmap == null) {
iconBitmap = Utils.generateIcon(context, loyaltyCard, true).getLetterTile();
} else {
iconBitmap = createAdaptiveBitmap(iconBitmap, Utils.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
iconBitmap = createAdaptiveBitmap(iconBitmap, ForegroundColorHelper.Companion.needsDarkForeground(Utils.getHeaderColor(context, loyaltyCard)) ? Color.BLACK : Color.WHITE);
}
IconCompat icon = IconCompat.createWithAdaptiveBitmap(iconBitmap);
@@ -39,7 +39,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RawRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.Insets;
import androidx.core.os.LocaleListCompat;
import androidx.core.view.ViewCompat;
@@ -95,6 +94,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import protect.card_locker.preferences.Settings;
import protect.card_locker.shared.ForegroundColorHelper;
public class Utils {
private static final String TAG = "Catima";
@@ -115,8 +115,6 @@ public class Utils {
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
static final double LUMINANCE_MIDPOINT = 0.5;
static final int BITMAP_SIZE_SMALL = 512;
static final int BITMAP_SIZE_BIG = 1600;
@@ -147,11 +145,7 @@ public class Utils {
}
return new LetterBitmap(context, store, store,
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
}
static public boolean needsDarkForeground(Integer backgroundColor) {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, ForegroundColorHelper.Companion.needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
}
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) {
@@ -1070,7 +1064,7 @@ public class Utils {
if (icon != null) {
// Use header colour to decide if this image will need a white or black background
backgroundOrIcon.setBackgroundColor(needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
backgroundOrIcon.setBackgroundColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
// Ensure correct cropping style
backgroundOrIcon.setScaleType(Utils.getRecommendedScaleTypeForThumbnailImage(icon));
@@ -1100,7 +1094,7 @@ public class Utils {
// Actually set the text and colour
textWhenNoImage.setVisibility(View.VISIBLE);
textWhenNoImage.setText(loyaltyCard.store);
textWhenNoImage.setTextColor(Utils.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
textWhenNoImage.setTextColor(ForegroundColorHelper.Companion.needsDarkForeground(headerColor) ? Color.BLACK : Color.WHITE);
}
return headerColor;
@@ -54,6 +54,7 @@ import protect.card_locker.cardimageview.LoyaltyCardImageViewActivity;
import protect.card_locker.databinding.LoyaltyCardViewLayoutBinding;
import protect.card_locker.preferences.Settings;
import protect.card_locker.preferences.SettingsActivity;
import protect.card_locker.shared.ForegroundColorHelper;
public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements BarcodeImageWriterResultCallback {
private LoyaltyCardViewLayoutBinding binding;
@@ -598,20 +599,20 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
// Set bottomAppBar and system navigation bar color
binding.bottomAppBar.setBackgroundColor(darkenedColor);
Utils.setNavigationBarColor(null, window, darkenedColor, Utils.needsDarkForeground(darkenedColor));
Utils.setNavigationBarColor(null, window, darkenedColor, ForegroundColorHelper.Companion.needsDarkForeground(darkenedColor));
int complementaryColor = Utils.getComplementaryColor(darkenedColor);
binding.fabEdit.setBackgroundTintList(ColorStateList.valueOf(complementaryColor));
Drawable editButtonIcon = binding.fabEdit.getDrawable();
editButtonIcon.mutate();
editButtonIcon.setTint(Utils.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
editButtonIcon.setTint(ForegroundColorHelper.Companion.needsDarkForeground(complementaryColor) ? Color.BLACK : Color.WHITE);
binding.fabEdit.setImageDrawable(editButtonIcon);
Bitmap icon = loyaltyCard.getImageThumbnail(this);
Utils.setIconOrTextWithBackground(this, loyaltyCard, icon, binding.iconImage, binding.iconText, 1);
// If the background is very bright, we should use dark icons
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
backgroundNeedsDarkIcons = ForegroundColorHelper.Companion.needsDarkForeground(backgroundHeaderColor);
fixBottomAppBarImageButtonColor(binding.bottomAppBarInfoButton);
fixBottomAppBarImageButtonColor(binding.bottomAppBarPreviousButton);
+1 -1
View File
@@ -47,8 +47,8 @@ ch-acra-acra-mail = { group = "ch.acra", name = "acra-mail", version.ref = "acra
ch-acra-acra-dialog = { group = "ch.acra", name = "acra-dialog", version.ref = "acra" }
# Wear OS
androidx-wear-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "wearCompose" }
androidx-wear-compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "wearCompose" }
androidx-wear-compose-material3 = { group = "androidx.wear.compose", name = "compose-material3", version.ref = "wearCompose" }
androidx-wear-compose-navigation = { group = "androidx.wear.compose", name = "compose-navigation", version.ref = "wearCompose" }
# Testing
@@ -0,0 +1,13 @@
package protect.card_locker.shared
import androidx.core.graphics.ColorUtils
class ForegroundColorHelper {
companion object {
const val LUMINANCE_MIDPOINT: Double = 0.5
fun needsDarkForeground(backgroundColor: Int): Boolean {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT
}
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ dependencies {
implementation(libs.androidx.activity.activity.compose)
// Wear OS Compose
implementation(libs.androidx.wear.compose.material)
implementation(libs.androidx.wear.compose.material3)
implementation(libs.androidx.wear.compose.foundation)
implementation(libs.androidx.wear.compose.navigation)
@@ -15,15 +15,19 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.items
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.CircularProgressIndicator
import androidx.wear.compose.material.Text
import androidx.wear.compose.material3.CircularProgressIndicator
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.transformedHeight
import me.hackerchick.catima.wear.R
import me.hackerchick.catima.wear.SyncStatus
import me.hackerchick.catima.wear.WearCard
import protect.card_locker.shared.ForegroundColorHelper
@Composable
fun CardListScreen(
@@ -63,14 +67,16 @@ fun CardListScreen(
)
}
else -> {
ScalingLazyColumn(
val transformationSpec = rememberTransformationSpec()
TransformingLazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 32.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
items(cards, key = { it.id }) { card ->
Chip(
modifier = Modifier.fillMaxWidth(),
Button(
modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
label = {
Text(
text = card.store,
@@ -79,23 +85,33 @@ fun CardListScreen(
},
onClick = { onCardClick(card) },
colors = if (card.headerColor != null) {
ChipDefaults.chipColors(backgroundColor = Color(card.headerColor))
ButtonDefaults.buttonColors(
containerColor = Color(card.headerColor),
contentColor = if (ForegroundColorHelper.needsDarkForeground(card.headerColor)) Color.Black else Color.White
)
} else {
ChipDefaults.primaryChipColors()
ButtonDefaults.buttonColors()
},
)
}
val footerLabel = syncStatus.labelRes
if (footerLabel != null) {
item {
Text(
text = stringResource(footerLabel),
textAlign = TextAlign.Center,
fontSize = 11.sp,
color = Color.Gray,
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
)
}
}
val footerLabel = syncStatus.labelRes
if (footerLabel != null) {
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.background(Color.Black.copy(alpha = 0.75f))
) {
Text(
text = stringResource(footerLabel),
textAlign = TextAlign.Center,
fontSize = 11.sp,
color = Color.LightGray,
modifier = Modifier
.padding(40.dp, 4.dp, 40.dp, 20.dp)
.align(Alignment.Center)
)
}
}
}
@@ -26,9 +26,9 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.CircularProgressIndicator
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
import androidx.wear.compose.material3.CircularProgressIndicator
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.Text
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
@@ -40,7 +40,7 @@ fun CardViewScreen(card: WearCard?) {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background),
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center,
) {
if (card == null) {