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
70 changes: 39 additions & 31 deletions app/src/main/java/protect/card_locker/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.core.content.edit
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout
Expand All @@ -40,7 +42,9 @@ import protect.card_locker.preferences.SettingsActivity
import protect.card_locker.wearos.WearSyncPermissionRequester
import java.io.UnsupportedEncodingException
import java.util.concurrent.atomic.AtomicInteger
import androidx.core.content.edit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
private lateinit var binding: MainActivityBinding
Expand Down Expand Up @@ -502,13 +506,11 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
return
}

val parseResultList: MutableList<ParseResult?>?

// Check for shared text
if (receivedAction == Intent.ACTION_SEND && receivedType == "text/plain") {
val loyaltyCard = LoyaltyCard()
loyaltyCard.setCardId(intent.getStringExtra(Intent.EXTRA_TEXT)!!)
parseResultList = mutableListOf(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard))
processParseResultList(mutableListOf(ParseResult(ParseResultType.BARCODE_ONLY, loyaltyCard)))
} else {
// Parse whatever file was sent, regardless of opening or sharing
val data: Uri? = when (receivedAction) {
Expand All @@ -524,36 +526,42 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
}
}

if (receivedType.startsWith("image/")) {
parseResultList = Utils.retrieveBarcodesFromImage(this, data)
} else if (receivedType == "application/pdf") {
parseResultList = Utils.retrieveBarcodesFromPdf(this, data)
} else if (mutableListOf<String?>(
"application/vnd.apple.pkpass",
"application/vnd-com.apple.pkpass"
).contains(receivedType)
) {
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data)
} else if (receivedType == "application/vnd.espass-espass") {
// FIXME: espass is not pkpass
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
parseResultList = Utils.retrieveBarcodesFromPkPass(this, data)
} else if (receivedType == "application/vnd.apple.pkpasses") {
parseResultList = Utils.retrieveBarcodesFromPkPasses(this, data)
} else {
Log.e(TAG, "Wrong mime-type")
return
}
importFile(data, receivedType)
}
}

// Give up if we should parse but there is nothing to parse
if (parseResultList == null || parseResultList.isEmpty()) {
finish()
return
}
private fun importFile(data: Uri?, receivedType: String) {
lifecycleScope.launch {
val parseResultList: MutableList<ParseResult?> = withContext(Dispatchers.IO) {
when {
receivedType.startsWith("image/") ->
Utils.retrieveBarcodesFromImage(this@MainActivity, data)
receivedType == "application/pdf" ->
Utils.retrieveBarcodesFromPdf(this@MainActivity, data)
receivedType == "application/vnd.apple.pkpass" ||
receivedType == "application/vnd-com.apple.pkpass" ->
Utils.retrieveBarcodesFromPkPass(this@MainActivity, data)
// FIXME: espass is not pkpass
// However, several users stated in https://github.com/CatimaLoyalty/Android/issues/2197 that the formats are extremely similar to the point they could rename an .espass file to .pkpass and have it imported
// So it makes sense to "unofficially" treat it as a PKPASS for now, even though not completely correct
receivedType == "application/vnd.espass-espass" ->
Utils.retrieveBarcodesFromPkPass(this@MainActivity, data)
receivedType == "application/vnd.apple.pkpasses" ->
Utils.retrieveBarcodesFromPkPasses(this@MainActivity, data)
else -> {
Log.e(TAG, "Wrong mime-type")
return@withContext null
}
}
} ?: return@launch

processParseResultList(parseResultList)
if (parseResultList.isEmpty()) {
finish()
return@launch
}

processParseResultList(parseResultList)
}
}

private fun extractIntentFields(intent: Intent) {
Expand Down
23 changes: 17 additions & 6 deletions app/src/main/java/protect/card_locker/ScanActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.zxing.DecodeHintType
import com.google.zxing.ResultPoint
import com.journeyapps.barcodescanner.BarcodeCallback
import com.journeyapps.barcodescanner.BarcodeResult
import com.journeyapps.barcodescanner.CaptureManager
import com.journeyapps.barcodescanner.DecoratedBarcodeView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import protect.card_locker.databinding.CustomBarcodeScannerBinding
import protect.card_locker.databinding.ScanActivityBinding

Expand Down Expand Up @@ -322,15 +326,22 @@ class ScanActivity : CatimaAppCompatActivity() {
private fun handleActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(resultCode, resultCode, intent)

val parseResultList: List<ParseResult> =
Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this)
lifecycleScope.launch(Dispatchers.IO) {
val parseResultList =
Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this@ScanActivity)

if (parseResultList.isEmpty()) {
setScannerActive(true)
return
}
withContext(Dispatchers.Main) {
if (parseResultList.isEmpty()) {
setScannerActive(true)
return@withContext
}

processParseResultList(parseResultList)
}
}
}

