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" /> + +