diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ed2c6f41ff..dba1631f26 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -48,6 +48,19 @@
android:resource="@xml/list_widget_info" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 0)
+
+ var barcodeBitmap = generateBarcode(cardIdStr, barcodeType, card.barcodeEncoding)
+
+ if (barcodeBitmap != null && isVertical && !barcodeType.isSquare()) {
+ barcodeBitmap = rotateBitmap(barcodeBitmap, 90f)
+ }
+
+ val views = RemoteViews(context.packageName, R.layout.barcode_widget)
+ views.setTextViewText(R.id.store_name, card.store)
+
+ if (barcodeBitmap != null) {
+ views.setImageViewBitmap(R.id.barcode_image, barcodeBitmap)
+ views.setViewVisibility(R.id.barcode_image, View.VISIBLE)
+ } else {
+ views.setViewVisibility(R.id.barcode_image, View.GONE)
+ }
+
+ if (showFormat) {
+ views.setTextViewText(R.id.barcode_format, barcodeType.prettyName())
+ views.setViewVisibility(R.id.barcode_format, View.VISIBLE)
+ } else {
+ views.setViewVisibility(R.id.barcode_format, View.GONE)
+ }
+
+ val cardIntent =
+ Intent(context, LoyaltyCardViewActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ putExtra(LoyaltyCardViewActivity.BUNDLE_ID, card.id)
+ }
+ val pendingIntent =
+ PendingIntent.getActivity(
+ context,
+ 0,
+ cardIntent,
+ PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ )
+ views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent)
+
+ appWidgetManager.updateAppWidget(appWidgetId, views)
+ }
+
+ private fun createEmptyViews(context: Context): RemoteViews {
+ val views = RemoteViews(context.packageName, R.layout.barcode_widget)
+ views.setTextViewText(
+ R.id.store_name,
+ context.getString(R.string.barcode_widget_not_configured),
+ )
+ views.setViewVisibility(R.id.barcode_image, View.GONE)
+ views.setViewVisibility(R.id.barcode_format, View.GONE)
+ return views
+ }
+
+ private fun generateBarcode(
+ cardId: String,
+ format: CatimaBarcode,
+ encoding: Charset,
+ ): Bitmap? {
+ if (cardId.isEmpty()) return null
+
+ val isSquare = format.isSquare()
+ val genWidth = if (isSquare) 500 else 1000
+ val genHeight = if (isSquare) 500 else 300
+
+ return try {
+ val writer = MultiFormatWriter()
+
+ val encodeHints = mutableMapOf()
+ if (encoding.name() != "ISO-8859-1") {
+ encodeHints[EncodeHintType.CHARACTER_SET] = encoding.name()
+ }
+
+ val bitMatrix =
+ try {
+ if (encodeHints.isNotEmpty()) {
+ writer.encode(cardId, format.format(), genWidth, genHeight, encodeHints)
+ } else {
+ writer.encode(cardId, format.format(), genWidth, genHeight)
+ }
+ } catch (e: WriterException) {
+ return null
+ } catch (e: Exception) {
+ return null
+ }
+
+ bitmapFromBitMatrix(bitMatrix)
+ } catch (e: OutOfMemoryError) {
+ null
+ }
+ }
+
+ private fun rotateBitmap(
+ bitmap: Bitmap,
+ degrees: Float,
+ ): Bitmap {
+ val matrix = Matrix()
+ matrix.postRotate(degrees)
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
+ }
+
+ private fun bitmapFromBitMatrix(bitMatrix: BitMatrix): Bitmap {
+ val width = bitMatrix.width
+ val height = bitMatrix.height
+ val pixels = IntArray(width * height)
+
+ for (y in 0 until height) {
+ val offset = y * width
+ for (x in 0 until width) {
+ pixels[offset + x] = if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE
+ }
+ }
+
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
+ return bitmap
+ }
+
+ companion object {
+ private const val PREFS_NAME = "barcode_widget_prefs"
+ private const val PREFS_CARD_ID_PREFIX = "card_id_"
+
+ fun saveCardPref(
+ context: Context,
+ appWidgetId: Int,
+ cardId: Int,
+ ) {
+ context
+ .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putInt(PREFS_CARD_ID_PREFIX + appWidgetId, cardId)
+ .apply()
+ }
+
+ fun triggerUpdate(context: Context) {
+ val appWidgetManager = AppWidgetManager.getInstance(context)
+ val componentName = ComponentName(context, BarcodeWidget::class.java)
+ val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
+ if (appWidgetIds.isNotEmpty()) {
+ BarcodeWidget().onUpdate(context, appWidgetManager, appWidgetIds)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/BarcodeWidgetConfigureActivity.kt b/app/src/main/java/protect/card_locker/BarcodeWidgetConfigureActivity.kt
new file mode 100644
index 0000000000..a24fb41d15
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/BarcodeWidgetConfigureActivity.kt
@@ -0,0 +1,83 @@
+package protect.card_locker
+
+import android.appwidget.AppWidgetManager
+import android.content.Intent
+import android.database.sqlite.SQLiteDatabase
+import android.os.Bundle
+import android.widget.Toast
+import androidx.recyclerview.widget.GridLayoutManager
+import protect.card_locker.databinding.BarcodeWidgetConfigureActivityBinding
+import protect.card_locker.preferences.Settings
+
+class BarcodeWidgetConfigureActivity : CatimaAppCompatActivity(),
+ LoyaltyCardCursorAdapter.CardAdapterListener {
+
+ private lateinit var binding: BarcodeWidgetConfigureActivityBinding
+ private lateinit var mDatabase: SQLiteDatabase
+ private lateinit var mAdapter: LoyaltyCardCursorAdapter
+ private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = BarcodeWidgetConfigureActivityBinding.inflate(layoutInflater)
+ mDatabase = DBHelper(this).readableDatabase
+
+ appWidgetId = intent?.getIntExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID
+ ) ?: AppWidgetManager.INVALID_APPWIDGET_ID
+
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ setResult(RESULT_CANCELED)
+ finish()
+ return
+ }
+
+ setResult(RESULT_CANCELED)
+
+ setContentView(binding.root)
+ Utils.applyWindowInsets(binding.root)
+
+ binding.toolbar.apply {
+ setTitle(R.string.barcode_widget_configure_title)
+ setSupportActionBar(this)
+ }
+
+ if (DBHelper.getLoyaltyCardCount(mDatabase) == 0) {
+ Toast.makeText(this, R.string.noCardsMessage, Toast.LENGTH_LONG).show()
+ finish()
+ return
+ }
+
+ val cardCursor = DBHelper.getLoyaltyCardCursor(mDatabase)
+ mAdapter = LoyaltyCardCursorAdapter(this, cardCursor, this, null)
+ binding.list.adapter = mAdapter
+ }
+
+ override fun onResume() {
+ super.onResume()
+ val layoutManager = binding.list.layoutManager as GridLayoutManager?
+ layoutManager?.spanCount = Settings(this).getPreferredColumnCount()
+ }
+
+ override fun onRowClicked(position: Int) {
+ val cursor = DBHelper.getLoyaltyCardCursor(mDatabase)
+ cursor.moveToPosition(position)
+ val card = LoyaltyCard.fromCursor(this, cursor)
+
+ BarcodeWidget.saveCardPref(this, appWidgetId, card.id)
+
+ val appWidgetManager = AppWidgetManager.getInstance(this)
+ BarcodeWidget().onUpdate(this, appWidgetManager, intArrayOf(appWidgetId))
+
+ val resultValue = Intent().putExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_ID,
+ appWidgetId
+ )
+ setResult(RESULT_OK, resultValue)
+ finish()
+ }
+
+ override fun onRowLongClicked(position: Int) {
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/preferences/Settings.java b/app/src/main/java/protect/card_locker/preferences/Settings.java
index f77f4b359f..dc4df14eb3 100644
--- a/app/src/main/java/protect/card_locker/preferences/Settings.java
+++ b/app/src/main/java/protect/card_locker/preferences/Settings.java
@@ -112,4 +112,8 @@ public int getPreferredColumnCount() {
public boolean useVolumeKeysForNavigation() {
return getBoolean(R.string.settings_key_use_volume_keys_navigation, false);
}
+
+ public boolean showBarcodeWidgetFormat() {
+ return getBoolean(R.string.settings_key_barcode_widget_show_format, false);
+ }
}
diff --git a/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt
index 8d48320d22..9049335f3c 100644
--- a/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt
+++ b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt
@@ -11,6 +11,8 @@ import androidx.core.os.LocaleListCompat
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceManager
+import protect.card_locker.BarcodeWidget
import protect.card_locker.BuildConfig
import protect.card_locker.CatimaAppCompatActivity
import protect.card_locker.MainActivity
@@ -160,6 +162,18 @@ class SettingsActivity : CatimaAppCompatActivity() {
// Hide crash reporter settings on builds it's not enabled on
val crashReporterPreference = findPreference("acra.enable")
crashReporterPreference!!.isVisible = BuildConfig.useAcraCrashReporter
+
+ val barcodeFormatPreference = findPreference(getString(R.string.settings_key_barcode_widget_show_format))
+ barcodeFormatPreference?.setOnPreferenceChangeListener { _, newValue ->
+ activity?.let { activity ->
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ .edit()
+ .putBoolean(activity.getString(R.string.settings_key_barcode_widget_show_format), newValue as Boolean)
+ .apply()
+ BarcodeWidget.triggerUpdate(activity)
+ }
+ true
+ }
}
private fun refreshActivity(reloadMain: Boolean) {
diff --git a/app/src/main/res/layout/barcode_widget.xml b/app/src/main/res/layout/barcode_widget.xml
new file mode 100644
index 0000000000..6f083194ad
--- /dev/null
+++ b/app/src/main/res/layout/barcode_widget.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/barcode_widget_configure_activity.xml b/app/src/main/res/layout/barcode_widget_configure_activity.xml
new file mode 100644
index 0000000000..07f2559b2d
--- /dev/null
+++ b/app/src/main/res/layout/barcode_widget_configure_activity.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6b3b8ce3b6..1ec650d0b5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -112,6 +112,7 @@
Switch cards using volume buttons
Use the volume buttons to change which card is displayed
pref_use_volume_keys_navigation
+ pref_barcode_widget_show_format
sharedpreference_active_tab
sharedpreference_sort
sharedpreference_sort_order
@@ -335,6 +336,9 @@
Card list
Set barcode width
After you add some loyalty cards in Catima, they will appear here. If you have cards, make sure they are not all archived.
+ Barcode
+ Select a card for the barcode widget
+ Configure barcode widget
Card %d
Card %d (%s)
Please do not rotate the device, as this will cancel the action
@@ -347,6 +351,8 @@
Copied to clipboard
No value found
Barcode encoding
+ Show barcode type on widget
+ Display the barcode format name below the barcode on the home screen widget
Back
NFC is paused
Change settings
diff --git a/app/src/main/res/xml/barcode_widget_info.xml b/app/src/main/res/xml/barcode_widget_info.xml
new file mode 100644
index 0000000000..f54bbeef1e
--- /dev/null
+++ b/app/src/main/res/xml/barcode_widget_info.xml
@@ -0,0 +1,13 @@
+
+
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index fec2bce50f..2fc2cc517a 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -118,6 +118,15 @@
android:title="@string/settings_disable_nfc_while_viewing_card"
app:iconSpaceReserved="false"
app:singleLineTitle="false" />
+
+