Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@
android:resource="@xml/list_widget_info" />
</receiver>

<receiver
android:name=".BarcodeWidget"
android:label="@string/barcode_widget_name"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/barcode_widget_info" />
</receiver>

<activity
android:name=".MainActivity"
android:exported="true"
Expand Down Expand Up @@ -144,6 +157,15 @@
android:name=".BarcodeSelectorActivity"
android:label="@string/selectBarcodeTitle"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".BarcodeWidgetConfigureActivity"
android:label="@string/barcode_widget_name"
android:theme="@style/AppTheme.NoActionBar"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name=".preferences.SettingsActivity"
android:label="@string/settings"
Expand Down
216 changes: 216 additions & 0 deletions app/src/main/java/protect/card_locker/BarcodeWidget.kt
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to duplicate a lot of the code of the BarcodeImageWriterTask class. Is there any reason for this? Duplicated code is harder to maintain and increases the risk of introducing bugs (for example: forgetting to update the widget code when the general drawing code gets updated).

Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package protect.card_locker

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Matrix
import android.os.Bundle
import android.view.View
import android.widget.RemoteViews
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
import com.google.zxing.WriterException
import com.google.zxing.common.BitMatrix
import protect.card_locker.cardview.LoyaltyCardViewActivity
import protect.card_locker.preferences.Settings
import java.nio.charset.Charset

class BarcodeWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
updateWidget(context, appWidgetManager, appWidgetId)
}
}

override fun onAppWidgetOptionsChanged(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
newOptions: Bundle?,
) {
updateWidget(context, appWidgetManager, appWidgetId)
}

private fun updateWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
) {
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val cardId = prefs.getInt(PREFS_CARD_ID_PREFIX + appWidgetId, -1)

if (cardId == -1) {
val views = createEmptyViews(context)
appWidgetManager.updateAppWidget(appWidgetId, views)
return
}

val db = DBHelper(context).readableDatabase
val card = DBHelper.getLoyaltyCard(context, db, cardId)

val barcodeType = card?.barcodeType
val cardIdStr = card?.cardId

if (card == null || barcodeType == null || cardIdStr.isNullOrEmpty()) {
val views = createEmptyViews(context)
appWidgetManager.updateAppWidget(appWidgetId, views)
return
}

val showFormat = Settings(context).showBarcodeWidgetFormat()

val options = appWidgetManager.getAppWidgetOptions(appWidgetId)
val isVertical =
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 0) >
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<EncodeHintType, Any>()
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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -160,6 +162,18 @@ class SettingsActivity : CatimaAppCompatActivity() {
// Hide crash reporter settings on builds it's not enabled on
val crashReporterPreference = findPreference<Preference>("acra.enable")
crashReporterPreference!!.isVisible = BuildConfig.useAcraCrashReporter

val barcodeFormatPreference = findPreference<Preference>(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
}
Comment on lines +165 to +176
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no use for this feature. I cannot imagine anyone possibly needing the barcode type displayed on their homescreen below it. So let's just remove it to lower maintenance burden.

}

private fun refreshActivity(reloadMain: Boolean) {
Expand Down
Loading
Loading