private fun processParseResultList(parseResultList: List<ParseResult>) {
Utils.makeUserChooseParseResultFromList(
this,
parseResultList,
Expand Down
57 changes: 37 additions & 20 deletions app/src/main/java/protect/card_locker/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import android.hardware.camera2.CameraManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.text.Layout;
Expand Down Expand Up @@ -118,6 +120,15 @@ public class Utils {
static final int BITMAP_SIZE_SMALL = 512;
static final int BITMAP_SIZE_BIG = 1600;

private static void showToast(Context context, int message) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
} else {
new Handler(Looper.getMainLooper()).post(
() -> Toast.makeText(context, message, Toast.LENGTH_LONG).show());
}
}
Comment on lines +123 to +130

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 think it'd be better if you could pass the toast length to this function, so that it is reusable also for short toasts in the future.

And adding a comment to this function to explain what it does, something like "displays toast instantly if on the main UI loop, otherwise ask Android to show it on the UI loop" would help clarity on why this function exists.

Also, all other functions seem to be static private instead of private static. Probably good to order it the same for easier skimming of the code.


static public LetterBitmap generateIcon(Context context, LoyaltyCard loyaltyCard, boolean forShortcut) {
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
}
Expand Down Expand Up @@ -153,7 +164,7 @@ static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri u

if (uri == null) {
Log.e(TAG, "Uri did not contain any data");
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingImage);
return new ArrayList<>();
}

Expand All @@ -163,15 +174,15 @@ static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri u
} catch (IOException e) {
Log.e(TAG, "Error getting data from image file");
e.printStackTrace();
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingImage);
return new ArrayList<>();
}

List<ParseResult> barcodesFromBitmap = getBarcodesFromBitmap(bitmap);

if (barcodesFromBitmap.isEmpty()) {
Log.i(TAG, "No barcode found in image file");
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
showToast(context, R.string.noBarcodeFound);
}

return barcodesFromBitmap;
Expand All @@ -181,7 +192,7 @@ static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri
Log.i(TAG, "Received Pkpass file with possible barcode");
if (uri == null) {
Log.e(TAG, "Pkpass did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}

Expand All @@ -190,7 +201,7 @@ static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri
pkpassParser = new PkpassParser(context, uri);
} catch (Exception e) {
Log.e(TAG, "Error reading pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}

Expand All @@ -200,7 +211,7 @@ static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri
return Collections.singletonList(new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(null)));
} catch (Exception e) {
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}
}
Expand All @@ -212,7 +223,7 @@ static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(locale));
} catch (Exception e) {
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}
parseResult.setNote(locale);
Expand All @@ -226,7 +237,7 @@ static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Ur
Log.i(TAG, "Received Pkpasses file with possible barcode");
if (uri == null) {
Log.e(TAG, "Pkpasses did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}

Expand All @@ -235,7 +246,7 @@ static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Ur
pkpassesParser = new PkpassesParser(context, uri);
} catch (Exception e) {
Log.e(TAG, "Error reading pkpasses file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}

Expand All @@ -249,7 +260,7 @@ static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Ur
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(null));
} catch (Exception e) {
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}
parseResult.setNote(String.format(context.getString(R.string.cardWithNumber), i+1));
Expand All @@ -260,7 +271,7 @@ static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Ur
parseResult = new ParseResult(ParseResultType.FULL, pkpassParser.toLoyaltyCard(locale));
} catch (Exception e) {
Log.e(TAG, "Error calling toLoyaltyCard on pkpass file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}
parseResult.setNote(String.format(context.getString(R.string.cardWithNumberAndLocale), i+1, locale));
Expand All @@ -278,7 +289,7 @@ static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri
Log.i(TAG, "Received PDF file with possible barcode");
if (uri == null) {
Log.e(TAG, "Uri did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}

Expand Down Expand Up @@ -315,7 +326,7 @@ static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri
}
} catch (IOException e) {
Log.e(TAG, "Error reading PDF file", e);
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
} finally {
// Resource handling
if (renderer != null) {
Expand All @@ -332,7 +343,7 @@ static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri

if (barcodesFromPdfPages.isEmpty()) {
Log.i(TAG, "No barcode found in pdf file");
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
showToast(context, R.string.noBarcodeFound);
}
return barcodesFromPdfPages;
}
Expand Down Expand Up @@ -365,19 +376,25 @@ static public List<ParseResult> parseSetBarcodeActivityResult(int requestCode, i
}

if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
Uri intentData = intent.getData();
Uri intentData = intent != null ? intent.getData() : null;

if (intentData == null) {
Log.e(TAG, "Uri did not contain any data");
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}

if (Objects.equals(context.getContentResolver().getType(intentData), "application/vnd.apple.pkpasses")) {
return retrieveBarcodesFromPkPasses(context, intentData);
}
try {
if (Objects.equals(context.getContentResolver().getType(intentData), "application/vnd.apple.pkpasses")) {
return retrieveBarcodesFromPkPasses(context, intentData);
}

return retrieveBarcodesFromPkPass(context, intentData);
return retrieveBarcodesFromPkPass(context, intentData);
} catch (Exception e) {
Log.e(TAG, "Error reading pkpass file", e);
showToast(context, R.string.errorReadingFile);
return new ArrayList<>();
}
}

if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
Expand Down
Loading