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)
+ }
+
+ 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)
+ 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/SettingsActivity.kt b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt
index 8d48320d22..23804de358 100644
--- a/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt
+++ b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt
@@ -19,7 +19,6 @@ import protect.card_locker.Utils
import protect.card_locker.databinding.SettingsActivityBinding
class SettingsActivity : CatimaAppCompatActivity() {
-
private lateinit var binding: SettingsActivityBinding
private lateinit var fragment: SettingsFragment
@@ -35,7 +34,8 @@ class SettingsActivity : CatimaAppCompatActivity() {
// Display the fragment as the main content.
fragment = SettingsFragment()
- supportFragmentManager.beginTransaction()
+ supportFragmentManager
+ .beginTransaction()
.replace(R.id.settings_container, fragment)
.commit()
@@ -44,11 +44,14 @@ class SettingsActivity : CatimaAppCompatActivity() {
fragment.mReloadMain = savedInstanceState.getBoolean(RELOAD_MAIN_STATE)
}
- onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- finishSettingsActivity()
- }
- })
+ onBackPressedDispatcher.addCallback(
+ this,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ finishSettingsActivity()
+ }
+ },
+ )
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -81,7 +84,10 @@ class SettingsActivity : CatimaAppCompatActivity() {
class SettingsFragment : PreferenceFragmentCompat() {
var mReloadMain: Boolean = false
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ override fun onCreatePreferences(
+ savedInstanceState: Bundle?,
+ rootKey: String?,
+ ) {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences)
@@ -92,9 +98,11 @@ class SettingsActivity : CatimaAppCompatActivity() {
getString(R.string.settings_key_light_theme) -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
+
getString(R.string.settings_key_dark_theme) -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
+
else -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
@@ -112,14 +120,15 @@ class SettingsActivity : CatimaAppCompatActivity() {
findPreference(getString(R.string.settings_key_locale))!!
localePreference.let {
val entryValues = it.entryValues
- val entries = entryValues.map { entry ->
- if (entry.isEmpty()) {
- getString(R.string.settings_system_locale)
- } else {
- val entryLocale = Utils.stringToLocale(entry.toString())
- entryLocale.getDisplayName(entryLocale)
+ val entries =
+ entryValues.map { entry ->
+ if (entry.isEmpty()) {
+ getString(R.string.settings_system_locale)
+ } else {
+ val entryLocale = Utils.stringToLocale(entry.toString())
+ entryLocale.getDisplayName(entryLocale)
+ }
}
- }
it.entries = entries.toTypedArray()
// Make locale picker preference in sync with system settings
@@ -153,7 +162,15 @@ class SettingsActivity : CatimaAppCompatActivity() {
}
val newLocale = newValue as String
// If newLocale is empty, that means "System" was selected
- AppCompatDelegate.setApplicationLocales(if (newLocale.isEmpty()) LocaleListCompat.getEmptyLocaleList() else LocaleListCompat.create(Utils.stringToLocale(newLocale)))
+ AppCompatDelegate.setApplicationLocales(
+ if (newLocale.isEmpty()) {
+ LocaleListCompat.getEmptyLocaleList()
+ } else {
+ LocaleListCompat.create(
+ Utils.stringToLocale(newLocale),
+ )
+ },
+ )
true
}
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..b8f14e0742
--- /dev/null
+++ b/app/src/main/res/layout/barcode_widget.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
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..383d71dfc0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -335,6 +335,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
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..6c29deae68
--- /dev/null
+++ b/app/src/main/res/xml/barcode_widget_info.xml
@@ -0,0 +1,13 @@
+
+