mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2026-09-10 05:08:58 -04:00
Compare commits
87
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e212fb27cf | ||
|
|
a8973296ee | ||
|
|
0d3fe0b253 | ||
|
|
275880944d | ||
|
|
36f38f9b3e | ||
|
|
e68dbe255f | ||
|
|
3fffc83c08 | ||
|
|
03e1f5c672 | ||
|
|
40295f7f37 | ||
|
|
bbf00e1d90 | ||
|
|
61637599e3 | ||
|
|
b748f2e002 | ||
|
|
f3424b716d | ||
|
|
3a413127a5 | ||
|
|
352086887d | ||
|
|
2e3770706c | ||
|
|
92abf69236 | ||
|
|
c9e6606524 | ||
|
|
9e58c9da44 | ||
|
|
b99204c55b | ||
|
|
15035fe38e | ||
|
|
5d58fae618 | ||
|
|
363fce3064 | ||
|
|
868f12f072 | ||
|
|
3332cf732e | ||
|
|
abec7ea821 | ||
|
|
555f3dff46 | ||
|
|
59e14a516f | ||
|
|
6fc798dfc6 | ||
|
|
b9462df24a | ||
|
|
9344df33d7 | ||
|
|
fbec74ae54 | ||
|
|
d493733abd | ||
|
|
dbae33492d | ||
|
|
2b8b48afdc | ||
|
|
3bb8b94c6d | ||
|
|
3b0f3b3a5b | ||
|
|
2885357b80 | ||
|
|
d08bbb5e3c | ||
|
|
1da01b846a | ||
|
|
1f2870d3d8 | ||
|
|
a0a2f061bd | ||
|
|
c70b3ec172 | ||
|
|
a3ad6a8568 | ||
|
|
1a9d162370 | ||
|
|
950d19ce8c | ||
|
|
b531081806 | ||
|
|
b46313a112 | ||
|
|
b54fc85fdb | ||
|
|
b71a2a0bbf | ||
|
|
8ba44eeba5 | ||
|
|
8ef9f39fb7 | ||
|
|
86543c8fb9 | ||
|
|
3c8bae3acc | ||
|
|
0a19db7c7e | ||
|
|
7b55f593d0 | ||
|
|
ced6b08e6c | ||
|
|
23589e88df | ||
|
|
4bc38a1162 | ||
|
|
69ce67cda4 | ||
|
|
ce76901d6e | ||
|
|
fa4b89d3a9 | ||
|
|
3bac76db8f | ||
|
|
f93e7d857a | ||
|
|
1b894e5043 | ||
|
|
08508952fc | ||
|
|
e7159b7980 | ||
|
|
223b5a04db | ||
|
|
b7aa25a5e3 | ||
|
|
1d9fbd6633 | ||
|
|
4be04fb057 | ||
|
|
efdb68cb90 | ||
|
|
1f61b00ed5 | ||
|
|
7afa0399d4 | ||
|
|
efe441b0bd | ||
|
|
59efd0892f | ||
|
|
1cee23baad | ||
|
|
d13519a75f | ||
|
|
5a12769f22 | ||
|
|
14ed18c4d0 | ||
|
|
59e2083511 | ||
|
|
a9daca77a2 | ||
|
|
8b67797e71 | ||
|
|
4195c2b75e | ||
|
|
5e3bb55ba5 | ||
|
|
0a0c378f56 | ||
|
|
2e4fb34dbe |
No files matched your search
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased (Android) - 1003
|
||||
|
||||
- Fix list widget not updating when opening a card from it
|
||||
- Fix opening pkpass/pdf files from network locations sometimes failing
|
||||
|
||||
## v1.0.1 (Wear OS) - 201 (2026-08-28)
|
||||
|
||||
- Add scroll bar to card list
|
||||
- Add splash screen
|
||||
- Fix some padding issues
|
||||
- Remove barcode value from card view
|
||||
|
||||
## v2.45.0 (Android) - 1002 (2026-08-12)
|
||||
|
||||
- Add a Bluetooth server for Wear OS companion app support
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima Ladění</string>
|
||||
<string name="app_name">Catima (ladicí verze)</string>
|
||||
</resources>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
@@ -524,36 +526,54 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun importFile(data: Uri?, receivedType: String) {
|
||||
lifecycleScope.launch {
|
||||
val parseResultList: MutableList<ParseResult?> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
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
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Parsing failed: $e");
|
||||
e.printStackTrace();
|
||||
Utils.showToast(this@MainActivity, R.string.errorReadingFile, Toast.LENGTH_LONG);
|
||||
return@withContext null;
|
||||
}
|
||||
} ?: return@launch
|
||||
|
||||
if (parseResultList.isEmpty()) {
|
||||
Utils.showToast(this@MainActivity, R.string.noBarcodeFound, Toast.LENGTH_LONG);
|
||||
finish()
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
// Give up if we should parse but there is nothing to parse
|
||||
if (parseResultList == null || parseResultList.isEmpty()) {
|
||||
finish()
|
||||
return
|
||||
processParseResultList(parseResultList)
|
||||
}
|
||||
|
||||
processParseResultList(parseResultList)
|
||||
}
|
||||
|
||||
private fun extractIntentFields(intent: Intent) {
|
||||
|
||||
@@ -22,6 +22,8 @@ class PkpassesParser(context: Context, uri: Uri?) {
|
||||
throw IOException(context.getString(R.string.errorReadingFile))
|
||||
}
|
||||
|
||||
var hasPkpassFiles = false
|
||||
|
||||
try {
|
||||
mContext.contentResolver.openInputStream(uri).use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zipInputStream ->
|
||||
@@ -42,6 +44,9 @@ class PkpassesParser(context: Context, uri: Uri?) {
|
||||
// Ignore non-pkpass files
|
||||
if (!localFileHeader.fileName.endsWith(".pkpass")) continue
|
||||
|
||||
// Mark as found
|
||||
hasPkpassFiles = true
|
||||
|
||||
// Extract .pkpass (.zip) inside .pkpasses to cache directory
|
||||
val tempFileName = "pkpassparser_" + System.currentTimeMillis() + "_" + localFileHeader.fileName
|
||||
val tempFile = Utils.copyToTempFile(mContext, zipInputStream, tempFileName)
|
||||
@@ -61,6 +66,11 @@ class PkpassesParser(context: Context, uri: Uri?) {
|
||||
} catch (e: Exception) {
|
||||
throw e
|
||||
}
|
||||
|
||||
if (!hasPkpassFiles) {
|
||||
Log.d(TAG, "No pkpass files found")
|
||||
throw IOException(mContext.getString(R.string.errorReadingFile))
|
||||
}
|
||||
}
|
||||
|
||||
fun getPkpassParsers(): ArrayList<PkpassParser> {
|
||||
|
||||
@@ -30,6 +30,7 @@ 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
|
||||
@@ -37,6 +38,9 @@ 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
|
||||
|
||||
@@ -322,15 +326,29 @@ 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 == null) {
|
||||
Utils.showToast(this@ScanActivity, R.string.errorReadingFile, Toast.LENGTH_LONG)
|
||||
setScannerActive(true)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
if (parseResultList.isEmpty()) {
|
||||
Utils.showToast(this@ScanActivity, R.string.noBarcodeFound, Toast.LENGTH_LONG)
|
||||
setScannerActive(true)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
processParseResultList(parseResultList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun processParseResultList(parseResultList: List<ParseResult>) {
|
||||
Utils.makeUserChooseParseResultFromList(
|
||||
this,
|
||||
parseResultList,
|
||||
|
||||
@@ -19,6 +19,8 @@ import android.hardware.camera2.CameraAccessException;
|
||||
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;
|
||||
@@ -118,6 +120,16 @@ public class Utils {
|
||||
static final int BITMAP_SIZE_SMALL = 512;
|
||||
static final int BITMAP_SIZE_BIG = 1600;
|
||||
|
||||
// Displays the toast immediately on the main UI loop, or asks Android to display it there otherwise.
|
||||
static public void showToast(Context context, int message, int duration) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
Toast.makeText(context, message, duration).show();
|
||||
} else {
|
||||
new Handler(Looper.getMainLooper()).post(
|
||||
() -> Toast.makeText(context, message, duration).show());
|
||||
}
|
||||
}
|
||||
|
||||
static public LetterBitmap generateIcon(Context context, LoyaltyCard loyaltyCard, boolean forShortcut) {
|
||||
return generateIcon(context, loyaltyCard.store, loyaltyCard.headerColor, forShortcut);
|
||||
}
|
||||
@@ -148,50 +160,35 @@ public class Utils {
|
||||
tileLetterFontSize, pixelSize, pixelSize, backgroundColor, ForegroundColorHelper.Companion.needsDarkForeground(backgroundColor) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) {
|
||||
static public List<ParseResult> retrieveBarcodesFromImage(Context context, Uri uri) throws FileNotFoundException {
|
||||
Log.i(TAG, "Received image file with possible barcode");
|
||||
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "Uri did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
Bitmap bitmap;
|
||||
try {
|
||||
bitmap = retrieveImageFromUri(context, uri);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error getting data from image file");
|
||||
e.printStackTrace();
|
||||
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error reading image file", e);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
return barcodesFromBitmap;
|
||||
// Returns barcodes or an empty list if nothing found
|
||||
return getBarcodesFromBitmap(bitmap);
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri uri) {
|
||||
static public List<ParseResult> retrieveBarcodesFromPkPass(Context context, Uri uri) throws FileNotFoundException {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
PkpassParser pkpassParser;
|
||||
try {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error reading pkpass file", e);
|
||||
}
|
||||
|
||||
List<String> locales = pkpassParser.listLocales();
|
||||
@@ -199,9 +196,7 @@ public class Utils {
|
||||
try {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,9 +206,7 @@ public class Utils {
|
||||
try {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
|
||||
}
|
||||
parseResult.setNote(locale);
|
||||
parseResultList.add(parseResult);
|
||||
@@ -222,21 +215,17 @@ public class Utils {
|
||||
return parseResultList;
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Uri uri) {
|
||||
static public List<ParseResult> retrieveBarcodesFromPkPasses(Context context, Uri uri) throws FileNotFoundException {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
PkpassesParser pkpassesParser;
|
||||
try {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error reading pkpasses file", e);
|
||||
}
|
||||
|
||||
List<ParseResult> parseResultList = new ArrayList<>();
|
||||
@@ -248,9 +237,7 @@ public class Utils {
|
||||
try {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
|
||||
}
|
||||
parseResult.setNote(String.format(context.getString(R.string.cardWithNumber), i+1));
|
||||
parseResultList.add(parseResult);
|
||||
@@ -259,9 +246,7 @@ public class Utils {
|
||||
try {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new IllegalArgumentException("Error calling toLoyaltyCard on pkpass file", e);
|
||||
}
|
||||
parseResult.setNote(String.format(context.getString(R.string.cardWithNumberAndLocale), i+1, locale));
|
||||
parseResultList.add(parseResult);
|
||||
@@ -274,12 +259,10 @@ public class Utils {
|
||||
return parseResultList;
|
||||
}
|
||||
|
||||
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) {
|
||||
static public List<ParseResult> retrieveBarcodesFromPdf(Context context, Uri uri) throws FileNotFoundException {
|
||||
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();
|
||||
return new ArrayList<>();
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
ParcelFileDescriptor parcelFileDescriptor = null;
|
||||
@@ -314,8 +297,7 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading PDF file", e);
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
throw new IllegalArgumentException("Error reading PDF file", e);
|
||||
} finally {
|
||||
// Resource handling
|
||||
if (renderer != null) {
|
||||
@@ -330,10 +312,7 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
if (barcodesFromPdfPages.isEmpty()) {
|
||||
Log.i(TAG, "No barcode found in pdf file");
|
||||
Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
// Returns barcodes or an empty list if nothing found
|
||||
return barcodesFromPdfPages;
|
||||
}
|
||||
|
||||
@@ -348,6 +327,7 @@ public class Utils {
|
||||
* @param context
|
||||
* @return List<ParseResult>
|
||||
*/
|
||||
@Nullable
|
||||
static public List<ParseResult> parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
|
||||
String contents;
|
||||
String format;
|
||||
@@ -356,28 +336,35 @@ public class Utils {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
|
||||
return retrieveBarcodesFromImage(context, intent.getData());
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
|
||||
return retrieveBarcodesFromPdf(context, intent.getData());
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
|
||||
Uri intentData = intent.getData();
|
||||
|
||||
if (intentData == null) {
|
||||
Log.e(TAG, "Uri did not contain any data");
|
||||
Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
|
||||
return new ArrayList<>();
|
||||
try {
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
|
||||
return retrieveBarcodesFromImage(context, intent.getData());
|
||||
}
|
||||
|
||||
if (Objects.equals(context.getContentResolver().getType(intentData), "application/vnd.apple.pkpasses")) {
|
||||
return retrieveBarcodesFromPkPasses(context, intentData);
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
|
||||
return retrieveBarcodesFromPdf(context, intent.getData());
|
||||
}
|
||||
|
||||
return retrieveBarcodesFromPkPass(context, intentData);
|
||||
if (requestCode == Utils.BARCODE_IMPORT_FROM_PKPASS_FILE) {
|
||||
Uri intentData = intent != null ? intent.getData() : null;
|
||||
|
||||
if (intentData == null) {
|
||||
throw new FileNotFoundException("Uri did not contain any data");
|
||||
}
|
||||
|
||||
// Mime type info is not always available. So we're going to first guess it's a pkpass file and if that fails, try pkpasses
|
||||
try {
|
||||
return retrieveBarcodesFromPkPass(context, intentData);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error reading pkpass file, attempting as pkpasses file", e);
|
||||
return retrieveBarcodesFromPkPasses(context, intentData);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Return null instead of an empty list to differentiate between error or no result
|
||||
Log.e(TAG, "Parsing failed: " + e);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
|
||||
|
||||
@@ -96,6 +96,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
private long initTime = System.currentTimeMillis();
|
||||
|
||||
private final ListWidget listWidget = new ListWidget();
|
||||
|
||||
private enum AdjacentCardDirection {
|
||||
PREVIOUS,
|
||||
NEXT
|
||||
@@ -454,6 +456,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
invalidateOptionsMenu();
|
||||
|
||||
ShortcutHelper.updateShortcuts(this);
|
||||
listWidget.updateAll(LoyaltyCardViewActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -775,7 +778,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
} else if (id == R.id.action_star_unstar) {
|
||||
DBHelper.updateLoyaltyCardStarStatus(database, loyaltyCardId, loyaltyCard.starStatus == 0 ? 1 : 0);
|
||||
|
||||
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
|
||||
listWidget.updateAll(LoyaltyCardViewActivity.this);
|
||||
|
||||
// Re-init loyaltyCard with new data from DB
|
||||
onResume();
|
||||
@@ -786,7 +789,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
DBHelper.updateLoyaltyCardArchiveStatus(database, loyaltyCardId, 1);
|
||||
Toast.makeText(LoyaltyCardViewActivity.this, R.string.archived, Toast.LENGTH_LONG).show();
|
||||
|
||||
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
|
||||
listWidget.updateAll(LoyaltyCardViewActivity.this);
|
||||
|
||||
// Re-init loyaltyCard with new data from DB
|
||||
onResume();
|
||||
@@ -811,7 +814,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
|
||||
|
||||
DBHelper.deleteLoyaltyCard(database, LoyaltyCardViewActivity.this, loyaltyCardId);
|
||||
|
||||
new ListWidget().updateAll(LoyaltyCardViewActivity.this);
|
||||
listWidget.updateAll(LoyaltyCardViewActivity.this);
|
||||
|
||||
finish();
|
||||
dialog.dismiss();
|
||||
|
||||
@@ -4,8 +4,8 @@ solokot
|
||||
J. Lavoie
|
||||
Allan Nordhøy
|
||||
Oğuz Ersen
|
||||
Heimen Stoffels
|
||||
B o d o
|
||||
Heimen Stoffels
|
||||
FC (Fay) Stegerman
|
||||
大王叫我来巡山
|
||||
StoyanDimitrov
|
||||
@@ -14,28 +14,28 @@ Katharine Chui / Katie
|
||||
Silvério Santos
|
||||
mondstern
|
||||
IllusiveMan196
|
||||
Edgars Andersons
|
||||
Максим Горпиніч
|
||||
Liner Seven
|
||||
Fjuro
|
||||
Priit Jõerüüt
|
||||
Edgars Andersons
|
||||
Liner Seven
|
||||
Максим Горпиніч
|
||||
GitSpoon
|
||||
Priit Jõerüüt
|
||||
Joel A
|
||||
Altonss
|
||||
Michael Moroni
|
||||
Eric
|
||||
GM
|
||||
laralem
|
||||
Petr Novák
|
||||
laralem
|
||||
Vasilis
|
||||
Taco
|
||||
Milo Ivir
|
||||
nadiafekihahmed
|
||||
pfaffenrodt
|
||||
josé m
|
||||
Scrambled777
|
||||
damjang
|
||||
Aayush Gupta
|
||||
damjang
|
||||
Scrambled777
|
||||
josé m
|
||||
Kachelkaiser
|
||||
ikanakova
|
||||
Nyatsuki
|
||||
@@ -46,21 +46,23 @@ HudobniVolk
|
||||
Warder
|
||||
Balázs Meskó
|
||||
Traductor
|
||||
AMIR-G98
|
||||
Samantaz Fox
|
||||
mdvhimself
|
||||
AMIR-G98
|
||||
NooB9496
|
||||
Cliff Heraldo
|
||||
Sergio Paredes
|
||||
Ankit Tiwari
|
||||
Arno-github
|
||||
Feike
|
||||
NooB9496
|
||||
Jose Delvani
|
||||
Milan Šalka
|
||||
Robin
|
||||
தமிழ்நேரம்
|
||||
huuhaa
|
||||
Watchman89
|
||||
Skrripy
|
||||
Hosted Weblate user 54392
|
||||
Govindgopalyadav
|
||||
Gideon
|
||||
waffshappen
|
||||
@@ -70,7 +72,6 @@ pacavarn
|
||||
ngocanhtve
|
||||
josé m.
|
||||
aradxxx
|
||||
Watchman89
|
||||
StellarSand
|
||||
Quentin PAGÈS
|
||||
Projjal Moitra
|
||||
@@ -78,26 +79,26 @@ Aliaksandr Trush
|
||||
e-michalak
|
||||
தமிழ் நேரம்
|
||||
hajertabbane
|
||||
inavleb
|
||||
Ziad OUALHADJ
|
||||
Robin Liu
|
||||
Ricky Tigg
|
||||
Renko
|
||||
Hosted Weblate user 54392
|
||||
Denis Shilin
|
||||
Renko
|
||||
Ricky Tigg
|
||||
Robin Liu
|
||||
Ziad OUALHADJ
|
||||
inavleb
|
||||
しいたけ
|
||||
Alexander Ivanov
|
||||
Thu-Trang Pham
|
||||
Tim Trek
|
||||
Virginie
|
||||
dukezas
|
||||
ehrt74
|
||||
ezn24
|
||||
mrestivill
|
||||
stavpup
|
||||
Miha Frangež
|
||||
stavpup
|
||||
mrestivill
|
||||
ezn24
|
||||
ehrt74
|
||||
dukezas
|
||||
Virginie
|
||||
Tim Trek
|
||||
Thu-Trang Pham
|
||||
Alì Mortacci
|
||||
Andre Costa
|
||||
Arif Budiman
|
||||
arshbeerSingh
|
||||
Augustin LAVILLE
|
||||
Freddo espresso
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<string name="action_search">Cerca</string>
|
||||
<string name="deleteTitle">Elimina la targeta</string>
|
||||
<string name="welcome">Benvingut a Catima</string>
|
||||
<string name="noGiftCards">Fes clic al botó + per afegir una targeta, o importa des del menú ⋮</string>
|
||||
<string name="noGiftCards">Fes clic al botó mes + per afegir una targeta, o importa des del menú ⋮</string>
|
||||
<string name="photos">Fotos</string>
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="moveDown">Baixar abaix</string>
|
||||
@@ -91,7 +91,7 @@
|
||||
<string name="importCancelled">Importació anulada</string>
|
||||
<string name="exportCancelled">Exportació cancelada</string>
|
||||
<string name="noGiftCardsGroup">Crea algunes targetes i després asigna-les en al grup aquí</string>
|
||||
<string name="noMatchingGiftCards">No hi ha resultats; prova de modificar la cerca.</string>
|
||||
<string name="noMatchingGiftCards">No hi ha resultats. Prova de modificar la cerca.</string>
|
||||
<string name="storeName">Nom</string>
|
||||
<string name="note">Nota</string>
|
||||
<string name="cardId">Id. de la Targeta</string>
|
||||
@@ -153,7 +153,7 @@
|
||||
<string name="enter_card_id">Introdueixi el nombre ID o text de la seva targeta</string>
|
||||
<string name="card_id_must_not_be_empty">ID de la Targeta no pot estar buit</string>
|
||||
<string name="add_a_card_in_a_different_way">Afegeix una targeta de forma diferent</string>
|
||||
<string name="unstar">Treure entre les preferides</string>
|
||||
<string name="unstar">Treure de les preferides</string>
|
||||
<string name="star">Afegir a les preferides</string>
|
||||
<string name="cancel">Cancel•la</string>
|
||||
<string name="deleteConfirmation">Vols eliminar de forma permanent aquesta targeta?</string>
|
||||
|
||||
@@ -287,4 +287,9 @@
|
||||
<string name="cardWithNumber">کارت <xliff:g>%d</xliff:g></string>
|
||||
<string name="cardWithNumberAndLocale">کارت <xliff:g>%d</xliff:g> (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="pleaseDoNotRotateTheDevice">لطفا دستگاهتان را نچرخوانید، زیرا کار در حال انجام را لغو میکند</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card">توقف NFC</string>
|
||||
<string name="settings_disable_nfc_while_viewing_card_summary">NFC را متوقف میکند تا هنگام مشاهده کارت، پرداختهای NFC فعال نشوند.</string>
|
||||
<string name="settings_wear_sync_blocked_devices">دستگاههای Wear OS مسدودشده</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">مدیریت دستگاههای Wear OS مسدودشده</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">هیچ دستگاه مسدودشدهای وجود ندارد</string>
|
||||
</resources>
|
||||
@@ -168,7 +168,7 @@
|
||||
<string name="version_history">Historique des versions</string>
|
||||
<string name="exportPassword">Définissez un mot de passe pour protéger vos exportations (facultatif)</string>
|
||||
<string name="exportPasswordHint">Entrez le mot de passe</string>
|
||||
<string name="editGroup">Modification du groupe : <xliff:g> %s </xliff:g></string>
|
||||
<string name="editGroup">Modification du groupe : <xliff:g>%s</xliff:g></string>
|
||||
<string name="noGiftCardsGroup">Créez des cartes, puis affectez-les au groupe ici</string>
|
||||
<string name="group_edit">Modifier le groupe</string>
|
||||
<string name="group_name_already_in_use">Nom de groupe déjà utilisé</string>
|
||||
@@ -334,9 +334,9 @@
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> appareils bloqués</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Supprimer l\'appareil ?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Supprimer <xliff:g>%1$s</xliff:g> de Catima ? Il ne pourra plus synchroniser de cartes.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Supprimer <xliff:g>%1$s</xliff:g> de la liste des appareils bloqués ? Il pourra demander l\'accès à nouveau.</string>
|
||||
<string name="settings_wear_sync_unpair">Supprimer</string>
|
||||
<string name="settings_wear_sync_unpair_message">Retirer <xliff:g>%1$s</xliff:g> de Catima ? Il ne pourra plus synchroniser de cartes.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Retirer <xliff:g>%1$s</xliff:g> de la liste des appareils bloqués ? Il pourra demander l\'accès à nouveau.</string>
|
||||
<string name="settings_wear_sync_unpair">Retirer</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Appuyer sur un appareil pour le supprimer.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Appairage d\'appareil Wear OS</string>
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
<string name="show_note">Tampilkan catatan</string>
|
||||
<string name="permissionReadCardsLabel">Bacalah kartu Catima</string>
|
||||
<string name="permissionReadCardsDescription">baca kartu Anda dan semua detailnya, termasuk catatan dan gambar</string>
|
||||
<string name="settings_allow_content_provider_read_title">Izinkan aplikasi lain mengakses data saya</string>
|
||||
<string name="settings_allow_content_provider_read_title">Sinkronkan dengan Gadgetbridge</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Memungkinkan aplikasi Gadgetbridge menyinkronkan kartu Anda ke jam tangan yang didukung</string>
|
||||
<string name="settings_keep_screen_on_summary">Menonaktifkan batas waktu layar saat melihat kartu</string>
|
||||
<string name="settings_oled_dark_summary">Mengurangi penggunaan baterai pada layar OLED</string>
|
||||
|
||||
@@ -314,4 +314,5 @@
|
||||
<string name="zoomFrontImage">Zoom immagine fronte</string>
|
||||
<string name="zoomBackImage">Zoom immagine retro</string>
|
||||
<string name="open_in_gallery">Apri nella galleria immagini</string>
|
||||
<string name="open_settings">Apri le impostazioni</string>
|
||||
</resources>
|
||||
@@ -222,8 +222,8 @@
|
||||
<string name="permissionReadCardsDescription">당신의 Catima 카드와 그에 대한 모든 세부 정보, 메모 및 이미지를 읽으세요</string>
|
||||
<string name="settings_oled_dark_summary">OLED 화면에서의 배터리 사용량 줄임</string>
|
||||
<string name="app_copyright_short">저작권 © Sylvia van Os 및 기여자</string>
|
||||
<string name="settings_allow_content_provider_read_summary">접근을 위해서는 해당 앱에서 별도로 권한 요청을 해야 합니다</string>
|
||||
<string name="settings_allow_content_provider_read_title">다른 앱의 내 정보 접근 허용</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Gadgetbridge 앱이 카드를 지원하는 시계와 동기화하도록 허용</string>
|
||||
<string name="settings_allow_content_provider_read_title">Gadgetbridge와 동기화</string>
|
||||
<string name="permissionReadCardsLabel">Catima 카드 읽기</string>
|
||||
<string name="settings_keep_screen_on_summary">카드가 보일 때 화면 꺼짐 방지</string>
|
||||
<string name="view_online">온라인에서 보기</string>
|
||||
@@ -297,4 +297,45 @@
|
||||
<string name="cardWithNumberAndLocale"><xliff:g>%d</xliff:g> 번 카드 (<xliff:g>%s</xliff:g>)</string>
|
||||
<string name="acra_explain_crash">가능하다면, 오류가 발생했을 때 어떤 작업을 하고 계셨는지 자세히 적어주세요:</string>
|
||||
<string name="barcodeEncoding">바코드 인코딩</string>
|
||||
<string name="settings_wear_sync_devices">허용한 Wear OS 기기</string>
|
||||
<string name="settings_wear_sync_devices_summary">블루투스 동기화를 허용한 기기 관리</string>
|
||||
<string name="settings_wear_sync_no_devices">허용한 기기 없음</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="other">기기 <xliff:g>%d</xliff:g>개 허용함</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">차단한 Wear OS 기기</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">차단한 Wear OS 기기 관리</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">차단한 기기 없음</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="other">기기 <xliff:g>%d</xliff:g>개 차단함</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">기기를 제거하시겠습니까?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Catima에서 <xliff:g>%1$s</xliff:g> 기기를 제거하시겠습니까? 더 이상 카드를 동기화하지 못하게 됩니다.</string>
|
||||
<string name="settings_wear_sync_unblock_message">차단한 목록에서 <xliff:g>%1$s</xliff:g> 기기를 제거하시겠습니까? 접근 요청을 다시 할 수 있게 됩니다.</string>
|
||||
<string name="settings_wear_sync_unpair">제거</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">기기를 눌러 제거하십시오.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Wear OS 기기 페어링</string>
|
||||
<string name="wear_bt_pairing_title">Wear OS 기기가 동기화하려 함</string>
|
||||
<string name="wear_bt_pairing_message"><xliff:g>%1$s</xliff:g> 기기가 카드에 접근하도록 허용합니까?</string>
|
||||
<string name="wear_bt_pairing_allow">허용</string>
|
||||
<string name="wear_bt_pairing_block">차단</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g>의 동기화를 허용했습니다.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> 기기를 차단했습니다.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title"><xliff:g>%1$s</xliff:g>의 새 인증 요청</string>
|
||||
<string name="wear_bt_pairing_mismatch_message">이 기기는 이전에 인증되었지만 지금은 다른 식별 토큰을 제시하고 있습니다. Wear OS 앱을 재설치하거나 데이터를 지웠을 때 이런 현상이 발생할 수 있습니다. 또한 블루투스 MAC 스푸핑 시도일 수도 있습니다.\n\n최근 이 시계에 Catima Wear OS 앱을 재설치한 경우에만 수락하십시오.</string>
|
||||
<string name="open_settings">설정 열기</string>
|
||||
<string name="open_in_gallery">사진 갤러리에서 열기</string>
|
||||
<string name="settings_category_title_smartwatch">스마트워치 지원</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Wear OS 동기화 서비스 시작 실패</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Wear OS 동기화 치명적 오류</string>
|
||||
<string name="wear_bt_channel_name">Wear OS 컴패니언</string>
|
||||
<string name="wear_bt_notification_title">Catima Wear OS 동기화 활성화됨</string>
|
||||
<string name="sort_by_most_recently_added">가장 최근에 추가함</string>
|
||||
<string name="show_card_id">카드 ID 표시</string>
|
||||
<string name="settings_wear_sync">Wear OS와 동기화</string>
|
||||
<string name="settings_wear_sync_summary">Wear OS 연동 앱이 블루투스로 카드를 읽을 수 있도록 허용</string>
|
||||
<string name="wear_sync_permission_required">Catima에서 Wear OS 스마트워치와 동기화하려면 거절하셨던 권한이 필요합니다</string>
|
||||
<string name="zoomFrontImage">전면 사진 확대</string>
|
||||
<string name="zoomBackImage">후면 사진 확대</string>
|
||||
</resources>
|
||||
@@ -337,4 +337,21 @@
|
||||
<string name="settings_category_title_smartwatch">Suporte a smartwatch</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Falhou ao iniciar o serviço de sincronização do Wear OS</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Erro crítico de sincronização do Wear OS</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo permitido</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos permitidos</item>
|
||||
</plurals>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dispositivo bloqueado</item>
|
||||
<item quantity="many"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dispositivos bloqueados</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_message">Remover <xliff:g>%1$s</xliff:g> do Catima? Ele não poderá mais sincronizar seus cartões.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Remover <xliff:g>%1$s</xliff:g> da lista de bloqueados? O item poderá solicitar acesso novamente.</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="wear_bt_pairing_message">Permitir que <xliff:g>%1$s</xliff:g> acesse seus cartões?</string>
|
||||
<string name="wear_bt_pairing_allowed">Agora, a sincronização de <xliff:g>%1$s</xliff:g> é permitida.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> foi bloqueado.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nova solicitação de autorização de <xliff:g>%1$s</xliff:g></string>
|
||||
</resources>
|
||||
@@ -231,8 +231,8 @@
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="settings_keep_screen_on_summary">Med ogledom kartice ohrani vključen zaslon</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">Med ogledom kartice onemogoči zaklepanje zaslona</string>
|
||||
<string name="settings_allow_content_provider_read_title">Dovoli drugim aplikacijam dostop do mojih podatkov</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Aplikacije bodo morale še vedno zahtevati dovoljenje za dostop</string>
|
||||
<string name="settings_allow_content_provider_read_title">Sinhronizacija z Gadgetbridgeom</string>
|
||||
<string name="settings_allow_content_provider_read_summary">Omogoča aplikaciji Gadgetbridge, da sinhronizira tvoje kartice s podprtimi urami</string>
|
||||
<string name="settings_use_volume_keys_navigation">Preklopi kartice z uporabo gumbov za glasnost</string>
|
||||
<string name="settings_use_volume_keys_navigation_summary">Uporabi gumbe za glasnost za preklop prikazane kartice</string>
|
||||
<string name="donate">Daruj</string>
|
||||
@@ -319,4 +319,46 @@
|
||||
<string name="zoomBackImage">Povečaj zadnjo sliko</string>
|
||||
<string name="show_card_id">Prikaži ID kartice</string>
|
||||
<string name="open_in_gallery">Odpri v galeriji slik</string>
|
||||
<string name="settings_wear_sync">Sinhronizacija z Wear OS</string>
|
||||
<string name="settings_wear_sync_summary">Omogoča spremljevalni aplikaciji Wear OS branje tvojih kartic prek Bluetootha</string>
|
||||
<string name="wear_sync_permission_required">Catima potrebuje dovoljenja, ki si jih zavrnil/a, za sinhronizacijo z vašo pametno uro Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Dovoljene naprave Wear OS</string>
|
||||
<string name="settings_wear_sync_devices_summary">Upravljanje naprav, ki imajo dovoljenje za sinhronizacijo prek Bluetootha</string>
|
||||
<string name="settings_wear_sync_no_devices">Ni dovoljenih naprav</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> dovoljena naprava</item>
|
||||
<item quantity="two"><xliff:g>%d</xliff:g> dovoljeni napravi</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> dovoljene naprave</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> dovoljenih naprav</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Blokirane naprave Wear OS</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Upravljanje blokiranih naprav Wear OS</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Ni blokiranih naprav</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> blokirana naprava</item>
|
||||
<item quantity="two"><xliff:g>%d</xliff:g> blokirani napravi</item>
|
||||
<item quantity="few"><xliff:g>%d</xliff:g> blokirane naprave</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> blokiranih naprav</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_unpair_title">Želiš odstraniti napravo?</string>
|
||||
<string name="settings_wear_sync_unpair_message">Želiš odstraniti <xliff:g>%1$s</xliff:g> iz Catime? Naprava ne bo več mogla sinhronizirati tvojih kartic.</string>
|
||||
<string name="settings_wear_sync_unblock_message">Želiš odstraniti <xliff:g>%1$s</xliff:g> s seznama blokiranih naprav? Naprava bo lahko ponovno zaprosila za dostop.</string>
|
||||
<string name="settings_wear_sync_unpair">Odstrani</string>
|
||||
<string name="settings_wear_sync_device_name"><xliff:g>%1$s</xliff:g> (<xliff:g>%2$s</xliff:g>)</string>
|
||||
<string name="settings_wear_sync_tap_to_remove">Tapni napravo, da jo odstraniš.</string>
|
||||
<string name="wear_bt_pairing_channel_name">Povezovanje naprav Wear OS</string>
|
||||
<string name="wear_bt_pairing_title">Naprava Wear OS se želi sinhronizirati</string>
|
||||
<string name="wear_bt_pairing_message">Želiš dovoliti napravi <xliff:g>%1$s</xliff:g> dostop do tvojih kartic?</string>
|
||||
<string name="wear_bt_pairing_allow">Dovoli</string>
|
||||
<string name="wear_bt_pairing_block">Blokiraj</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> ima zdaj dovoljenje za sinhronizacijo.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> je bila blokirana.</string>
|
||||
<string name="wear_bt_pairing_mismatch_title">Nova zahteva za dostop od <xliff:g>%1$s</xliff:g></string>
|
||||
<string name="wear_bt_pairing_mismatch_message">Ta naprava je bila že avtorizirana, zdaj pa prikazuje drugačen identifikacijski žeton. To se zgodi, ko se aplikacija Wear OS ponovno namesti ali se njeni podatki izbrišejo. Lahko pa gre tudi za poskus ponarejanja MAC-naslova prek Bluetootha.\n\nSprejmi le, če si pred kratkim na tej uri ponovno namestil/a aplikacijo Catima Wear OS.</string>
|
||||
<string name="open_settings">Odpri nastavitve</string>
|
||||
<string name="wear_bt_channel_name">Spremljevalna aplikacija Wear OS</string>
|
||||
<string name="wear_bt_notification_title">Sinhronizacija Catima Wear OS je aktivna</string>
|
||||
<string name="settings_category_title_smartwatch">Podpora za pametne ure</string>
|
||||
<string name="wear_bt_failed_foreground_notification_title">Zagon storitve sinhronizacije Wear OS ni uspel</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Kritična napaka pri sinhronizaciji Wear OS</string>
|
||||
</resources>
|
||||
@@ -232,7 +232,7 @@
|
||||
<string name="settings_allow_content_provider_read_summary">Appar måste fortfarande begära åtkomst för att få tillgång</string>
|
||||
<string name="setBarcodeHeight">Ställ in streckkodens höjd</string>
|
||||
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Kopieringsskydd © 2019–<xliff:g>%d</xliff:g> Sylvia van Os och medverkande</string>
|
||||
<string name="settings_allow_content_provider_read_title">Tillåt andra appar att komma åt min data</string>
|
||||
<string name="settings_allow_content_provider_read_title">Synka med Gadgetbridge</string>
|
||||
<string name="permissionReadCardsLabel">Läsa Catima-kort</string>
|
||||
<string name="donate">Donera</string>
|
||||
<string name="show_archived_cards">Visa arkiverade kort</string>
|
||||
@@ -308,4 +308,26 @@
|
||||
<string name="zoomBackImage">Zooma baksidesbilden</string>
|
||||
<string name="show_card_id">Visa kort-ID</string>
|
||||
<string name="open_in_gallery">Öppna i bildgalleriet</string>
|
||||
<string name="settings_wear_sync">Synka med Wear OS</string>
|
||||
<string name="wear_sync_permission_required">Catima behöver få de behörigheter du nekade appen för att kunna synka med din smarta klocka med Wear OS</string>
|
||||
<string name="settings_wear_sync_devices">Tillåtna Wear OS-enheter</string>
|
||||
<string name="settings_wear_sync_devices_summary">Hantera enheter som tillåts synka över Bluetooth</string>
|
||||
<string name="settings_wear_sync_no_devices">Inga tillåtna enheter</string>
|
||||
<plurals name="settings_wear_sync_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> tillåten enhet</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> tillåtna enheter</item>
|
||||
</plurals>
|
||||
<string name="settings_wear_sync_blocked_devices">Blockerade Wear OS-enheter</string>
|
||||
<string name="settings_wear_sync_blocked_devices_summary">Hantera blockerade Wear OS-enheter</string>
|
||||
<string name="settings_wear_sync_no_blocked_devices">Inga blockerade enheter</string>
|
||||
<plurals name="settings_wear_sync_blocked_devices_summary_count">
|
||||
<item quantity="one"><xliff:g>%d</xliff:g> blockerad enhet</item>
|
||||
<item quantity="other"><xliff:g>%d</xliff:g> blockerade enheter</item>
|
||||
</plurals>
|
||||
<string name="wear_bt_pairing_allow">Tillåt</string>
|
||||
<string name="wear_bt_pairing_block">Blockera</string>
|
||||
<string name="wear_bt_pairing_allowed"><xliff:g>%1$s</xliff:g> tillåts nu synka.</string>
|
||||
<string name="wear_bt_pairing_blocked"><xliff:g>%1$s</xliff:g> har blockerats.</string>
|
||||
<string name="wear_bt_critical_error_channel_name">Kritiskt fel vid Wear OS-synkning</string>
|
||||
<string name="open_settings">Öppna inställningar</string>
|
||||
</resources>
|
||||
@@ -222,8 +222,8 @@
|
||||
<string name="app_name">卡提碼</string>
|
||||
<string name="permissionReadCardsDescription">讀取卡提碼卡片及卡片資訊,包含註記與圖片</string>
|
||||
<string name="settings_disable_lockscreen_while_viewing_card_summary">觀看卡片時避免螢幕鎖定</string>
|
||||
<string name="settings_allow_content_provider_read_summary">其他程式仍然需要取得權限</string>
|
||||
<string name="settings_allow_content_provider_read_title">允許其他程式使用我的資料</string>
|
||||
<string name="settings_allow_content_provider_read_summary">允許 Gadgetbridge 應用程式同步卡片到支援的手表</string>
|
||||
<string name="settings_allow_content_provider_read_title">與 Gadgetbridge 同步</string>
|
||||
<string name="settings_keep_screen_on_summary">觀看卡片時避免螢幕關閉</string>
|
||||
<string name="balanceParsingFailed">無效餘額</string>
|
||||
<string name="view_online">網站上顯示</string>
|
||||
@@ -296,4 +296,8 @@
|
||||
<string name="nfc_blocked_while_viewing_card">NFC 已暫停</string>
|
||||
<string name="change_settings">變更設定</string>
|
||||
<string name="nfc_block_system_error">暫停 NFC 失敗</string>
|
||||
<string name="sort_by_most_recently_added">最近添加</string>
|
||||
<string name="show_card_id">顯示卡片 ID</string>
|
||||
<string name="settings_wear_sync">與 WearOS 同步</string>
|
||||
<string name="settings_wear_sync_devices">允許的 Wear OS 裝置</string>
|
||||
</resources>
|
||||
@@ -2,16 +2,21 @@ package protect.card_locker;
|
||||
|
||||
import static android.os.Looper.getMainLooper;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -30,17 +35,197 @@ import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.shadows.ShadowActivity;
|
||||
import org.robolectric.shadows.ShadowContentResolver;
|
||||
import org.robolectric.shadows.ShadowToast;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MainActivityTest {
|
||||
private SharedPreferences prefs;
|
||||
|
||||
@Test
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
public void pkpassSharedIntentReadsOffMainThreadAndProcessesOnMainThread() throws Exception {
|
||||
Uri uri = Uri.parse("content://test/cloud-backed.pkpass");
|
||||
CountDownLatch readStarted = new CountDownLatch(1);
|
||||
CountDownLatch streamClosed = new CountDownLatch(1);
|
||||
AtomicReference<Looper> readLooper = new AtomicReference<>();
|
||||
InputStream pkpass = getClass().getResourceAsStream(
|
||||
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass");
|
||||
assertNotNull(pkpass);
|
||||
new ShadowContentResolver().registerInputStream(
|
||||
uri, new RecordingInputStream(pkpass, readStarted, streamClosed, readLooper));
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW)
|
||||
.setDataAndType(uri, "application/vnd.apple.pkpass");
|
||||
ActivityController<MainActivity> controller =
|
||||
Robolectric.buildActivity(MainActivity.class, intent).create().start().resume().visible();
|
||||
MainActivity activity = controller.get();
|
||||
|
||||
// Do NOT idle the main looper here: the read runs on a real background
|
||||
// thread, so the latch fires without it, and idling would run the
|
||||
// main-thread continuation and defeat the ordering assertion below.
|
||||
assertTrue(readStarted.await(5, TimeUnit.SECONDS));
|
||||
// The read must not happen on the main looper. This is the property the
|
||||
// fix is about; it is asserted directly rather than inferred from
|
||||
// ordering. Robolectric drains the main looper during .visible(), so by
|
||||
// the time the test regains control the whole import has already run to
|
||||
// completion and there is no observable in-between state to assert on.
|
||||
assertTrue(readLooper.get() != getMainLooper());
|
||||
assertTrue(streamClosed.await(5, TimeUnit.SECONDS));
|
||||
|
||||
waitFor(() -> shadowOf(activity).peekNextStartedActivity() != null);
|
||||
Intent editIntent = shadowOf(activity).getNextStartedActivity();
|
||||
assertEquals(LoyaltyCardEditActivity.class.getName(), editIntent.getComponent().getClassName());
|
||||
assertNotNull(editIntent.getExtras());
|
||||
controller.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
public void destroyingActivityCancelsPendingPkpassResult() throws Exception {
|
||||
Uri uri = Uri.parse("content://test/pending-cloud-backed.pkpass");
|
||||
CountDownLatch readStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseRead = new CountDownLatch(1);
|
||||
CountDownLatch streamClosed = new CountDownLatch(1);
|
||||
InputStream pkpass = getClass().getResourceAsStream(
|
||||
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass");
|
||||
assertNotNull(pkpass);
|
||||
new ShadowContentResolver().registerInputStream(
|
||||
uri, new BlockingInputStream(pkpass, readStarted, releaseRead, streamClosed));
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW)
|
||||
.setDataAndType(uri, "application/vnd.apple.pkpass");
|
||||
ActivityController<MainActivity> controller =
|
||||
Robolectric.buildActivity(MainActivity.class, intent).create().start().resume();
|
||||
MainActivity activity = controller.get();
|
||||
|
||||
waitFor(() -> readStarted.getCount() == 0);
|
||||
controller.destroy();
|
||||
releaseRead.countDown();
|
||||
assertTrue(streamClosed.await(5, TimeUnit.SECONDS));
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertNull(shadowOf(activity).peekNextStartedActivity());
|
||||
assertFalse(ShadowToast.showedToast(activity.getString(R.string.errorReadingFile)));
|
||||
}
|
||||
|
||||
private static void waitFor(BooleanSupplier condition) throws Exception {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||
while (!condition.getAsBoolean() && System.nanoTime() < deadline) {
|
||||
shadowOf(getMainLooper()).idle();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
shadowOf(getMainLooper()).idle();
|
||||
assertTrue("Timed out waiting for asynchronous activity work", condition.getAsBoolean());
|
||||
}
|
||||
|
||||
private static class RecordingInputStream extends FilterInputStream {
|
||||
private final CountDownLatch readStarted;
|
||||
private final CountDownLatch streamClosed;
|
||||
private final AtomicReference<Looper> readLooper;
|
||||
|
||||
RecordingInputStream(
|
||||
InputStream inputStream,
|
||||
CountDownLatch readStarted,
|
||||
CountDownLatch streamClosed,
|
||||
AtomicReference<Looper> readLooper) {
|
||||
super(inputStream);
|
||||
this.readStarted = readStarted;
|
||||
this.streamClosed = streamClosed;
|
||||
this.readLooper = readLooper;
|
||||
}
|
||||
|
||||
private void recordRead() {
|
||||
readLooper.compareAndSet(null, Looper.myLooper());
|
||||
readStarted.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
recordRead();
|
||||
return super.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
recordRead();
|
||||
return super.read(buffer, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
streamClosed.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class BlockingInputStream extends FilterInputStream {
|
||||
private final CountDownLatch readStarted;
|
||||
private final CountDownLatch releaseRead;
|
||||
private final CountDownLatch streamClosed;
|
||||
|
||||
BlockingInputStream(
|
||||
InputStream inputStream,
|
||||
CountDownLatch readStarted,
|
||||
CountDownLatch releaseRead,
|
||||
CountDownLatch streamClosed) {
|
||||
super(inputStream);
|
||||
this.readStarted = readStarted;
|
||||
this.releaseRead = releaseRead;
|
||||
this.streamClosed = streamClosed;
|
||||
}
|
||||
|
||||
private void awaitRelease() throws IOException {
|
||||
readStarted.countDown();
|
||||
try {
|
||||
if (!releaseRead.await(5, TimeUnit.SECONDS)) {
|
||||
throw new IOException("Timed out waiting to release provider stream");
|
||||
}
|
||||
} catch (InterruptedException exception) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
awaitRelease();
|
||||
return super.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
awaitRelease();
|
||||
return super.read(buffer, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
streamClosed.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initiallyNoLoyaltyCards() {
|
||||
Activity activity = Robolectric.setupActivity(MainActivity.class);
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Looper
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.android.controller.ActivityController
|
||||
import org.robolectric.annotation.LooperMode
|
||||
import org.robolectric.shadows.ShadowContentResolver
|
||||
import org.robolectric.shadows.ShadowToast
|
||||
import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
class ScanActivityTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
ShadowToast.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pkpassPickerReadsOffMainThreadAndReturnsResultOnMainThread() {
|
||||
val uri = Uri.parse("content://test/picker-cloud-backed.pkpass")
|
||||
val readStarted = CountDownLatch(1)
|
||||
val streamClosed = CountDownLatch(1)
|
||||
val readLooper = AtomicReference<Looper>()
|
||||
val pkpass = javaClass.getResourceAsStream(
|
||||
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass"
|
||||
)
|
||||
assertNotNull(pkpass)
|
||||
ShadowContentResolver().registerInputStream(
|
||||
uri,
|
||||
RecordingInputStream(pkpass, readStarted, streamClosed, readLooper)
|
||||
)
|
||||
val controller = createActivity()
|
||||
val activity = controller.get()
|
||||
|
||||
handlePkpassResult(activity, Intent().setData(uri))
|
||||
|
||||
assertTrue(readStarted.await(5, TimeUnit.SECONDS))
|
||||
// The read must not happen on the main looper. Asserted directly rather
|
||||
// than inferred from an intermediate result code: waiting on the main
|
||||
// looper drains it, so the flow has already reached its final state by
|
||||
// the time the test regains control.
|
||||
assertTrue(readLooper.get() !== Looper.getMainLooper())
|
||||
assertTrue(streamClosed.await(5, TimeUnit.SECONDS))
|
||||
|
||||
waitFor { shadowOf(activity).resultCode == Activity.RESULT_OK }
|
||||
assertNotNull(shadowOf(activity).resultIntent)
|
||||
assertTrue(activity.isFinishing)
|
||||
controller.destroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun providerFailureShowsOneErrorAndReenablesScanner() {
|
||||
val uri = Uri.parse("content://test/unavailable-cloud-backed.pkpass")
|
||||
ShadowContentResolver().registerInputStream(uri, FailingInputStream())
|
||||
val controller = createActivity()
|
||||
val activity = controller.get()
|
||||
setScannerActive(activity, false)
|
||||
val initialToastCount = ShadowToast.shownToastCount()
|
||||
|
||||
handlePkpassResult(activity, Intent().setData(uri))
|
||||
|
||||
waitFor { ShadowToast.shownToastCount() > initialToastCount }
|
||||
assertEquals(initialToastCount + 1, ShadowToast.shownToastCount())
|
||||
assertEquals(activity.getString(R.string.errorReadingFile), ShadowToast.getTextOfLatestToast())
|
||||
assertTrue(isScannerActive(activity))
|
||||
assertFalse(activity.isFinishing)
|
||||
controller.destroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun destroyingActivityCancelsPendingPkpassResult() {
|
||||
val uri = Uri.parse("content://test/pending-picker-cloud-backed.pkpass")
|
||||
val readStarted = CountDownLatch(1)
|
||||
val releaseRead = CountDownLatch(1)
|
||||
val streamClosed = CountDownLatch(1)
|
||||
val pkpass = javaClass.getResourceAsStream(
|
||||
"pkpass/DCBLN24/DCBLN24-QLUKT-1-passbook.pkpass"
|
||||
)
|
||||
assertNotNull(pkpass)
|
||||
ShadowContentResolver().registerInputStream(
|
||||
uri,
|
||||
BlockingInputStream(pkpass, readStarted, releaseRead, streamClosed)
|
||||
)
|
||||
val controller = createActivity()
|
||||
val activity = controller.get()
|
||||
|
||||
handlePkpassResult(activity, Intent().setData(uri))
|
||||
waitFor { readStarted.count == 0L }
|
||||
controller.destroy()
|
||||
releaseRead.countDown()
|
||||
assertTrue(streamClosed.await(5, TimeUnit.SECONDS))
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
|
||||
assertEquals(Activity.RESULT_CANCELED, shadowOf(activity).resultCode)
|
||||
assertFalse(ShadowToast.showedToast(activity.getString(R.string.errorReadingFile)))
|
||||
}
|
||||
|
||||
private fun createActivity(): ActivityController<ScanActivity> =
|
||||
Robolectric.buildActivity(ScanActivity::class.java).create().start().resume().visible()
|
||||
|
||||
private fun handlePkpassResult(activity: ScanActivity, intent: Intent) {
|
||||
ScanActivity::class.java.getDeclaredMethod(
|
||||
"handleActivityResult",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
Intent::class.java
|
||||
).apply { isAccessible = true }.invoke(
|
||||
activity,
|
||||
Utils.BARCODE_IMPORT_FROM_PKPASS_FILE,
|
||||
Activity.RESULT_OK,
|
||||
intent
|
||||
)
|
||||
}
|
||||
|
||||
private fun setScannerActive(activity: ScanActivity, active: Boolean) {
|
||||
ScanActivity::class.java.getDeclaredMethod(
|
||||
"setScannerActive",
|
||||
Boolean::class.javaPrimitiveType
|
||||
).apply { isAccessible = true }.invoke(activity, active)
|
||||
}
|
||||
|
||||
private fun isScannerActive(activity: ScanActivity): Boolean =
|
||||
ScanActivity::class.java.getDeclaredField("mScannerActive")
|
||||
.apply { isAccessible = true }
|
||||
.getBoolean(activity)
|
||||
|
||||
private fun waitFor(condition: () -> Boolean) {
|
||||
val deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5)
|
||||
while (!condition() && System.nanoTime() < deadline) {
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
Thread.sleep(10)
|
||||
}
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
assertTrue("Timed out waiting for asynchronous activity work", condition())
|
||||
}
|
||||
|
||||
private class RecordingInputStream(
|
||||
inputStream: InputStream,
|
||||
private val readStarted: CountDownLatch,
|
||||
private val streamClosed: CountDownLatch,
|
||||
private val readLooper: AtomicReference<Looper>
|
||||
) : FilterInputStream(inputStream) {
|
||||
private fun recordRead() {
|
||||
readLooper.compareAndSet(null, Looper.myLooper())
|
||||
readStarted.countDown()
|
||||
}
|
||||
|
||||
override fun read(): Int {
|
||||
recordRead()
|
||||
return super.read()
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
|
||||
recordRead()
|
||||
return super.read(buffer, offset, length)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
try {
|
||||
super.close()
|
||||
} finally {
|
||||
streamClosed.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockingInputStream(
|
||||
inputStream: InputStream,
|
||||
private val readStarted: CountDownLatch,
|
||||
private val releaseRead: CountDownLatch,
|
||||
private val streamClosed: CountDownLatch
|
||||
) : FilterInputStream(inputStream) {
|
||||
private fun awaitRelease() {
|
||||
readStarted.countDown()
|
||||
try {
|
||||
if (!releaseRead.await(5, TimeUnit.SECONDS)) {
|
||||
throw IOException("Timed out waiting to release provider stream")
|
||||
}
|
||||
} catch (exception: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
throw IOException(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(): Int {
|
||||
awaitRelease()
|
||||
return super.read()
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
|
||||
awaitRelease()
|
||||
return super.read(buffer, offset, length)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
try {
|
||||
super.close()
|
||||
} finally {
|
||||
streamClosed.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FailingInputStream : InputStream() {
|
||||
override fun read(): Int = throw IOException("Remote provider unavailable")
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -2,7 +2,8 @@
|
||||
|
||||
Copylefted libre software (GPLv3+) card management app for Android 6.0 and up.
|
||||
|
||||
[](https://github.com/CatimaLoyalty/Android/releases)
|
||||
[](https://github.com/CatimaLoyalty/Android/releases)
|
||||
[&filter=*-wearos)](https://github.com/CatimaLoyalty/Android/releases)
|
||||
[](https://apt.izzysoft.de/fdroid/index/apk/me.hackerchick.catima)
|
||||
[](https://f-droid.org/packages/me.hackerchick.catima/)
|
||||
[](https://play.google.com/store/apps/details?id=me.hackerchick.catima)
|
||||
@@ -18,6 +19,8 @@ Copylefted libre software (GPLv3+) card management app for Android 6.0 and up.
|
||||
<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" alt="Get it on IzzyOnDroid" height="90"/></a>
|
||||
<a href="https://f-droid.org/repository/browse/?fdid=me.hackerchick.catima" target="_blank">
|
||||
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="90"/></a>
|
||||
<a href="https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22me.hackerchick.catima%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2FCatimaLoyalty%2FAndroid%22%2C%22author%22%3A%22CatimaLoyalty%22%2C%22name%22%3A%22Catima%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5E((%3F!.*Wear.*).)*%24%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Afalse%2C%5C%22sortMethodChoice%5C%22%3A%5C%22smartname%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Atrue%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22minimumUpdateAgeDays%5C%22%3A%5C%22%5C%22%2C%5C%22appName%5C%22%3A%5C%22%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22Sylvia%20van%20Os%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%2C%5C%22includeZips%5C%22%3Afalse%2C%5C%22zippedApkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22includeTarballs%5C%22%3Afalse%2C%5C%22tarballedApkFilterRegEx%5C%22%3A%5C%22%5C%22%7D%22%2C%22overrideSource%22%3Anull%7D" target="_blank">
|
||||
<img src="https://github.com/user-attachments/assets/713d71c5-3dec-4ec4-a3f2-8d28d025a9c6" alt="Get it on Obtainium" height="90"/></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=me.hackerchick.catima" target="_blank">
|
||||
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="90"/></a>
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- Opravena neaktualizace seznamu widgetu při otevření karty z něj
|
||||
@@ -0,0 +1,4 @@
|
||||
- Přidán posuvník na seznam karet
|
||||
- Přidána úvodní obrazovka
|
||||
- Opraveny problémy s odsazením
|
||||
- Odstraněna hodnota čárového kódu ze zobrazení karty
|
||||
@@ -0,0 +1 @@
|
||||
- Fehler behoben, bei welchem das Listen-Widget nicht aktualisiert, wenn eine Karte von dort geöffnet wird
|
||||
@@ -0,0 +1,4 @@
|
||||
- Bildlaufleiste zur Kartenliste hinzugefügt
|
||||
- Startbildschirm hinzugefügt
|
||||
- Einige Probleme mit den Abständen zum Bildschirmrand behoben
|
||||
- Barcode vorübergehend aus der Kartenansicht entfernt
|
||||
@@ -0,0 +1,2 @@
|
||||
- Fix list widget not updating when opening a card from it
|
||||
- Fix opening pkpass/pdf files from network locations sometimes failing
|
||||
@@ -0,0 +1,4 @@
|
||||
- Add scroll bar to card list
|
||||
- Add splash screen
|
||||
- Fix some padding issues
|
||||
- Remove barcode value from card view
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 7.9 KiB |
@@ -0,0 +1 @@
|
||||
- Correction du widget de liste qui ne se met pas à jour lorsqu'on ouvre une carte depuis celui-ci
|
||||
@@ -0,0 +1,4 @@
|
||||
- Ajout d'une barre de défilement dans la liste des cartes
|
||||
- Ajout d'un écran de démarrage
|
||||
- Correction de quelques soucis sur les marges
|
||||
- Suppression de la valeur du code-barres dans la vue des cartes
|
||||
@@ -0,0 +1 @@
|
||||
- カードをリストウィジェットから開いた場合にリストが更新されない問題を修正しました
|
||||
@@ -0,0 +1,4 @@
|
||||
- スクロールばーをカード一覧に追加
|
||||
- スプラッシュ画面を追加
|
||||
- パディングに関する幾つかの問題を修正
|
||||
- カード表示からバーコードの値を削除
|
||||
@@ -0,0 +1,2 @@
|
||||
- Wear OS 컴패니언 앱을 지원하기 위해 블루투스 서버 추가
|
||||
- 내장 변경 내역 제거
|
||||
@@ -0,0 +1 @@
|
||||
- 목록 위젯에서 카드를 열 때 업데이트되지 않던 오류 수정
|
||||
@@ -0,0 +1,3 @@
|
||||
- 카드 목록에서 카드 ID 표시 지원 추가
|
||||
- 가장 최근에 추가된 순으로 정렬 지원 추가
|
||||
- 카드 전면/후면/아이콘 이미지를 확대할 숭 씨도록 내장 이미지 뷰어 추가
|
||||
@@ -0,0 +1 @@
|
||||
- 갤러리의 내장 이미지 뷰어에서 이미지를 여는 옵션 추가
|
||||
@@ -0,0 +1 @@
|
||||
- Wear OS 컴패니언 앱 첫 출시
|
||||
@@ -0,0 +1,4 @@
|
||||
- 카드 목록에 스크롤바 추가
|
||||
- 스플래시 화면 추가
|
||||
- 일부 여백 문제 수정
|
||||
- 카드 보기에서 바코드 값 제거
|
||||
@@ -0,0 +1 @@
|
||||
- Opgelost dat de lijstwidget niet update als er een kaart door geopend wordt
|
||||
@@ -0,0 +1,4 @@
|
||||
- Scrollbalk toegevoegd aan kaartenlijst
|
||||
- Welkomstscherm toegevoegd
|
||||
- Enkele problemen met afstanden tot beeldschermrand opgelost
|
||||
- Barcodewaarde verwijderd van kaartweergave
|
||||
@@ -0,0 +1 @@
|
||||
- Naprawiono błąd, w wyniku którego widżet listy nie aktualizował się po otwarciu karty z tego widżetu
|
||||
@@ -0,0 +1,4 @@
|
||||
- Dodano pasek przewijania do listy kart
|
||||
- Dodano ekran powitalny
|
||||
- Naprawiono pewne problemy związane z odstępami
|
||||
- Usunięto wartość kodu kreskowego z widoku karty
|
||||
@@ -0,0 +1 @@
|
||||
- Corrigir a não atualização do widget de lista ao abrir um cartão a partir dele
|
||||
@@ -1,3 +1,3 @@
|
||||
- Adicionada opção de menu para bloquear a rotação da tela ao exibir um cartão. Se bloqueada, a tela mudará para sua orientação "natural" e novas rotações serão impedidas. (pull #128 (https://github.com/brarcher/loyalty-card-locker/pull/128))
|
||||
- Se um cartão for selecionado na tela principal, mas não puder ser carregado, o aplicativo trata a falha adequadamente e exibe uma mensagem
|
||||
- Corrigido um caso em que os IDs de layout do assistente de introdução não eram encontrados.
|
||||
- Adicionado bloqueio de rotação ao exibir cartão. Se bloqueado, a tela muda para orientação "natural" e impede novas rotações.
|
||||
- Se um cartão na tela principal falhar ao carregar, o app trata o erro e exibe mensagem. (pull #132 (https://github.com/brarcher/loyalty-card-locker/pull/132))
|
||||
- Corrigido erro de IDs de layout do assistente não encontrados. (pull #128 (https://github.com/brarcher/loyalty-card-locker/pull/128))
|
||||
@@ -1,5 +1,5 @@
|
||||
- Adicionado suporte para criar atalhos na tela inicial ao adicionar ou editar um cartão.
|
||||
- Widget removido, pois era um substituto insatisfatório para os atalhos.
|
||||
- Corrigida a exportação de backups no Android 7+.
|
||||
- Informado um tipo mime mais preciso ao exportar dados de backup.
|
||||
- Corrigido um erro que impedia a edição de cartões.
|
||||
- Suporte a atalhos na tela inicial ao criar/editar cartão (pull #155)
|
||||
- Removido widget por ser um péssimo substituto para atalhos (pull #155)
|
||||
- Corrigida a exportação de backups no Android 7+ (pull #153)
|
||||
- Tipo mime mais preciso ao exportar dados de backup (pull #156)
|
||||
- Corrigido erro onde o cartão não podia ser editado (pull #155)
|
||||
@@ -0,0 +1,4 @@
|
||||
- Adicionar barra de rolagem à lista de cartões
|
||||
- Adicionar tela de abertura (splash screen)
|
||||
- Corrigir alguns problemas de espaçamento (padding)
|
||||
- Remover o valor do código de barras da visualização do cartão
|
||||
@@ -1,2 +1,2 @@
|
||||
- Permitir que o usuário insira o código de barras manualmente. Se o usuário optar por inserir um código de barras manualmente, uma lista de todas as imagens de código de barras válidas e suportadas é exibida. O usuário pode então selecionar a imagem do código de barras correspondente ao que deseja.
|
||||
- Corrigir problema em que alguns códigos de barras exibidos ficavam borrados.
|
||||
- Permite inserir código de barras manualmente. Se optado, é exibida lista de imagens válidas e suportadas para seleção. (issue #33, pull #44)
|
||||
- Corrigido problema onde alguns códigos de barras exibidos ficavam borrados (issue #37)
|
||||
@@ -1,2 +1,2 @@
|
||||
- Um clique longo em um cartão exibe a opção de copiar o ID do cartão para a área de transferência. (pull #49 (https://github.com/brarcher/loyalty-card-locker/issues/49))
|
||||
- O botão Voltar na visualização de Importação/Exportação agora funciona, levando o usuário de volta à visualização principal.
|
||||
- O botão Voltar na visualização de Importação/Exportação agora funciona, levando o usuário para a tela principal
|
||||
@@ -1,4 +1,4 @@
|
||||
- O brilho da tela aumenta para o máximo ao exibir um cartão, para ajudar os leitores de código de barras a capturá-lo com sucesso.
|
||||
- Adição de confirmação ao excluir um cartão.
|
||||
- Adição de traduções para alemão e tcheco.
|
||||
- Alteração para maior clareza na tradução para italiano.
|
||||
- Brilho da tela aumenta ao máximo ao exibir cartão para ajudar os leitores na leitura. (pull #54)
|
||||
- Adicionada confirmação ao excluir um cartão. (pull #55)
|
||||
- Adicionadas traduções para alemão (pull #57) e tcheco (pull #58)
|
||||
- Esclarecimento na tradução para o italiano (pull #66)
|
||||
@@ -1,22 +1,23 @@
|
||||
Pare de procurar cartões de fidelidade de plástico na hora de pagar em lojas físicas ou virtuais.
|
||||
<b>Escaneie códigos de barras usando a câmera do seu dispositivo e esqueça os cartões físicos.</b>
|
||||
<b>Escaneie códigos de barras com a câmera do seu dispositivo e esqueça os cartões físicos.</b>
|
||||
|
||||
Esqueça a carteira ou mantenha-a leve, guardando apenas itens essenciais.
|
||||
Deixe a carteira em casa ou mantenha-a leve, guardando apenas itens essenciais de valor.
|
||||
|
||||
Com esta ferramenta indispensável para o dia a dia (EDC), você pode substituir o plástico inútil por uma solução digital prática.
|
||||
Com esta ferramenta indispensável para o dia a dia (EDC), você substitui o plástico inútil pela praticidade digital.
|
||||
|
||||
- Privacidade garantida com o mínimo de permissões: sem acesso à internet e sem anúncios.
|
||||
- Adicione cartões ou códigos com nomes e cores personalizáveis.
|
||||
- Insira códigos manualmente caso não haja código de barras ou se ele não puder ser utilizado.
|
||||
- Importe cartões e códigos de arquivos e de aplicativos como Catima, FidMe, Loyalty Card Keychain e Voucher Vault.
|
||||
- Faça backup de todos os seus cartões e transfira-os para um novo dispositivo, se desejar.
|
||||
- Compartilhe cupons, ofertas exclusivas, códigos promocionais ou os próprios cartões e códigos usando qualquer aplicativo.
|
||||
- Insira códigos manualmente caso não haja código de barras ou se a leitura não for possível.
|
||||
- Importe cartões e códigos de arquivos ou de aplicativos como Catima, FidMe, Loyalty Card Keychain e Voucher Vault.
|
||||
- Faça backup de todos os seus cartões e transfira-os para um novo dispositivo quando quiser.
|
||||
- Compartilhe cupons, ofertas exclusivas, códigos promocionais, cartões ou códigos usando qualquer aplicativo.
|
||||
- Sincronize com smartwatches compatíveis (Gadgetbridge ou Wear OS).
|
||||
- Inclui tema escuro e opções de acessibilidade para usuários com deficiência visual.
|
||||
- Criado para todos pela comunidade de software livre.
|
||||
- Traduções manuais e localizadas para mais de 40 idiomas.
|
||||
- Gratuito e mantido por contribuições da comunidade.
|
||||
- Use, estude, modifique e compartilhe como quiser; <i>com todos</i>.
|
||||
- Mais do que apenas Software Livre / Código Aberto: um gerenciador de cartões de software livre sob licença <i>copyleft</i> (GPLv3+).
|
||||
- Mais do que apenas Software Livre/Código Aberto: um gerenciador de cartões de software livre com licença <i>copyleft</i> (GPLv3+).
|
||||
|
||||
Simplifique sua vida e suas compras; nunca mais perca um recibo de papel, um cartão-presente ou uma passagem aérea.
|
||||
Leve todos os seus cartões de fidelidade e bônus com você e economize sempre.
|
||||
@@ -0,0 +1 @@
|
||||
- Исправлена ошибка, при которой виджет списка не обновлялся при открытии карты из него
|
||||
@@ -0,0 +1,4 @@
|
||||
- Добавлена полоса прокрутки в список карт
|
||||
- Добавлен заставочный экран
|
||||
- Исправлены некоторые проблемы с отступами
|
||||
- Убрано значение штрих-кода из отображения карты
|
||||
@@ -0,0 +1,2 @@
|
||||
- Dodan strežnik Bluetooth za podporo spremljevalni aplikaciji Wear OS
|
||||
- Odstranitev vgrajenega seznama sprememb (CHANGELOG)
|
||||
@@ -0,0 +1 @@
|
||||
- Popravljena napaka, zaradi katere se gradnik seznama ni posodobil ob odpiranju kartice iz njega
|
||||
@@ -0,0 +1 @@
|
||||
- Prva izdaja spremljevalne aplikacije za Wear OS
|
||||
@@ -0,0 +1,4 @@
|
||||
- Dodan drsnik na seznam kartic
|
||||
- Dodan začetni zaslon
|
||||
- Popravljene nekatere težave z razmiki
|
||||
- Odstranjena vrednost črtne kode iz pogleda kartice
|
||||
@@ -2,7 +2,8 @@
|
||||
- Omogočen je vklop svetilke med skeniranjem
|
||||
- Dodana podpora za UPC-E
|
||||
- Podpora dodajanju slike prednje/zadnje strani kartice
|
||||
- Podpora uvozu datotek ZIP, zaščitenih z geslom in iz Stocard (Beta)
|
||||
- Podpora uvozu datotek ZIP, zaščitenih z geslom
|
||||
- Podpora uvozu datotek iz Stocard (Beta)
|
||||
- Popravek praznih prostorov v opombah pri uvozu iz Fidme
|
||||
- Podpora novemu formatu izvoza Voucher Vault
|
||||
- Popravek plavajočih gumbov v Android 4
|
||||
|
||||
@@ -11,11 +11,12 @@ S tem osnovnim orodjem na svoji elektronski napravi, lahko neuporabno plastiko n
|
||||
- Uvozi kartice in kode iz datotek, Catima, FidMe, Loyalty Card Keychain in Voucher Vault.
|
||||
- Naredi varnostno kopijo vseh svojih kartic in jih po želji prenesi v novo napravo.
|
||||
- Deli kupone, ekskluzivne ponudbe, promocijske kode ali kartice in kode s katero koli aplikacijo.
|
||||
- Sinhronizacija s podprtimi pametnimi urami Gadgetbridge ali Wear OS.
|
||||
- Temna tema in možnosti dostopnosti za uporabnike z okvarami vida.
|
||||
- Izdelana za vsakogar s strani skupnosti programske opreme libre.
|
||||
- Izdelana za vsakogar s strani skupnosti proste programske opreme.
|
||||
- Lokalizirani ročno izdelani prevodi za več kot 40 jezikov.
|
||||
- Brezplačno, podprto s prispevki skupnosti.
|
||||
- Uporabljaj, preučuj, spreminjaj in delite po želji; <i>z vsemi</i>.
|
||||
- Uporabljaj, preučuj, spreminjaj in deli po želji; <i>z vsemi</i>.
|
||||
- Programska oprema je odprtokodna in brezplačna. Upravljanje kartic s pomočjo <i>Copylefted</i> proste programske opreme (GPLv3+ licenca).
|
||||
|
||||
Poenostavi si življenje in nakupovanje ter nikoli več ne izgubi papirnatega računa, darilne kartice za plačilo v trgovini ali letalske karte.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- 修复从列表小部件打开卡片时,列表小部件不显示的问题
|
||||
@@ -0,0 +1,4 @@
|
||||
- 卡片列表添加滚动条
|
||||
- 添加启动画面
|
||||
- 修复一些填充问题
|
||||
- 从卡片视图删除条形码值
|
||||
@@ -62,8 +62,8 @@ junit-junit = { group = "junit", name = "junit", version = "4.13.2" }
|
||||
org-robolectric-robolectric = { group = "org.robolectric", name = "robolectric", version = "4.16.1" }
|
||||
|
||||
[plugins]
|
||||
com-android-application = { id = "com.android.application", version = "9.3.1" }
|
||||
com-android-library = { id = "com.android.library", version = "9.3.1" }
|
||||
com-android-application = { id = "com.android.application", version = "9.4.0" }
|
||||
com-android-library = { id = "com.android.library", version = "9.4.0" }
|
||||
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
org-jetbrains-kotlin-plugin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=84fbba45c7f4c64abc77460e1c00f541e9f960e3c7ed2538f1ede19eacd873ae
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
|
||||
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
|
||||
@@ -16,8 +16,8 @@ android {
|
||||
applicationId = "me.hackerchick.catima"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 200
|
||||
versionName = "1.0.0 Wear OS"
|
||||
versionCode = 201
|
||||
versionName = "1.0.1 Wear OS"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima (ladicí verze)</string>
|
||||
</resources>
|
||||
@@ -17,10 +17,12 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
|
||||
import androidx.wear.compose.foundation.lazy.items
|
||||
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
|
||||
import androidx.wear.compose.material3.CircularProgressIndicator
|
||||
import androidx.wear.compose.material3.Text
|
||||
import androidx.wear.compose.material3.Button
|
||||
import androidx.wear.compose.material3.ButtonDefaults
|
||||
import androidx.wear.compose.material3.ScreenScaffold
|
||||
import androidx.wear.compose.material3.SurfaceTransformation
|
||||
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
|
||||
import androidx.wear.compose.material3.lazy.transformedHeight
|
||||
@@ -67,32 +69,43 @@ fun CardListScreen(
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
val transformationSpec = rememberTransformationSpec()
|
||||
TransformingLazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 32.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(cards, key = { it.id }) { card ->
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
|
||||
transformation = SurfaceTransformation(transformationSpec),
|
||||
label = {
|
||||
Text(
|
||||
text = card.store,
|
||||
maxLines = 1,
|
||||
)
|
||||
},
|
||||
onClick = { onCardClick(card) },
|
||||
colors = if (card.headerColor != null) {
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = Color(card.headerColor),
|
||||
contentColor = if (ForegroundColorHelper.needsDarkForeground(card.headerColor)) Color.Black else Color.White
|
||||
)
|
||||
} else {
|
||||
ButtonDefaults.buttonColors()
|
||||
},
|
||||
)
|
||||
val columnState = rememberTransformingLazyColumnState()
|
||||
ScreenScaffold(
|
||||
scrollState = columnState,
|
||||
) { contentPadding ->
|
||||
val transformationSpec = rememberTransformationSpec()
|
||||
TransformingLazyColumn(
|
||||
state = columnState,
|
||||
contentPadding = contentPadding,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(cards, key = { it.id }) { card ->
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.transformedHeight(this, transformationSpec)
|
||||
.minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
|
||||
transformation = SurfaceTransformation(transformationSpec),
|
||||
label = {
|
||||
Text(
|
||||
text = card.store,
|
||||
maxLines = 1,
|
||||
)
|
||||
},
|
||||
onClick = { onCardClick(card) },
|
||||
colors = if (card.headerColor != null) {
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = Color(card.headerColor),
|
||||
contentColor = if (ForegroundColorHelper.needsDarkForeground(
|
||||
card.headerColor
|
||||
)
|
||||
) Color.Black else Color.White
|
||||
)
|
||||
} else {
|
||||
ButtonDefaults.buttonColors()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val footerLabel = syncStatus.labelRes
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color as ComposeColor
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -138,6 +139,9 @@ private fun CardDetail(card: WearCard) {
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
color = ComposeColor.Black,
|
||||
// Hide the barcode value until we can be completely certain the element won't partially fall out of the UI
|
||||
// This is a workaround for a requirement for Google Play: https://github.com/CatimaLoyalty/Android/issues/3284
|
||||
modifier = Modifier.alpha(0f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Catima</string>
|
||||
<string name="no_cards">Nenalezeny žádné karty.\nOtevřete aplikaci Catima na telefonu pro přidání karet.</string>
|
||||
<string name="phone_not_connected">Nepodařilo se spojit s aplikací Catima na vašem telefonu.\nUjistěte se, že na vašem telefonu běží aplikace Catima, vaše hodinky jsou připojeny a v nastavení Catima je povolena podpora Wear OS.</string>
|
||||
<string name="loading_cards">Načítání karet…</string>
|
||||
<string name="no_barcode">Pro tuto kartu není k dispozici žádný čárový kód.</string>
|
||||
<string name="card_id_label">ID karty</string>
|
||||
<string name="syncing">Synchronizace s telefonem…</string>
|
||||
<string name="sync_failed">Telefon není dostupný – zobrazuji karty z mezipaměti</string>
|
||||
<string name="version_incompatible">Aplikace Catima na vašem telefonu a hodinkách jsou nekompatibilní.\nAktualizujte prosím obě.</string>
|
||||
<string name="bluetooth_permission_denied">Oprávnění Bluetooth zamítnuto.\nUdělte jej v Nastavení pro synchronizaci karet.</string>
|
||||
<string name="bluetooth_disabled">Bluetooth je vypnut.\nPovolte jej pro synchronizaci karet z vašeho telefonu.</string>
|
||||
<string name="sync_error">Nepodařilo se přečíst karty z vašeho telefonu.\nZkuste znovu provést synchronizaci.</string>
|
||||
<string name="unauthorized">Neautorizováno.\nPovolte těmto hodinkám číst vaše karty v nastavení aplikace Catima.</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user