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 @@ + +