Compare commits

..

1 Commits

Author SHA1 Message Date
Sylvia van Os
65d8d45018 WIP 2022-10-05 21:48:31 +02:00
740 changed files with 6197 additions and 12620 deletions

View File

@@ -3,12 +3,12 @@ name: Android CI
on:
push:
branches:
- main
- master
- staging
- trying
pull_request:
branches:
- main
- master
jobs:
build:
@@ -20,11 +20,11 @@ jobs:
- name: Fail on bad translations
run: if grep -ri "<xliff" app/src/main/res/values*/strings.xml; then echo "Invalidly escaped translations found"; exit 1; fi
- uses: gradle/wrapper-validation-action@v1
- name: set up JDK 17
- name: set up JDK 11
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '17'
distribution: 'adopt'
java-version: '11'
- name: Build
run: ./gradlew assembleRelease
- name: Check lint

View File

@@ -1,8 +1,8 @@
name: Compress Images on Push to Main
name: Compress Images on Push to Master
on:
push:
branches:
- main
- master
paths:
- '**.jpg'
- '**.jpeg'

View File

@@ -2,7 +2,7 @@ name: Convert CHANGELOG to Fastlane
on:
push:
branches:
- main
- master
jobs:
convert_changelog_to_fastlane:

73
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches:
- master
pull_request:
# The branches below must be a subset of the branches above
branches:
- master
schedule:
- cron: '33 1 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -6,7 +6,7 @@ on:
jobs:
contributors_to_file:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/master'
name: Write contributors to file
steps:
- name: Checkout repo

View File

@@ -1,10 +0,0 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1

5
.gitignore vendored
View File

@@ -8,8 +8,3 @@ captures/
**/release
**/debug
app/*.log
# Bundle
/.bundle/
/vendor/bundle
/lib/bundler/man/

View File

@@ -1,30 +1,8 @@
# Changelog
## v2.22.1 - 119
- Use Material You colours on more devices (Google library update)
## v2.22.0 - 118
- Support setting start of card validity
- Fix Stocard import (Stocard's export format changed)
## v2.21.2 - 117
- Remove unnecessary permissions
- Target Android 13
## v2.21.1 - 116
- Fix quick spend dialog not allowing , separator
- Support loading image from file manager
## v2.21.0 - 115
## Unreleased - 115
- Open image in gallery on long-press
- Apply Material style to dialogs
- Support creating card by sharing an image to Catima
- Add quick spend button to card screen
## v2.20.0 - 114
@@ -618,7 +596,7 @@ Additional features/improvements:
## v0.7 - 7 (2016-07-14)
- Long-click of a card brings up option to copy card ID to the clipboard. ([pull #49](https://github.com/brarcher/loyalty-card-locker/issues/49))
- Back button on Import/Export view now works, moving user to main view
- Back button on Input/Export view now works, moving user to main view
## v0.6 - 6 (2016-05-23)

View File

@@ -100,5 +100,5 @@ your real name, saying:
Finally, you will need to submit your patches so that they can be reviewed
and potentially merged into the main Catima repository. The preferred
way to do this is to submit a Pull Request to the Catima project.
Changes need to apply cleanly onto the main branch and pass all
Changes need to apply cleanly onto the master branch and pass all
unit tests and produce no errors during static analysis.

View File

@@ -1,27 +1,27 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
CFPropertyList (3.0.5)
rexml
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.701.0)
aws-sdk-core (3.170.0)
aws-partitions (1.597.0)
aws-sdk-core (3.131.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.62.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.119.0)
aws-sdk-core (~> 3, >= 3.165.0)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.57.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.2)
aws-sigv4 (1.5.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
@@ -34,10 +34,10 @@ GEM
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
dotenv (2.7.6)
emoji_regex (3.2.3)
excon (0.98.0)
faraday (1.10.3)
excon (0.92.3)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -66,7 +66,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.211.0)
fastlane (2.206.2)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -106,9 +106,9 @@ GEM
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.32.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-core (0.10.0)
google-apis-androidpublisher_v3 (0.21.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.5.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -117,27 +117,27 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.16.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-playcustomapp_v1 (0.12.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-iamcredentials_v1 (0.10.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.7.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.14.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.0)
google-cloud-storage (1.44.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.36.2)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.3.0)
googleauth (1.1.3)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -148,11 +148,11 @@ GEM
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.3)
jwt (2.6.0)
jmespath (1.6.1)
json (2.6.2)
jwt (2.4.1)
memoist (0.16.2)
mini_magick (4.12.0)
mini_magick (4.11.0)
mini_mime (1.1.2)
multi_json (1.15.0)
multipart-post (2.0.0)
@@ -161,7 +161,7 @@ GEM
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
public_suffix (5.0.1)
public_suffix (4.0.7)
rake (13.0.6)
representable (3.2.0)
declarative (< 0.1.0)
@@ -173,12 +173,12 @@ GEM
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
signet (0.16.1)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
faraday (>= 0.17.5, < 3.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
simctl (1.6.8)
CFPropertyList
naturally
terminal-notifier (2.0.0)
@@ -194,9 +194,9 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.8.1)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.22.0)
xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@@ -215,4 +215,4 @@ DEPENDENCIES
fastlane
BUNDLED WITH
2.3.26
2.1.4

View File

@@ -1,9 +1,7 @@
import com.github.spotbugs.snom.SpotBugsTask
plugins {
id 'com.android.application'
id 'com.github.spotbugs'
}
apply plugin: 'com.android.application'
apply plugin: 'com.github.spotbugs'
spotbugs {
ignoreFailures = false
@@ -13,14 +11,15 @@ spotbugs {
}
android {
compileSdk 33
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
applicationId "me.hackerchick.catima"
minSdk 21
targetSdk 33
versionCode 119
versionName "2.22.1"
minSdkVersion 21
targetSdkVersion 31
versionCode 114
versionName "2.20.0"
vectorDrawables.useSupportLibrary true
multiDexEnabled true
@@ -38,10 +37,6 @@ android {
}
}
buildFeatures {
viewBinding true
}
bundle {
language {
enableSplit = false
@@ -58,6 +53,10 @@ android {
targetCompatibility JavaVersion.VERSION_11
}
lintOptions {
disable "GoogleAppIndexingWarning", "ButtonStyle", "AlwaysShowAction",
"MissingTranslation", "MissingPrefix"
}
sourceSets {
test {
@@ -77,41 +76,37 @@ android {
includeAndroidResources true
}
}
lint {
lintConfig file('lint.xml')
}
namespace 'protect.card_locker'
}
dependencies {
// AndroidX
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.exifinterface:exifinterface:1.3.6'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.palette:palette:1.0.0'
implementation 'androidx.preference:preference:1.2.0'
implementation 'com.google.android.material:material:1.8.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'com.github.yalantis:ucrop:2.2.8'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
// Splash Screen
implementation 'androidx.core:core-splashscreen:1.0.0'
// Third-party
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
implementation 'com.google.zxing:core:3.5.1'
implementation 'org.apache.commons:commons-csv:1.10.0'
implementation 'com.google.zxing:core:3.5.0'
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.4'
implementation 'net.lingala.zip4j:zip4j:2.11.5'
implementation 'net.lingala.zip4j:zip4j:2.11.2'
// SpotBugs
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
// Testing
testImplementation 'androidx.test:core:1.5.0'
testImplementation 'androidx.test:core:1.4.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.10'
testImplementation 'org.robolectric:robolectric:4.9'
}
tasks.withType(SpotBugsTask) {

View File

@@ -6,8 +6,5 @@
<Match>
<Class name="~.*Manifest\$.*"/>
</Match>
<Match>
<Class name="~.*Binding" />
</Match>
</FindBugsFilter>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="AlwaysShowAction" severity="ignore" />
<issue id="ButtonStyle" severity="ignore" />
<issue id="GoogleAppIndexingWarning" severity="ignore" />
<issue id="MissingTranslation" severity="ignore" />
<issue id="MissingPrefix" severity="ignore" />
</lint>

View File

@@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
package="protect.card_locker">
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.camera"
@@ -31,12 +33,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name=".AboutActivity"
@@ -106,39 +102,7 @@
<activity
android:name=".ImportExportActivity"
android:label="@string/importExport"
android:exported="true"
android:theme="@style/AppTheme.NoActionBar">
<!-- ZIP Intent Filter -->
<intent-filter
android:label="@string/importCards">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/zip" />
<data android:scheme="content"/>
<data android:host="*"/>
</intent-filter>
<!-- JSON Intent Filter -->
<intent-filter
android:label="@string/importCards">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/json" />
<data android:scheme="content"/>
<data android:host="*"/>
</intent-filter>
<!-- CSV Intent Filter -->
<intent-filter
android:label="@string/importCards">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content"/>
<data android:host="*" />
<data android:mimeType="text/comma-separated-values" />
</intent-filter>
</activity>
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".CardShortcutConfigure"
android:exported="true"

View File

@@ -1,46 +1,138 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.text.HtmlCompat;
import protect.card_locker.databinding.AboutActivityBinding;
public class AboutActivity extends CatimaAppCompatActivity {
public class AboutActivity extends CatimaAppCompatActivity implements View.OnClickListener {
private static final String TAG = "Catima";
private AboutActivityBinding binding;
private AboutContent content;
ConstraintLayout version_history, translate, license, repo, privacy, error, credits, rate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = AboutActivityBinding.inflate(getLayoutInflater());
content = new AboutContent(this);
setTitle(content.getPageTitle());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
enableToolbarBackButton();
setTitle(R.string.about);
setContentView(R.layout.about_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
TextView copyright = binding.creditsSub;
copyright.setText(content.getCopyright());
TextView versionHistory = binding.versionHistorySub;
versionHistory.setText(content.getVersionHistory());
StringBuilder contributors = new StringBuilder().append("<br/>");
binding.versionHistory.setTag("https://catima.app/changelog/");
binding.translate.setTag("https://hosted.weblate.org/engage/catima/");
binding.license.setTag("https://github.com/CatimaLoyalty/Android/blob/main/LICENSE");
binding.repo.setTag("https://github.com/CatimaLoyalty/Android/");
binding.privacy.setTag("https://catima.app/privacy-policy/");
binding.reportError.setTag("https://github.com/CatimaLoyalty/Android/issues");
binding.rate.setTag("https://play.google.com/store/apps/details?id=me.hackerchick.catima");
BufferedReader reader = new BufferedReader(new InputStreamReader(getResources().openRawResource(R.raw.contributors), StandardCharsets.UTF_8));
bindClickListeners();
try {
while (true) {
String tmp = reader.readLine();
if (tmp == null || tmp.isEmpty()) {
reader.close();
break;
}
contributors.append("<br/>");
contributors.append(tmp);
}
} catch (IOException ignored) {
}
final List<ThirdPartyInfo> USED_LIBRARIES = new ArrayList<>();
USED_LIBRARIES.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("uCrop", "https://github.com/Yalantis/uCrop", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "Apache 2.0"));
USED_LIBRARIES.add(new ThirdPartyInfo("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded", "Apache 2.0"));
final List<ThirdPartyInfo> USED_ASSETS = new ArrayList<>();
USED_ASSETS.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0"));
StringBuilder libs = new StringBuilder().append("<br/>");
for (ThirdPartyInfo entry : USED_LIBRARIES) {
libs.append("<br/><a href=\"").append(entry.url()).append("\">").append(entry.name()).append("</a> (").append(entry.license()).append(")");
}
StringBuilder resources = new StringBuilder().append("<br/>");
for (ThirdPartyInfo entry : USED_ASSETS) {
resources.append("<br/><a href=\"").append(entry.url()).append("\">").append(entry.name()).append("</a> (").append(entry.license()).append(")");
}
String appName = getString(R.string.app_name);
int year = Calendar.getInstance().get(Calendar.YEAR);
String version = "?";
try {
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pi.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package name not found", e);
}
TextView copyright = findViewById(R.id.credits_sub);
copyright.setText(String.format(getString(R.string.app_copyright_fmt), year));
TextView vHistory = findViewById(R.id.version_history_sub);
vHistory.setText(String.format(getString(R.string.debug_version_fmt), version));
setTitle(String.format(getString(R.string.about_title_fmt), appName));
version_history = findViewById(R.id.version_history);
translate = findViewById(R.id.translate);
license = findViewById(R.id.license);
repo = findViewById(R.id.repo);
privacy = findViewById(R.id.privacy);
error = findViewById(R.id.report_error);
credits = findViewById(R.id.credits);
rate = findViewById(R.id.rate);
version_history.setOnClickListener(this);
translate.setOnClickListener(this);
license.setOnClickListener(this);
repo.setOnClickListener(this);
privacy.setOnClickListener(this);
error.setOnClickListener(this);
rate.setOnClickListener(this);
StringBuilder contributorInfo = new StringBuilder();
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_contributors), contributors.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
contributorInfo.append("\n\n");
contributorInfo.append(getString(R.string.app_copyright_old));
contributorInfo.append("\n\n");
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_libraries), libs.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
contributorInfo.append("\n\n");
contributorInfo.append(HtmlCompat.fromHtml(String.format(getString(R.string.app_resources), resources.toString()), HtmlCompat.FROM_HTML_MODE_COMPACT));
credits.setOnClickListener(view -> new AlertDialog.Builder(this)
.setTitle(R.string.credits)
.setMessage(contributorInfo.toString())
.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
})
.show());
}
@Override
@@ -53,47 +145,36 @@ public class AboutActivity extends CatimaAppCompatActivity {
}
@Override
protected void onDestroy() {
super.onDestroy();
content.destroy();
clearClickListeners();
binding = null;
public void onClick(View view) {
int id = view.getId();
String url;
if (id == R.id.version_history) {
url = "https://catima.app/changelog/";
} else if (id == R.id.translate) {
url = "https://hosted.weblate.org/engage/catima/";
} else if (id == R.id.license) {
url = "https://github.com/CatimaLoyalty/Android/blob/master/LICENSE";
} else if (id == R.id.repo) {
url = "https://github.com/CatimaLoyalty/Android/";
} else if (id == R.id.privacy) {
url = "https://catima.app/privacy-policy/";
} else if (id == R.id.report_error) {
url = "https://github.com/CatimaLoyalty/Android/issues";
} else if (id == R.id.rate) {
url = "https://play.google.com/store/apps/details?id=me.hackerchick.catima";
} else {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.failedToOpenUrl, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
private void bindClickListeners() {
View.OnClickListener openExternalBrowser = view -> {
Object tag = view.getTag();
if (tag instanceof String && ((String) tag).startsWith("https://")) {
(new OpenWebLinkHandler()).openBrowser(this, (String) tag);
}
};
binding.versionHistory.setOnClickListener(openExternalBrowser);
binding.translate.setOnClickListener(openExternalBrowser);
binding.license.setOnClickListener(openExternalBrowser);
binding.repo.setOnClickListener(openExternalBrowser);
binding.privacy.setOnClickListener(openExternalBrowser);
binding.reportError.setOnClickListener(openExternalBrowser);
binding.rate.setOnClickListener(openExternalBrowser);
binding.credits.setOnClickListener(view -> showCredits());
}
private void clearClickListeners() {
binding.versionHistory.setOnClickListener(null);
binding.translate.setOnClickListener(null);
binding.license.setOnClickListener(null);
binding.repo.setOnClickListener(null);
binding.privacy.setOnClickListener(null);
binding.reportError.setOnClickListener(null);
binding.rate.setOnClickListener(null);
binding.credits.setOnClickListener(null);
}
private void showCredits() {
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.credits)
.setMessage(content.getContributorInfo())
.setPositiveButton(R.string.ok, null)
.show();
}
}

View File

@@ -1,111 +0,0 @@
package protect.card_locker;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.core.text.HtmlCompat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
public class AboutContent {
public static final String TAG = "Catima";
public Context context;
public AboutContent(Context context) {
this.context = context;
}
public void destroy() {
this.context = null;
}
public String getPageTitle() {
return String.format(context.getString(R.string.about_title_fmt), context.getString(R.string.app_name));
}
public String getAppVersion() {
String version = "?";
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
version = pi.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package name not found", e);
}
return version;
}
public int getCurrentYear() {
return Calendar.getInstance().get(Calendar.YEAR);
}
public String getCopyright() {
return String.format(context.getString(R.string.app_copyright_fmt), getCurrentYear());
}
public String getContributors() {
String contributors;
try {
contributors = "<br/>" + Utils.readTextFile(context, R.raw.contributors);
} catch (IOException ignored) {
return "";
}
return contributors.replace("\n", "<br />");
}
public String getThirdPartyLibraries() {
final List<ThirdPartyInfo> usedLibraries = new ArrayList<>();
usedLibraries.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("Commons CSV", "https://commons.apache.org/proper/commons-csv/", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference", "GNU LGPL 3.0"));
usedLibraries.add(new ThirdPartyInfo("uCrop", "https://github.com/Yalantis/uCrop", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("Zip4j", "https://github.com/srikanth-lingala/zip4j", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("ZXing", "https://github.com/zxing/zxing", "Apache 2.0"));
usedLibraries.add(new ThirdPartyInfo("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded", "Apache 2.0"));
StringBuilder result = new StringBuilder("<br/>");
for (ThirdPartyInfo entry : usedLibraries) {
result.append("<br/>")
.append(entry.toHtml());
}
return result.toString();
}
public String getUsedThirdPartyAssets() {
final List<ThirdPartyInfo> usedAssets = new ArrayList<>();
usedAssets.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0"));
StringBuilder result = new StringBuilder().append("<br/>");
for (ThirdPartyInfo entry : usedAssets) {
result.append("<br/>")
.append(entry.toHtml());
}
return result.toString();
}
public String getContributorInfo() {
StringBuilder contributorInfo = new StringBuilder();
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_contributors), getContributors()), HtmlCompat.FROM_HTML_MODE_COMPACT));
contributorInfo.append("\n\n");
contributorInfo.append(context.getString(R.string.app_copyright_old));
contributorInfo.append("\n\n");
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()), HtmlCompat.FROM_HTML_MODE_COMPACT));
contributorInfo.append("\n\n");
contributorInfo.append(HtmlCompat.fromHtml(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssets()), HtmlCompat.FROM_HTML_MODE_COMPACT));
return contributorInfo.toString();
}
public String getVersionHistory() {
return String.format(context.getString(R.string.debug_version_fmt), getAppVersion());
}
}

View File

@@ -17,6 +17,7 @@ import com.google.zxing.common.BitMatrix;
import java.lang.ref.WeakReference;
import protect.card_locker.async.CompatCallable;
import protect.card_locker.barcodes.Barcode;
/**
* This task will generate a barcode and load it into an ImageView.
@@ -38,7 +39,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private final WeakReference<TextView> textViewReference;
private String cardId;
private final CatimaBarcode format;
private final Barcode format;
private final int imageHeight;
private final int imageWidth;
private final boolean showFallback;
@@ -46,7 +47,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
BarcodeImageWriterTask(
Context context, ImageView imageView, String cardIdString,
CatimaBarcode barcodeFormat, TextView textView,
Barcode barcodeFormat, TextView textView,
boolean showFallback, Runnable callback, boolean roundCornerPadding
) {
mContext = context;
@@ -90,68 +91,8 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
this.showFallback = showFallback;
}
private int getMaxWidth(CatimaBarcode format) {
switch (format.format()) {
// 2D barcodes
case AZTEC:
case DATA_MATRIX:
case MAXICODE:
case PDF_417:
case QR_CODE:
return MAX_WIDTH_2D;
// 1D barcodes:
case CODABAR:
case CODE_39:
case CODE_93:
case CODE_128:
case EAN_8:
case EAN_13:
case ITF:
case UPC_A:
case UPC_E:
case RSS_14:
case RSS_EXPANDED:
case UPC_EAN_EXTENSION:
default:
return MAX_WIDTH_1D;
}
}
private String getFallbackString(CatimaBarcode format) {
switch (format.format()) {
// 2D barcodes
case AZTEC:
return "AZTEC";
case DATA_MATRIX:
return "DATA_MATRIX";
case PDF_417:
return "PDF_417";
case QR_CODE:
return "QR_CODE";
// 1D barcodes:
case CODABAR:
return "C0C";
case CODE_39:
return "CODE_39";
case CODE_93:
return "CODE_93";
case CODE_128:
return "CODE_128";
case EAN_8:
return "32123456";
case EAN_13:
return "5901234123457";
case ITF:
return "1003";
case UPC_A:
return "123456789012";
case UPC_E:
return "0123456";
default:
throw new IllegalArgumentException("No fallback known for this barcode type");
}
private int getMaxWidth(Barcode format) {
return format.is2D() ? MAX_WIDTH_2D : MAX_WIDTH_1D;
}
private Bitmap generate() {
@@ -227,7 +168,7 @@ public class BarcodeImageWriterTask implements CompatCallable<Bitmap> {
if (showFallback && !Thread.currentThread().isInterrupted()) {
Log.i(TAG, "Barcode generation failed, generating fallback...");
cardId = getFallbackString(format);
cardId = format.exampleValue();
bitmap = generate();
return bitmap;
}

View File

@@ -18,8 +18,9 @@ import java.util.ArrayList;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
import protect.card_locker.barcodes.BarcodeWithValue;
/**
* This activity is callable and will allow a user to enter
@@ -28,7 +29,6 @@ import protect.card_locker.databinding.BarcodeSelectorActivityBinding;
* data and type will be returned to the caller.
*/
public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements BarcodeSelectorAdapter.BarcodeSelectorListener {
private BarcodeSelectorActivityBinding binding;
private static final String TAG = "Catima";
// Result this activity will return
@@ -43,15 +43,17 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = BarcodeSelectorActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.selectBarcodeTitle);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.barcode_selector_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
EditText cardId = binding.cardId;
ListView mBarcodeList = binding.barcodes;
EditText cardId = findViewById(R.id.cardId);
ListView mBarcodeList = findViewById(R.id.barcodes);
mAdapter = new BarcodeSelectorAdapter(this, new ArrayList<>(), this);
mBarcodeList.setAdapter(mAdapter);
@@ -67,7 +69,7 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
runOnUiThread(() -> {
generateBarcodes(s.toString());
View noBarcodeButtonView = binding.noBarcode;
View noBarcodeButtonView = findViewById(R.id.noBarcode);
setButtonListener(noBarcodeButtonView, s.toString());
noBarcodeButtonView.setEnabled(s.length() > 0);
});
@@ -87,10 +89,10 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
private void generateBarcodes(String value) {
// Update barcodes
ArrayList<CatimaBarcodeWithValue> barcodes = new ArrayList<>();
for (BarcodeFormat barcodeFormat : CatimaBarcode.barcodeFormats) {
CatimaBarcode catimaBarcode = CatimaBarcode.fromBarcode(barcodeFormat);
barcodes.add(new CatimaBarcodeWithValue(catimaBarcode, value));
ArrayList<BarcodeWithValue> barcodes = new ArrayList<>();
for (BarcodeFormat barcodeFormat : BarcodeFactory.getAllFormats()) {
Barcode catimaBarcode = BarcodeFactory.fromBarcode(barcodeFormat);
barcodes.add(new BarcodeWithValue(catimaBarcode, value));
}
mAdapter.setBarcodes(barcodes);
}
@@ -119,7 +121,7 @@ public class BarcodeSelectorActivity extends CatimaAppCompatActivity implements
@Override
public void onRowClicked(int inputPosition, View view) {
CatimaBarcodeWithValue barcodeWithValue = mAdapter.getItem(inputPosition);
BarcodeWithValue barcodeWithValue = mAdapter.getItem(inputPosition);
CatimaBarcode catimaBarcode = barcodeWithValue.catimaBarcode();
if (!mAdapter.isValid(view)) {

View File

@@ -13,9 +13,11 @@ import android.widget.TextView;
import java.util.ArrayList;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.BarcodeLayoutBinding;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
import protect.card_locker.barcodes.BarcodeWithValue;
public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue> {
public class BarcodeSelectorAdapter extends ArrayAdapter<BarcodeWithValue> {
private static final String TAG = "Catima";
private final TaskHandler mTasks = new TaskHandler();
@@ -30,12 +32,12 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
void onRowClicked(int inputPosition, View view);
}
public BarcodeSelectorAdapter(Context context, ArrayList<CatimaBarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) {
public BarcodeSelectorAdapter(Context context, ArrayList<BarcodeWithValue> barcodes, BarcodeSelectorListener barcodeSelectorListener) {
super(context, 0, barcodes);
mListener = barcodeSelectorListener;
}
public void setBarcodes(ArrayList<CatimaBarcodeWithValue> barcodes) {
public void setBarcodes(ArrayList<BarcodeWithValue> barcodes) {
clear();
addAll(barcodes);
notifyDataSetChanged();
@@ -44,18 +46,17 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
@Override
public View getView(int position, View convertView, ViewGroup parent) {
CatimaBarcodeWithValue catimaBarcodeWithValue = getItem(position);
CatimaBarcode catimaBarcode = catimaBarcodeWithValue.catimaBarcode();
String value = catimaBarcodeWithValue.value();
BarcodeWithValue barcodeWithValue = getItem(position);
Barcode catimaBarcode = barcodeWithValue.barcode();
String value = barcodeWithValue.value();
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
BarcodeLayoutBinding barcodeLayoutBinding = BarcodeLayoutBinding.inflate(inflater, parent, false);
convertView = barcodeLayoutBinding.getRoot();
viewHolder.image = barcodeLayoutBinding.barcodeImage;
viewHolder.text = barcodeLayoutBinding.barcodeName;
convertView = inflater.inflate(R.layout.barcode_layout, parent, false);
viewHolder.image = convertView.findViewById(R.id.barcodeImage);
viewHolder.text = convertView.findViewById(R.id.barcodeName);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
@@ -75,7 +76,7 @@ public class BarcodeSelectorAdapter extends ArrayAdapter<CatimaBarcodeWithValue>
}
private void createBarcodeOption(final ImageView image, final String formatType, final String cardId, final TextView text) {
final CatimaBarcode format = CatimaBarcode.fromName(formatType);
final Barcode format = BarcodeFactory.fromName(formatType);
image.setImageBitmap(null);
image.setClipToOutline(true);

View File

@@ -14,13 +14,10 @@ import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.SimpleToolbarListActivityBinding;
/**
* The configuration screen for creating a shortcut.
*/
public class CardShortcutConfigure extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
private SimpleToolbarListActivityBinding binding;
static final String TAG = "Catima";
private SQLiteDatabase mDatabase;
private LoyaltyCardCursorAdapter mAdapter;
@@ -28,15 +25,15 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
binding = SimpleToolbarListActivityBinding.inflate(getLayoutInflater());
mDatabase = new DBHelper(this).getReadableDatabase();
// Set the result to CANCELED. This will cause nothing to happen if the
// aback button is pressed.
setResult(RESULT_CANCELED);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.simple_toolbar_list_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(R.string.shortcutSelectCard);
setSupportActionBar(toolbar);
@@ -53,7 +50,7 @@ public class CardShortcutConfigure extends CatimaAppCompatActivity implements Lo
finish();
}
final RecyclerView cardList = binding.list;
final RecyclerView cardList = findViewById(R.id.list);
GridLayoutManager layoutManager = (GridLayoutManager) cardList.getLayoutManager();
if (layoutManager != null) {
layoutManager.setSpanCount(getResources().getInteger(R.integer.main_view_card_columns));

View File

@@ -1,5 +1,6 @@
package protect.card_locker;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -68,9 +69,10 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
subscriber.onSubscribe(new NoOpSubscription());
for (String controlId : controlIds) {
Control control;
Integer cardId = this.controlIdToCardId(controlId);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, cardId);
if (card != null) {
try {
Integer cardId = this.controlIdToCardId(controlId);
LoyaltyCard card = DBHelper.getLoyaltyCard(mDatabase, cardId);
Intent openIntent = new Intent(this, LoyaltyCardViewActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra("id", card.id);
@@ -83,7 +85,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
.setControlTemplate(new StatelessTemplate(controlId))
.setCustomIcon(Icon.createWithBitmap(getIcon(this, card)))
.build();
} else {
} catch (NullPointerException ignored) {
Intent mainScreenIntent = new Intent(this, MainActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), -1, mainScreenIntent, PendingIntent.FLAG_IMMUTABLE);
@@ -135,7 +137,7 @@ public class CardsOnPowerScreenService extends ControlsProviderService {
closePowerScreenOnAndroid11();
}
@SuppressWarnings({"MissingPermission", "deprecation"})
@SuppressLint({"MissingPermission", "deprecation"})
private void closePowerScreenOnAndroid11() {
// Android 12 will auto-close the power screen, but earlier versions won't
// Lint complains about this but on Android 11 the permission is not needed

View File

@@ -6,11 +6,8 @@ import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowInsetsControllerCompat;
public class CatimaAppCompatActivity extends AppCompatActivity {
@Override
@@ -32,10 +29,8 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
// XXX changing this in onCreate causes issues with the splash screen activity, so doing this here
boolean darkMode = Utils.isDarkModeEnabled(this);
if (Build.VERSION.SDK_INT >= 23) {
View decorView = getWindow().getDecorView();
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
wic.setAppearanceLightStatusBars(!darkMode);
getWindow().setStatusBarColor(Color.TRANSPARENT);
getWindow().getDecorView().setSystemUiVisibility(darkMode ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
// icons are always white back then
getWindow().setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0));
@@ -43,14 +38,4 @@ public class CatimaAppCompatActivity extends AppCompatActivity {
// XXX android 9 and below has a nasty rendering bug if the theme was patched earlier
Utils.postPatchColors(this);
}
protected void enableToolbarBackButton() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
}
}

View File

@@ -1,97 +0,0 @@
package protect.card_locker;
import com.google.zxing.BarcodeFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CatimaBarcode {
public static final List<BarcodeFormat> barcodeFormats = Collections.unmodifiableList(Arrays.asList(
BarcodeFormat.AZTEC,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_93,
BarcodeFormat.CODE_128,
BarcodeFormat.CODABAR,
BarcodeFormat.DATA_MATRIX,
BarcodeFormat.EAN_8,
BarcodeFormat.EAN_13,
BarcodeFormat.ITF,
BarcodeFormat.PDF_417,
BarcodeFormat.QR_CODE,
BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E
));
public static final List<String> barcodePrettyNames = Collections.unmodifiableList(Arrays.asList(
"Aztec",
"Code 39",
"Code 93",
"Code 128",
"Codabar",
"Data Matrix",
"EAN 8",
"EAN 13",
"ITF",
"PDF 417",
"QR Code",
"UPC A",
"UPC E"
));
private final BarcodeFormat mBarcodeFormat;
private CatimaBarcode(BarcodeFormat barcodeFormat) {
mBarcodeFormat = barcodeFormat;
}
public static CatimaBarcode fromBarcode(BarcodeFormat barcodeFormat) {
return new CatimaBarcode(barcodeFormat);
}
public static CatimaBarcode fromName(String name) {
return new CatimaBarcode(BarcodeFormat.valueOf(name));
}
public static CatimaBarcode fromPrettyName(String prettyName) {
try {
return new CatimaBarcode(barcodeFormats.get(barcodePrettyNames.indexOf(prettyName)));
} catch (IndexOutOfBoundsException e) {
throw new IllegalArgumentException("No barcode type with pretty name " + prettyName + " known!");
}
}
public boolean isSupported() {
return barcodeFormats.contains(mBarcodeFormat);
}
public boolean isSquare() {
return mBarcodeFormat == BarcodeFormat.AZTEC
|| mBarcodeFormat == BarcodeFormat.DATA_MATRIX
|| mBarcodeFormat == BarcodeFormat.MAXICODE
|| mBarcodeFormat == BarcodeFormat.QR_CODE;
}
public boolean hasInternalPadding() {
return mBarcodeFormat == BarcodeFormat.PDF_417
|| mBarcodeFormat == BarcodeFormat.QR_CODE;
}
public BarcodeFormat format() {
return mBarcodeFormat;
}
public String name() {
return mBarcodeFormat.name();
}
public String prettyName() {
int index = barcodeFormats.indexOf(mBarcodeFormat);
if (index == -1 || index >= barcodePrettyNames.size()) {
return mBarcodeFormat.name();
}
return barcodePrettyNames.get(index);
}
}

View File

@@ -1,19 +0,0 @@
package protect.card_locker;
public class CatimaBarcodeWithValue {
private final CatimaBarcode mCatimaBarcode;
private final String mValue;
public CatimaBarcodeWithValue(CatimaBarcode catimaBarcode, String value) {
mCatimaBarcode = catimaBarcode;
mValue = value;
}
public CatimaBarcode catimaBarcode() {
return mCatimaBarcode;
}
public String value() {
return mValue;
}
}

View File

@@ -18,10 +18,12 @@ import java.util.Currency;
import java.util.Date;
import java.util.List;
import protect.card_locker.barcodes.Barcode;
public class DBHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 16;
public static final int DATABASE_VERSION = 15;
public static class LoyaltyCardDbGroups {
public static final String TABLE = "groups";
@@ -33,7 +35,6 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String TABLE = "cards";
public static final String ID = "_id";
public static final String STORE = "store";
public static final String VALID_FROM = "validfrom";
public static final String EXPIRY = "expiry";
public static final String BALANCE = "balance";
public static final String BALANCE_TYPE = "balancetype";
@@ -96,7 +97,6 @@ public class DBHelper extends SQLiteOpenHelper {
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
LoyaltyCardDbIds.NOTE + " TEXT not null," +
LoyaltyCardDbIds.VALID_FROM + " INTEGER," +
LoyaltyCardDbIds.EXPIRY + " INTEGER," +
LoyaltyCardDbIds.BALANCE + " TEXT not null DEFAULT '0'," +
LoyaltyCardDbIds.BALANCE_TYPE + " TEXT," +
@@ -316,11 +316,6 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.ARCHIVE_STATUS + " INTEGER DEFAULT '0' ");
}
if (oldVersion < 16 && newVersion >= 16) {
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.VALID_FROM + " INTEGER");
}
}
private static ContentValues generateFTSContentValues(final int id, final String store, final String note) {
@@ -365,9 +360,9 @@ public class DBHelper extends SQLiteOpenHelper {
}
public static long insertLoyaltyCard(
final SQLiteDatabase database, final String store, final String note, final Date validFrom,
final Date expiry, final BigDecimal balance, final Currency balanceType, final String cardId,
final String barcodeId, final CatimaBarcode barcodeType, final Integer headerColor,
final SQLiteDatabase database, final String store, final String note, final Date expiry,
final BigDecimal balance, final Currency balanceType, final String cardId,
final String barcodeId, final Barcode barcodeType, final Integer headerColor,
final int starStatus, final Long lastUsed, final int archiveStatus) {
database.beginTransaction();
@@ -375,7 +370,6 @@ public class DBHelper extends SQLiteOpenHelper {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.VALID_FROM, validFrom != null ? validFrom.getTime() : null);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
@@ -399,10 +393,9 @@ public class DBHelper extends SQLiteOpenHelper {
public static long insertLoyaltyCard(
final SQLiteDatabase database, final int id, final String store, final String note,
final Date validFrom, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId, final String barcodeId,
final CatimaBarcode barcodeType, final Integer headerColor, final int starStatus,
final Long lastUsed, final int archiveStatus) {
final Date expiry, final BigDecimal balance, final Currency balanceType,
final String cardId, final String barcodeId, final Barcode barcodeType,
final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) {
database.beginTransaction();
// Card
@@ -410,7 +403,6 @@ public class DBHelper extends SQLiteOpenHelper {
contentValues.put(LoyaltyCardDbIds.ID, id);
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.VALID_FROM, validFrom != null ? validFrom.getTime() : null);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
@@ -434,17 +426,15 @@ public class DBHelper extends SQLiteOpenHelper {
public static boolean updateLoyaltyCard(
SQLiteDatabase database, final int id, final String store, final String note,
final Date validFrom, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId, final String barcodeId,
final CatimaBarcode barcodeType, final Integer headerColor, final int starStatus,
final Long lastUsed, final int archiveStatus) {
final Date expiry, final BigDecimal balance, final Currency balanceType,
final String cardId, final String barcodeId, final Barcode barcodeType,
final Integer headerColor, final int starStatus, final Long lastUsed, final int archiveStatus) {
database.beginTransaction();
// Card
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.STORE, store);
contentValues.put(LoyaltyCardDbIds.NOTE, note);
contentValues.put(LoyaltyCardDbIds.VALID_FROM, validFrom != null ? validFrom.getTime() : null);
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
contentValues.put(LoyaltyCardDbIds.BALANCE, balance.toString());
contentValues.put(LoyaltyCardDbIds.BALANCE_TYPE, balanceType != null ? balanceType.getCurrencyCode() : null);
@@ -506,15 +496,6 @@ public class DBHelper extends SQLiteOpenHelper {
return (rowsUpdated == 1);
}
public static boolean updateLoyaltyCardBalance(SQLiteDatabase database, final int id, final BigDecimal newBalance) {
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.BALANCE, newBalance.toString());
int rowsUpdated = database.update(LoyaltyCardDbIds.TABLE, contentValues,
whereAttrs(LoyaltyCardDbIds.ID),
withArgs(id));
return (rowsUpdated == 1);
}
public static LoyaltyCard getLoyaltyCard(SQLiteDatabase database, final int id) {
Cursor data = database.query(LoyaltyCardDbIds.TABLE, null, whereAttrs(LoyaltyCardDbIds.ID), withArgs(id), null, null, null);

View File

@@ -7,13 +7,11 @@ import android.database.sqlite.SQLiteDatabase;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.GroupLayoutBinding;
import protect.card_locker.preferences.Settings;
public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.GroupListItemViewHolder> {
@@ -35,14 +33,9 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
@NonNull
@Override
public GroupCursorAdapter.GroupListItemViewHolder onCreateViewHolder(@NonNull ViewGroup inputParent, int inputViewType) {
return new GroupListItemViewHolder(
GroupLayoutBinding.inflate(
LayoutInflater.from(inputParent.getContext()),
inputParent,
false
)
);
public GroupCursorAdapter.GroupListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.group_layout, inputParent, false);
return new GroupListItemViewHolder(itemView);
}
public void onBindViewHolder(GroupListItemViewHolder inputHolder, Cursor inputCursor) {
@@ -88,16 +81,16 @@ public class GroupCursorAdapter extends BaseCursorAdapter<GroupCursorAdapter.Gro
public static class GroupListItemViewHolder extends RecyclerView.ViewHolder {
public TextView mName, mCardCount;
public ImageButton mMoveUp, mMoveDown, mEdit, mDelete;
public AppCompatImageButton mMoveUp, mMoveDown, mEdit, mDelete;
public GroupListItemViewHolder(GroupLayoutBinding groupLayoutBinding) {
super(groupLayoutBinding.getRoot());
mName = groupLayoutBinding.name;
mCardCount = groupLayoutBinding.cardCount;
mMoveUp = groupLayoutBinding.moveUp;
mMoveDown = groupLayoutBinding.moveDown;
mEdit = groupLayoutBinding.edit;
mDelete = groupLayoutBinding.delete;
public GroupListItemViewHolder(View inputView) {
super(inputView);
mName = inputView.findViewById(R.id.name);
mCardCount = inputView.findViewById(R.id.cardCount);
mMoveUp = inputView.findViewById(R.id.moveUp);
mMoveDown = inputView.findViewById(R.id.moveDown);
mEdit = inputView.findViewById(R.id.edit);
mDelete = inputView.findViewById(R.id.delete);
}
}
}

View File

@@ -24,25 +24,21 @@ import java.util.List;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.ImportExportActivityBinding;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.ImportExportResultType;
public class ImportExportActivity extends CatimaAppCompatActivity {
private ImportExportActivityBinding binding;
private static final String TAG = "Catima";
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
private ImportExportTask importExporter;
private String importAlertTitle;
@@ -59,16 +55,26 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ImportExportActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.importExport);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.import_export_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
Intent fileIntent = getIntent();
if (fileIntent != null && fileIntent.getType() != null) {
chooseImportType(false, fileIntent.getData());
// If the application does not have permissions to external
// storage, ask for it now
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE);
}
// would use ActivityResultContracts.CreateDocument() but mime type cannot be set
@@ -120,9 +126,9 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
intentCreateDocumentAction.setType("application/zip");
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip");
Button exportButton = binding.exportButton;
Button exportButton = findViewById(R.id.exportButton);
exportButton.setOnClickListener(v -> {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ImportExportActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(ImportExportActivity.this);
builder.setTitle(R.string.exportPassword);
FrameLayout container = new FrameLayout(ImportExportActivity.this);
@@ -152,12 +158,12 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
});
// Check that there is a file manager available
Button importFilesystem = binding.importOptionFilesystemButton;
importFilesystem.setOnClickListener(v -> chooseImportType(false, null));
Button importFilesystem = findViewById(R.id.importOptionFilesystemButton);
importFilesystem.setOnClickListener(v -> chooseImportType(false));
// Check that there is an app that data can be imported from
Button importApplication = binding.importOptionApplicationButton;
importApplication.setOnClickListener(v -> chooseImportType(true, null));
Button importApplication = findViewById(R.id.importOptionApplicationButton);
importApplication.setOnClickListener(v -> chooseImportType(true));
}
private void openFileForImport(Uri uri, char[] password) {
@@ -171,9 +177,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
}
}
private void chooseImportType(boolean choosePicker,
@Nullable Uri fileData) {
private void chooseImportType(boolean choosePicker) {
List<CharSequence> betaImportOptions = new ArrayList<>();
betaImportOptions.add("Fidme");
betaImportOptions.add("Stocard");
@@ -187,7 +191,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
importOptions.add(importOption);
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.chooseImportType)
.setItems(importOptions.toArray(new CharSequence[importOptions.size()]), (dialog, which) -> {
switch (which) {
@@ -225,12 +229,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
throw new IllegalArgumentException("Unknown DataFormat");
}
if (fileData != null) {
openFileForImport(fileData, null);
return;
}
new MaterialAlertDialogBuilder(this)
new AlertDialog.Builder(this)
.setTitle(importAlertTitle)
.setMessage(importAlertMessage)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@@ -297,6 +296,30 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSIONS_EXTERNAL_STORAGE) {
// If request is cancelled, the result arrays are empty.
boolean success = grantResults.length > 0;
for (int grant : grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
success = false;
}
}
if (!success) {
// External storage permission rejected, inform user that
// import/export is prevented
Toast.makeText(getApplicationContext(), R.string.noExternalStoragePermissionError,
Toast.LENGTH_LONG).show();
}
}
}
@Override
protected void onDestroy() {
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
@@ -317,7 +340,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
}
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.passwordRequired);
final EditText input = new EditText(this);
@@ -360,7 +383,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
return;
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
builder.setMessage(buildResultDialogMessage(result, true));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
@@ -371,7 +394,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
private void onExportComplete(ImportExportResult result, final Uri path) {
ImportExportResultType resultType = result.resultType();
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle);
builder.setMessage(buildResultDialogMessage(result, false));
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());

View File

@@ -15,10 +15,12 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
public class ImportURIHelper {
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
private static final String VALID_FROM = DBHelper.LoyaltyCardDbIds.VALID_FROM;
private static final String EXPIRY = DBHelper.LoyaltyCardDbIds.EXPIRY;
private static final String BALANCE = DBHelper.LoyaltyCardDbIds.BALANCE;
private static final String BALANCE_TYPE = DBHelper.LoyaltyCardDbIds.BALANCE_TYPE;
@@ -62,8 +64,7 @@ public class ImportURIHelper {
try {
// These values are allowed to be null
CatimaBarcode barcodeType = null;
Date validFrom = null;
Barcode barcodeType = null;
Date expiry = null;
BigDecimal balance = new BigDecimal("0");
Currency balanceType = null;
@@ -97,7 +98,7 @@ public class ImportURIHelper {
String unparsedBarcodeType = kv.get(BARCODE_TYPE);
if (unparsedBarcodeType != null && !unparsedBarcodeType.equals("")) {
barcodeType = CatimaBarcode.fromName(unparsedBarcodeType);
barcodeType = BarcodeFactory.fromName(unparsedBarcodeType);
}
String unparsedBalance = kv.get(BALANCE);
@@ -108,10 +109,6 @@ public class ImportURIHelper {
if (unparsedBalanceType != null && !unparsedBalanceType.equals("")) {
balanceType = Currency.getInstance(unparsedBalanceType);
}
String unparsedValidFrom = kv.get(VALID_FROM);
if (unparsedValidFrom != null && !unparsedValidFrom.equals("")) {
validFrom = new Date(Long.parseLong(unparsedValidFrom));
}
String unparsedExpiry = kv.get(EXPIRY);
if (unparsedExpiry != null && !unparsedExpiry.equals("")) {
expiry = new Date(Long.parseLong(unparsedExpiry));
@@ -122,8 +119,8 @@ public class ImportURIHelper {
headerColor = Integer.parseInt(unparsedHeaderColor);
}
return new LoyaltyCard(-1, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100, 0);
} catch (NumberFormatException | UnsupportedEncodingException | ArrayIndexOutOfBoundsException ex) {
return new LoyaltyCard(-1, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, 0, Utils.getUnixTime(), 100,0);
} catch (NullPointerException | NumberFormatException | UnsupportedEncodingException ex) {
throw new InvalidObjectException("Not a valid import URI");
}
}
@@ -155,9 +152,6 @@ public class ImportURIHelper {
if (loyaltyCard.balanceType != null) {
fragment = appendFragment(fragment, BALANCE_TYPE, loyaltyCard.balanceType.getCurrencyCode());
}
if (loyaltyCard.validFrom != null) {
fragment = appendFragment(fragment, VALID_FROM, String.valueOf(loyaltyCard.validFrom.getTime()));
}
if (loyaltyCard.expiry != null) {
fragment = appendFragment(fragment, EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
}

View File

@@ -9,12 +9,13 @@ import java.util.Currency;
import java.util.Date;
import androidx.annotation.Nullable;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
public class LoyaltyCard implements Parcelable {
public final int id;
public final String store;
public final String note;
public final Date validFrom;
public final Date expiry;
public final BigDecimal balance;
public final Currency balanceType;
@@ -24,7 +25,7 @@ public class LoyaltyCard implements Parcelable {
public final String barcodeId;
@Nullable
public final CatimaBarcode barcodeType;
public final Barcode barcodeType;
@Nullable
public final Integer headerColor;
@@ -34,16 +35,14 @@ public class LoyaltyCard implements Parcelable {
public final long lastUsed;
public int zoomLevel;
public LoyaltyCard(final int id, final String store, final String note, final Date validFrom,
final Date expiry, final BigDecimal balance, final Currency balanceType,
final String cardId, @Nullable final String barcodeId,
@Nullable final CatimaBarcode barcodeType,
public LoyaltyCard(final int id, final String store, final String note, final Date expiry,
final BigDecimal balance, final Currency balanceType, final String cardId,
@Nullable final String barcodeId, @Nullable final Barcode barcodeType,
@Nullable final Integer headerColor, final int starStatus,
final long lastUsed, final int zoomLevel, final int archiveStatus) {
this.id = id;
this.store = store;
this.note = note;
this.validFrom = validFrom;
this.expiry = expiry;
this.balance = balance;
this.balanceType = balanceType;
@@ -61,8 +60,6 @@ public class LoyaltyCard implements Parcelable {
id = in.readInt();
store = in.readString();
note = in.readString();
long tmpValidFrom = in.readLong();
validFrom = tmpValidFrom != -1 ? new Date(tmpValidFrom) : null;
long tmpExpiry = in.readLong();
expiry = tmpExpiry != -1 ? new Date(tmpExpiry) : null;
balance = (BigDecimal) in.readValue(BigDecimal.class.getClassLoader());
@@ -70,7 +67,7 @@ public class LoyaltyCard implements Parcelable {
cardId = in.readString();
barcodeId = in.readString();
String tmpBarcodeType = in.readString();
barcodeType = !tmpBarcodeType.isEmpty() ? CatimaBarcode.fromName(tmpBarcodeType) : null;
barcodeType = !tmpBarcodeType.isEmpty() ? BarcodeFactory.fromName(tmpBarcodeType) : null;
int tmpHeaderColor = in.readInt();
headerColor = tmpHeaderColor != -1 ? tmpHeaderColor : null;
starStatus = in.readInt();
@@ -84,7 +81,6 @@ public class LoyaltyCard implements Parcelable {
parcel.writeInt(id);
parcel.writeString(store);
parcel.writeString(note);
parcel.writeLong(validFrom != null ? validFrom.getTime() : -1);
parcel.writeLong(expiry != null ? expiry.getTime() : -1);
parcel.writeValue(balance);
parcel.writeValue(balanceType);
@@ -102,7 +98,6 @@ public class LoyaltyCard implements Parcelable {
int id = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ID));
String store = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STORE));
String note = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.NOTE));
long validFromLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.VALID_FROM));
long expiryLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY));
BigDecimal balance = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE)));
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
@@ -116,24 +111,19 @@ public class LoyaltyCard implements Parcelable {
int balanceTypeColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BALANCE_TYPE);
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
CatimaBarcode barcodeType = null;
Barcode barcodeType = null;
Currency balanceType = null;
Date validFrom = null;
Date expiry = null;
Integer headerColor = null;
if (cursor.isNull(barcodeTypeColumn) == false) {
barcodeType = CatimaBarcode.fromName(cursor.getString(barcodeTypeColumn));
barcodeType = BarcodeFactory.fromName(cursor.getString(barcodeTypeColumn));
}
if (cursor.isNull(balanceTypeColumn) == false) {
balanceType = Currency.getInstance(cursor.getString(balanceTypeColumn));
}
if (validFromLong > 0) {
validFrom = new Date(validFromLong);
}
if (expiryLong > 0) {
expiry = new Date(expiryLong);
}
@@ -142,7 +132,7 @@ public class LoyaltyCard implements Parcelable {
headerColor = cursor.getInt(headerColorColumn);
}
return new LoyaltyCard(id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed, zoomLevel, archived);
return new LoyaltyCard(id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starred, lastUsed, zoomLevel,archived);
}
@Override

View File

@@ -17,20 +17,17 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.color.MaterialColors;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Currency;
import java.util.Date;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.BlendModeColorFilterCompat;
import androidx.core.graphics.BlendModeCompat;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.LoyaltyCardLayoutBinding;
import protect.card_locker.preferences.Settings;
public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCursorAdapter.LoyaltyCardListItemViewHolder> {
@@ -85,15 +82,10 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
return mShowDetails;
}
@NonNull
@Override
public LoyaltyCardListItemViewHolder onCreateViewHolder(@NonNull ViewGroup inputParent, int inputViewType) {
LoyaltyCardLayoutBinding loyaltyCardLayoutBinding = LoyaltyCardLayoutBinding.inflate(
LayoutInflater.from(inputParent.getContext()),
inputParent,
false
);
return new LoyaltyCardListItemViewHolder(loyaltyCardLayoutBinding, mListener);
public LoyaltyCardListItemViewHolder onCreateViewHolder(ViewGroup inputParent, int inputViewType) {
View itemView = LayoutInflater.from(inputParent.getContext()).inflate(R.layout.loyalty_card_layout, inputParent, false);
return new LoyaltyCardListItemViewHolder(itemView, mListener);
}
public LoyaltyCard getCard(int position) {
@@ -115,21 +107,15 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
}
if (mShowDetails && !loyaltyCard.balance.equals(new BigDecimal("0"))) {
inputHolder.setExtraField(inputHolder.mBalanceField, Utils.formatBalance(mContext, loyaltyCard.balance, loyaltyCard.balanceType), null);
inputHolder.setBalanceField(loyaltyCard.balance, loyaltyCard.balanceType);
} else {
inputHolder.setExtraField(inputHolder.mBalanceField, null, null);
}
if (mShowDetails && loyaltyCard.validFrom != null) {
inputHolder.setExtraField(inputHolder.mValidFromField, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.validFrom), Utils.isNotYetValid(loyaltyCard.validFrom) ? Color.RED : null);
} else {
inputHolder.setExtraField(inputHolder.mValidFromField, null, null);
inputHolder.setBalanceField(null, null);
}
if (mShowDetails && loyaltyCard.expiry != null) {
inputHolder.setExtraField(inputHolder.mExpiryField, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry), Utils.hasExpired(loyaltyCard.expiry) ? Color.RED : null);
inputHolder.setExpiryField(loyaltyCard.expiry);
} else {
inputHolder.setExtraField(inputHolder.mExpiryField, null, null);
inputHolder.setExpiryField(null);
}
setHeaderHeight(inputHolder, mShowDetails);
@@ -141,7 +127,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
inputHolder.mCardIcon.setImageBitmap(Utils.generateIcon(mContext, loyaltyCard.store, loyaltyCard.headerColor).getLetterTile());
inputHolder.mCardIcon.setScaleType(ImageView.ScaleType.FIT_CENTER);
}
inputHolder.setIconBackgroundColor(loyaltyCard.headerColor != null ? loyaltyCard.headerColor : androidx.appcompat.R.attr.colorPrimary);
inputHolder.setIconBackgroundColor(loyaltyCard.headerColor != null ? loyaltyCard.headerColor : R.attr.colorPrimary);
inputHolder.toggleCardStateIcon(loyaltyCard.starStatus != 0, loyaltyCard.archiveStatus != 0, itemSelected(inputCursor.getPosition()));
@@ -241,7 +227,7 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
public class LoyaltyCardListItemViewHolder extends RecyclerView.ViewHolder {
public TextView mStoreField, mNoteField, mBalanceField, mValidFromField, mExpiryField;
public TextView mStoreField, mNoteField, mBalanceField, mExpiryField;
public ImageView mCardIcon, mStarBackground, mStarBorder, mTickIcon, mArchivedBackground;
public MaterialCardView mRow, mIconLayout;
public ConstraintLayout mStar, mArchived;
@@ -251,24 +237,22 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
protected LoyaltyCardListItemViewHolder(LoyaltyCardLayoutBinding loyaltyCardLayoutBinding, CardAdapterListener inputListener) {
super(loyaltyCardLayoutBinding.getRoot());
View inputView = loyaltyCardLayoutBinding.getRoot();
mRow = loyaltyCardLayoutBinding.row;
mDivider = loyaltyCardLayoutBinding.infoDivider;
mStoreField = loyaltyCardLayoutBinding.store;
mNoteField = loyaltyCardLayoutBinding.note;
mBalanceField = loyaltyCardLayoutBinding.balance;
mValidFromField = loyaltyCardLayoutBinding.validFrom;
mExpiryField = loyaltyCardLayoutBinding.expiry;
mIconLayout = loyaltyCardLayoutBinding.iconLayout;
mCardIcon = loyaltyCardLayoutBinding.thumbnail;
mStar = loyaltyCardLayoutBinding.star;
mStarBackground = loyaltyCardLayoutBinding.starBackground;
mStarBorder = loyaltyCardLayoutBinding.starBorder;
mArchived = loyaltyCardLayoutBinding.archivedIcon;
mArchivedBackground = loyaltyCardLayoutBinding.archiveBackground;
mTickIcon = loyaltyCardLayoutBinding.selectedThumbnail;
protected LoyaltyCardListItemViewHolder(View inputView, CardAdapterListener inputListener) {
super(inputView);
mRow = inputView.findViewById(R.id.row);
mDivider = inputView.findViewById(R.id.info_divider);
mStoreField = inputView.findViewById(R.id.store);
mNoteField = inputView.findViewById(R.id.note);
mBalanceField = inputView.findViewById(R.id.balance);
mExpiryField = inputView.findViewById(R.id.expiry);
mIconLayout = inputView.findViewById(R.id.icon_layout);
mCardIcon = inputView.findViewById(R.id.thumbnail);
mStar = inputView.findViewById(R.id.star);
mStarBackground = inputView.findViewById(R.id.star_background);
mStarBorder = inputView.findViewById(R.id.star_border);
mArchived = inputView.findViewById(R.id.archivedIcon);
mArchivedBackground = inputView.findViewById(R.id.archive_background);
mTickIcon = inputView.findViewById(R.id.selected_thumbnail);
inputView.setOnLongClickListener(view -> {
inputListener.onRowClicked(getAdapterPosition());
inputView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
@@ -276,41 +260,6 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
});
}
private void setExtraField(TextView field, String text, Integer color) {
// If text is null, hide the field
// If iconColor is null, use the default text and icon color based on theme
if (text == null) {
field.setVisibility(View.GONE);
field.requestLayout();
return;
}
int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
field.setVisibility(View.VISIBLE);
field.setText(text);
field.setTextSize(size);
field.setTextColor(color != null ? color : MaterialColors.getColor(mContext, com.google.android.material.R.attr.colorSecondary, ContextCompat.getColor(mContext, mDarkModeEnabled ? R.color.md_theme_dark_secondary : R.color.md_theme_light_secondary)));
int drawableSize = dpToPx((size * 24) / 14, mContext);
mDivider.setVisibility(View.VISIBLE);
field.setVisibility(View.VISIBLE);
Drawable icon = field.getCompoundDrawables()[0];
if (icon != null) {
icon.mutate();
icon.setBounds(0, 0, drawableSize, drawableSize);
field.setCompoundDrawablesRelative(icon, null, null, null);
if (color != null) {
icon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP));
} else {
icon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(mDarkModeEnabled ? Color.WHITE : Color.BLACK, BlendModeCompat.SRC_ATOP));
}
}
field.requestLayout();
}
public void setStoreField(String text) {
mStoreField.setText(text);
mStoreField.setTextSize(mSettings.getFontSizeMax(mSettings.getMediumFont()));
@@ -328,6 +277,55 @@ public class LoyaltyCardCursorAdapter extends BaseCursorAdapter<LoyaltyCardCurso
mNoteField.requestLayout();
}
public void setBalanceField(BigDecimal balance, Currency balanceType) {
if (balance == null) {
mBalanceField.setVisibility(View.GONE);
} else {
int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
int drawableSize = dpToPx((size * 24) / 14, mContext);
mDivider.setVisibility(View.VISIBLE);
mBalanceField.setVisibility(View.VISIBLE);
Drawable balanceIcon = mBalanceField.getCompoundDrawables()[0];
if (balanceIcon != null) {
balanceIcon.setBounds(0, 0, drawableSize, drawableSize);
mBalanceField.setCompoundDrawablesRelative(balanceIcon, null, null, null);
if (mDarkModeEnabled) {
balanceIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
}
mBalanceField.setText(Utils.formatBalance(mContext, balance, balanceType));
mBalanceField.setTextSize(size);
}
mBalanceField.requestLayout();
}
public void setExpiryField(Date expiry) {
if (expiry == null) {
mExpiryField.setVisibility(View.GONE);
} else {
int size = mSettings.getFontSizeMax(mSettings.getSmallFont());
int drawableSize = dpToPx((size * 24) / 14, mContext);
mDivider.setVisibility(View.VISIBLE);
mExpiryField.setVisibility(View.VISIBLE);
Drawable expiryIcon = mExpiryField.getCompoundDrawables()[0];
if (expiryIcon != null) {
expiryIcon.setBounds(0, 0, drawableSize, drawableSize);
mExpiryField.setCompoundDrawablesRelative(expiryIcon, null, null, null);
if (Utils.hasExpired(expiry)) {
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.SRC_ATOP));
} else if (mDarkModeEnabled) {
expiryIcon.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(Color.WHITE, BlendModeCompat.SRC_ATOP));
}
}
mExpiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry));
if (Utils.hasExpired(expiry)) {
mExpiryField.setTextColor(Color.RED);
}
mExpiryField.setTextSize(size);
}
mExpiryField.requestLayout();
}
public void toggleCardStateIcon(boolean enableStar, boolean enableArchive, boolean colorByTheme) {
/* the below code does not work in android 5! hence the change of drawable instead
boolean needDarkForeground = Utils.needsDarkForeground(mIconBackgroundColor);

View File

@@ -1,5 +1,6 @@
package protect.card_locker;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DatePickerDialog;
@@ -19,7 +20,6 @@ import android.os.LocaleList;
import android.text.Editable;
import android.text.InputType;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -37,7 +37,6 @@ import android.widget.Toast;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
@@ -58,7 +57,6 @@ import java.util.Calendar;
import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -70,8 +68,7 @@ import java.util.concurrent.Callable;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.Toolbar;
@@ -80,11 +77,10 @@ import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface;
import androidx.fragment.app.DialogFragment;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.LayoutChipChoiceBinding;
import protect.card_locker.databinding.LoyaltyCardEditActivityBinding;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.barcodes.BarcodeFactory;
public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
private LoyaltyCardEditActivityBinding binding;
private static final String TAG = "Catima";
private final String STATE_TAB_INDEX = "savedTab";
@@ -111,9 +107,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_FRONT = 100;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_BACK = 101;
private static final int PERMISSION_REQUEST_CAMERA_IMAGE_ICON = 102;
private static final int PERMISSION_REQUEST_STORAGE_IMAGE_FRONT = 103;
private static final int PERMISSION_REQUEST_STORAGE_IMAGE_BACK = 104;
private static final int PERMISSION_REQUEST_STORAGE_IMAGE_ICON = 105;
public static final String BUNDLE_ID = "id";
public static final String BUNDLE_DUPLICATE_ID = "duplicateId";
@@ -130,7 +123,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
EditText storeFieldEdit;
EditText noteFieldEdit;
ChipGroup groupsChips;
AutoCompleteTextView validFromField;
AutoCompleteTextView expiryField;
EditText balanceField;
AutoCompleteTextView balanceCurrencyField;
@@ -209,13 +201,12 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
(int) (fieldName == LoyaltyCardField.id ? value : loyaltyCard.id),
(String) (fieldName == LoyaltyCardField.store ? value : loyaltyCard.store),
(String) (fieldName == LoyaltyCardField.note ? value : loyaltyCard.note),
(Date) (fieldName == LoyaltyCardField.validFrom ? value : loyaltyCard.validFrom),
(Date) (fieldName == LoyaltyCardField.expiry ? value : loyaltyCard.expiry),
(BigDecimal) (fieldName == LoyaltyCardField.balance ? value : loyaltyCard.balance),
(Currency) (fieldName == LoyaltyCardField.balanceType ? value : loyaltyCard.balanceType),
(String) (fieldName == LoyaltyCardField.cardId ? value : loyaltyCard.cardId),
(String) (fieldName == LoyaltyCardField.barcodeId ? value : loyaltyCard.barcodeId),
(CatimaBarcode) (fieldName == LoyaltyCardField.barcodeType ? value : loyaltyCard.barcodeType),
(Barcode) (fieldName == LoyaltyCardField.barcodeType ? value : loyaltyCard.barcodeType),
(Integer) (fieldName == LoyaltyCardField.headerColor ? value : loyaltyCard.headerColor),
(int) (fieldName == LoyaltyCardField.starStatus ? value : loyaltyCard.starStatus),
0, // Unimportant, always set to null in doSave so the DB updates it to the current timestamp
@@ -255,7 +246,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
tabs = binding.tabs;
tabs = findViewById(R.id.tabs);
savedInstanceState.putInt(STATE_TAB_INDEX, tabs.getSelectedTabPosition());
savedInstanceState.putParcelable(STATE_TEMP_CARD, tempLoyaltyCard);
savedInstanceState.putInt(STATE_REQUESTED_IMAGE, mRequestedImage);
@@ -292,7 +283,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
tempLoyaltyCard = savedInstanceState.getParcelable(STATE_TEMP_CARD);
super.onRestoreInstanceState(savedInstanceState);
tabs = binding.tabs;
tabs = findViewById(R.id.tabs);
tabs.selectTab(tabs.getTabAt(savedInstanceState.getInt(STATE_TAB_INDEX)));
mRequestedImage = savedInstanceState.getInt(STATE_REQUESTED_IMAGE);
mFrontImageUnsaved = savedInstanceState.getInt(STATE_FRONT_IMAGE_UNSAVED) == 1;
@@ -308,11 +299,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = LoyaltyCardEditActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
toolbar = binding.toolbar;
setContentView(R.layout.loyalty_card_edit_activity);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mDatabase = new DBHelper(this).getWritableDatabase();
@@ -324,29 +318,28 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
currencies.put(currency.getSymbol(), currency);
}
tabs = binding.tabs;
thumbnail = binding.thumbnail;
thumbnailEditIcon = binding.thumbnailEditIcon;
storeFieldEdit = binding.storeNameEdit;
noteFieldEdit = binding.noteEdit;
groupsChips = binding.groupChips;
validFromField = binding.validFromField;
expiryField = binding.expiryField;
balanceField = binding.balanceField;
balanceCurrencyField = binding.balanceCurrencyField;
cardIdFieldView = binding.cardIdView;
barcodeIdField = binding.barcodeIdField;
barcodeTypeField = binding.barcodeTypeField;
barcodeImage = binding.barcode;
tabs = findViewById(R.id.tabs);
thumbnail = findViewById(R.id.thumbnail);
thumbnailEditIcon = findViewById(R.id.thumbnailEditIcon);
storeFieldEdit = findViewById(R.id.storeNameEdit);
noteFieldEdit = findViewById(R.id.noteEdit);
groupsChips = findViewById(R.id.groupChips);
expiryField = findViewById(R.id.expiryField);
balanceField = findViewById(R.id.balanceField);
balanceCurrencyField = findViewById(R.id.balanceCurrencyField);
cardIdFieldView = findViewById(R.id.cardIdView);
barcodeIdField = findViewById(R.id.barcodeIdField);
barcodeTypeField = findViewById(R.id.barcodeTypeField);
barcodeImage = findViewById(R.id.barcode);
barcodeImage.setClipToOutline(true);
barcodeImageLayout = binding.barcodeLayout;
barcodeCaptureLayout = binding.barcodeCaptureLayout;
cardImageFrontHolder = binding.frontImageHolder;
cardImageBackHolder = binding.backImageHolder;
cardImageFront = binding.frontImage;
cardImageBack = binding.backImage;
barcodeImageLayout = findViewById(R.id.barcodeLayout);
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
cardImageFrontHolder = findViewById(R.id.frontImageHolder);
cardImageBackHolder = findViewById(R.id.backImageHolder);
cardImageFront = findViewById(R.id.frontImage);
cardImageBack = findViewById(R.id.backImage);
enterButton = binding.enterButton;
enterButton = findViewById(R.id.enterButton);
barcodeImageGenerationFinishedCallback = () -> {
if (!(boolean) barcodeImage.getTag()) {
@@ -370,9 +363,38 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}
});
addDateFieldTextChangedListener(validFromField, R.string.anyDate, R.string.chooseValidFromDate, LoyaltyCardField.validFrom);
expiryField.addTextChangedListener(new SimpleTextWatcher() {
CharSequence lastValue;
addDateFieldTextChangedListener(expiryField, R.string.never, R.string.chooseExpiryDate, LoyaltyCardField.expiry);
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
lastValue = s;
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().equals(getString(R.string.never))) {
expiryField.setTag(null);
} else if (s.toString().equals(getString(R.string.chooseExpiryDate))) {
if (!lastValue.toString().equals(getString(R.string.chooseExpiryDate))) {
expiryField.setText(lastValue);
}
DialogFragment datePickerFragment = new DatePickerFragment(LoyaltyCardEditActivity.this, expiryField);
datePickerFragment.show(getSupportFragmentManager(), "datePicker");
}
updateTempState(LoyaltyCardField.expiry, expiryField.getTag());
}
@Override
public void afterTextChanged(Editable s) {
ArrayList<String> expiryList = new ArrayList<>();
expiryList.add(0, getString(R.string.never));
expiryList.add(1, getString(R.string.chooseExpiryDate));
ArrayAdapter<String> expiryAdapter = new ArrayAdapter<>(LoyaltyCardEditActivity.this, android.R.layout.select_dialog_item, expiryList);
expiryField.setAdapter(expiryAdapter);
}
});
balanceField.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
@@ -489,7 +511,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
barcodeIdField.setText(lastValue);
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(LoyaltyCardEditActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(LoyaltyCardEditActivity.this);
builder.setTitle(R.string.setBarcodeId);
final EditText input = new EditText(LoyaltyCardEditActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
@@ -533,13 +555,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
updateTempState(LoyaltyCardField.barcodeType, null);
} else {
try {
CatimaBarcode barcodeFormat = CatimaBarcode.fromPrettyName(s.toString());
updateTempState(LoyaltyCardField.barcodeType, barcodeFormat);
if (!barcodeFormat.isSupported()) {
Toast.makeText(LoyaltyCardEditActivity.this, getString(R.string.unsupportedBarcodeType), Toast.LENGTH_LONG).show();
}
updateTempState(LoyaltyCardField.barcodeType, barcodeTypeField.getTag());
} catch (IllegalArgumentException e) {
}
}
@@ -670,16 +686,16 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}
mCropperOptions.setAspectRatioOptions(selectedByDefault,
new AspectRatio(null, 1, 1),
new AspectRatio(getResources().getString(com.yalantis.ucrop.R.string.ucrop_label_original).toUpperCase(), sourceWidth, sourceHeight),
new AspectRatio(getResources().getString(R.string.ucrop_label_original).toUpperCase(), sourceWidth, sourceHeight),
new AspectRatio(getResources().getString(R.string.card).toUpperCase(), 85.6f, 53.98f)
);
// Fix theming
int colorPrimary = MaterialColors.getColor(this, androidx.appcompat.R.attr.colorPrimary, ContextCompat.getColor(this, R.color.md_theme_light_primary));
int colorOnPrimary = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnPrimary, ContextCompat.getColor(this, R.color.md_theme_light_onPrimary));
int colorSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
int colorPrimary = MaterialColors.getColor(this, R.attr.colorPrimary, ContextCompat.getColor(this, R.color.md_theme_light_primary));
int colorOnPrimary = MaterialColors.getColor(this, R.attr.colorOnPrimary, ContextCompat.getColor(this, R.color.md_theme_light_onPrimary));
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
int colorBackground = MaterialColors.getColor(this, android.R.attr.colorBackground, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
mCropperOptions.setToolbarColor(colorSurface);
mCropperOptions.setStatusBarColor(colorSurface);
@@ -753,7 +769,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}
} else {
// New card, use default values
tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(), 100,0);
tempLoyaltyCard = new LoyaltyCard(-1, "", "", null, new BigDecimal("0"), null, "", null, null, null, 0, Utils.getUnixTime(), 100,0);
}
}
@@ -796,12 +812,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
storeFieldEdit.setText(tempLoyaltyCard.store);
noteFieldEdit.setText(tempLoyaltyCard.note);
formatDateField(this, validFromField, tempLoyaltyCard.validFrom);
formatDateField(this, expiryField, tempLoyaltyCard.expiry);
formatExpiryField(this, expiryField, tempLoyaltyCard.expiry);
formatBalanceCurrencyField(tempLoyaltyCard.balanceType);
cardIdFieldView.setText(tempLoyaltyCard.cardId);
barcodeIdField.setText(tempLoyaltyCard.barcodeId != null ? tempLoyaltyCard.barcodeId : getString(R.string.sameAsCardId));
barcodeTypeField.setText(tempLoyaltyCard.barcodeType != null ? tempLoyaltyCard.barcodeType.prettyName() : getString(R.string.noBarcode));
setbarcodeTypeField(tempLoyaltyCard.barcodeType);
if (groupsChips.getChildCount() == 0) {
List<Group> existingGroups = DBHelper.getGroups(mDatabase);
@@ -815,9 +830,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}
for (Group group : DBHelper.getGroups(mDatabase)) {
LayoutChipChoiceBinding chipChoiceBinding = LayoutChipChoiceBinding
.inflate(LayoutInflater.from(groupsChips.getContext()), groupsChips, false);
Chip chip = chipChoiceBinding.getRoot();
Chip chip = (Chip) getLayoutInflater().inflate(R.layout.layout_chip_choice, groupsChips, false);
chip.setText(group._id);
chip.setTag(group);
@@ -852,9 +865,10 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
// Update from intent
if (barcodeType != null) {
try {
barcodeTypeField.setText(CatimaBarcode.fromName(barcodeType).prettyName());
Barcode barcode = BarcodeFactory.fromName(barcodeType);
setbarcodeTypeField(barcode);
} catch (IllegalArgumentException e) {
barcodeTypeField.setText(getString(R.string.noBarcode));
setbarcodeTypeField(null);
}
}
@@ -889,7 +903,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
cardImageFrontHolder.setOnClickListener(new ChooseCardImage());
cardImageBackHolder.setOnClickListener(new ChooseCardImage());
FloatingActionButton saveButton = binding.fabSave;
FloatingActionButton saveButton = findViewById(R.id.fabSave);
saveButton.setOnClickListener(v -> doSave());
saveButton.bringToFront();
@@ -911,7 +925,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
protected void setColorFromIcon() {
Object icon = thumbnail.getTag();
if (icon != null && (icon instanceof Bitmap)) {
int headerColor = Utils.getHeaderColorFromImage((Bitmap) icon, tempLoyaltyCard.headerColor != null ? tempLoyaltyCard.headerColor : androidx.appcompat.R.attr.colorPrimary);
int headerColor = Utils.getHeaderColorFromImage((Bitmap) icon, tempLoyaltyCard.headerColor != null ? tempLoyaltyCard.headerColor : R.attr.colorPrimary);
updateTempState(LoyaltyCardField.headerColor, headerColor);
@@ -932,62 +946,18 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}
}
protected void addDateFieldTextChangedListener(AutoCompleteTextView dateField, @StringRes int defaultOptionStringId, @StringRes int chooseDateOptionStringId, LoyaltyCardField loyaltyCardField) {
dateField.addTextChangedListener(new SimpleTextWatcher() {
CharSequence lastValue;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
lastValue = s;
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().equals(getString(defaultOptionStringId))) {
dateField.setTag(null);
} else if (s.toString().equals(getString(chooseDateOptionStringId))) {
if (!lastValue.toString().equals(getString(chooseDateOptionStringId))) {
dateField.setText(lastValue);
}
DialogFragment datePickerFragment = new DatePickerFragment(
LoyaltyCardEditActivity.this,
dateField,
// if the expiry date is being set, set date picker's minDate to the 'valid from' date
loyaltyCardField == LoyaltyCardField.expiry ? (Date) validFromField.getTag() : null,
// if the 'valid from' date is being set, set date picker's maxDate to the expiry date
loyaltyCardField == LoyaltyCardField.validFrom ? (Date) expiryField.getTag() : null);
datePickerFragment.show(getSupportFragmentManager(), "datePicker");
}
updateTempState(loyaltyCardField, dateField.getTag());
}
@Override
public void afterTextChanged(Editable s) {
ArrayList<String> dropdownOptions = new ArrayList<>();
dropdownOptions.add(0, getString(defaultOptionStringId));
dropdownOptions.add(1, getString(chooseDateOptionStringId));
ArrayAdapter<String> dropdownOptionsAdapter = new ArrayAdapter<>(LoyaltyCardEditActivity.this, android.R.layout.select_dialog_item, dropdownOptions);
dateField.setAdapter(dropdownOptionsAdapter);
}
});
private void setbarcodeTypeField(Barcode barcode) {
barcodeTypeField.setTag(barcode);
barcodeTypeField.setText(barcode != null ? barcode.prettyName() : getString(R.string.noBarcode));
}
protected static void formatDateField(Context context, EditText textField, Date date) {
textField.setTag(date);
protected static void formatExpiryField(Context context, EditText expiryField, Date expiry) {
expiryField.setTag(expiry);
if (date == null) {
String text;
if (textField.getId() == R.id.validFromField) {
text = context.getString(R.string.anyDate);
} else if (textField.getId() == R.id.expiryField) {
text = context.getString(R.string.never);
} else {
throw new IllegalArgumentException("Unknown textField Id " + textField.getId());
}
textField.setText(text);
if (expiry == null) {
expiryField.setText(context.getString(R.string.never));
} else {
textField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(date));
expiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(expiry));
}
}
@@ -1008,59 +978,14 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
onMockedRequestPermissionsResult(requestCode, permissions, grantResults);
}
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
Integer failureReason = null;
if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) {
if (granted) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_FRONT) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_FRONT);
return;
}
failureReason = R.string.cameraPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_BACK) {
if (granted) {
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_BACK) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_BACK);
return;
}
failureReason = R.string.cameraPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_ICON) {
if (granted) {
} else if (requestCode == PERMISSION_REQUEST_CAMERA_IMAGE_ICON) {
takePhotoForCard(Utils.CARD_IMAGE_FROM_CAMERA_ICON);
return;
}
failureReason = R.string.cameraPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_STORAGE_IMAGE_FRONT) {
if (granted) {
selectImageFromGallery(Utils.CARD_IMAGE_FROM_FILE_FRONT);
return;
}
failureReason = R.string.storageReadPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_STORAGE_IMAGE_BACK) {
if (granted) {
selectImageFromGallery(Utils.CARD_IMAGE_FROM_FILE_BACK);
return;
}
failureReason = R.string.storageReadPermissionRequired;
} else if (requestCode == PERMISSION_REQUEST_STORAGE_IMAGE_ICON) {
if (granted) {
selectImageFromGallery(Utils.CARD_IMAGE_FROM_FILE_ICON);
return;
}
failureReason = R.string.storageReadPermissionRequired;
}
if (failureReason != null) {
Toast.makeText(this, failureReason, Toast.LENGTH_LONG).show();
}
}
@@ -1077,7 +1002,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
return;
}
new MaterialAlertDialogBuilder(this)
new AlertDialog.Builder(this)
.setTitle(R.string.updateBarcodeQuestionTitle)
.setMessage(R.string.updateBarcodeQuestionText)
.setPositiveButton(R.string.yes, (dialog, which) -> {
@@ -1111,7 +1036,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}
if (confirmExitDialog == null) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.leaveWithoutSaveTitle);
builder.setMessage(R.string.leaveWithoutSaveConfirmation);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
@@ -1132,24 +1057,6 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
mPhotoTakerLauncher.launch(photoURI);
}
private void selectImageFromGallery(int type) {
mRequestedImage = type;
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentIntent.setType("image/*");
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(R.string.addFromImage));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
try {
mPhotoPickerLauncher.launch(chooserIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
class EditCardIdAndBarcode implements View.OnClickListener {
@Override
public void onClick(View v) {
@@ -1231,37 +1138,58 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
}
cardOptions.put(getString(R.string.takePhoto), () -> {
int permissionRequestType;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int permissionRequestType;
if (v.getId() == R.id.frontImageHolder) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_FRONT;
} else if (v.getId() == R.id.backImageHolder) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_BACK;
} else if (v.getId() == R.id.thumbnail) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_ICON;
if (v.getId() == R.id.frontImageHolder) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_FRONT;
} else if (v.getId() == R.id.backImageHolder) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_BACK;
} else if (v.getId() == R.id.thumbnail) {
permissionRequestType = PERMISSION_REQUEST_CAMERA_IMAGE_ICON;
} else {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
}
requestPermissions(new String[]{Manifest.permission.CAMERA}, permissionRequestType);
} else {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
int cardImageType;
if (v.getId() == R.id.frontImageHolder) {
cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_FRONT;
} else if (v.getId() == R.id.backImageHolder) {
cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_BACK;
} else if (v.getId() == R.id.thumbnail) {
cardImageType = Utils.CARD_IMAGE_FROM_CAMERA_ICON;
} else {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
}
takePhotoForCard(cardImageType);
}
PermissionUtils.requestCameraPermission(LoyaltyCardEditActivity.this, permissionRequestType);
return null;
});
cardOptions.put(getString(R.string.addFromImage), () -> {
int permissionRequestType;
if (v.getId() == R.id.frontImageHolder) {
permissionRequestType = PERMISSION_REQUEST_STORAGE_IMAGE_FRONT;
mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_FRONT;
} else if (v.getId() == R.id.backImageHolder) {
permissionRequestType = PERMISSION_REQUEST_STORAGE_IMAGE_BACK;
mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_BACK;
} else if (v.getId() == R.id.thumbnail) {
permissionRequestType = PERMISSION_REQUEST_STORAGE_IMAGE_ICON;
mRequestedImage = Utils.CARD_IMAGE_FROM_FILE_ICON;
} else {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
}
PermissionUtils.requestStorageReadPermission(LoyaltyCardEditActivity.this, permissionRequestType);
Intent i = new Intent(Intent.ACTION_PICK);
i.setType("image/*");
try {
mPhotoPickerLauncher.launch(i);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
return null;
});
@@ -1278,7 +1206,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
throw new IllegalArgumentException("Unknown ID type " + v.getId());
}
new MaterialAlertDialogBuilder(LoyaltyCardEditActivity.this)
new AlertDialog.Builder(LoyaltyCardEditActivity.this)
.setTitle(getString(titleResource))
.setItems(cardOptions.keySet().toArray(new CharSequence[cardOptions.size()]), (dialog, which) -> {
Iterator<Callable<Void>> callables = cardOptions.values().iterator();
@@ -1306,17 +1234,11 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
implements DatePickerDialog.OnDateSetListener {
final Context context;
final EditText textFieldEdit;
@Nullable
final Date minDate;
@Nullable
final Date maxDate;
final EditText expiryFieldEdit;
DatePickerFragment(Context context, EditText textFieldEdit, @Nullable Date minDate, @Nullable Date maxDate) {
DatePickerFragment(Context context, EditText expiryFieldEdit) {
this.context = context;
this.textFieldEdit = textFieldEdit;
this.minDate = minDate;
this.maxDate = maxDate;
this.expiryFieldEdit = expiryFieldEdit;
}
@NonNull
@@ -1325,7 +1247,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
// Use the current date as the default date in the picker
final Calendar c = Calendar.getInstance();
Date date = (Date) textFieldEdit.getTag();
Date date = (Date) expiryFieldEdit.getTag();
if (date != null) {
c.setTime(date);
}
@@ -1336,29 +1258,23 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
// Create a new instance of DatePickerDialog and return it
DatePickerDialog datePickerDialog = new DatePickerDialog(getActivity(), this, year, month, day);
datePickerDialog.getDatePicker().setMinDate(minDate != null ? minDate.getTime() : getDefaultMinDateOfDatePicker());
datePickerDialog.getDatePicker().setMaxDate(maxDate != null ? maxDate.getTime() : getDefaultMaxDateOfDatePicker());
datePickerDialog.getDatePicker().setMinDate(getMinDateOfDatePicker());
return datePickerDialog;
}
private long getDefaultMinDateOfDatePicker() {
private long getMinDateOfDatePicker()
{
Calendar minDateCalendar = Calendar.getInstance();
minDateCalendar.set(1970, 0, 1);
return minDateCalendar.getTimeInMillis();
}
private long getDefaultMaxDateOfDatePicker() {
Calendar maxDateCalendar = Calendar.getInstance();
maxDateCalendar.set(2100, 11, 31);
return maxDateCalendar.getTimeInMillis();
}
public void onDateSet(DatePicker view, int year, int month, int day) {
Calendar c = new GregorianCalendar();
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, year);
c.set(Calendar.MONTH, month);
c.set(Calendar.DAY_OF_MONTH, day);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.HOUR, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
@@ -1367,7 +1283,7 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
Date date = new Date(unixTime);
formatDateField(context, textFieldEdit, date);
formatExpiryField(context, expiryFieldEdit, date);
}
}
@@ -1408,9 +1324,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
// This makes the DBHelper set it to the current date
// So that new and edited card are always on top when sorting by recently used
if (updateLoyaltyCard) {
DBHelper.updateLoyaltyCard(mDatabase, loyaltyCardId, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.validFrom, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, tempLoyaltyCard.starStatus, null, tempLoyaltyCard.archiveStatus);
DBHelper.updateLoyaltyCard(mDatabase, loyaltyCardId, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, tempLoyaltyCard.starStatus, null, tempLoyaltyCard.archiveStatus);
} else {
loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.validFrom, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, 0, null, 0);
loyaltyCardId = (int) DBHelper.insertLoyaltyCard(mDatabase, tempLoyaltyCard.store, tempLoyaltyCard.note, tempLoyaltyCard.expiry, tempLoyaltyCard.balance, tempLoyaltyCard.balanceType, tempLoyaltyCard.cardId, tempLoyaltyCard.barcodeId, tempLoyaltyCard.barcodeType, tempLoyaltyCard.headerColor, 0, null, 0);
}
try {
@@ -1584,9 +1500,9 @@ public class LoyaltyCardEditActivity extends CatimaAppCompatActivity {
return;
}
View cardPart = binding.cardPart;
View optionsPart = binding.optionsPart;
View picturesPart = binding.picturesPart;
View cardPart = findViewById(R.id.cardPart);
View optionsPart = findViewById(R.id.optionsPart);
View picturesPart = findViewById(R.id.picturesPart);
if (getString(R.string.card).equals(part)) {
cardPart.setVisibility(View.VISIBLE);

View File

@@ -4,7 +4,6 @@ public enum LoyaltyCardField {
id,
store,
note,
validFrom,
expiry,
balance,
balanceType,

View File

@@ -1,7 +1,6 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
@@ -14,11 +13,9 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.DigitsKeyListener;
import android.text.style.ForegroundColorSpan;
import android.text.util.Linkify;
import android.util.Log;
@@ -28,14 +25,10 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -43,7 +36,6 @@ import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
@@ -57,33 +49,25 @@ import androidx.core.graphics.BlendModeColorFilterCompat;
import androidx.core.graphics.BlendModeCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.core.widget.TextViewCompat;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomappbar.BottomAppBar;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputEditText;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Currency;
import java.util.Date;
import java.util.List;
import java.util.function.Predicate;
import protect.card_locker.async.TaskHandler;
import protect.card_locker.databinding.LoyaltyCardViewLayoutBinding;
import protect.card_locker.barcodes.Barcode;
import protect.card_locker.preferences.Settings;
public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements GestureDetector.OnGestureListener {
private LoyaltyCardViewLayoutBinding binding;
private static final String TAG = "Catima";
private GestureDetector mGestureDetector;
@@ -95,7 +79,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
ImageButton bottomAppBarInfoButton;
ImageButton bottomAppBarPreviousButton;
ImageButton bottomAppBarNextButton;
ImageButton bottomAppBarUpdateBalanceButton;
AppCompatTextView storeName;
ImageButton maximizeButton;
ImageView mainImage;
@@ -119,7 +102,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
String cardIdString;
String barcodeIdString;
CatimaBarcode format;
Barcode format;
FloatingActionButton editButton;
@@ -135,7 +118,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
int mainImageIndex = 0;
List<ImageType> imageTypes;
private ImageView[] dots;
boolean isBarcodeSupported = true;
static final String STATE_IMAGEINDEX = "imageIndex";
static final String STATE_FULLSCREEN = "isFullscreen";
@@ -204,7 +186,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
.setDataAndType(FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file), "image/*")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
} catch (ActivityNotFoundException e) {
}
catch (ActivityNotFoundException e) {
// Display a toast message if an image viewer is not installed on device
Toast.makeText(this, R.string.failedLaunchingPhotoPicker, Toast.LENGTH_SHORT).show();
e.printStackTrace();
@@ -328,7 +311,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
super.onCreate(savedInstanceState);
binding = LoyaltyCardViewLayoutBinding.inflate(getLayoutInflater());
settings = new Settings(this);
@@ -350,31 +332,30 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
extractIntentFields(getIntent());
setContentView(binding.getRoot());
setContentView(R.layout.loyalty_card_view_layout);
database = new DBHelper(this).getWritableDatabase();
importURIHelper = new ImportURIHelper(this);
coordinatorLayout = binding.coordinatorLayout;
mainLayout = binding.mainLayout;
cardIdFieldView = binding.cardIdView;
storeName = binding.storeName;
maximizeButton = binding.maximizeButton;
mainImage = binding.mainImage;
coordinatorLayout = findViewById(R.id.coordinator_layout);
mainLayout = findViewById(R.id.mainLayout);
cardIdFieldView = findViewById(R.id.cardIdView);
storeName = findViewById(R.id.storeName);
maximizeButton = findViewById(R.id.maximizeButton);
mainImage = findViewById(R.id.mainImage);
mainImage.setClipToOutline(true);
dotIndicator = binding.dotIndicator;
minimizeButton = binding.minimizeButton;
collapsingToolbarLayout = binding.collapsingToolbarLayout;
appBarLayout = binding.appBarLayout;
bottomAppBar = binding.bottomAppBar;
iconImage = binding.iconImage;
portraitToolbar = binding.toolbar;
landscapeToolbar = binding.toolbarLandscape;
dotIndicator = findViewById(R.id.dotIndicator);
minimizeButton = findViewById(R.id.minimizeButton);
collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout);
appBarLayout = findViewById(R.id.app_bar_layout);
bottomAppBar = findViewById(R.id.bottom_app_bar);
iconImage = findViewById(R.id.icon_image);
portraitToolbar = findViewById(R.id.toolbar);
landscapeToolbar = findViewById(R.id.toolbar_landscape);
bottomAppBarInfoButton = binding.buttonShowInfo;
bottomAppBarPreviousButton = binding.buttonPrevious;
bottomAppBarNextButton = binding.buttonNext;
bottomAppBarUpdateBalanceButton = binding.buttonUpdateBalance;
bottomAppBarInfoButton = findViewById(R.id.button_show_info);
bottomAppBarPreviousButton = findViewById(R.id.button_previous);
bottomAppBarNextButton = findViewById(R.id.button_next);
barcodeImageGenerationFinishedCallback = () -> {
if (!(boolean) mainImage.getTag()) {
@@ -389,8 +370,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
};
centerGuideline = binding.centerGuideline;
barcodeScaler = binding.barcodeScaler;
centerGuideline = findViewById(R.id.centerGuideline);
barcodeScaler = findViewById(R.id.barcodeScaler);
barcodeScaler.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -428,7 +409,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
maximizeButton.setOnClickListener(v -> setFullscreen(true));
minimizeButton.setOnClickListener(v -> setFullscreen(false));
editButton = binding.fabEdit;
editButton = findViewById(R.id.fabEdit);
editButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = new Bundle();
@@ -451,7 +432,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
bottomAppBarInfoButton.setOnClickListener(view -> showInfoDialog());
bottomAppBarPreviousButton.setOnClickListener(view -> prevNextCard(false));
bottomAppBarNextButton.setOnClickListener(view -> prevNextCard(true));
bottomAppBarUpdateBalanceButton.setOnClickListener(view -> showBalanceUpdateDialog());
mGestureDetector = new GestureDetector(this, this);
View.OnTouchListener gestureTouchListener = (v, event) -> mGestureDetector.onTouchEvent(event);
@@ -466,7 +446,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
iconImage.setClipBounds(new Rect(left, top, right, bottom));
}
});
this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
}
private SpannableStringBuilder padSpannableString(SpannableStringBuilder spannableStringBuilder) {
@@ -482,29 +461,17 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
private void showInfoDialog() {
AlertDialog.Builder infoDialog = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder infoDialog = new AlertDialog.Builder(this);
int dialogContentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
int dialogTitlePadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_title_padding);
TextView infoTitleView = new TextView(this);
infoTitleView.setPadding(
dialogContentPadding,
dialogContentPadding,
dialogContentPadding,
dialogTitlePadding
);
infoTitleView.setPadding(20, 20, 20, 20);
infoTitleView.setTextSize(settings.getFontSizeMax(settings.getMediumFont()));
infoTitleView.setText(loyaltyCard.store);
infoDialog.setCustomTitle(infoTitleView);
infoDialog.setTitle(loyaltyCard.store);
TextView infoTextview = new TextView(this);
infoTextview.setPadding(
dialogContentPadding,
0,
dialogContentPadding,
0
);
infoTextview.setPadding(20, 0, 20, 0);
infoTextview.setAutoLinkMask(Linkify.ALL);
infoTextview.setTextIsSelectable(true);
@@ -528,9 +495,19 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
infoText.append(getString(R.string.balanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
}
appendDateInfo(infoText, loyaltyCard.validFrom, (Utils::isNotYetValid), R.string.validFromSentence, R.string.validFromSentence);
if (loyaltyCard.expiry != null) {
String formattedExpiry = DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry);
appendDateInfo(infoText, loyaltyCard.expiry, (Utils::hasExpired), R.string.expiryStateSentenceExpired, R.string.expiryStateSentence);
padSpannableString(infoText);
if (Utils.hasExpired(loyaltyCard.expiry)) {
int start = infoText.length();
infoText.append(getString(R.string.expiryStateSentenceExpired, formattedExpiry));
infoText.setSpan(new ForegroundColorSpan(Color.RED), start, infoText.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
} else {
infoText.append(getString(R.string.expiryStateSentence, formattedExpiry));
}
}
infoTextview.setText(infoText);
@@ -539,97 +516,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
infoDialog.create().show();
}
private void appendDateInfo(SpannableStringBuilder infoText, Date date, Predicate<Date> dateCheck, @StringRes int dateCheckTrueString, @StringRes int dateCheckFalseString) {
if (date != null) {
String formattedDate = DateFormat.getDateInstance(DateFormat.LONG).format(date);
padSpannableString(infoText);
if (dateCheck.test(date)) {
int start = infoText.length();
infoText.append(getString(dateCheckTrueString, formattedDate));
infoText.setSpan(new ForegroundColorSpan(Color.RED), start, infoText.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} else {
infoText.append(getString(dateCheckFalseString, formattedDate));
}
}
}
private void showBalanceUpdateDialog() {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.updateBalanceTitle);
FrameLayout container = new FrameLayout(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
int contentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
params.leftMargin = contentPadding;
params.rightMargin = contentPadding;
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
TextView currentTextview = new TextView(this);
currentTextview.setText(getString(R.string.currentBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
layout.addView(currentTextview);
TextView updateTextView = new TextView(this);
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(this, loyaltyCard.balance, loyaltyCard.balanceType)));
layout.addView(updateTextView);
final TextInputEditText input = new TextInputEditText(this);
Context dialogContext = this;
input.setInputType(InputType.TYPE_CLASS_NUMBER);
input.setKeyListener(DigitsKeyListener.getInstance("0123456789,."));
input.setHint(R.string.updateBalanceHint);
input.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
BigDecimal newBalance;
try {
newBalance = calculateNewBalance(loyaltyCard.balance, loyaltyCard.balanceType, s.toString());
} catch (ParseException e) {
input.setTag(null);
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(dialogContext, loyaltyCard.balance, loyaltyCard.balanceType)));
return;
}
// Save new balance into this element
input.setTag(newBalance);
updateTextView.setText(getString(R.string.newBalanceSentence, Utils.formatBalance(dialogContext, newBalance, loyaltyCard.balanceType)));
}
});
layout.addView(input);
layout.setLayoutParams(params);
container.addView(layout);
builder.setView(container);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
// Grab calculated balance from input field
BigDecimal newBalance = (BigDecimal) input.getTag();
if (newBalance == null) {
return;
}
// Actually update balance
DBHelper.updateLoyaltyCardBalance(database, loyaltyCardId, newBalance);
// Reload UI
this.onResume();
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
AlertDialog dialog = builder.create();
dialog.show();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
input.requestFocus();
}
private BigDecimal calculateNewBalance(BigDecimal currentBalance, Currency currency, String unparsedSubtraction) throws ParseException {
BigDecimal subtraction = Utils.parseBalance(unparsedSubtraction, currency);
return currentBalance.subtract(subtraction).max(new BigDecimal(0));
}
private void setBottomAppBarButtonState() {
if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.validFrom != null || loyaltyCard.expiry != null) {
if (!loyaltyCard.note.isEmpty() || !loyaltyCardGroups.isEmpty() || hasBalance(loyaltyCard) || loyaltyCard.expiry != null) {
bottomAppBarInfoButton.setVisibility(View.VISIBLE);
} else {
bottomAppBarInfoButton.setVisibility(View.GONE);
@@ -642,8 +530,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
bottomAppBarPreviousButton.setVisibility(View.VISIBLE);
bottomAppBarNextButton.setVisibility(View.VISIBLE);
}
bottomAppBarUpdateBalanceButton.setVisibility(hasBalance(loyaltyCard) ? View.VISIBLE : View.GONE);
}
private void prevNextCard(boolean next) {
@@ -720,11 +606,8 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
}
if (settings.getDisableLockscreenWhileViewingCard()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true);
} else {
showWhenLockedSdkLessThan27(window);
}
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
window.setAttributes(attributes);
@@ -816,14 +699,11 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
fixImageButtonColor(bottomAppBarInfoButton);
fixImageButtonColor(bottomAppBarPreviousButton);
fixImageButtonColor(bottomAppBarNextButton);
fixImageButtonColor(bottomAppBarUpdateBalanceButton);
setBottomAppBarButtonState();
// Make notification area light if dark icons are needed
if (Build.VERSION.SDK_INT >= 23) {
View decorView = getWindow().getDecorView();
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
wic.setAppearanceLightStatusBars(backgroundNeedsDarkIcons);
window.getDecorView().setSystemUiVisibility(backgroundNeedsDarkIcons ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0);
window.setStatusBarColor(Color.TRANSPARENT);
} else {
// Darken statusbar if icons won't be visible otherwise
@@ -833,17 +713,9 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
// Set shadow colour of store text so even same color on same color would be readable
storeName.setShadowLayer(1, 1, 1, backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE);
if (format != null && !format.isSupported()) {
isBarcodeSupported = false;
Toast.makeText(this, getString(R.string.unsupportedBarcodeType), Toast.LENGTH_LONG).show();
} else if (format == null) {
isBarcodeSupported = false;
}
imageTypes = new ArrayList<>();
if (isBarcodeSupported) {
if (format != null) {
imageTypes.add(ImageType.BARCODE);
}
@@ -864,12 +736,6 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
DBHelper.updateLoyaltyCardLastUsed(database, loyaltyCard.id);
}
@SuppressWarnings("deprecation")
private void showWhenLockedSdkLessThan27(Window window) {
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
private void fixImageButtonColor(ImageButton imageButton) {
imageButton.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE, BlendModeCompat.SRC_ATOP));
}
@@ -966,7 +832,7 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
return true;
} else if (id == R.id.action_delete) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.deleteTitle);
builder.setMessage(R.string.deleteConfirmation);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
@@ -1012,7 +878,10 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
setSupportActionBar(portraitToolbar);
}
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
private void drawBarcode(boolean addPadding) {
@@ -1166,15 +1035,11 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
editButton.setVisibility(View.GONE);
// Set Android to fullscreen mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
getWindow().setDecorFitsSystemWindows(false);
if (getWindow().getInsetsController() != null) {
getWindow().getInsetsController().hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
getWindow().getInsetsController().setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
} else {
setFullscreenModeSdkLessThan30();
}
getWindow().getDecorView().setSystemUiVisibility(
getWindow().getDecorView().getSystemUiVisibility()
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN
);
} else {
Log.d(TAG, "Move out of fullscreen");
@@ -1205,35 +1070,13 @@ public class LoyaltyCardViewActivity extends CatimaAppCompatActivity implements
bottomAppBar.setVisibility(View.VISIBLE);
// Unset fullscreen mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
getWindow().setDecorFitsSystemWindows(true);
if (getWindow().getInsetsController() != null) {
getWindow().getInsetsController().show(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
getWindow().getInsetsController().setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
}
} else {
unsetFullscreenModeSdkLessThan30();
}
getWindow().getDecorView().setSystemUiVisibility(
getWindow().getDecorView().getSystemUiVisibility()
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
);
}
Log.d("setFullScreen", "Is full screen enabled? " + enabled + " Zoom Level = " + barcodeScaler.getProgress());
}
@SuppressWarnings("deprecation")
private void unsetFullscreenModeSdkLessThan30() {
getWindow().getDecorView().setSystemUiVisibility(
getWindow().getDecorView().getSystemUiVisibility()
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
);
}
@SuppressWarnings("deprecation")
private void setFullscreenModeSdkLessThan30() {
getWindow().getDecorView().setSystemUiVisibility(
getWindow().getDecorView().getSystemUiVisibility()
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN
);
}
}

View File

@@ -9,56 +9,50 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.database.CursorIndexOutOfBoundsException;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.splashscreen.SplashScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import protect.card_locker.databinding.ArchiveActivityBinding;
import protect.card_locker.databinding.ContentMainBinding;
import protect.card_locker.databinding.MainActivityBinding;
import protect.card_locker.databinding.SortingOptionBinding;
import protect.card_locker.preferences.SettingsActivity;
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener, GestureDetector.OnGestureListener {
private MainActivityBinding binding;
private ArchiveActivityBinding archiveActivityBinding;
private ContentMainBinding contentMainBinding;
private static final String TAG = "Catima";
public static final String RESTART_ACTIVITY_INTENT = "restart_activity_intent";
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
private SQLiteDatabase mDatabase;
private LoyaltyCardCursorAdapter mAdapter;
private ActionMode mCurrentActionMode;
@@ -74,7 +68,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
private View mHelpSection;
private View mNoMatchingCardsText;
private View mNoGroupCardsText;
private TabLayout groupsTabLayout;
private boolean mArchiveMode;
public static final String BUNDLE_ARCHIVE_MODE = "archiveMode";
@@ -148,7 +141,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
inputMode.finish();
return true;
} else if (inputItem.getItemId() == R.id.action_delete) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
// The following may seem weird, but it is necessary to give translators enough flexibility.
// For example, in Russian, Android's plural quantity "one" actually refers to "any number ending on 1 but not ending in 11".
// So while in English the extra non-plural form seems unnecessary duplication, it is necessary to give translators enough flexibility.
@@ -170,7 +163,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
ShortcutHelper.removeShortcut(MainActivity.this, loyaltyCard.id);
}
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
TabLayout.Tab tab = ((TabLayout) findViewById(R.id.groups)).getTabAt(selectedTab);
mGroup = tab != null ? tab.getTag() : null;
updateLoyaltyCardList(true);
@@ -182,25 +175,28 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
dialog.show();
return true;
} else if (inputItem.getItemId() == R.id.action_archive) {
}
else if(inputItem.getItemId() == R.id.action_archive){
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Archiving card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 1);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id,1);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
} else if (inputItem.getItemId() == R.id.action_unarchive) {
}
else if(inputItem.getItemId() == R.id.action_unarchive){
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Unarchiving card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id, 0);
DBHelper.updateLoyaltyCardArchiveStatus(mDatabase, loyaltyCard.id,0);
updateLoyaltyCardList(false);
inputMode.finish();
invalidateOptionsMenu();
}
return true;
} else if (inputItem.getItemId() == R.id.action_star) {
}
else if(inputItem.getItemId() == R.id.action_star){
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Starring card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 1);
@@ -208,7 +204,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
inputMode.finish();
}
return true;
} else if (inputItem.getItemId() == R.id.action_unstar) {
}
else if(inputItem.getItemId() == R.id.action_unstar){
for (LoyaltyCard loyaltyCard : mAdapter.getSelectedItems()) {
Log.d(TAG, "Unstarring card: " + loyaltyCard.id);
DBHelper.updateLoyaltyCardStarStatus(mDatabase, loyaltyCard.id, 0);
@@ -233,28 +230,28 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
extractIntentFields(getIntent());
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState);
if (!mArchiveMode) {
binding = MainActivityBinding.inflate(getLayoutInflater());
if(!mArchiveMode) {
setTitle(R.string.app_name);
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
groupsTabLayout = binding.groups;
contentMainBinding = ContentMainBinding.bind(binding.include.getRoot());
} else {
archiveActivityBinding = ArchiveActivityBinding.inflate(getLayoutInflater());
setContentView(R.layout.main_activity);
}
else{
setTitle(R.string.archiveList);
setContentView(archiveActivityBinding.getRoot());
setSupportActionBar(archiveActivityBinding.toolbar);
groupsTabLayout = archiveActivityBinding.groups;
contentMainBinding = ContentMainBinding.bind(archiveActivityBinding.include.getRoot());
setContentView(R.layout.archive_activity);
}
if(mArchiveMode) {
enableToolbarBackButton();
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if(mArchiveMode){
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
mDatabase = new DBHelper(this).getWritableDatabase();
TabLayout groupsTabLayout = findViewById(R.id.groups);
groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
@@ -286,10 +283,10 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
View.OnTouchListener gestureTouchListener = (v, event) -> mGestureDetector.onTouchEvent(event);
mHelpSection = contentMainBinding.helpSection;
mNoMatchingCardsText = contentMainBinding.noMatchingCardsText;
mNoGroupCardsText = contentMainBinding.noGroupCardsText;
mCardList = contentMainBinding.list;
mHelpSection = findViewById(R.id.helpSection);
mNoMatchingCardsText = findViewById(R.id.noMatchingCardsText);
mNoGroupCardsText = findViewById(R.id.noGroupCardsText);
mCardList = findViewById(R.id.list);
mNoMatchingCardsText.setOnTouchListener(gestureTouchListener);
mCardList.setOnTouchListener(gestureTouchListener);
@@ -334,17 +331,21 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
*/
mBarcodeScannerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
// Exit early if the user cancelled the scan (pressed back/home)
if (result.getResultCode() != RESULT_OK) {
return;
}
Intent intent = result.getData();
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
Bundle inputBundle = intent.getExtras();
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
processBarcodeValues(barcodeValues, group);
if (!barcodeValues.isEmpty()) {
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle newBundle = new Bundle();
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
Bundle inputBundle = intent.getExtras();
if (inputBundle != null && inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) != null) {
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP));
}
newIntent.putExtras(newBundle);
startActivity(newIntent);
}
});
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
@@ -373,30 +374,21 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
// Start of active tab logic
TabLayout groupsTabLayout = findViewById(R.id.groups);
updateTabGroups(groupsTabLayout);
// Restore selected tab from Shared Preference
// Restore settings from Shared Preference
SharedPreferences activeTabPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_active_tab),
Context.MODE_PRIVATE);
selectedTab = activeTabPref.getInt(getString(R.string.sharedpreference_active_tab), 0);
// Restore sort preferences from Shared Preferences
// If one of the sorting prefererences has never been set or is set to an invalid value,
// stick to the defaults.
SharedPreferences sortPref = getApplicationContext().getSharedPreferences(
getString(R.string.sharedpreference_sort),
Context.MODE_PRIVATE);
String orderString = sortPref.getString(getString(R.string.sharedpreference_sort_order), null);
String orderDirectionString = sortPref.getString(getString(R.string.sharedpreference_sort_direction), null);
if (orderString != null && orderDirectionString != null) {
try {
mOrder = DBHelper.LoyaltyCardOrder.valueOf(orderString);
mOrderDirection = DBHelper.LoyaltyCardOrderDirection.valueOf(orderDirectionString);
} catch (IllegalArgumentException ignored) {
}
try {
mOrder = DBHelper.LoyaltyCardOrder.valueOf(sortPref.getString(getString(R.string.sharedpreference_sort_order), null));
mOrderDirection = DBHelper.LoyaltyCardOrderDirection.valueOf(sortPref.getString(getString(R.string.sharedpreference_sort_direction), null));
} catch (IllegalArgumentException | NullPointerException ignored) {
}
mGroup = null;
@@ -410,15 +402,12 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
groupsTabLayout.selectTab(tab);
assert tab != null;
mGroup = tab.getTag();
} else if (!mArchiveMode) {
scaleScreen();
}
updateLoyaltyCardList(true);
// End of active tab logic
if (!mArchiveMode) {
FloatingActionButton addButton = binding.fabAdd;
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), ScanActivity.class);
@@ -435,7 +424,8 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
@Override
public void onBackPressed() {
if (mSearchView != null && !mSearchView.isIconified()) {
if (!mSearchView.isIconified()) {
mSearchView.setIconified(true);
return;
}
@@ -508,70 +498,9 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
private void processBarcodeValues(BarcodeValues barcodeValues, String group) {
if (barcodeValues.isEmpty()) {
throw new IllegalArgumentException("barcodesValues may not be empty");
}
Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle newBundle = new Bundle();
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
if (group != null) {
newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
}
newIntent.putExtras(newBundle);
startActivity(newIntent);
}
private void onSharedIntent(Intent intent) {
String receivedAction = intent.getAction();
String receivedType = intent.getType();
// Check if an image was shared to us
if (Intent.ACTION_SEND.equals(receivedAction)) {
if (!receivedType.startsWith("image/")) {
Log.e(TAG, "Wrong mime-type");
return;
}
BarcodeValues barcodeValues;
Bitmap bitmap;
Uri data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (data == null) {
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
finish();
return;
}
try {
bitmap = Utils.retrieveImageFromUri(this, data);
} catch (IOException e) {
Log.e(TAG, "Error getting data from image file");
e.printStackTrace();
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
finish();
return;
}
barcodeValues = Utils.getBarcodeFromBitmap(bitmap);
if (barcodeValues.isEmpty()) {
Log.i(TAG, "No barcode found in image file");
Toast.makeText(this, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
finish();
return;
}
processBarcodeValues(barcodeValues, null);
}
}
private void extractIntentFields(Intent intent) {
final Bundle b = intent.getExtras();
mArchiveMode = b != null && b.getBoolean(BUNDLE_ARCHIVE_MODE, false);
onSharedIntent(intent);
}
public void updateTabGroups(TabLayout groupsTabLayout) {
@@ -603,9 +532,9 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
@Override
public boolean onCreateOptionsMenu(Menu inputMenu) {
if (!mArchiveMode) {
if(!mArchiveMode)
getMenuInflater().inflate(R.menu.main_menu, inputMenu);
} else {
else{
getMenuInflater().inflate(R.menu.archive_menu, inputMenu);
}
@@ -633,6 +562,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
public boolean onQueryTextChange(String newText) {
mFilter = newText;
TabLayout groupsTabLayout = findViewById(R.id.groups);
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
mGroup = currentTab != null ? currentTab.getTag() : null;
@@ -643,7 +573,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
});
}
if (!mArchiveMode) {
if(!mArchiveMode) {
if (DBHelper.getArchivedCardsCount(mDatabase) == 0) {
inputMenu.findItem(R.id.action_archived).setVisible(false);
} else {
@@ -679,15 +609,13 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
}
}
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(MainActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(R.string.sort_by);
SortingOptionBinding sortingOptionBinding = SortingOptionBinding
.inflate(LayoutInflater.from(MainActivity.this), null, false);
final View customLayout = sortingOptionBinding.getRoot();
final View customLayout = getLayoutInflater().inflate(R.layout.sorting_option, null);
builder.setView(customLayout);
CheckBox showReversed = sortingOptionBinding.checkBoxReverse;
CheckBox showReversed = (CheckBox) customLayout.findViewById(R.id.checkBox_reverse);
showReversed.setChecked(mOrderDirection == DBHelper.LoyaltyCardOrderDirection.Descending);
@@ -808,6 +736,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
return false;
}
TabLayout groupsTabLayout = findViewById(R.id.groups);
if (groupsTabLayout.getTabCount() < 2) {
return false;
}
@@ -857,16 +786,6 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
toggleSelection(inputPosition);
}
private void scaleScreen() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
binding.include.welcomeIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
}
private void toggleSelection(int inputPosition) {
mAdapter.toggleSelection(inputPosition);
int count = mAdapter.getSelectedItemCount();
@@ -885,10 +804,11 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
boolean hasStarred = false;
boolean hasUnstarred = false;
if (!mArchiveMode) {
if(!mArchiveMode) {
unarchiveItem.setVisible(false);
archiveItem.setVisible(true);
} else {
}
else{
unarchiveItem.setVisible(true);
archiveItem.setVisible(false);
}

View File

@@ -13,7 +13,6 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
@@ -26,11 +25,8 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.ActivityManageGroupBinding;
public class ManageGroupActivity extends CatimaAppCompatActivity implements ManageGroupCursorAdapter.CardAdapterListener {
private ActivityManageGroupBinding binding;
private SQLiteDatabase mDatabase;
private ManageGroupCursorAdapter mAdapter;
@@ -47,18 +43,17 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
@Override
protected void onCreate(Bundle inputSavedInstanceState) {
super.onCreate(inputSavedInstanceState);
binding = ActivityManageGroupBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.activity_manage_group);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mDatabase = new DBHelper(this).getWritableDatabase();
noGroupCardsText = binding.include.noGroupCardsText;
mCardList = binding.include.list;
FloatingActionButton saveButton = binding.fabSave;
noGroupCardsText = findViewById(R.id.noGroupCardsText);
mCardList = findViewById(R.id.list);
FloatingActionButton saveButton = findViewById(R.id.fabSave);
mGroupNameText = binding.editTextGroupName;
mGroupNameText = findViewById(R.id.editTextGroupName);
mGroupNameText.addTextChangedListener(new TextWatcher() {
@Override
@@ -110,7 +105,12 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
mGroupNameText.setText(inputSavedInstanceState.getString(SAVE_INSTANCE_CURRENT_GROUP_NAME));
}
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar == null) {
throw (new RuntimeException("mActionBar is not expected to be null here"));
}
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
saveButton.setOnClickListener(v -> {
String currentGroupName = mGroupNameText.getText().toString().trim();
@@ -200,7 +200,7 @@ public class ManageGroupActivity extends CatimaAppCompatActivity implements Mana
private void leaveWithoutSaving() {
if (hasChanged()) {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ManageGroupActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(ManageGroupActivity.this);
builder.setTitle(R.string.leaveWithoutSaveTitle);
builder.setMessage(R.string.leaveWithoutSaveConfirmation);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> finish());

View File

@@ -8,14 +8,11 @@ import android.os.Bundle;
import android.text.InputType;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
@@ -27,10 +24,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.databinding.ManageGroupsActivityBinding;
public class ManageGroupsActivity extends CatimaAppCompatActivity implements GroupCursorAdapter.GroupAdapterListener {
private ManageGroupsActivityBinding binding;
private static final String TAG = "Catima";
private SQLiteDatabase mDatabase;
@@ -41,12 +35,14 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ManageGroupsActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.groups);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.manage_groups_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mDatabase = new DBHelper(this).getWritableDatabase();
}
@@ -55,12 +51,12 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
protected void onResume() {
super.onResume();
FloatingActionButton addButton = binding.fabAdd;
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(v -> createGroup());
addButton.bringToFront();
mGroupList = binding.include.list;
mHelpText = binding.include.helpText;
mGroupList = findViewById(R.id.list);
mHelpText = findViewById(R.id.helpText);
// Init group list
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
@@ -112,60 +108,24 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
return super.onOptionsItemSelected(item);
}
private void setGroupNameError(EditText input) {
String string = sanitizeAddGroupNameField(input.getText());
if (string.length() == 0) {
input.setError(getString(R.string.group_name_is_empty));
return;
}
if (DBHelper.getGroup(mDatabase, string) != null) {
input.setError(getString(R.string.group_name_already_in_use));
return;
}
input.setError(null);
}
private String sanitizeAddGroupNameField(CharSequence s) {
return s.toString().trim();
}
private void createGroup() {
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.enter_group_name);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.addTextChangedListener(new SimpleTextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
setGroupNameError(input);
}
});
setGroupNameError(input);
// Add spacing to EditText
FrameLayout container = new FrameLayout(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
params.leftMargin = 50;
params.rightMargin = 50;
input.setLayoutParams(params);
container.addView(input);
builder.setView(container);
builder.setView(input);
builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> {
CharSequence error = input.getError();
if (error != null) {
Toast.makeText(getApplicationContext(), error.toString(), Toast.LENGTH_SHORT).show();
String inputString = input.getText().toString().trim();
if (inputString.length() == 0) {
Toast.makeText(getApplicationContext(), R.string.group_name_is_empty, Toast.LENGTH_SHORT).show();
return;
}
DBHelper.insertGroup(mDatabase, sanitizeAddGroupNameField(input.getText()));
if (DBHelper.getGroup(mDatabase, inputString) != null) {
Toast.makeText(getApplicationContext(), R.string.group_name_already_in_use, Toast.LENGTH_SHORT).show();
return;
}
DBHelper.insertGroup(mDatabase, inputString);
updateGroupList();
});
builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.cancel());
@@ -233,7 +193,7 @@ public class ManageGroupsActivity extends CatimaAppCompatActivity implements Gro
public void onDeleteButtonClicked(View view) {
final String groupName = getGroupName(view);
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.deleteConfirmationGroup);
builder.setMessage(groupName);

View File

@@ -1,29 +0,0 @@
package protect.card_locker;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class OpenWebLinkHandler {
private static final String TAG = "Catima";
public void openBrowser(AppCompatActivity activity, String url) {
if (url == null) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
try {
activity.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(activity, R.string.failedToOpenUrl, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
}

View File

@@ -1,94 +0,0 @@
package protect.card_locker;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class PermissionUtils {
/**
* Check if storage read permission is needed.
*
* This is only necessary on Android 6.0 (Marshmallow) and below. See
* https://github.com/CatimaLoyalty/Android/issues/979 for more info.
*
* @param activity
* @return
*/
private static boolean needsStorageReadPermission(Activity activity) {
// Testing showed this permission wasn't needed for anything Catima did past Marshmallow
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return false;
}
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED;
}
/**
* Check if camera permission is needed.
*
* @param activity
* @return
*/
public static boolean needsCameraPermission(Activity activity) {
// Android only introduced the runtime permission system in Marshmallow (Android 6.0)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED;
}
/**
* Call onRequestPermissionsResult after storage read permission was granted.
* Mocks a successful grant if a grant is not necessary.
*
* @param activity
* @param requestCode
*/
public static void requestStorageReadPermission(CatimaAppCompatActivity activity, int requestCode) {
String[] permissions = new String[]{ android.Manifest.permission.READ_EXTERNAL_STORAGE };
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
if (needsStorageReadPermission(activity)) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else {
// FIXME: This points to onMockedRequestPermissionResult instead of to
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
//
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
// the activity input variable should be changed from CatimaAppCompatActivity to
// Activity.
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
/**
* Call onRequestPermissionsResult after camera permission was granted.
* Mocks a successful grant if a grant is not necessary.
*
* @param activity
* @param requestCode
*/
public static void requestCameraPermission(CatimaAppCompatActivity activity, int requestCode) {
String[] permissions = new String[]{ Manifest.permission.CAMERA };
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
if (needsCameraPermission(activity)) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else {
// FIXME: This points to onMockedRequestPermissionResult instead of to
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
//
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
// the activity input variable should be changed from CatimaAppCompatActivity to
// Activity.
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
}

View File

@@ -1,30 +1,17 @@
package protect.card_locker;
import android.Manifest;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.BarcodeCallback;
@@ -34,8 +21,10 @@ import com.journeyapps.barcodescanner.DecoratedBarcodeView;
import java.util.List;
import protect.card_locker.databinding.CustomBarcodeScannerBinding;
import protect.card_locker.databinding.ScanActivityBinding;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
/**
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
@@ -44,16 +33,8 @@ import protect.card_locker.databinding.ScanActivityBinding;
* originally licensed under Apache 2.0
*/
public class ScanActivity extends CatimaAppCompatActivity {
private ScanActivityBinding binding;
private CustomBarcodeScannerBinding customBarcodeScannerBinding;
private static final String TAG = "Catima";
private static final int MEDIUM_SCALE_FACTOR_DIP = 460;
private static final int COMPAT_SCALE_FACTOR_DIP = 320;
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;
@@ -75,22 +56,23 @@ public class ScanActivity extends CatimaAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ScanActivityBinding.inflate(getLayoutInflater());
customBarcodeScannerBinding = CustomBarcodeScannerBinding.bind(binding.zxingBarcodeScanner);
setTitle(R.string.scanCardBarcode);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.scan_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
extractIntentFields(getIntent());
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
customBarcodeScannerBinding.addFromImage.setOnClickListener(this::addFromImage);
customBarcodeScannerBinding.addManually.setOnClickListener(this::addManually);
findViewById(R.id.add_from_image).setOnClickListener(this::addFromImage);
findViewById(R.id.add_manually).setOnClickListener(this::addManually);
barcodeScannerView = binding.zxingBarcodeScanner;
barcodeScannerView = findViewById(R.id.zxing_barcode_scanner);
// Even though we do the actual decoding with the barcodeScannerView
// CaptureManager needs to be running to show the camera and scanning bar
@@ -127,10 +109,6 @@ public class ScanActivity extends CatimaAppCompatActivity {
protected void onResume() {
super.onResume();
capture.onResume();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
showCameraPermissionMissingText(false);
}
scaleScreen();
}
@Override
@@ -193,22 +171,27 @@ public class ScanActivity extends CatimaAppCompatActivity {
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
BarcodeValues barcodeValues;
if (barcodeValues.isEmpty()) {
try {
barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
} catch (NullPointerException e) {
Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
return;
}
Intent manualResult = new Intent();
Bundle manualResultBundle = new Bundle();
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, barcodeValues.content());
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, barcodeValues.format());
if (addGroup != null) {
manualResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
if (!barcodeValues.isEmpty()) {
Intent manualResult = new Intent();
Bundle manualResultBundle = new Bundle();
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, barcodeValues.content());
manualResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, barcodeValues.format());
if (addGroup != null) {
manualResultBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, addGroup);
}
manualResult.putExtras(manualResultBundle);
ScanActivity.this.setResult(RESULT_OK, manualResult);
finish();
}
manualResult.putExtras(manualResultBundle);
ScanActivity.this.setResult(RESULT_OK, manualResult);
finish();
}
public void addManually(View view) {
@@ -222,74 +205,13 @@ public class ScanActivity extends CatimaAppCompatActivity {
}
public void addFromImage(View view) {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
}
private void addFromImageAfterPermission() {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentIntent.setType("image/*");
Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(R.string.addFromImage));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
try {
photoPickerLauncher.launch(chooserIntent);
photoPickerLauncher.launch(photoPickerIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
private void showCameraPermissionMissingText(boolean show) {
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedClickableArea.setOnClickListener(show ? v -> {
navigateToSystemPermissionSetting();
} : null);
customBarcodeScannerBinding.cardInputContainer.setBackgroundColor(show ? obtainThemeAttribute(com.google.android.material.R.attr.colorSurface) : Color.TRANSPARENT);
customBarcodeScannerBinding.cameraPermissionDeniedLayout.getRoot().setVisibility(show ? View.VISIBLE : View.GONE);
}
private void scaleScreen() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
float mediumSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,MEDIUM_SCALE_FACTOR_DIP,getResources().getDisplayMetrics());
boolean shouldScaleSmaller = screenHeight < mediumSizePx;
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedIcon.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
customBarcodeScannerBinding.cameraPermissionDeniedLayout.cameraPermissionDeniedTitle.setVisibility(shouldScaleSmaller ? View.GONE : View.VISIBLE);
}
private int obtainThemeAttribute(int attribute) {
TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(attribute, typedValue, true);
return typedValue.data;
}
private void navigateToSystemPermissionSetting() {
Intent permissionIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", getPackageName(), null));
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(permissionIntent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
onMockedRequestPermissionsResult(requestCode, permissions, grantResults);
}
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
showCameraPermissionMissingText(!granted);
} else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
if (granted) {
addFromImageAfterPermission();
} else {
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
}
}
}
}

View File

@@ -22,8 +22,4 @@ public class ThirdPartyInfo {
public String license() {
return mLicense;
}
public String toHtml() {
return String.format("<a href=\"%s\">%s</a> (%s)", url(), name(), license());
}
}

View File

@@ -18,7 +18,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.view.WindowInsetsControllerCompat;
public class UCropWrapper extends UCropActivity {
public static final String UCROP_TOOLBAR_TYPEFACE_STYLE = "ucop_toolbar_typeface_style";
@@ -29,9 +28,7 @@ public class UCropWrapper extends UCropActivity {
boolean darkMode = Utils.isDarkModeEnabled(this);
// setup status bar to look like the rest of the app
if (Build.VERSION.SDK_INT >= 23) {
View decorView = getWindow().getDecorView();
WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(getWindow(), decorView);
wic.setAppearanceLightStatusBars(!darkMode);
getWindow().getDecorView().setSystemUiVisibility(darkMode ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
// icons are always white back then
if (!darkMode) {
@@ -53,8 +50,8 @@ public class UCropWrapper extends UCropActivity {
AppCompatImageView controlsBackgroundImage = (AppCompatImageView) check;
// everything gathered and are as expected, now perform color patching
Utils.patchColors(this);
int colorSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface, ContextCompat.getColor(this, R.color.md_theme_light_surface));
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface, ContextCompat.getColor(this, R.color.md_theme_light_onSurface));
Drawable controlsBackgroundImageDrawable = controlsBackgroundImage.getBackground();
controlsBackgroundImageDrawable.mutate();

View File

@@ -11,7 +11,6 @@ import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Build;
import android.os.LocaleList;
import android.provider.MediaStore;
@@ -20,13 +19,6 @@ import android.util.TypedValue;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.RawRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.ColorUtils;
import androidx.exifinterface.media.ExifInterface;
import androidx.palette.graphics.Palette;
import com.google.android.material.color.DynamicColors;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
@@ -36,17 +28,13 @@ import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Calendar;
@@ -56,6 +44,11 @@ import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Map;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.ColorUtils;
import androidx.exifinterface.media.ExifInterface;
import androidx.palette.graphics.Palette;
import protect.card_locker.preferences.Settings;
public class Utils {
@@ -112,17 +105,6 @@ public class Utils {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
}
/**
* Returns the Barcode format and content based on the result of an activity.
* It shows toasts to notify the end-user as needed itself and will return an empty
* BarcodeValues object if the activity was cancelled or nothing could be found.
*
* @param requestCode
* @param resultCode
* @param intent
* @param context
* @return BarcodeValues
*/
static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
String contents;
String format;
@@ -134,16 +116,14 @@ public class Utils {
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
Log.i(TAG, "Received image file with possible barcode");
Uri data = intent.getData();
if (data == null) {
Log.e(TAG, "Intent did not contain any data");
Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
return new BarcodeValues(null, null);
}
Bitmap bitmap;
try {
bitmap = retrieveImageFromUri(context, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ImageDecoder.Source image_source = ImageDecoder.createSource(context.getContentResolver(), intent.getData());
bitmap = ImageDecoder.decodeBitmap(image_source, (decoder, info, source) -> decoder.setMutableRequired(true));
} else {
bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), intent.getData());
}
} catch (IOException e) {
Log.e(TAG, "Error getting data from image file");
e.printStackTrace();
@@ -183,20 +163,6 @@ public class Utils {
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
}
static public Bitmap retrieveImageFromUri(Context context, Uri data) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ImageDecoder.Source image_source = ImageDecoder.createSource(context.getContentResolver(), data);
return ImageDecoder.decodeBitmap(image_source, (decoder, info, source) -> decoder.setMutableRequired(true));
} else {
return getBitmapSdkLessThan29(data, context);
}
}
@SuppressWarnings("deprecation")
private static Bitmap getBitmapSdkLessThan29(Uri data, Context context) throws IOException {
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), data);
}
static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) {
// This function is vulnerable to OOM, so we try again with a smaller bitmap is we get OOM
for (int i = 0; i < 10; i++) {
@@ -230,21 +196,7 @@ public class Utils {
}
}
static public Boolean isNotYetValid(Date validFromDate) {
// The note in `hasExpired` does not apply here, since the bug was fixed before this feature was added.
return validFromDate.after(getStartOfToday().getTime());
}
static public Boolean hasExpired(Date expiryDate) {
// Note: In #1083 it was discovered that `DatePickerFragment` may sometimes store the expiryDate
// at 12:00 PM instead of 12:00 AM in the DB. While this has been fixed and the 12-hour difference
// is not a problem for the way the comparison currently works, it's good to keep in mind such
// dates may exist in the DB in case the comparison changes in the future and the new one relies
// on both dates being set at 12:00 AM.
return expiryDate.before(getStartOfToday().getTime());
}
static private Calendar getStartOfToday() {
// today
Calendar date = new GregorianCalendar();
// reset hour, minutes, seconds and millis
@@ -252,7 +204,8 @@ public class Utils {
date.set(Calendar.MINUTE, 0);
date.set(Calendar.SECOND, 0);
date.set(Calendar.MILLISECOND, 0);
return date;
return expiryDate.before(date.getTime());
}
static public String formatBalance(Context context, BigDecimal value, Currency currency) {
@@ -443,7 +396,8 @@ public class Utils {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
setLocalesSdkLessThan24(chosenLocale, configuration, res);
configuration.locale = chosenLocale != null ? chosenLocale : Locale.getDefault();
res.updateConfiguration(configuration, res.getDisplayMetrics());
return context;
}
@@ -453,12 +407,6 @@ public class Utils {
return context.createConfigurationContext(configuration);
}
@SuppressWarnings("deprecation")
private static void setLocalesSdkLessThan24(Locale chosenLocale, Configuration configuration, Resources res) {
configuration.locale = chosenLocale != null ? chosenLocale : Locale.getDefault();
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
static public long getUnixTime() {
return System.currentTimeMillis() / 1000;
}
@@ -540,7 +488,7 @@ public class Utils {
} else {
// final catch all in case of invalid theme value from older versions
// also handles R.string.settings_key_system_theme
DynamicColors.applyToActivityIfAvailable(activity);
DynamicColors.applyIfAvailable(activity);
}
if (isDarkModeEnabled(activity) && settings.getOledDark()) {
@@ -572,7 +520,7 @@ public class Utils {
return fallback;
}
return new Palette.Builder(image).generate().getDominantColor(androidx.appcompat.R.attr.colorPrimary);
return new Palette.Builder(image).generate().getDominantColor(R.attr.colorPrimary);
}
public static int getRandomHeaderColor(Context context) {
@@ -580,23 +528,4 @@ public class Utils {
final int color = (int) (Math.random() * colors.length());
return colors.getColor(color, Color.BLACK);
}
public static String readTextFile(Context context, @RawRes int resourceId) throws IOException {
InputStream input = context.getResources().openRawResource(resourceId);
BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
StringBuilder result = new StringBuilder();
while (true) {
String nextLine = reader.readLine();
if (nextLine == null || nextLine.isEmpty()) {
reader.close();
break;
}
result.append("\n");
result.append(nextLine);
}
return result.toString();
}
}

View File

@@ -15,15 +15,7 @@ import java.io.Reader;
import java.nio.charset.StandardCharsets;
public class ZipUtils {
public static Bitmap readImage(ZipInputStream zipInputStream) {
return BitmapFactory.decodeStream(zipInputStream);
}
public static JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException {
return new JSONObject(read(zipInputStream));
}
private static String read(ZipInputStream zipInputStream) throws IOException {
static public String read(ZipInputStream zipInputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, StandardCharsets.UTF_8));
int c;
@@ -32,4 +24,12 @@ public class ZipUtils {
}
return stringBuilder.toString();
}
static public Bitmap readImage(ZipInputStream zipInputStream) {
return BitmapFactory.decodeStream(zipInputStream);
}
static public JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException {
return new JSONObject(read(zipInputStream));
}
}

View File

@@ -0,0 +1,35 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class AztecBarcode extends Barcode {
@Override
public String prettyName() {
return "Aztec";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.AZTEC;
}
@Override
public String exampleValue() {
return "AZTEC";
}
@Override
public boolean isSquare() {
return true;
}
@Override
public boolean is2D() {
return true;
}
@Override
public boolean hasInternalPadding() {
return false;
}
}

View File

@@ -0,0 +1,22 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
/**
* Abstract barcode class
*/
public abstract class Barcode {
public String name() {
return format().name();
};
abstract public String prettyName();
abstract public BarcodeFormat format();
abstract public String exampleValue();
abstract public boolean isSquare();
abstract public boolean is2D();
public boolean hasInternalPadding() {
return false;
};
public boolean isSupported() { return true; };
}

View File

@@ -0,0 +1,62 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class BarcodeFactory {
public static final Map<String, BarcodeFormat> barcodeNames = new HashMap<>() {{
put(BarcodeFormat.AZTEC.name(), BarcodeFormat.AZTEC);
put(BarcodeFormat.CODE_39.name(), BarcodeFormat.CODE_39);
put(BarcodeFormat.CODE_93.name(), BarcodeFormat.CODE_93);
put(BarcodeFormat.CODE_128.name(), BarcodeFormat.CODE_128);
put(BarcodeFormat.CODABAR.name(), BarcodeFormat.CODABAR);
put(BarcodeFormat.DATA_MATRIX.name(), BarcodeFormat.DATA_MATRIX);
put(BarcodeFormat.EAN_8.name(), BarcodeFormat.EAN_8);
put(BarcodeFormat.EAN_13.name(), BarcodeFormat.EAN_13);
put(BarcodeFormat.ITF.name(), BarcodeFormat.ITF);
put(BarcodeFormat.PDF_417.name(), BarcodeFormat.PDF_417);
put(BarcodeFormat.QR_CODE.name(), BarcodeFormat.QR_CODE);
put(BarcodeFormat.UPC_A.name(), BarcodeFormat.UPC_A);
put(BarcodeFormat.UPC_E.name(), BarcodeFormat.UPC_E);
}};
public static Barcode fromBarcode(BarcodeFormat barcodeFormat) {
switch (barcodeFormat) {
case AZTEC: return new AztecBarcode();
case CODE_39: return new Code39Barcode();
case CODE_93: return new Code93Barcode();
case CODE_128: return new Code128Barcode();
case CODABAR: return new CodabarBarcode();
case DATA_MATRIX: return new DataMatrixBarcode();
case EAN_8: return new Ean8Barcode();
case EAN_13: return new Ean13Barcode();
case ITF: return new ItfBarcode();
case PDF_417: return new Pdf417Barcode();
case QR_CODE: return new QrCodeBarcode();
case UPC_A: return new UpcABarcode();
case UPC_E: return new UpcEBarcode();
default: throw new IllegalArgumentException();
}
}
public static Barcode fromName(String name) {
return fromBarcode(Objects.requireNonNull(barcodeNames.get(name)));
}
public static boolean isSupported(BarcodeFormat barcodeFormat) {
return barcodeNames.containsValue(barcodeFormat);
}
public static boolean isSupported(String name) {
return barcodeNames.containsKey(name);
}
public static Collection<BarcodeFormat> getAllFormats() {
return barcodeNames.values();
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.barcodes;
public class BarcodeWithValue {
private final Barcode mBarcode;
private final String mValue;
public BarcodeWithValue(Barcode barcode, String value) {
mBarcode = barcode;
mValue = value;
}
public Barcode barcode() {
return mBarcode;
}
public String value() {
return mValue;
}
}

View File

@@ -0,0 +1,35 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class CodabarBarcode extends Barcode {
@Override
public String prettyName() {
return "Codabar";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.CODABAR;
}
@Override
public String exampleValue() {
return "C0C";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
@Override
public boolean hasInternalPadding() {
return false;
}
}

View File

@@ -0,0 +1,30 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Code128Barcode extends Barcode {
@Override
public String prettyName() {
return "Code 128";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.CODE_128;
}
@Override
public String exampleValue() {
return "CODE_128";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -0,0 +1,35 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Code39Barcode extends Barcode {
@Override
public String prettyName() {
return "Code 39";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.CODE_39;
}
@Override
public String exampleValue() {
return "CODE_39";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
@Override
public boolean hasInternalPadding() {
return false;
}
}

View File

@@ -0,0 +1,30 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Code93Barcode extends Barcode {
@Override
public String prettyName() {
return "Code 93";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.CODE_93;
}
@Override
public String exampleValue() {
return "CODE_93";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -0,0 +1,30 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class DataMatrixBarcode extends Barcode {
@Override
public String prettyName() {
return "Data Matrix";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.DATA_MATRIX;
}
@Override
public String exampleValue() {
return "DATA_MATRIX";
}
@Override
public boolean isSquare() {
return true;
}
@Override
public boolean is2D() {
return true;
}
}

View File

@@ -0,0 +1,30 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Ean13Barcode extends Barcode {
@Override
public String prettyName() {
return "EAN 13";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.EAN_13;
}
@Override
public String exampleValue() {
return "5901234123457";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -0,0 +1,30 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Ean8Barcode extends Barcode {
@Override
public String prettyName() {
return "EAN 8";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.EAN_8;
}
@Override
public String exampleValue() {
return "32123456";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -0,0 +1,30 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class ItfBarcode extends Barcode {
@Override
public String prettyName() {
return "ITF";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.ITF;
}
@Override
public String exampleValue() {
return "1003";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -0,0 +1,35 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class Pdf417Barcode extends Barcode {
@Override
public String prettyName() {
return "PDF 417";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.PDF_417;
}
@Override
public String exampleValue() {
return "PDF_417";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return true;
}
@Override
public boolean hasInternalPadding() {
return true;
}
}

View File

@@ -0,0 +1,35 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class QrCodeBarcode extends Barcode {
@Override
public String prettyName() {
return "QR Code";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.QR_CODE;
}
@Override
public String exampleValue() {
return "QR_CODE";
}
@Override
public boolean isSquare() {
return true;
}
@Override
public boolean is2D() {
return true;
}
@Override
public boolean hasInternalPadding() {
return true;
}
}

View File

@@ -0,0 +1,30 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class UpcABarcode extends Barcode {
@Override
public String prettyName() {
return "UPC A";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.UPC_A;
}
@Override
public String exampleValue() {
return "123456789012";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -0,0 +1,30 @@
package protect.card_locker.barcodes;
import com.google.zxing.BarcodeFormat;
public class UpcEBarcode extends Barcode {
@Override
public String prettyName() {
return "UPC E";
}
@Override
public BarcodeFormat format() {
return BarcodeFormat.UPC_E;
}
@Override
public String exampleValue() {
return "0123456";
}
@Override
public boolean isSquare() {
return false;
}
@Override
public boolean is2D() {
return false;
}
}

View File

@@ -8,14 +8,22 @@ public class CSVHelpers {
/**
* Extract a string from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, defaultValue is returned.
* "key" as the key. If no such key exists, defaultValue is returned
* if it is not null. Otherwise, a FormatException is thrown.
*/
static String extractString(String key, CSVRecord record, String defaultValue) {
static String extractString(String key, CSVRecord record, String defaultValue)
throws FormatException {
String toReturn = defaultValue;
if (record.isMapped(key)) {
return record.get(key);
toReturn = record.get(key);
} else {
if (defaultValue == null) {
throw new FormatException("Field not used but expected: " + key);
}
}
return defaultValue;
return toReturn;
}
/**
@@ -24,15 +32,15 @@ public class CSVHelpers {
* "key" as the key. If no such key exists, or the data is not a valid
* int, a FormatException is thrown.
*/
static Integer extractInt(String key, CSVRecord record)
static Integer extractInt(String key, CSVRecord record, boolean nullIsOk)
throws FormatException {
if (!record.isMapped(key)) {
if (record.isMapped(key) == false) {
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
if (value.isEmpty()) {
throw new FormatException("Field is empty: " + key);
if (value.isEmpty() && nullIsOk) {
return null;
}
try {
@@ -48,15 +56,15 @@ public class CSVHelpers {
* "key" as the key. If no such key exists, or the data is not a valid
* int, a FormatException is thrown.
*/
static Long extractLong(String key, CSVRecord record)
static Long extractLong(String key, CSVRecord record, boolean nullIsOk)
throws FormatException {
if (!record.isMapped(key)) {
if (record.isMapped(key) == false) {
throw new FormatException("Field not used but expected: " + key);
}
String value = record.get(key);
if (value.isEmpty()) {
throw new FormatException("Field is empty: " + key);
if (value.isEmpty() && nullIsOk) {
return null;
}
try {

View File

@@ -127,7 +127,6 @@ public class CatimaExporter implements Exporter {
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
DBHelper.LoyaltyCardDbIds.STORE,
DBHelper.LoyaltyCardDbIds.NOTE,
DBHelper.LoyaltyCardDbIds.VALID_FROM,
DBHelper.LoyaltyCardDbIds.EXPIRY,
DBHelper.LoyaltyCardDbIds.BALANCE,
DBHelper.LoyaltyCardDbIds.BALANCE_TYPE,
@@ -147,7 +146,6 @@ public class CatimaExporter implements Exporter {
printer.printRecord(card.id,
card.store,
card.note,
card.validFrom != null ? card.validFrom.getTime() : "",
card.expiry != null ? card.expiry.getTime() : "",
card.balance,
card.balanceType,

View File

@@ -13,11 +13,11 @@ import org.apache.commons.csv.CSVRecord;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UncheckedIOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -25,7 +25,6 @@ import java.util.Currency;
import java.util.Date;
import java.util.List;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Group;
@@ -55,7 +54,7 @@ public class CatimaImporter implements Importer {
String fileName = Uri.parse(localFileHeader.getFileName()).getLastPathSegment();
if (fileName.equals("catima.csv")) {
importCSV(context, database, zipInputStream);
importCSV(context, database, new ByteArrayInputStream(ZipUtils.read(zipInputStream).getBytes(StandardCharsets.UTF_8)));
} else if (fileName.endsWith(".png")) {
Utils.saveCardImage(context, ZipUtils.readImage(zipInputStream), fileName);
} else {
@@ -68,17 +67,26 @@ public class CatimaImporter implements Importer {
bufferedInputStream.reset();
importCSV(context, database, bufferedInputStream);
}
input.close();
}
public void importCSV(Context context, SQLiteDatabase database, InputStream input) throws IOException, FormatException, InterruptedException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
int version = parseVersion(bufferedReader);
bufferedReader.mark(100);
Integer version = 1;
try {
version = Integer.parseInt(bufferedReader.readLine());
} catch (NumberFormatException _e) {
// Assume version 1
}
bufferedReader.reset();
switch (version) {
case 1:
parseV1(database, bufferedReader);
parseV1(context, database, bufferedReader);
break;
case 2:
parseV2(context, database, bufferedReader);
@@ -86,14 +94,16 @@ public class CatimaImporter implements Importer {
default:
throw new FormatException(String.format("No code to parse version %s", version));
}
bufferedReader.close();
}
public void parseV1(SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
public void parseV1(Context context, SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
final CSVParser parser = new CSVParser(input, CSVFormat.RFC4180.builder().setHeader().build());
try {
for (CSVRecord record : parser) {
importLoyaltyCard(database, record);
importLoyaltyCard(context, database, record);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
@@ -101,14 +111,14 @@ public class CatimaImporter implements Importer {
}
parser.close();
} catch (UncheckedIOException e) {
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
}
}
public void parseV2(Context context, SQLiteDatabase database, BufferedReader input) throws IOException, FormatException, InterruptedException {
int part = 0;
StringBuilder stringPart = new StringBuilder();
Integer part = 0;
String stringPart = "";
try {
while (true) {
@@ -124,7 +134,7 @@ public class CatimaImporter implements Importer {
break;
case 1:
try {
parseV2Groups(database, stringPart.toString());
parseV2Groups(database, stringPart);
sectionParsed = true;
} catch (FormatException e) {
// We may have a multiline field, try again
@@ -132,7 +142,7 @@ public class CatimaImporter implements Importer {
break;
case 2:
try {
parseV2Cards(context, database, stringPart.toString());
parseV2Cards(context, database, stringPart);
sectionParsed = true;
} catch (FormatException e) {
// We may have a multiline field, try again
@@ -140,7 +150,7 @@ public class CatimaImporter implements Importer {
break;
case 3:
try {
parseV2CardGroups(database, stringPart.toString());
parseV2CardGroups(database, stringPart);
sectionParsed = true;
} catch (FormatException e) {
// We may have a multiline field, try again
@@ -156,12 +166,12 @@ public class CatimaImporter implements Importer {
if (sectionParsed) {
part += 1;
stringPart = new StringBuilder();
stringPart = "";
} else {
stringPart.append(tmp).append('\n');
stringPart += tmp + "\n";
}
} else {
stringPart.append(tmp).append('\n');
stringPart += tmp + "\n";
}
}
} catch (FormatException e) {
@@ -183,7 +193,7 @@ public class CatimaImporter implements Importer {
throw new InterruptedException();
}
}
} catch (UncheckedIOException e) {
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
groupParser.close();
@@ -208,14 +218,14 @@ public class CatimaImporter implements Importer {
throw new InterruptedException();
}
}
} catch (UncheckedIOException e) {
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
cardParser.close();
}
for (CSVRecord record : records) {
importLoyaltyCard(database, record);
importLoyaltyCard(context, database, record);
}
}
@@ -233,7 +243,7 @@ public class CatimaImporter implements Importer {
throw new InterruptedException();
}
}
} catch (UncheckedIOException e) {
} catch (IllegalArgumentException | IllegalStateException e) {
throw new FormatException("Issue parsing CSV data", e);
} finally {
cardGroupParser.close();
@@ -244,42 +254,13 @@ public class CatimaImporter implements Importer {
}
}
/**
* Parse the version number of the import file
*
* @param reader the reader containing the import file
* @return the parsed version number, defaulting to 1 if none is found
* @throws IOException there was a problem reading the file
*/
private int parseVersion(BufferedReader reader) throws IOException {
reader.mark(10); // slightly over the search limit just to be sure
StringBuilder sb = new StringBuilder();
int searchLimit = 5; // gives you version numbers up to 99999
int codePoint;
// search until the next whitespace, indicating the end of the version
while (!Character.isWhitespace(codePoint = reader.read())) {
// we found something that isn't a digit, or we ran out of chars
if (!Character.isDigit(codePoint) || searchLimit <= 0) {
reader.reset();
return 1; // default value
}
sb.append((char) codePoint);
searchLimit--;
}
reader.reset();
if (sb.length() == 0) {
return 1;
}
return Integer.parseInt(sb.toString());
}
/**
* Import a single loyalty card into the database using the given
* session.
*/
private void importLoyaltyCard(SQLiteDatabase database, CSVRecord record)
throws FormatException {
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record);
private void importLoyaltyCard(Context context, SQLiteDatabase database, CSVRecord record)
throws IOException, FormatException {
int id = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ID, record, false);
String store = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.STORE, record, "");
if (store.isEmpty()) {
@@ -287,38 +268,19 @@ public class CatimaImporter implements Importer {
}
String note = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, "");
Date validFrom = null;
Long validFromLong;
try {
validFromLong = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.VALID_FROM, record);
} catch (FormatException ignored) {
validFromLong = null;
}
if (validFromLong != null) {
validFrom = new Date(validFromLong);
}
Date expiry = null;
Long expiryLong;
try {
expiryLong = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record);
} catch (FormatException ignored) {
expiryLong = null;
}
if (expiryLong != null) {
expiry = new Date(expiryLong);
expiry = new Date(CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true));
} catch (NullPointerException | FormatException e) {
}
// These fields did not exist in versions 1.8.1 and before
// We default to 0 so we can still import old backups
BigDecimal balance = new BigDecimal("0");
String balanceString = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null);
if (balanceString != null) {
try {
balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null));
} catch (NumberFormatException ignored) {
}
BigDecimal balance;
try {
balance = new BigDecimal(CSVHelpers.extractString(DBHelper.LoyaltyCardDbIds.BALANCE, record, null));
} catch (FormatException _e) {
// These fields did not exist in versions 1.8.1 and before
// We catch this exception so we can still import old backups
balance = new BigDecimal("0");
}
Currency balanceType = null;
@@ -344,14 +306,14 @@ public class CatimaImporter implements Importer {
}
Integer headerColor = null;
try {
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record);
} catch (FormatException ignored) {
if (record.isMapped(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)) {
headerColor = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.HEADER_COLOR, record, true);
}
int starStatus = 0;
try {
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record);
starStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.STAR_STATUS, record, false);
} catch (FormatException _e) {
// This field did not exist in versions 0.28 and before
// We catch this exception so we can still import old backups
@@ -360,7 +322,7 @@ public class CatimaImporter implements Importer {
int archiveStatus = 0;
try {
archiveStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS, record);
archiveStatus = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIds.ARCHIVE_STATUS, record, false);
} catch (FormatException _e) {
// This field did not exist in versions 2.16.3 and before
// We catch this exception so we can still import old backups
@@ -369,13 +331,13 @@ public class CatimaImporter implements Importer {
Long lastUsed = 0L;
try {
lastUsed = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.LAST_USED, record);
lastUsed = CSVHelpers.extractLong(DBHelper.LoyaltyCardDbIds.LAST_USED, record, false);
} catch (FormatException _e) {
// This field did not exist in versions 2.5.0 and before
// We catch this exception so we can still import old backups
}
DBHelper.insertLoyaltyCard(database, id, store, note, validFrom, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed, archiveStatus);
DBHelper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus, lastUsed,archiveStatus);
}
/**
@@ -385,10 +347,6 @@ public class CatimaImporter implements Importer {
private void importGroup(SQLiteDatabase database, CSVRecord record) throws FormatException {
String id = CSVHelpers.extractString(DBHelper.LoyaltyCardDbGroups.ID, record, null);
if (id == null) {
throw new FormatException("Group has no ID: " + record);
}
DBHelper.insertGroup(database, id);
}
@@ -397,15 +355,11 @@ public class CatimaImporter implements Importer {
* session.
*/
private void importCardGroupMapping(SQLiteDatabase database, CSVRecord record) throws FormatException {
int cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record);
Integer cardId = CSVHelpers.extractInt(DBHelper.LoyaltyCardDbIdsGroups.cardID, record, false);
String groupId = CSVHelpers.extractString(DBHelper.LoyaltyCardDbIdsGroups.groupID, record, null);
if (groupId == null) {
throw new FormatException("Group has no ID: " + record);
}
List<Group> cardGroups = DBHelper.getLoyaltyCardGroups(database, cardId);
cardGroups.add(DBHelper.getGroup(database, groupId));
DBHelper.setLoyaltyCardGroups(database, cardId, cardGroups);
}
}
}

View File

@@ -18,7 +18,6 @@ import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Utils;
@@ -128,6 +127,6 @@ public class FidmeImporter implements Importer {
// TODO: Front and back image
DBHelper.insertLoyaltyCard(database, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, null,archiveStatus);
DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, null,archiveStatus);
}
}

View File

@@ -24,7 +24,6 @@ import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.HashMap;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.ImageLocationType;
@@ -74,31 +73,29 @@ public class StocardImporter implements Importer {
String fileName = localFileHeader.getFileName();
String[] nameParts = fileName.split("/");
if (nameParts.length < 2) {
continue;
}
if (providersFileName == null) {
providersFileName = new String[]{
"extracts",
nameParts[1],
nameParts[0],
"sync",
"data",
"users",
nameParts[1],
"analytics-properties",
"content.json"
nameParts[0],
"analytics-properties.json"
};
customProvidersBaseName = new String[]{
"extracts",
nameParts[1],
nameParts[0],
"sync",
"data",
"users",
nameParts[1],
nameParts[0],
"loyalty-card-custom-providers"
};
cardBaseName = new String[]{
"extracts",
nameParts[1],
nameParts[0],
"sync",
"data",
"users",
nameParts[1],
nameParts[0],
"loyalty-cards"
};
}
@@ -108,15 +105,18 @@ public class StocardImporter implements Importer {
customProviderId = nameParts[customProvidersBaseName.length].split("\\.", 2)[0];
// Name file
if (fileName.endsWith(customProviderId + "/content.json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
if (nameParts.length == customProvidersBaseName.length + 1) {
// Ignore the .txt file
if (fileName.endsWith(".json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
providers = appendToHashMap(
providers,
customProviderId,
"name",
jsonObject.getString("name")
);
providers = appendToHashMap(
providers,
customProviderId,
"name",
jsonObject.getString("name")
);
}
} else if (fileName.endsWith("logo.png")) {
providers = appendToHashMap(
providers,
@@ -132,43 +132,46 @@ public class StocardImporter implements Importer {
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
// This is the card itself
if (fileName.endsWith(cardName + "/content.json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
if (nameParts.length == cardBaseName.length + 1) {
// Ignore the .txt file
if (fileName.endsWith(".json")) {
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
"cardId",
jsonObject.getString("input_id")
);
// Provider ID can be either custom or not, extract whatever version is relevant
String customProviderPrefix = "/users/" + nameParts[1] + "/loyalty-card-custom-providers/";
String providerId = jsonObject
.getJSONObject("input_provider_reference")
.getString("identifier");
if (providerId.startsWith(customProviderPrefix)) {
providerId = providerId.substring(customProviderPrefix.length());
} else {
providerId = providerId.substring("/loyalty-card-providers/".length());
}
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
"_providerId",
providerId
);
if (jsonObject.has("input_barcode_format")) {
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
"barcodeType",
jsonObject.getString("input_barcode_format")
"cardId",
jsonObject.getString("input_id")
);
// Provider ID can be either custom or not, extract whatever version is relevant
String customProviderPrefix = "/users/" + nameParts[0] + "/loyalty-card-custom-providers/";
String providerId = jsonObject
.getJSONObject("input_provider_reference")
.getString("identifier");
if (providerId.startsWith(customProviderPrefix)) {
providerId = providerId.substring(customProviderPrefix.length());
} else {
providerId = providerId.substring("/loyalty-card-providers/".length());
}
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
"_providerId",
providerId
);
if (jsonObject.has("input_barcode_format")) {
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
"barcodeType",
jsonObject.getString("input_barcode_format")
);
}
}
} else if (fileName.endsWith("notes/default/content.json")) {
} else if (fileName.endsWith("notes/default.json")) {
loyaltyCardHashMap = appendToHashMap(
loyaltyCardHashMap,
cardName,
@@ -230,7 +233,7 @@ public class StocardImporter implements Importer {
headerColor = Utils.getHeaderColorFromImage(cardIcon, headerColor);
}
long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, null,0);
long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, null,0);
if (cardIcon != null) {
Utils.saveCardImage(context, cardIcon, (int) loyaltyCardInternalId, ImageLocationType.icon);

View File

@@ -23,7 +23,6 @@ import java.util.Currency;
import java.util.Date;
import java.util.TimeZone;
import protect.card_locker.CatimaBarcode;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Utils;
@@ -126,7 +125,7 @@ public class VoucherVaultImporter implements Importer {
throw new FormatException("Unknown colour type found: " + colorFromJSON);
}
DBHelper.insertLoyaltyCard(database, store, "", null, expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(),0);
DBHelper.insertLoyaltyCard(database, store, "", expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(),0);
}
bufferedReader.close();

View File

@@ -17,35 +17,32 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentResultListener;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import kotlin.Suppress;
import nl.invissvenska.numberpickerpreference.NumberDialogPreference;
import nl.invissvenska.numberpickerpreference.NumberPickerPreferenceDialogFragment;
import protect.card_locker.CatimaAppCompatActivity;
import protect.card_locker.MainActivity;
import protect.card_locker.R;
import protect.card_locker.Utils;
import protect.card_locker.databinding.SettingsActivityBinding;
public class SettingsActivity extends CatimaAppCompatActivity {
private SettingsActivityBinding binding;
private final static String RELOAD_MAIN_STATE = "mReloadMain";
private SettingsFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = SettingsActivityBinding.inflate(getLayoutInflater());
setTitle(R.string.settings);
setContentView(binding.getRoot());
Toolbar toolbar = binding.toolbar;
setContentView(R.layout.settings_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
enableToolbarBackButton();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
// Display the fragment as the main content.
fragment = new SettingsFragment();
@@ -166,11 +163,6 @@ public class SettingsActivity extends CatimaAppCompatActivity {
}
@Override
@SuppressWarnings("deprecation") // setTargetFragment
// androidx.preference.PreferenceDialogFragmentCompat uses the deprecated method
// `getTargetFragment()`, which throws if `setTargetFragment()` isn't used before.
// While this isn't fixed on upstream, suppress the deprecation warning
// https://issuetracker.google.com/issues/181793702
public void onDisplayPreferenceDialog(Preference preference) {
if (preference instanceof NumberDialogPreference) {
NumberDialogPreference dialogPreference = (NumberDialogPreference) preference;

View File

@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#BD0000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10.94,8.12L7.48,4.66L9,3h6l1.83,2H20c1.1,0 2,0.9 2,2v12c0,0.05 -0.01,0.1 -0.02,0.16l-5.1,-5.1C16.96,13.71 17,13.36 17,13c0,-2.76 -2.24,-5 -5,-5C11.64,8 11.29,8.04 10.94,8.12zM20.49,23.31L18.17,21H4c-1.1,0 -2,-0.9 -2,-2V7c0,-0.59 0.27,-1.12 0.68,-1.49l-2,-2L2.1,2.1l19.8,19.8L20.49,23.31zM14.49,17.32l-1.5,-1.5C12.67,15.92 12.35,16 12,16c-1.66,0 -3,-1.34 -3,-3c0,-0.35 0.08,-0.67 0.19,-0.98l-1.5,-1.5C7.25,11.24 7,12.09 7,13c0,2.76 2.24,5 5,5C12.91,18 13.76,17.75 14.49,17.32z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2L7.42,15c-0.14,0 -0.25,-0.11 -0.25,-0.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.08,-0.14 0.12,-0.31 0.12,-0.48 0,-0.55 -0.45,-1 -1,-1L5.21,4l-0.94,-2L1,2zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@@ -1,16 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
<path
android:fillColor="@android:color/white"
android:pathData="M18,15.782V22l4.886,-3.109z" />
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12C2,17.52 6.47,22 11.99,22C13.417,22 14.772,21.699 16,21.162L16,18.928C14.823,19.608 13.458,20 12,20C7.58,20 4,16.42 4,12C4,7.58 7.58,4 12,4C16.42,4 20,7.58 20,12C20,13.061 19.791,14.073 19.416,15L21.541,15C21.839,14.053 22,13.045 22,12C22,6.48 17.52,2 11.99,2zM11,7L11,13L16,16L16,15L16.951,15L17,14.92L12.5,12.25L12.5,7L11,7z" />
</vector>

View File

@@ -27,8 +27,6 @@
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/include"
layout="@layout/content_main" />
<include layout="@layout/content_main"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="protect.card_locker.MainActivity"
tools:showIn="@layout/custom_barcode_scanner">
<LinearLayout
android:id="@+id/camera_permission_denied_clickable_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/camera_permission_denied_icon"
android:layout_width="match_parent"
android:layout_height="84dp"
android:scaleType="fitCenter"
android:src="@drawable/camera_permission_denied" />
<TextView
android:id="@+id/camera_permission_denied_title"
style="@style/TextAppearance.Material3.HeadlineLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/cameraPermissionDeniedTitle" />
<TextView
android:id="@+id/camera_permission_denied_message"
style="@style/AppTheme.TextView.NoData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/noCameraPermissionDirectToSystemSetting" />
</LinearLayout>
</RelativeLayout>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@@ -9,21 +10,19 @@
tools:showIn="@layout/main_activity">
<LinearLayout
android:visibility="gone"
android:id="@+id/helpSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="@+id/welcome_icon"
android:layout_width="match_parent"
android:layout_height="184dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_launcher_foreground" />
<TextView
android:id="@+id/welcome_text"
style="@style/TextAppearance.Material3.HeadlineLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -31,40 +30,40 @@
android:text="@string/welcome" />
<TextView
android:id="@+id/add_card_instruction"
style="@style/AppTheme.TextView.NoData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/noGiftCards" />
android:text="@string/noGiftCards"/>
</LinearLayout>
<TextView
android:id="@+id/noMatchingCardsText"
style="@style/AppTheme.TextView.NoData"
android:id="@+id/noMatchingCardsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/noMatchingGiftCards"
android:visibility="gone" />
android:visibility="gone"/>
<TextView
android:id="@+id/noGroupCardsText"
style="@style/AppTheme.TextView.NoData"
android:id="@+id/noGroupCardsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/noGroupCards"
android:visibility="gone" />
android:visibility="gone"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="@integer/main_view_card_columns"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="80dp"
android:clipToPadding="false"
android:scrollbars="vertical"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="@integer/main_view_card_columns" />
android:visibility="gone"/>
</RelativeLayout>

View File

@@ -1,57 +1,41 @@
<?xml version="1.0" encoding="utf-8"?><!-- Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/res/layout/custom_barcode_scanner.xml originally released under Apache 2.0 -->
<?xml version="1.0" encoding="utf-8"?>
<!-- Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/res/layout/custom_barcode_scanner.xml originally released under Apache 2.0 -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.journeyapps.barcodescanner.BarcodeView
android:id="@+id/zxing_barcode_surface"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:zxing_framing_rect_height="250dp"
app:zxing_framing_rect_width="250dp" />
android:id="@+id/zxing_barcode_surface"
app:zxing_framing_rect_width="250dp"
app:zxing_framing_rect_height="250dp"/>
<com.journeyapps.barcodescanner.ViewfinderView
android:id="@+id/zxing_viewfinder_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/zxing_viewfinder_view"
app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
app:zxing_result_view="@color/zxing_custom_result_view"
app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask" />
app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/card_input_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_scanner_padding">
<include
android:id="@+id/camera_permission_denied_layout"
layout="@layout/camera_permission_failed_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:gravity="center_horizontal"
android:orientation="vertical">
<Button
android:id="@+id/add_from_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addFromImage"
app:layout_constraintBottom_toTopOf="@+id/add_manually"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/camera_permission_denied_layout"
app:layout_constraintVertical_bias="1.0" />
android:text="@string/addFromImage" />
<Button
android:id="@+id/add_manually"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addManually"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:text="@string/addManually" />
</LinearLayout>
</merge>

View File

@@ -288,7 +288,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:digits="0123456789,." />
android:digits="0123456789.,$" />
</com.google.android.material.textfield.TextInputLayout>
@@ -311,32 +311,6 @@
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Valid from -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/inputPadding"
android:paddingTop="@dimen/inputPadding"
android:orientation="horizontal">
<!-- Valid from -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/validFromView"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:hint="@string/validFromDate"
android:labelFor="@+id/validFromField">
<AutoCompleteTextView
android:id="@+id/validFromField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Expiration -->
<LinearLayout
android:layout_width="match_parent"

View File

@@ -176,30 +176,11 @@
android:drawablePadding="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/info_divider"
app:layout_constraintBottom_toTopOf="@+id/validFrom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible"
tools:text="525 points"/>
<TextView
android:id="@+id/validFrom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
app:drawableLeftCompat="@drawable/ic_valid_from_24dp"
android:drawablePadding="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/balance"
app:layout_constraintBottom_toTopOf="@+id/expiry"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible"
tools:text="Today"/>
tools:text="525 points"/>
<TextView
android:id="@+id/expiry"
@@ -213,7 +194,7 @@
app:drawableLeftCompat="@drawable/ic_baseline_access_time_24"
android:drawablePadding="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/validFrom"
app:layout_constraintTop_toBottomOf="@+id/balance"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -8,7 +8,6 @@
android:layout_height="fill_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="fill_parent"
@@ -265,18 +264,6 @@
android:tooltipText="@string/nextCard"
android:visibility="gone" />
<ImageButton
android:id="@+id/button_update_balance"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@android:color/transparent"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:src="@drawable/ic_baseline_shopping_cart_24"
android:tooltipText="@string/updateBalance"
android:visibility="gone" />
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton

View File

@@ -36,8 +36,6 @@
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/include"
layout="@layout/content_main" />
<include layout="@layout/content_main"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -29,8 +29,6 @@
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/include"
layout="@layout/group_main" />
<include layout="@layout/group_main"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -8,60 +8,40 @@ Oğuz Ersen
Katharine Chui
mondstern
IllusiveMan196
StoyanDimitrov
Altonss
SlavekB
Michael Moroni
Gediminas Murauskas
StoyanDimitrov
Petr Novák
Joel A
Michael Moroni
Taco
laralem
pfaffenrodt
SlavekB
Gediminas Murauskas
Nyatsuki
gallegonovato
HudobniVolk
Samantaz Fox
laralem
arno-github
Ankit Tiwari
Sergio Paredes
Aayush Gupta
huuhaa
Quentin PAGÈS
Alexander Ivanov
arshbeerSingh
Denis Shilin
Freddo espresso
Silvério Santos
Quentin PAGÈS
Miha Frangež
Arnis Jaundžeikars
Kefir2105
sr093906
Giovanni Donisi
Freddo espresso
mdvhimself
Katarzyna
echo r"0xX4H" | rev
Magnitudee
Maciej Błędkowski
Olivia (Zoe)
Projjal Moitra
betsythefc
Silvério Santos
waffshappen
Robin
Eric
ati3
(Cliff Heraldo)
Evgeniy Khramov
Giovanni
Jane Kong
Jiri Grönroos
K. Herbert
Lisa A.
Mawuena M. KODZO A.
Max
Magnitudee
Still Hsu
String E. Fighter
Tapu
Yurical
Eryk Michalak
rr-vesp
alajemba-vik
/usr/local/ΕΨΗΕΛΩΝ
@@ -70,142 +50,87 @@ Alessandro Mandelli
KovalevArtem
Artem M.
Astrohops1
BMN
BootVirtual
Clonewayx
D. Domig
Diego
Fede Pujol
francescbassas
Jean-Luc Tibaux
Jesse Davids
Lukas Grassauer
bittin
Marnick L'Eau
Michalis
Michał
Mohamed A. Salah
the7thNightmare
Rishi Agarwal
Neko Nekowazarashi
Rosdyana Kusuma
umoenks
Simon Rusinov
Siriusmart
Mritunjay
Tarik Dzambic
Thomas Bertels
Thomas Cruveilher
Tian Jiale
Tong Liu
Wanath
Runner
ce i moa
inesre
lgasp
phlostically
sal0max
Ágata Leuck
BmBKun
Aditya Das
Asier
Kevin Sicong Jiang
Tomer Ben-Rachel
Tom Sawyer
tfuxu
Ahmed Saleh
Airat
Alexander
Alexander Ivanov
sNiXx
Angela Enogieru
AnimeshChatterjee1
Ashish Yadav
Asier
Aya Elsaadany
BMN
Biren
Booc Sylvan
Brage Nesteby Reitan
CherryMonster222
Colgrave
Csaba
Mylou53
danieluhrinyi
Kasina Dheeraj
Donno
Eric
Evgeniy Khramov
Flav
Franciszek Stefan
Grzegorz
gneiss15
Hamustra Scans
HowITsDone
Izzy
Jacek
Jacopo Gennaro Esposito
Jasielprogramador
Jean Mareilles
Jean-Baptiste
Kung-chih
Karvjorm
Jean-Luc Tibaux
krkk
Laura Ferraz
Lucas da Costa
Lisa
bittin
Manan Jhaveri
Marco
BRBsoup
Mattia
Md. Al-Amin
Michael Gangolf
3DN1M
Mobashir Raihan
Moi Toi
DivideEtImpera
Nicolas
Nosnahc
pa4k
pbeckmann
Peer Beckmann
Piotr Strebski
Piotr Zet
Quang Nguyen
Ratnesh
Reza
Rohan Babbar
Ronak Upadhyay
Rose Liverman
SKULD
Salem Malus
Samarth Asthan
Shailendra Maurya
Simone Dotto
Subhashish Anand
SziaTomi
Mehedi Hasan
Titas Pažereckas
atakujonc
Tom Sawyer
Tomer Ben-Rachel
Tony C
Tymofii Lytvynenko
Vancha March
Yevgeny M
Yusril A
ahmed-awad26
Avik Kundu
diksha-2911
gbonaspetti
gittyboy-cell
huang ivan
lucafont2
mtrmirez
opsik
polarhun
pooyanazari
psa-jforestier
Robin
sergio
080502
Marcus
techwebpd
Truestorybaby
tygyh
unstartdev
wmilan 17
يوسف لطفي
luoyang3
JaeBeom An
JungHee Lee

View File

@@ -2,18 +2,6 @@
stocard_stores.csv was created by extracting /data/data/de.stocard/de.stocard.stocard/databases/stores on a rooted devices and running the following command over it:
```
sqlite3 -header -csv sync_db "select id,content from synced_resources where collection = '/loyalty-card-providers/'" > stocard_providers.csv
while IFS= read -r line; do
if [ "$line" = "id,content" ]; then
echo "_id,name,barcodeFormat" > stocard_stores.csv
else
id="$(echo "$line" | cut -d ',' -f1)"
name="$(echo "$line" | cut -d ',' -f2- | sed 's/""/"/g' | sed 's/^"//g' | sed 's/"$//g' | jq -r .name)"
barcodeFormat="$(echo "$line" | cut -d ',' -f2- | sed 's/""/"/g' | sed 's/^"//g' | sed 's/"$//g' | jq -r .default_barcode_format)"
echo "$id,\"$name\",$barcodeFormat" >> stocard_stores.csv
fi
done < stocard_providers.csv
```
sqlite3 -header -csv stores "select _id,name,barcodeFormat from stores" > stocard_stores.csv
Only used for data portability reasons (ensuring importing works). Do NOT copy this anywhere else or use it for any purpose other than ensuring we can import a GDPR-provided export. We want to make sure this stays under fair use.

View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_search">بحث</string>
<string name="action_add">اضف</string>
<string name="noGiftCards">اضغط على + الزر الإضافي لاضافة بطاقة, او استورد من ⋮ القائمة.</string>
<string name="noMatchingGiftCards">لا نتائج. حاول تغيير كلمات البحث.</string>
<string name="noGiftCards">اضغط علي زر + لاضافة بطاقة, او استورد بعض منهم من قائمة ال ⋮.</string>
<string name="noMatchingGiftCards">لا نتائج. حاول تغيير بحثك.</string>
<string name="storeName">اسم</string>
<string name="note">مذكرة</string>
<string name="cardId">بطاقة شخصية</string>
@@ -12,17 +12,17 @@
<string name="noBarcode">لا يوجد باركود</string>
<string name="star">اضف الي المفضلة</string>
<string name="unstar">حذف من المفضلة</string>
<string name="cancel">إلغاء</string>
<string name="cancel">الغاء</string>
<string name="save">حفظ</string>
<string name="edit">تعديل</string>
<string name="delete">مسح</string>
<string name="confirm">تأكيد</string>
<string name="deleteConfirmation">مسح هذة البطاقة نهائيا؟</string>
<string name="ok">حسنا</string>
<string name="copy_to_clipboard">نسخ البطاقة الشخصية الى الحافظة</string>
<string name="copy_to_clipboard">نسخ البطاقة الشخصية الي الحافظة</string>
<string name="share">شارك</string>
<string name="sendLabel">ارسل…</string>
<string name="editCardTitle">عدل البطاقة</string>
<string name="editCardTitle">عدل بطاقة</string>
<string name="addCardTitle">اضف بطاقة</string>
<string name="scanCardBarcode">مسح باركود</string>
<string name="cardShortcut">اختصار البطاقة</string>
@@ -31,14 +31,15 @@
<string name="barcodeImageDescriptionWithType">صورة <xliff:g>%s</xliff:g> باركود</string>
<string name="noStoreError">لا يوجد اسم مدخل</string>
<string name="noCardIdError">لا يوجد بطاقة شخصية مدخلة</string>
<string name="noCardExistsError">لا يمكن العثور على هذه البطاقة</string>
<string name="noCardExistsError">لا يمكن العثور علي هذه البطاقة</string>
<string name="failedParsingImportUriError">لا يمكن تحليل الرابط المستورد</string>
<string name="importExport">استيراد/تصدير</string>
<string name="importExportHelp">دعم بياناتك يسمح بنقلها إلى جهاز آخر.</string>
<string name="importFailed">تعذر إجراء الاستيراد</string>
<string name="importExportHelp">عمل نسخ احتطياتي لبياناتك يسمح نقلها لجهاز اخر.</string>
<string name="importFailed">لا يمكن عمل الاستيراد</string>
<string name="exportSuccessfulTitle">متصدر</string>
<string name="exportFailedTitle">فشل التصدير</string>
<string name="exportFailed">لا يمكن عمل التصدير</string>
<string name="noExternalStoragePermissionError">امنح التخزين الخارجي اذن لاستيراد وتصدير البيانات</string>
<string name="exportOptionExplanation">ستتم كتابة البيانات في الموقع الذي تختاره.</string>
<string name="importOptionFilesystemButton">من نظام الملفات</string>
<string name="importOptionApplicationTitle">استخدم تطبيقًا آخر</string>
@@ -84,7 +85,7 @@
<string name="points">نقاط</string>
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> لا يبدو أنه رصيد صالح.</string>
<string name="chooseImportType">استيراد البيانات من</string>
<string name="app_loyalty_card_keychain">سلسلة مفاتيح بطاقة الولاء</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="privacy_policy">سياسة الخصوصية</string>
<string name="accept">قبول</string>
<string name="importCatima">الاستيراد من Catima</string>
@@ -93,7 +94,7 @@
<string name="importFidme">الاستيراد من FidMe</string>
<string name="importFidmeMessage">حدد ملفك <i>fidme-export-request-xxxxxx.zip</i> تصدير من FidMe للاستيراد ، ثم حدد أنواع الباركود يدويًا بعد ذلك.
\nقم بإنشائه من ملف تعريف FidMe الخاص بك عن طريق اختيار حماية البيانات ثم الضغط على استخراج بياناتي أولاً.</string>
<string name="importStocardMessage">حدد ملفك <i>***.zip</i> تصدير من Stocard للاستيراد.
<string name="importStocardMessage">حدد ملفك <i>***-sync.zip</i> تصدير من Stocard للاستيراد.
\nاحصل عليه عن طريق إرسال بريد إلكتروني إلى support@stocardapp.com لطلب تصدير بياناتك.</string>
<string name="importVoucherVault">الاستيراد من Voucher Vault</string>
<string name="importVoucherVaultMessage">حدد ملفك <i>vouchervault.json</i> تصدير من Voucher Vault للاستيراد.
@@ -101,7 +102,6 @@
<string name="barcodeId">قيمة الباركود</string>
<string name="sameAsCardId">نفس بطاقة الهوية</string>
<string name="setBarcodeId">قم بتعيين قيمة الباركود</string>
<string name="unsupportedBarcodeType">لا يمكن عرض نوع الباركود هذا. قد يكون مدعومًا في إصدار أحدث من التطبيق.</string>
<string name="wrongValueForBarcodeType">القيمة غير صالحة لنوع الباركود المحدد</string>
<string name="copy_to_clipboard_multiple_toast">تم نسخ بطاقات الهوية إلى الحافظة</string>
<string name="intent_import_card_from_url_share_multiple_text">أريد مشاركة بعض البطاقات معك</string>
@@ -137,7 +137,7 @@
<string name="sort">فرز</string>
<string name="showMoreInfo">اظهر المعلومات</string>
<string name="hideMoreInfo">إخفاء المعلومات</string>
<string name="swipeToSwitchImages">امسح لتبديل الصور, اضغط لفترة طويلة لفتح الصورة في تطبيق المعرض</string>
<string name="swipeToSwitchImages">اسحب أو اضغط لفترة طويلة لتبديل الصور</string>
<string name="sort_by_balance">الرصيد</string>
<string name="reverse">... بترتيب معكوس</string>
<string name="sort_by">صنف حسب</string>
@@ -159,7 +159,7 @@
<string name="unarchived">البطاقة غير مؤرشفة</string>
<string name="archiveList">أرشيف</string>
<string name="failedLaunchingPhotoPicker">تعذر العثور على تطبيق معرض مدعوم</string>
<string name="noGiftCardsGroup">انشئ بعض من البطاقات, و عيينهم لهذه المجموعة.</string>
<string name="noGiftCardsGroup">انشئ بعض من البطاقات, و عيينهم لهذة المجموعة.</string>
<string name="deleteTitle">مسح بطاقة</string>
<plurals name="selectedCardCount">
<item quantity="zero"><xliff:g>%d</xliff:g> محدد</item>
@@ -173,7 +173,7 @@
<string name="exportName">تصدير</string>
<string name="exporting">جار التصدير…</string>
<string name="importSuccessfulTitle">مستورد</string>
<string name="importFailedTitle">فشل بالاستيراد</string>
<string name="importFailedTitle">فشل الاستيراد</string>
<string name="sort_by_most_recently_used">الأكثر أستعمالا مؤخرا</string>
<string name="license">رخصة</string>
<string name="sort_by_name">اسم</string>
@@ -190,18 +190,18 @@
<plurals name="deleteCardsTitle">
<item quantity="zero">مسح <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="one">مسح <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="two">مسح <xliff:g>%d</xliff:g> بطاقتين</item>
<item quantity="two">مسح <xliff:g>%d</xliff:g> بطاقة</item>
<item quantity="few">مسح <xliff:g>%d</xliff:g> بطاقات</item>
<item quantity="many">مسح <xliff:g>%d</xliff:g> بطاقات</item>
<item quantity="other">مسح <xliff:g>%d</xliff:g> بطاقات</item>
</plurals>
<plurals name="deleteCardsConfirmation">
<item quantity="zero">مسح هذه <xliff:g>%d</xliff:g> البطاقة نهائيا؟</item>
<item quantity="one">مسح هذه <xliff:g>%d</xliff:g> البطاقة نهائيا؟</item>
<item quantity="two">مسح هذه <xliff:g>%d</xliff:g> البطاقتين نهائيا؟</item>
<item quantity="few">مسح هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
<item quantity="many">مسح هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
<item quantity="other">مسح هذه <xliff:g>%d</xliff:g> البطاقات نهائيا؟</item>
<item quantity="zero">مسح هذه <xliff:g>%d</xliff:g> بطاقة نهائيا؟</item>
<item quantity="one">مسح هذه <xliff:g>%d</xliff:g> بطاقة نهائيا؟</item>
<item quantity="two">مسح هذه <xliff:g>%d</xliff:g> بطاقة نهائيا؟</item>
<item quantity="few">مسح هذه <xliff:g>%d</xliff:g> بطاقات نهائيا؟</item>
<item quantity="many">مسح هذه <xliff:g>%d</xliff:g> بطاقات نهائيا؟</item>
<item quantity="other">مسح هذه <xliff:g>%d</xliff:g> بطاقات نهائيا؟</item>
</plurals>
<string name="importOptionFilesystemTitle">الاستيراد من نظام الملفات</string>
<string name="importOptionFilesystemExplanation">اختر ملفًا محددًا من نظام الملفات.</string>
@@ -270,26 +270,4 @@
<string name="settings_theme_color">لون المظهر</string>
<string name="previousCard">السابق</string>
<string name="nextCard">التالي</string>
<string name="failedToRetrieveImageFile">فشل في استخراج ملف الصورة</string>
<string name="barcodeLongPressMessage">يمكن فتح صور فقط في تطبيق معرض الصور</string>
<string name="failedToOpenUrl">ثبت متصفح ويب أولاً</string>
<string name="welcome">مرحبا بك في كاتيما</string>
<string name="updateBalanceTitle">كم أنفقت؟</string>
<string name="currentBalanceSentence">الرصيد الحالي: <xliff:g> %s </xliff:g></string>
<plurals name="viewArchivedCardsWithCount">
<item quantity="zero">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقة)</item>
<item quantity="one">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقة)</item>
<item quantity="two">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقتين)</item>
<item quantity="few">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقات)</item>
<item quantity="many">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقات)</item>
<item quantity="other">عرض الأرشيف (<xliff:g>%1$d</xliff:g> بطاقات)</item>
</plurals>
<string name="importCards">استيراد البطاقات</string>
<string name="newBalanceSentence">الرصيد الجديد: <xliff:g>%s</xliff:g></string>
<string name="cameraPermissionDeniedTitle">تعذر الوصول إلى الكاميرا</string>
<string name="noCameraPermissionDirectToSystemSetting">لمسح الباركود، ستحتاج Catima إلى الوصول إلى الكاميرا. اضغط هنا لتغيير إعدادات الأذونات.</string>
<string name="updateBalance">تحديث الرصيد</string>
<string name="updateBalanceHint">أدخل المبلغ</string>
<string name="storageReadPermissionRequired">الصلاحيه للوصل للتخزين مطلوبة لهذا الاجراء</string>
<string name="validFromDate">عربيه</string>
</resources>

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -34,6 +34,7 @@
<string name="setFrontImage">Снимка на предната страна</string>
<string name="photos">Снимки</string>
<string name="importOptionApplicationExplanation">Изберете файл на друго приложение.</string>
<string name="noExternalStoragePermissionError">Разрешете достъп до хранилището, за да работи внасянето и изнасянето</string>
<string name="noCardExistsError">Картата не е намерена</string>
<string name="updateBarcodeQuestionText">Идентификаторът е променен. Желаете ли с неговата стойност да бъде променен и щрихкодът\?</string>
<string name="updateBarcodeQuestionTitle">Обновяване на щрихкода\?</string>
@@ -71,13 +72,13 @@
<string name="moveBarcodeToCenterOfScreen">Премества щрихкода в центъра на екрана</string>
<string name="moveBarcodeToTopOfScreen">Премества щрихкода най-горе на екрана</string>
<string name="never">Не изтича</string>
<string name="chooseExpiryDate">Определена дата</string>
<string name="expiryDate">Валидна до</string>
<string name="chooseExpiryDate">Дата на изтичане</string>
<string name="expiryDate">Валидност</string>
<string name="editBarcode">Редактиране на щрихкод</string>
<string name="barcode">Щрихкод</string>
<string name="card">Карта</string>
<string name="groupsList">Списъци: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Валидна до: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Валидност: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">Изтекла: <xliff:g>%s</xliff:g></string>
<string name="balanceSentence">Наличност: <xliff:g>%s</xliff:g></string>
<string name="noGroups">Докоснете бутона +, за да добавите списък.</string>
@@ -147,12 +148,11 @@
<string name="addFromImage">Избор от галерията</string>
<string name="addManually">Ръчно въвеждане</string>
<string name="leaveWithoutSaveConfirmation">Оставяте промените незапазени\?</string>
<string name="unsupportedBarcodeType">Щрихкод от този вид не може да бъде показан. Може да бъде поддържан в следващо издание.</string>
<string name="importStocard">Внасяне от Stocard</string>
<string name="importVoucherVault">Внасяне от Voucher Vault</string>
<string name="importVoucherVaultMessage">Изберете файла <i>vouchervault.json</i>, предварително изнесен от Voucher Vault.
\nСъздайте такъв файл от меню „Export“ във Voucher Vault.</string>
<string name="importStocardMessage">Изберете файла <i>***.zip</i>, предварително изнесен от Stocard.
<string name="importStocardMessage">Изберете файла <i>***-sync.zip</i>, предварително изнесен от Stocard.
\nПолучете го като изпратите писмо на support@stocardapp.com с искане за изнасяне вашите данни.</string>
<string name="importLoyaltyCardKeychainMessage">Изберете файла <i>LoyaltyCardKeychain.csv</i>, предварително изнесен от Loyalty Card Keychain.
\nСъздайте такъв файл от меню Внасяне/изнасяне от друго устройство с Loyalty Card Keychain като изберете Изнасяне.</string>
@@ -183,7 +183,7 @@
<string name="settings_locale">Език</string>
<string name="noGroupCards">Групата е празна</string>
<string name="barcodeImageDescriptionWithType">Изображение на щрихкод от вид <xliff:g>%s</xliff:g></string>
<string name="swipeToSwitchImages">За да размените изображенията - плъзнете, а за да ги отворите в галерията - задръжте</string>
<string name="swipeToSwitchImages">Плъзване или задържане за смяна на изображения</string>
<string name="sort_by">Сортиране по</string>
<string name="reverse">…в обратен ред</string>
<string name="sort_by_balance">Наличност</string>
@@ -239,7 +239,7 @@
<string name="unarchived">Карта е извадена от архива</string>
<string name="archiveList">Архив</string>
<string name="noUnarchivedCardsMessage">Няма карти извън архива</string>
<string name="failedLaunchingPhotoPicker">Не е намерено поддържано приложение за галерия</string>
<string name="failedLaunchingPhotoPicker">Не е намерено поддържано приложение за галерия.</string>
<plurals name="groupCardCountWithArchived">
<item quantity="one"><xliff:g>%1$d</xliff:g> карта (<xliff:g id="archivedCount">%2$d</xliff:g> архивирана)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> карти (<xliff:g id="archivedCount">%2$d</xliff:g> архивирани)</item>
@@ -252,20 +252,4 @@
<item quantity="one">Преглед на архива (<xliff:g>%1$d</xliff:g> карта)</item>
<item quantity="other">Преглед на архива (<xliff:g>%1$d</xliff:g> карти)</item>
</plurals>
<string name="barcodeLongPressMessage">В приложението галерия могат да бъдат отваряни само изображения</string>
<string name="failedToRetrieveImageFile">Не е възможно извличане на изображение</string>
<string name="noCameraPermissionDirectToSystemSetting">За да сканирате щрихкодове с Catima е необходим достъп до камерата. За да промените разрешението докоснете тук.</string>
<string name="updateBalanceTitle">Каква е промяната\?</string>
<string name="updateBalanceHint">Въведете стойност</string>
<string name="newBalanceSentence">Нов баланс: <xliff:g>%s</xliff:g></string>
<string name="cameraPermissionDeniedTitle">Камерата е недостъпна</string>
<string name="currentBalanceSentence">Текущ баланс: <xliff:g>%s</xliff:g></string>
<string name="updateBalance">Обновяване на баланса</string>
<string name="importCards">Внасяне на карти</string>
<string name="storageReadPermissionRequired">За това действие е необходимо разрешение за четене на хранилището…</string>
<string name="cameraPermissionRequired">За това действие е необходимо разрешение за достъп до камерата…</string>
<string name="validFromDate">Валидна от</string>
<string name="anyDate">Без значение от датата</string>
<string name="validFromSentence">Валидна от: <xliff:g>%s</xliff:g></string>
<string name="chooseValidFromDate">Определена дата</string>
</resources>

View File

@@ -1,25 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<resources>
<string name="save">সংরক্ষণ</string>
<string name="cancel">বাতিল</string>
<string name="unstar">তারা মুক্ত</string>
<string name="star">তারা</string>
<string name="barcodeNoBarcode">কোনো বারকোড নেই</string>
<string name="barcodeNoBarcode">বারকোড না বারকোড</string>
<string name="barcodeType">বারকোড ধরন</string>
<string name="note">বিঃদ্রঃ</string>
<string name="storeName">দোকানের নাম</string>
<string name="noMatchingGiftCards">কোনো ফলাফল পাওয়া যায়নি। অনুসন্ধানের বাক্যটি বদলে দেখুন।</string>
<string name="noGiftCards">প্লাস বোতামটি টিপে একটি কার্ড যোগ করুন বা ⋮ মেনু থেকে কিছু নিয়ে আসুন।</string>
<string name="noMatchingGiftCards">কোন ম্যাচিং উপহার কার্ড নেই</string>
<string name="noGiftCards">উপহার কার্ড নেই</string>
<string name="action_add">কর্ম যোগ</string>
<string name="all">সব</string>
<string name="noGroupCards">এই গ্রুপটি খালি</string>
<string name="noGroups">+ যোগ বোতামটি টিপে গ্রুপ যোগ করুন যাতে তাদের শ্রেণীকরণ করা যায়।</string>
<string name="noGroupCards">গোষ্ঠী কার্ড নেই</string>
<string name="noGroups">গোষ্ঠীগুলি নেই</string>
<string name="groups">গোষ্ঠীগুলি</string>
<string name="enter_group_name">গোষ্ঠী নাম লিখুন </string>
<string name="exportSuccessful">তথ্য রপ্তানি করা শেষ</string>
<string name="importSuccessful">তথ্য আনা শেষ</string>
<string name="exportSuccessful">রপ্তানি সফল</string>
<string name="importSuccessful">আগম সফল</string>
<string name="intent_import_card_from_url_share_text">url শেয়ার টেক্সট থেকে ইন্টেন্ট ইম্পোর্ট কার্ড</string>
<string name="settings_disable_lockscreen_while_viewing_card">স্ক্রীন লক হতে দেবেন না</string>
<string name="settings_disable_lockscreen_while_viewing_card"> কার্ড দেখা কালিন লকস্ক্রিন নিষ্ক্রিয়</string>
<string name="settings_keep_screen_on">সেটিংস পর্দা খোলা রাখুন</string>
<string name="settings_max_font_size_scale">সর্বোচ্চ হরফ আকার</string>
<string name="settings_light_theme">সাদাটে থিম</string>
@@ -32,25 +32,24 @@
<string name="importStocard">স্টো কার্ড আমদানি করুন</string>
<string name="importVoucherVault">আমদানি ভাউচার ভল্ট</string>
<string name="barcodeId">বারকোড আইডি</string>
<string name="sameAsCardId">আইডি আর এটা এক</string>
<string name="sameAsCardId">কার্ড আইডির মতো</string>
<string name="setBarcodeId">বারকোড আইডি সেট করুন</string>
<string name="unsupportedBarcodeType">এই বারকোডের টাইপটি এখন দেখানো যাচ্ছে না। অ্যাপের পরের সংস্করণে হয়ত এটি সমর্থন করা যেতে পারে।</string>
<string name="wrongValueForBarcodeType">বারকোড টাইপের জন্য ভুল মান</string>
<string name="copy_to_clipboard_multiple_toast">আইডিগুলি ক্লিপবোর্ডে কপি হল</string>
<string name="copy_to_clipboard_multiple_toast">ক্লিপবোর্ড একাধিক টোস্টে অনুলিপি করুন</string>
<string name="intent_import_card_from_url_share_multiple_text">url থেকে ইন্টেন্ট ইম্পোর্ট কার্ড একাধিক টেক্সট শেয়ার করে</string>
<string name="frontImageDescription">সামনের চিত্র</string>
<string name="backImageDescription">পিছনের চিত্র</string>
<string name="frontImageDescription">সামনের চিত্রের বর্ণনা</string>
<string name="backImageDescription">পিছনের চিত্রের বর্ণনা</string>
<string name="photos">ছবি</string>
<string name="setFrontImage">সদর ছবি স্থাপন</string>
<string name="setBackImage">পিছনের ছবি স্থাপন</string>
<string name="removeImage">ছবি অপসারণ</string>
<string name="takePhoto">ছবি নেত্তয়া</string>
<string name="updateBarcodeQuestionTitle">বারকোডের মানটি আপডেট করবেন\?</string>
<string name="updateBarcodeQuestionText">আপনি আইডিটি পাল্টেছেন, এটির কোনো বারকোড দিয়ে কি এখনের বারকোডটি আপডেট করে দেবেন\?</string>
<string name="updateBarcodeQuestionTitle">হালনাগাদ বারকোড প্রশ্ন শিরোনাম</string>
<string name="updateBarcodeQuestionText">হালনাগাদ বারকোড প্রশ্ন টেক্সট </string>
<string name="yes">হাঁ</string>
<string name="no">না</string>
<string name="passwordRequired">পাসওয়ার্ড প্রয়োজন</string>
<string name="failedGeneratingShareURL">শেয়ার করার ইউআরএল তৈরি করা গেল না। অনুগ্রহ করে এটিকে রিপোর্ট করে দিন।</string>
<string name="failedGeneratingShareURL">শেয়ার ইউআরএল তৈরি করতে ব্যর্থ হয়েছে</string>
<string name="turn_flashlight_on">টর্চলাইট চালু করুন</string>
<string name="turn_flashlight_off">টর্চলাইট বন্ধ করুন</string>
<string name="settings_locale">লোকেল</string>
@@ -65,136 +64,23 @@
<string name="settings_green_theme">সবুজ থিম</string>
<string name="settings_brown_theme">বাদামী থিম</string>
<string name="sort">সাজান</string>
<string name="swipeToSwitchImages">ছবি পাল্টানোর জন্য সোয়াইপ করুন, ছবিটি গ্যালারি অ্যাপে খোলার জন্য টিপে ধড়ে থাকুন</string>
<string name="swipeToSwitchImages">ছবি পরিবর্তন করতে সোয়াইপ করুন</string>
<string name="sort_by_name">নামের দ্বারা সাজান</string>
<string name="sort_by_most_recently_used">সর্বাধিক সম্প্রতি ব্যবহৃত দ্বারা সাজান</string>
<string name="sort_by_expiry">মেয়াদ শেষ করে সাজান</string>
<string name="reverse">...উল্টো ক্রমে</string>
<string name="reverse">বিপরীত</string>
<string name="sort_by">ক্রমানুসার</string>
<string name="noCardExistsError">ার্ডটি পাওয়া যায়নি</string>
<string name="noCardExistsError">োন কার্ড নেই ত্রুটি</string>
<string name="noStoreError">স্টোরেজ ত্রুটি নেই</string>
<string name="card_ids_copied">আইডি কপি করা হয়েছে</string>
<string name="card_ids_copied">কার্ড আইডি কপি করা হয়েছে</string>
<string name="noCardsMessage">কোন কার্ড বার্তা নেই</string>
<string name="addCardTitle">কার্ডের শিরোনাম যোগ করুন</string>
<string name="editCardTitle">কার্ডের শিরোনাম সম্পাদনা করুন</string>
<string name="sendLabel">পাঠান</string>
<string name="sendLabel">লেবেল পাঠান</string>
<string name="share">ভাগ</string>
<string name="copy_to_clipboard">নকল করুন ক্লিপবোর্ড এ</string>
<string name="deleteConfirmation">এই কার্ডটি চিরকালের জন্য মুছে দেবো\?</string>
<string name="deleteConfirmation">নিশ্চিতকরণ মুছে দিন</string>
<string name="confirm">নিশ্চিত করুন</string>
<string name="delete">মুছে ফেলুন</string>
<string name="edit">সম্পাদনা</string>
<string name="action_search">খুঁজুন</string>
<string name="card">কার্ড</string>
<string name="currency">মুদ্রা</string>
<string name="cardId">কার্ড আইডি</string>
<string name="noBarcode">বারকোড নেই</string>
<string name="deleteTitle">কার্ড ডিলিট করুন</string>
<string name="ok">ঠিক আছে</string>
<string name="about">সম্পর্কিত</string>
<string name="debug_version_fmt">সংস্করণ: <xliff:g id="version">%s</xliff:g></string>
<string name="importOptionApplicationButton">অন্য অ্যাপ ব্যাবহার করুন</string>
<string name="moveUp">উপরে উঠান</string>
<string name="moveDown">নিচে নামান</string>
<string name="barcode">বারকোড</string>
<string name="expiryDate">মেয়াদোত্তীর্ণ তারিখ</string>
<string name="noBarcodeFound">কোনো বারকোড পাওয়া যায়নি</string>
<string name="cameraPermissionRequired">এই কাজটির জন্য ক্যামেরা ব্যবহার করার অনুমতি লাগবে…</string>
<string name="noCameraPermissionDirectToSystemSetting">বারকোড স্ক্যান করার জন্য, কাটিমাকে ফোনের ক্যামেরা ব্যবহার করার অনুমতি দিতে হবে। এইখানে টাচ করে আপনার অনুমতি সেটিংস পালটে নিন।</string>
<string name="importOptionApplicationExplanation">আপনার প্রিয় ফাইল ম্যানেজার বা আর যেকোনো অ্যাপ দিয়ে একটি ফাইল খুলুন।</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">মেধাস্বত্ব © ২০১৯-<xliff:g>%d</xliff:g> সিলভিয়া ভান অস</string>
<string name="app_license">কপিলেফ্ট করা মুক্ত সফটওয়্যার, জিপিএলের ৩য় এবং তার অধিক সংস্করণে লাইসেন্স করা</string>
<string name="enterBarcodeInstructions">আইডিটি লিখুন আর নয় নিচ থেকে একটি বারকোডের প্রকার বা \"কোনো বারকোড নেই\", নির্বাচন করুন।</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">এই <xliff:g>%d</xliff:g>টি কার্ড কি চিরকালের জন্য মুছে দেবো\?</item>
<item quantity="other">এই <xliff:g>%d</xliff:g>টি কার্ড কি চিরকালের জন্য মুছে দেবো\?</item>
</plurals>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> একটি নির্বাচিত</item>
<item quantity="other"><xliff:g>%d</xliff:g> টি নির্বাচিত</item>
</plurals>
<plurals name="deleteCardsTitle">
<item quantity="one"><xliff:g>%d</xliff:g>টি কার্ড মুছে ফেলুন</item>
<item quantity="other"><xliff:g>%d</xliff:g>টি কার্ড মুছে ফেলুন</item>
</plurals>
<string name="cameraPermissionDeniedTitle">ফোনের ক্যামেরা ব্যবহার করা যাচ্ছে না</string>
<string name="importOptionFilesystemExplanation">ফোনের স্টোরেজ থেকে নির্দিষ্ট একটি ফাইল আনুন।</string>
<string name="app_libraries">মুক্ত লাইব্রেরি যেগুলি আমার নয়: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="about_title_fmt"><xliff:g id="app_name">%s</xliff:g>টির ব্যাপারে</string>
<string name="app_revision_fmt">সংশোধন তথ্য: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_resources">মুক্ত সম্পদ যেগুলি আমার নয়: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="thumbnailDescription">থাম্বনেইল</string>
<string name="settings_card_orientation">বারকোড অভিমুখ</string>
<string name="settings_follow_system_orientation">সিস্টেমের অনুসারে</string>
<string name="settings_portrait_orientation">প্রতিকৃতি</string>
<string name="barcodeImageDescriptionWithType">ছবি <xliff:g>%s</xliff:g> বারকোড</string>
<string name="exportName">রপ্তানি</string>
<string name="failedParsingImportUriError">আমদানির ইউআরআই বোঝা যাচ্ছে না</string>
<string name="importExport">আমদানি/রপ্তানি</string>
<string name="cardShortcut">কার্ড শর্টকাট</string>
<string name="exportFailed">বার করা যাচ্ছে না</string>
<string name="copy_to_clipboard_toast">আইডি ক্লিপবোর্ডে নকল করা হল</string>
<string name="noCardIdError">কোনো আইডি দওয়া হয়নি</string>
<string name="importExportHelp">নিজের তথ্য অন্য কোথাও সংরক্ষণ করে রাখলে পরে সেটা অন্য ফোনে আবার নিয়ে নাওয়া যাই।</string>
<string name="importFailed">আনা যাচ্ছে না</string>
<string name="noGiftCardsGroup">কিছু কার্ড বানান আর এই গ্রুপে স্থির করুন।</string>
<string name="scanCardBarcode">বারকোড স্ক্যান করুন</string>
<string name="importSuccessfulTitle">আনা শেষ</string>
<string name="importFailedTitle">আনা ব্যর্থ</string>
<string name="exportSuccessfulTitle">বার করা শেষ</string>
<string name="exportFailedTitle">বার করা ব্যর্থ</string>
<string name="importing">আনা হচ্ছে…</string>
<string name="exporting">বার করা হচ্ছে…</string>
<string name="storageReadPermissionRequired">এই কাজটির জন্য ফোনের স্টোরেজ দেখার অনুমতি লাগবে…</string>
<string name="exportOptionExplanation">তথ্যটি আপনার পছন্দের জায়গায় রাখা হবে।</string>
<string name="importOptionFilesystemTitle">ফোনের স্টোরেজ থেকে আনুন</string>
<string name="importOptionFilesystemButton">ফোনের স্টোরেজ থেকে</string>
<string name="importOptionApplicationTitle">অন্য অ্যাপ ব্যবহার করুন</string>
<string name="app_copyright_old">লয়ালটি কার্ড কিচ্যেনের উপর ভিত্তি করে
\nমেধাস্বত্ব © ২০১৬-২০২০ ব্রানডেন আর্চার</string>
<string name="selectBarcodeTitle">বারকোড নির্বাচন করুন</string>
<string name="settings">সেটিংস</string>
<string name="settings_dark_theme">অন্ধকার</string>
<string name="settings_landscape_orientation">অনুভূমিক</string>
<string name="settings_lock_on_opening_orientation">কার্ড খোলার সময় যে অভিমুখ থাকে সেটিতে লক করে দেবেন</string>
<string name="group_name_already_in_use">গ্রুপটির নাম আগে একবার ব্যবহার করে ফেলেছেন</string>
<string name="group_edit">গ্রুপ সম্পাদনা করুন</string>
<string name="group_updated">গ্রুপটি আপডেট করা হল</string>
<string name="group_name_is_empty">গ্রুপের একটি নাম থাকতে হবে</string>
<string name="deleteConfirmationGroup">গ্রুপটি মুছে দেবেন\?</string>
<string name="failedOpeningFileManager">প্রথমে একটি ফাইল ম্যানেজার ইনস্টল করুন।</string>
<string name="leaveWithoutSaveConfirmation">সংরক্ষণ না করেই চলে যাবেন\?</string>
<string name="addManually">নিজে হাতে আইডি লিখুন</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g>টি কার্ড</item>
<item quantity="other"><xliff:g>%d</xliff:g>টি কার্ড</item>
</plurals>
<string name="leaveWithoutSaveTitle">প্রস্থান</string>
<string name="settings_display_barcode_max_brightness">বারকোড উজ্জ্বল করুন</string>
<string name="editGroup">যেই গ্রুপ সম্পাদনা করা হচ্ছে: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentenceExpired">মেয়াদ শেষ হয়ে গিয়েছে: <xliff:g>%s</xliff:g></string>
<string name="editBarcode">বারকোড সম্পাদন করুন</string>
<string name="never">কখনই না</string>
<string name="addFromImage">গ্যালারি থেকে ছবি বাছুন</string>
<string name="groupsList">গ্রুপগুলি: <xliff:g>%s</xliff:g></string>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> পয়েন্ট</item>
<item quantity="other"><xliff:g>%s</xliff:g> পয়েন্ট</item>
</plurals>
<string name="expiryStateSentence">মেয়াদ শেষ হবে: <xliff:g>%s</xliff:g></string>
<string name="balanceSentence">ব্যালেন্স: <xliff:g>%s</xliff:g></string>
<string name="chooseExpiryDate">মেয়াদ শেষ হওয়ার তারিখ মনোনীত করুন</string>
<string name="moveBarcodeToTopOfScreen">বারকোডটি স্ক্রিনের উপরে উঠিয়ে দিন</string>
<string name="moveBarcodeToCenterOfScreen">বারকোডটি স্ক্রিনের কেন্দ্রে সরিয়ে দিন</string>
<string name="errorReadingImage">ছবিটি স্ক্যান করা যাচ্ছে না</string>
<string name="privacy_policy_popup_text">ব্যক্তিগত তথ্যের গোপনীয়তা নীতি নোটিশ (কিছু অ্যাপ স্টোরের এটি লাগে):
\n
\nকোন তথ্য একেবারেই সংগ্রহ করা হয় না, যা যে কেউ নিশ্চিত করতে পারবেন কারন আমাদের অ্যাপ মুক্ত সফটওয়্যার।</string>
<string name="balance">ব্যালান্স</string>
<string name="points">পয়েন্ট</string>
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> কোনো বৈধ ব্যালান্স মনে হচ্ছে না।</string>
<string name="chooseImportType">এখান থেকে তথ্য আমদানি করুন</string>
<string name="app_loyalty_card_keychain">আনুগত্য কার্ড কীচেন</string>
<string name="privacy_policy">ব্যক্তিগত তথ্যের গোপনীয়তা নীতি</string>
<string name="accept">গ্রহণ</string>
</resources>

View File

@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="settings_locale">ভাষা</string>
<string name="action_search">খুঁজুন</string>
<string name="settings_pink_theme">গুলাপি</string>
<string name="settings_blue_theme">নীল</string>
<string name="settings_green_theme">সবুজ</string>
<string name="settings_brown_theme">বাদামি</string>
<string name="save">সংরক্ষণ</string>
<string name="cardId">কার্ড আইডি</string>
<string name="barcodeType">বারকোডের ধরন</string>
<string name="about">সম্পর্কিত</string>
<string name="card">কার্ড</string>
<string name="yes">হ্যাঁ</string>
<string name="settings_grey_theme">ধূসর</string>
<string name="ok">ঠিক আছে</string>
<string name="sendLabel">পাঠান…</string>
<string name="sort_by_name">নাম</string>
<string name="previousCard">পূর্ববর্তী</string>
<string name="all">সকল</string>
<string name="barcode">বারকোড</string>
<string name="never">কখনো না</string>
<string name="currency">মুদ্রা</string>
<string name="settings_violet_theme">বেগুনি</string>
<string name="no">না</string>
<string name="nextCard">পরবর্তী</string>
<string name="action_add">যুক্ত করুন</string>
<string name="noGiftCardsGroup">কিছু কার্ড তৈরি করুন এবং তারপর সেগুলিকে এখানে গ্রুপে বরাদ্দ করুন।</string>
<string name="noMatchingGiftCards">কোন ফলাফল নেই. আপনার অনুসন্ধান পরিবর্তন করার চেষ্টা করুন.</string>
<string name="storeName">নাম</string>
<string name="note">বিঃদ্রঃ</string>
<string name="star">ফেভারিটে যোগ করুন</string>
<string name="barcodeNoBarcode">কোন বারকোড নেই</string>
<string name="noBarcode">বারকোড নেই</string>
<string name="unstar">পছন্দের তালিকা থেকে অপসারণ</string>
<string name="cancel">বাতিল করুন</string>
<string name="edit">সম্পাদনা করুন</string>
<string name="delete">মুছে ফেলা</string>
<string name="confirm">নিশ্চিত করুন</string>
<string name="deleteConfirmation">এই কার্ডটি স্থায়ীভাবে মুছবেন\?</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">এই <xliff:g>%d</xliff:g> কার্ডটি স্থায়ীভাবে মুছবেন\?</item>
<item quantity="other">এই <xliff:g>%d</xliff:g> কার্ডগুলিকে স্থায়ীভাবে মুছবেন\?</item>
</plurals>
<string name="copy_to_clipboard">ক্লিপবোর্ডে আইডি কপি করুন</string>
<string name="share">শেয়ার করুন</string>
<string name="editCardTitle">কার্ড সম্পাদনা করুন</string>
<string name="addCardTitle">কার্ড যোগ করুন</string>
<string name="scanCardBarcode">বারকোড স্ক্যান করুন</string>
<plurals name="deleteCardsTitle">
<item quantity="one"><xliff:g>%d</xliff:g> কার্ড মুছুন</item>
<item quantity="other"><xliff:g>%d</xliff:g> কার্ডগুলো মুছুন</item>
</plurals>
<string name="deleteTitle">কার্ড মুছুন</string>
<string name="noGiftCards">একটি কার্ড যোগ করতে + প্লাস বোতামে ক্লিক করুন বা ⋮ মেনু থেকে আমদানি করুন।</string>
</resources>

View File

@@ -4,11 +4,11 @@
<string name="cancel">Odustani</string>
<string name="unstar">Ukloni sve omiljene</string>
<string name="star">Omiljene</string>
<string name="barcodeNoBarcode">Ne postoji barkod</string>
<string name="barcodeNoBarcode">Ova kartica nema barkode</string>
<string name="barcodeType">Barcode tip</string>
<string name="note">Bilježnica</string>
<string name="storeName">Ime</string>
<string name="noMatchingGiftCards">Nema rezultata. Pokušaj promijeniti pretragu.</string>
<string name="noMatchingGiftCards">Nisam našao ništa. Pokušaj promijeniti pretragu.</string>
<string name="noGiftCards">Kliknite + Plus dugme da dodate kartu ili uvozite nešto iz menija prvo.</string>
<string name="action_add">Dodaj</string>
<string name="all">Sve</string>
@@ -34,7 +34,6 @@
<string name="barcodeId">Barcode vrijednost</string>
<string name="sameAsCardId">Isto kao i kartica</string>
<string name="setBarcodeId">Postavi vrijednost za bar kod</string>
<string name="unsupportedBarcodeType">Ovaj bar kod još nije prikazan. Ona može biti podržana u kasnijoj verziji app.</string>
<string name="wrongValueForBarcodeType">Izabrana vrijednost nije izvršna</string>
<string name="copy_to_clipboard_multiple_toast">IDs kartica kopiran u clipboard</string>
<string name="intent_import_card_from_url_share_multiple_text">Želim podijeliti karte s tobom</string>
@@ -71,9 +70,9 @@
<string name="sort_by_expiry">Sajam</string>
<string name="reverse">Rikverc</string>
<string name="sort_by">Sortiraj</string>
<string name="noCardExistsError">Nisam mogao pronaći tu karticu</string>
<string name="noCardExistsError">Nisam mogao pronaći karticu</string>
<string name="noStoreError">Nije uneseno ime</string>
<string name="card_ids_copied">Kopiran ID/ovi</string>
<string name="card_ids_copied">Kopiran ID kartice(s)</string>
<string name="noCardsMessage">Dodaj prvo kartu</string>
<string name="addCardTitle">Dodaj Kartu</string>
<string name="editCardTitle">Izmijeni Karticu</string>
@@ -84,33 +83,4 @@
<string name="confirm">Potvrdi</string>
<string name="delete">Obriši</string>
<string name="edit">Izmijeni</string>
<string name="action_search">Traži</string>
<string name="ok">OK</string>
<string name="cardId">ID kartice</string>
<string name="exportFailed">Nisam uspio izvršiti izvoz</string>
<string name="app_copyright_old">Bazirano na Loyalty Card privjesku
\ncopyright © 2016-2020 Branden Archer</string>
<string name="noGiftCardsGroup">Kreirajte kartice, i dodajte ih ovdje u grupu.</string>
<string name="importExportHelp">Backupovanje vaših podataka omogućava njihov prenos na drugi uređaj.</string>
<string name="importSuccessfulTitle">Uvezeno</string>
<string name="exportFailedTitle">Izvoz neuspješan</string>
<string name="scanCardBarcode">Skeniraj barkod</string>
<string name="cardShortcut">Prečica kartice</string>
<string name="noCardIdError">ID nije unesen</string>
<string name="failedParsingImportUriError">Nisam uspio parsirati URI za uvoz</string>
<string name="importExport">Uvoz/Izvoz</string>
<string name="exportName">Izvoz</string>
<string name="importFailedTitle">Uvoz neuspješan</string>
<string name="importFailed">Nisam uspio odraditi uvoz</string>
<string name="exportSuccessfulTitle">Izvezeno</string>
<string name="importing">Uvozim…</string>
<string name="exporting">Izvozim…</string>
<string name="exportOptionExplanation">Ovi podaci će biti zapisani na lokaciju po Vašoj želji.</string>
<string name="importOptionFilesystemTitle">Uvoz iz file sistema</string>
<string name="importOptionFilesystemExplanation">Izaberite specifični file iz file sistema.</string>
<string name="importOptionFilesystemButton">Iz file sistema</string>
<string name="importOptionApplicationTitle">Koristi drugu aplikaciju</string>
<string name="importOptionApplicationExplanation">Koristi bilo koju aplikaciju ili Vašu omiljenu aplikaciju da bi otvorili file.</string>
<string name="importOptionApplicationButton">Koristi drugu aplikaciju</string>
<string name="about">O</string>
</resources>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="groups">Grups</string>
<string name="action_add">Afegeix</string>
<string name="save">Desa</string>
<string name="edit">Edita</string>
<string name="delete">Elimina</string>
<string name="confirm">Confirma</string>
<string name="ok">D\'acord</string>
<string name="importExport">Importa/Exporta</string>
<string name="exportName">Exporta</string>
<string name="action_search">Cerca</string>
<string name="deleteTitle">Elimina la targeta</string>
<string name="welcome">Benvingut a Catima</string>
<string name="noGiftCards">Cliqueu el botó + més per afegir una targeta, o importeu-ne des del ⋮ menú.</string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Přidat</string>
<string name="noGiftCards">Klepněte na tlačítko + plus pro přidá karty nebo naimportujete karty z nabídky ⋮.</string>
<string name="noGiftCards">Klepnutím na tlačítko + plus přidáte kartu nebo ji nejprve importujete z nabídky ⋮.</string>
<string name="storeName">Název</string>
<string name="note">Poznámka</string>
<string name="cardId">ID karty</string>
@@ -29,14 +29,15 @@
<string name="exportFailed">Export nelze provést</string>
<string name="importing">Importuji…</string>
<string name="exporting">Exportuji…</string>
<string name="noExternalStoragePermissionError">Udělit oprávnění přístupu k externímu úložišti pro import nebo export dat</string>
<string name="importOptionFilesystemTitle">Import ze souborového systému</string>
<string name="importOptionFilesystemExplanation">Vyberte konkrétní soubor v úložišti.</string>
<string name="importOptionFilesystemButton">Ze souborového systému</string>
<string name="importOptionApplicationTitle">Použitím jiné aplikace</string>
<string name="importOptionApplicationExplanation">K otevření souboru použijte libovolnou aplikaci nebo svého oblíbeného správce souborů.</string>
<string name="importOptionApplicationExplanation">K otevření souboru použijte libovolnou aplikaci nebo svého oblíbeného správce souborů.</string>
<string name="importOptionApplicationButton">Použít jinou aplikaci</string>
<string name="about">O aplikaci</string>
<string name="app_license">Copyleftovaný svobodný software s licencí GPLv3+</string>
<string name="app_license">Copyleftovaný svobodný software s licencí GPLv3+</string>
<string name="about_title_fmt">O aplikaci <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Verze: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Informace o revizi: <xliff:g id="app_revision_url">%s</xliff:g></string>
@@ -48,20 +49,20 @@
<string name="moveBarcodeToTopOfScreen">Přesunout čárový kód do horní části obrazovky</string>
<string name="chooseExpiryDate">Zvolte datum vypršení platnosti</string>
<string name="never">Nikdy</string>
<string name="expiryDate">Vypršení platnosti</string>
<string name="expiryDate">Platnost</string>
<string name="editBarcode">Upravit čárový kód</string>
<string name="barcode">Kód</string>
<string name="app_resources">Svobodné zdroje třetích stran: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Svobodné knihovny třetích stran: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_copyright_old">Založeno na Loyalty Card Keychain
<string name="app_copyright_old">Na základě Loyalty Card Keychain
\ncopyright © 20162020 Branden Archer</string>
<string name="exportOptionExplanation">Data budou zapsána na místo podle vašeho výběru.</string>
<string name="failedParsingImportUriError">Nelze analyzovat importovanou URI</string>
<string name="failedParsingImportUriError">Nelze analyzovat import URI</string>
<string name="noCardExistsError">Takovou kartu nelze najít</string>
<string name="noCardsMessage">Nejprve přidejte kartu</string>
<string name="cardShortcut">Zástupce karty</string>
<string name="share">Sdílet</string>
<string name="unstar">Odebrat z oblíbených</string>
<string name="share">Podíl</string>
<string name="unstar">Odebrat z oblíbených</string>
<string name="star">Přidat do oblíbených</string>
<string name="noBarcode">Žádný čárový kód</string>
<string name="barcodeNoBarcode">Tato karta nemá čárový kód</string>
@@ -71,9 +72,9 @@
<string name="thumbnailDescription">Miniatura</string>
<string name="card_ids_copied">ID zkopírováno</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">Opravdu chcete trvale odstranit <xliff:g>%d</xliff:g> kartu\?</item>
<item quantity="few">Opravdu chcete trvale odstranit <xliff:g>%d</xliff:g> karty\?</item>
<item quantity="other">Opravdu chcete trvale odstranit <xliff:g>%d</xliff:g> karet\?</item>
<item quantity="one">Opravdu chcete <xliff:g>%d</xliff:g> kartu trvale odstranit\?</item>
<item quantity="few">Opravdu chcete <xliff:g>%d</xliff:g> karty trvale odstranit\?</item>
<item quantity="other">Opravdu chcete <xliff:g>%d</xliff:g> karet trvale odstranit\?</item>
</plurals>
<plurals name="deleteCardsTitle">
<item quantity="one">Odstranit <xliff:g>%d</xliff:g> kartu</item>
@@ -81,7 +82,7 @@
<item quantity="other">Odstranit <xliff:g>%d</xliff:g> karet</item>
</plurals>
<string name="importSuccessful">Data importována</string>
<string name="intent_import_card_from_url_share_text">Chci s Vámi sdílet kartu</string>
<string name="intent_import_card_from_url_share_text">Chci s Vámi sdílet kartu</string>
<string name="settings_disable_lockscreen_while_viewing_card">Bránit uzamykání obrazovky</string>
<string name="settings_keep_screen_on">Udržovat obrazovku zapnutou</string>
<string name="settings_max_font_size_scale">Maximální velikost písma</string>
@@ -112,7 +113,7 @@
<string name="settings_locale">Jazyk</string>
<string name="turn_flashlight_off">Vypnout světlo</string>
<string name="turn_flashlight_on">Zapnout světlo</string>
<string name="failedGeneratingShareURL">Nepodařilo se vygenerovat adresu URL pro sdílení. Nahlaste to prosím.</string>
<string name="failedGeneratingShareURL">Nepodařilo se vygenerovat sdílenou adresu URL. Nahlaste to prosím.</string>
<string name="passwordRequired">Zadejte prosím heslo</string>
<string name="no">Ne</string>
<string name="yes">Ano</string>
@@ -128,27 +129,26 @@
<string name="intent_import_card_from_url_share_multiple_text">Chci s vámi sdílet karty</string>
<string name="copy_to_clipboard_multiple_toast">ID zkopírována do schránky</string>
<string name="wrongValueForBarcodeType">Hodnota není platná pro vybraný typ čárového kódu</string>
<string name="unsupportedBarcodeType">Tento typ čárového kódu zatím nelze zobrazit. Možná bude podporován v pozdější verzi aplikace.</string>
<string name="barcodeId">Hodnota čárového kódu</string>
<string name="setBarcodeId">Nastavení hodnoty čárového kódu</string>
<string name="sameAsCardId">Stejné jako ID</string>
<string name="importVoucherVaultMessage">Vyberte k importu svůj <i>vouchervault.json</i> exportovaný z Voucher Vault.
<string name="importVoucherVaultMessage">Vyberte svůj <i>vouchervault.json</i> exportovaný z Voucher Vault, který chcete importovat.
\nVytvoříte jej tak, že nejprve stisknete tlačítko Exportovat v aplikaci Voucher Vault.</string>
<string name="importVoucherVault">Import z Voucher Vault</string>
<string name="importStocardMessage">Vyberte k importu svůj <i>***.zip</i> exportovaný z aplikace Stocard.
<string name="importVoucherVault">Import z Voucher Vault</string>
<string name="importStocardMessage">Vyberte svůj <i>***-sync.zip</i> exportovaný z aplikace Stocard, který chcete importovat.
\nZískejte ji zasláním e-mailu na adresu support@stocardapp.com s žádostí o export vašich dat.</string>
<string name="importStocard">Import ze Stocard</string>
<string name="importLoyaltyCardKeychainMessage">Vyberte k importu <i>LoyaltyCardKeychain.csv</i> exportovaný z Loyalty Card Keychain.
<string name="importLoyaltyCardKeychainMessage">Vyberte <i>LoyaltyCardKeychain.csv</i> exportovaný z Loyalty Card Keychain, který chcete importovat.
\nVytvoříte jej z nabídky Import/Export v Loyalty Card Keychain tak, že tam nejprve stisknete tlačítko Exportovat.</string>
<string name="importLoyaltyCardKeychain">Import z Loyalty Card Keychain</string>
<string name="importFidmeMessage">Vyberte k importu svůj <i>fidme-export-request-xxxxxx.zip</i> exportovaný z FidMe a poté vyberte typy čárových kódů ručně.
<string name="importLoyaltyCardKeychain">Import z Loyalty Card Keychain</string>
<string name="importFidmeMessage">Vyberte svůj <i>fidme-export-request-xxxxxx.zip</i> exportovaný z FidMe k importu a poté vyberte typy čárových kódů ručně.
\nVytvoříte jej ze svého profilu FidMe tak, že nejprve zvolíte možnost Ochrana dat a poté stisknete tlačítko Extrahovat moje data.</string>
<string name="importFidme">Import z FidMe</string>
<string name="importFidme">Import z FidMe</string>
<string name="importCatimaMessage">Vyberte <i>catima.zip</i> exportovaný z aplikace Catima, který chcete importovat.
\nVytvoříte jej z nabídky Import/Export jiné aplikace Catima tak, že v ní nejprve stisknete tlačítko Exportovat.</string>
<string name="importCatima">Import z Catima</string>
<string name="importCatima">Import z Catima</string>
<string name="accept">Přijmout</string>
<string name="privacy_policy_popup_text">Oznámení o zásadách ochrany osobních údajů (vyžadováno některými obchody s aplikacemi):
<string name="privacy_policy_popup_text">Oznámení o zásadách ochrany osobních údajů (vyžadováno některými obchody s aplikacemi):
\n
\nNejsou shromažďovány žádné údaje, což může potvrdit každý, protože naše aplikace je svobodný software.</string>
<string name="privacy_policy">Zásady soukromí</string>
@@ -161,7 +161,7 @@
<string name="errorReadingImage">Obrázek se nepodařilo přečíst</string>
<string name="noBarcodeFound">Čárový kód nenalezen</string>
<string name="groupsList">Skupiny: <xliff:g>%s</xliff:g></string>
<string name="addFromImage">Výběr obrázku z galerie</string>
<string name="addFromImage">Výběr obrázku z galerie</string>
<string name="addManually">Ruční zadání ID</string>
<string name="leaveWithoutSaveConfirmation">Ukončit bez uložení\?</string>
<string name="leaveWithoutSaveTitle">Ukončit</string>
@@ -193,7 +193,7 @@
<string name="sort_by_expiry">Vypršení</string>
<string name="sort_by_most_recently_used">Naposledy použité</string>
<string name="sort_by_name">Název</string>
<string name="swipeToSwitchImages">Přejetím přepínáte obrázky, dlouhým stisknutím otevřete obrázek v aplikaci pro galerii</string>
<string name="swipeToSwitchImages">Přejetím nebo dlouhým stisknutím přepínáte obrázky</string>
<string name="sort">Seřadit</string>
<string name="barcodeImageDescriptionWithType">Obrázek čárového kódu <xliff:g>%s</xliff:g></string>
<string name="version_history">Historie verzí</string>
@@ -203,7 +203,7 @@
<string name="on_github">na GitHubu</string>
<string name="source_repository">Úložiště zdrojů</string>
<string name="license">Licence</string>
<string name="help_translate_this_app">Pomozte s překladem této aplikace</string>
<string name="help_translate_this_app">Pomozte s překladem této aplikace</string>
<string name="report_error">Nahlásit chybu</string>
<string name="on_google_play">na Google Play</string>
<string name="exportPassword">Nastavení hesla pro ochranu exportu (volitelné)</string>
@@ -215,14 +215,14 @@
<string name="group_name_is_empty">Název skupiny nemůže být prázdný</string>
<string name="group_updated">Skupina aktualizována</string>
<string name="editGroup">Úprava skupiny: <xliff:g>%s</xliff:g></string>
<string name="noGiftCardsGroup">Zatím nemáte žádné věrnostní karty. Jakmile nějaké přidáte, můžete je zde přiřadit do skupiny.</string>
<string name="noGiftCardsGroup">Zatím nemáte žádné věrnostní karty. Jakmile nějaké přidáte, můžete je přiřadit do skupiny zde.</string>
<string name="shortcutSelectCard">Vybrat kartu</string>
<string name="action_show_details">Zobrazit detaily</string>
<string name="action_hide_details">Skrýt detaily</string>
<string name="translate_platform">na Weblate</string>
<string name="showMoreInfo">Zobrazit podrobnosti</string>
<string name="hideMoreInfo">Skrýt podrobnosti</string>
<string name="starred">S hvězdičkou</string>
<string name="starred">S hvězdičkou</string>
<string name="set_scale">Nastavení měřítka</string>
<string name="options">Volby</string>
<plurals name="balancePoints">
@@ -237,7 +237,7 @@
<string name="settings_lock_on_opening_orientation">Zamknout podle orientace použité při otevření karty</string>
<string name="archive">Archivovat</string>
<string name="unarchive">Vrátit z archivu</string>
<string name="archiveList">Archiv</string>
<string name="archiveList">Archivovat</string>
<string name="noUnarchivedCardsMessage">Nejsou žádné karty vrácené z archivu</string>
<string name="unarchived">Karta vrácena z archivu</string>
<string name="settings_card_orientation">Orientace čárového kódu</string>
@@ -259,20 +259,4 @@
<item quantity="few">Zobrazit archiv (<xliff:g>%1$d</xliff:g> karty)</item>
<item quantity="other">Zobrazit archiv (<xliff:g>%1$d</xliff:g> karet)</item>
</plurals>
<string name="barcodeLongPressMessage">V aplikaci pro galerii mohou být otevírány pouze obrázky</string>
<string name="failedToRetrieveImageFile">Nepodařilo se získat soubor obrázku</string>
<string name="cameraPermissionDeniedTitle">Nelze získat přístup k fotoaparátu</string>
<string name="importCards">Importovat karty</string>
<string name="updateBalance">Aktualizovat zůstatek</string>
<string name="currentBalanceSentence">Současný zůstatek: <xliff:g>%s</xliff:g></string>
<string name="noCameraPermissionDirectToSystemSetting">Pro skenování čárových kódů bude Catima potřebovat přístup k fotoaparátu. Klepněte zde pro změnu nastavení oprávnění.</string>
<string name="updateBalanceTitle">Kolik jste utratil\?</string>
<string name="updateBalanceHint">Zadejte výši</string>
<string name="newBalanceSentence">Nový zůstatek: <xliff:g>%s</xliff:g></string>
<string name="storageReadPermissionRequired">Pro tuto akci je potřeba oprávnění ke čtení úložiště…</string>
<string name="cameraPermissionRequired">Pro tuto akci je potřeba oprávnění k přístupu k fotoaparátu…</string>
<string name="validFromDate">Platnost od</string>
<string name="anyDate">Jakékoliv datum</string>
<string name="chooseValidFromDate">Vyberte datum počátku platnosti</string>
<string name="validFromSentence">Platnost od: <xliff:g>%s</xliff:g></string>
</resources>

View File

@@ -67,6 +67,7 @@
<string name="importOptionFilesystemExplanation">Vælg en bestemt fil fra filsystemet.</string>
<string name="importOptionFilesystemTitle">Import fra filsystem</string>
<string name="exportOptionExplanation">Dataene skrives til en placering efter eget valg.</string>
<string name="noExternalStoragePermissionError">Giv først tilladelse til ekstern lagring til at importere eller eksportere kort</string>
<string name="failedParsingImportUriError">Kunne ikke analysere import-URI\'en</string>
<string name="noCardExistsError">Kunne ikke finde kort</string>
<string name="noCardIdError">Der er ikke angivet noget kort-ID</string>

View File

@@ -36,6 +36,7 @@
<string name="exportFailed">Export konnte nicht durchgeführt werden</string>
<string name="importing">Importiere…</string>
<string name="exporting">Exportiere…</string>
<string name="noExternalStoragePermissionError">Berechtigung für den externen Speicher zum Importieren oder Exportieren von Daten erteilen</string>
<string name="importOptionFilesystemTitle">Importiere aus dem Dateisystem</string>
<string name="importOptionFilesystemExplanation">Wähle eine Datei vom Dateisystem aus.</string>
<string name="importOptionFilesystemButton">Wähle vom Dateisystem</string>
@@ -52,7 +53,7 @@
<string name="thumbnailDescription">Vorschaubild</string>
<string name="settings">Einstellungen</string>
<string name="settings_category_title_ui">Benutzeroberfläche</string>
<string name="settings_display_barcode_max_brightness">Displayhelligkeit in der Barcodeansicht erhöhen</string>
<string name="settings_display_barcode_max_brightness">Barcodeansicht aufhellen</string>
<string name="exportSuccessful">Daten exportiert</string>
<string name="importSuccessful">Daten importiert</string>
<string name="intent_import_card_from_url_share_text">Ich würde gerne diese Karte mit dir teilen</string>
@@ -105,7 +106,7 @@
<string name="expiryStateSentenceExpired">Abgelaufen: <xliff:g>%s</xliff:g></string>
<string name="expiryStateSentence">Läuft ab: <xliff:g>%s</xliff:g></string>
<string name="settings_disable_lockscreen_while_viewing_card">Sperrbildschirm verhindern</string>
<string name="settings_keep_screen_on">Bildschirm aktiv lassen</string>
<string name="settings_keep_screen_on">Bildschirm aktiv halten</string>
<string name="privacy_policy_popup_text">Hinweis zum Datenschutz (oft gefordert):
\n
\nKEINE DATEN WERDEN GESAMMELT, was jeder bestätigen kann, da unsere Anwendung eine freie Software ist.</string>
@@ -130,7 +131,6 @@
<string name="noBarcodeFound">Kein Barcode erkannt</string>
<string name="addFromImage">Bild aus der Galerie wählen</string>
<string name="settings_max_font_size_scale">Maximale Schriftgröße</string>
<string name="unsupportedBarcodeType">Dieser Barcodetyp kann noch nicht angezeigt werden. Wir hoffen das Format in einer zukünftigen Version zu unterstützen.</string>
<string name="wrongValueForBarcodeType">Der Wert ist für den gewählten Barcodetyp leider nicht gültig</string>
<string name="app_resources">Freie Ressourcen von Drittanbietern: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Freie Bibliotheken von Drittanbietern: <xliff:g id="app_libraries_list">%s</xliff:g></string>
@@ -150,7 +150,7 @@
<string name="frontImageDescription">Bild auf der Vorseite</string>
<string name="backImageDescription">Bild auf der Rückseite</string>
<string name="passwordRequired">Bitte gib das Passwort ein</string>
<string name="importStocardMessage">Wähle deinen <i>***.zip</i>-Export aus Stocard zum Importieren aus.
<string name="importStocardMessage">Wähle deinen <i>***-sync.zip</i>-Export aus Stocard zum Importieren aus.
\nSie erhalten ihn, indem du eine E-Mail an support@stocardapp.com sendest und um einen Export deiner Daten bitten.</string>
<string name="importStocard">Von Stocard importieren</string>
<string name="turn_flashlight_off">Licht ausschalten</string>
@@ -184,7 +184,7 @@
<string name="settings_theme_color">Designfarbe</string>
<string name="app_contributors">Ermöglicht durch: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="barcodeImageDescriptionWithType">Bild <xliff:g>%s</xliff:g> Barcode</string>
<string name="swipeToSwitchImages">Wischen zum Wechseln der Bilder, Halten zum Öffnen des Bildes in der Galerie</string>
<string name="swipeToSwitchImages">Wischen oder langes Drücken zum Wechseln der Bilder</string>
<string name="sort_by">Sortieren nach</string>
<string name="sort_by_balance">Kontostand</string>
<string name="sort_by_expiry">Ablaufdatum</string>
@@ -225,7 +225,7 @@
<item quantity="one"><xliff:g>%s</xliff:g> Punkt</item>
<item quantity="other"><xliff:g>%s</xliff:g> Punkte</item>
</plurals>
<string name="settings_oled_dark">Komplett schwarzer Hintergrund im dunklen Design</string>
<string name="settings_oled_dark">Rein schwarzer Hintergrund für dunkles Thema</string>
<string name="include_if_asking_support">Wenn Sie Unterstützung anfordern möchten, geben Sie bitte die folgenden Informationen an:</string>
<string name="settings_follow_system_orientation">System folgen</string>
<string name="settings_landscape_orientation">Landschaft</string>
@@ -252,20 +252,4 @@
<item quantity="other">Archiv ansehen (<xliff:g>%1$d</xliff:g> Karten)</item>
</plurals>
<string name="welcome">Willkommen bei Catima</string>
<string name="barcodeLongPressMessage">In der Galerie können nur Bilder geöffnet werden</string>
<string name="failedToRetrieveImageFile">Bilddatei konnte nicht abgerufen werden</string>
<string name="updateBalanceTitle">Wie viel haben Sie ausgegeben\?</string>
<string name="cameraPermissionDeniedTitle">Kein Zugriff auf die Kamera möglich</string>
<string name="noCameraPermissionDirectToSystemSetting">Um Strichcodes zu scannen, benötigt Catima Zugriff auf Ihre Kamera. Tippen Sie hier, um Ihre Berechtigungseinstellungen zu ändern.</string>
<string name="updateBalanceHint">Betrag eingeben</string>
<string name="importCards">Karten importieren</string>
<string name="currentBalanceSentence">Aktuelles Guthaben: <xliff:g>%s</xliff:g></string>
<string name="newBalanceSentence">Neues Guthaben: <xliff:g>%s</xliff:g></string>
<string name="updateBalance">Guthaben aktualisieren</string>
<string name="storageReadPermissionRequired">Berechtigung zum Lesen des Speichers für diese Aktion erforderlich </string>
<string name="cameraPermissionRequired">Für diese Aktion wird die Erlaubnis zum Zugriff auf die Kamera benötigt </string>
<string name="validFromDate">Gültig ab</string>
<string name="validFromSentence">Gültig ab: <xliff:g>%s</xliff:g></string>
<string name="chooseValidFromDate">Gültig-ab-Datum wählen</string>
<string name="anyDate">Beliebiges Datum</string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Προσθήκη</string>
<string name="noGiftCards">Κάντε κλικ στο + κουμπί για να προσθέσετε μία κάρτα ή προσθέστε από το ⋮ μενού.</string>
<string name="noGiftCards">Κάντε κλικ στο + κουμπί για να προσθέσετε μία κάρτα ή εισάγετε κάρτες από το ⋮ μενού.</string>
<string name="storeName">Όνομα</string>
<string name="note">Σημείωση</string>
<string name="cardId">Κωδικός Κάρτας</string>
@@ -32,6 +32,7 @@
<string name="exportFailed">Δεν ήταν δυνατή η εξαγωγή</string>
<string name="importing">Γίνεται εισαγωγή του…</string>
<string name="exporting">Γίνεται εξαγωγή του…</string>
<string name="noExternalStoragePermissionError">Εγκρίνετε την άδεια εξωτερικής αποθήκευσης για να εισάγετε ή εξάγετε δεδομένα</string>
<string name="importOptionFilesystemTitle">Εισαγωγή από το σύστημα αρχείων</string>
<string name="importOptionFilesystemExplanation">Επιλέξτε ένα συγκεκριμένο αρχείο από το σύστημα αρχείων.</string>
<string name="importOptionFilesystemButton">Από το σύστημα αρχείων</string>
@@ -84,7 +85,7 @@
<string name="expiryDate">Ημερομηνία λήξης</string>
<string name="settings_keep_screen_on">Κράτα την οθόνη ανοιχτή</string>
<string name="leaveWithoutSaveTitle">Έξοδος</string>
<string name="swipeToSwitchImages">Σύρετε για να αλλάξετε εικόνες, κρατήστε για να ανοίξετε μια εικόνα στην συλλογή</string>
<string name="swipeToSwitchImages">Σύρετε ή πατήστε παρατεταμένα για να αλλάξετε εικόνες</string>
<string name="sort_by_balance">Υπόλοιπο</string>
<string name="reverse">... σε αντίθετη σειρά</string>
<string name="version_history">Ιστορικό έκδοσης</string>
@@ -140,14 +141,13 @@
</plurals>
<string name="importCatimaMessage">Επιλέξτε την <i>catima.zip</i> εξαγωγή από το Catima για εισαγωγή
\nΔημιουργήστε το από το μενού Εισαγωγής/Εξαγωγής μιας άλλης εφαρμογής Catima κάνοντας εξαγωγή εκεί πρώτα.</string>
<string name="importStocardMessage">Επιλέξτε την <i>***.zip</i> εξαγωγή από το Stocard για εισαγωγή.
<string name="importStocardMessage">Επιλέξτε την <i>***-sync.zip</i> εξαγωγή από το Stocard για εισαγωγή.
\nΠάρτε το στέλνοντας email στο: support@stocardapp.com ζητώντας μια εξαγωγή αρχείων των δεδομένων σας.</string>
<string name="intent_import_card_from_url_share_multiple_text">Θέλω να μοιραστώ μερικές κάρτες μαζί σου</string>
<string name="editGroup">Επεξεργασία Ομάδας: <xliff:g>%s</xliff:g></string>
<string name="setFrontImage">Επιλογή μπροστινής εικόνας</string>
<string name="importVoucherVaultMessage">Επιλέξτε την <i>vouchervault.json</i> εξαγωγή από το Voucher Vault για εισαγωγή.
\nΔημιουργήστε το επιλέγοντας Εξαγωγή στο Voucher Vault.</string>
<string name="unsupportedBarcodeType">Ο τύπος γραμμοκώδικα δεν γίνεται να εμφανιστεί ακόμα. Μπορεί να υποστηρίζεται σε μια μελλοντική έκδοση της εφαρμογής.</string>
<string name="frontImageDescription">Μπροστινή εικόνα</string>
<string name="photos">Φωτογραφίες</string>
<string name="backImageDescription">Οπίσθια εικόνα</string>
@@ -244,22 +244,4 @@
<string name="unarchive">Αφαίρεση από το αρχείο</string>
<string name="archiveList">Αρχείο</string>
<string name="noUnarchivedCardsMessage">Δεν υπάρχουν αρχειοθετημένες κάρτες</string>
<string name="updateBalanceTitle">Πόσα ξοδέψατε;</string>
<string name="cameraPermissionDeniedTitle">Αδύνατη η πρόσβαση στην κάμερα</string>
<string name="failedToRetrieveImageFile">Αποτυχία ανάκτησης αρχείου εικόνας</string>
<string name="previousCard">Προηγούμενη</string>
<string name="nextCard">Επόμενη</string>
<string name="updateBalance">Ενημέρωση υπολοίπου</string>
<string name="barcodeLongPressMessage">Μόνο εικόνες μπορούν να ανοιχτούν στην εφαρμογή φωτογραφιών</string>
<string name="noCameraPermissionDirectToSystemSetting">Για να σκανάρετε γραμμοκώδικες, θα χρειαστεί πρόσβαση στην κάμερα από το Catima. Πατήστε εδώ για να δώσετε πρόσβαση.</string>
<plurals name="viewArchivedCardsWithCount">
<item quantity="one">Προβολή αρχείου (<xliff:g>%1$d</xliff:g> κάρτας)</item>
<item quantity="other">Προβολή αρχείου (<xliff:g>%1$d</xliff:g> καρτών)</item>
</plurals>
<string name="importCards">Εισαγωγή καρτών</string>
<string name="updateBalanceHint">Εισάγετε ποσό</string>
<string name="currentBalanceSentence">Τωρινό υπόλοιπο <xliff:g>%s</xliff:g></string>
<string name="newBalanceSentence">Νέο υπόλοιπο: <xliff:g>%s</xliff:g></string>
<string name="failedToOpenUrl">Εγκαταστήστε έναν περιηγητή πρώτα</string>
<string name="welcome">Καλώς ήρθατε στο Catima</string>
</resources>

View File

@@ -63,6 +63,7 @@
<string name="importOptionApplicationTitle">Uzi alian app</string>
<string name="importOptionFilesystemExplanation">Elektu specifa dosiero de la dosiersistemo.</string>
<string name="exportOptionExplanation">La datumoj estos skribita al loko de via elekto.</string>
<string name="noExternalStoragePermissionError">Grant ekstera stokado permeso de importado aŭ eksportado kartoj unua</string>
<string name="exportFailed">Ne povis eksporti kartoj</string>
<string name="importFailed">Ne povis importi kartoj</string>
<string name="importExportHelp">Subtenanta supre vian kartoj permesas vin movi ilin al alia aparato.</string>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="action_add">Añadir</string>
<string name="noGiftCards">Haz clic en el botón + para añadir una tarjeta, o importa desde el menú ⋮.</string>
<string name="noGiftCards">Pulse el botón «+» para añadir una tarjeta, o importe algunas del menú ⋮.</string>
<string name="storeName">Nombre</string>
<string name="note">Nota</string>
<string name="cardId">ID. de tarjeta</string>
@@ -32,6 +32,7 @@
<string name="exportFailed">No se han podido exportar</string>
<string name="importing">Importando…</string>
<string name="exporting">Exportando…</string>
<string name="noExternalStoragePermissionError">Otorgar permiso de almacenamiento para importar o exportar datos</string>
<string name="importOptionFilesystemTitle">Importar desde el sistema de archivos</string>
<string name="importOptionFilesystemExplanation">Elegir un archivo concreto del sistema de archivos.</string>
<string name="importOptionFilesystemButton">Desde el sistema de archivos</string>
@@ -108,7 +109,7 @@
\nCréalo primero desde tu perfil de FidMe eligiendo Protección de datos y pulsa Extraer mis datos.</string>
<string name="importLoyaltyCardKeychainMessage">Seleccione su <i>LoyaltyCardKeychain.csv</i> exportado desde Loyalty Card Keychain para importarlo.
\nCréalo primero desde el menú Importar/Exportar en Loyalty Card Keychain pulsando Exportar desde allí.</string>
<string name="importStocardMessage">Seleccione su exportación <i>*.zip</i> de Stocard para importarla.
<string name="importStocardMessage">Seleccione su exportación <i>*-sync.zip</i> de Stocard para importarla.
\nConsígalo enviando un correo electrónico a support@stocardapp.com solicitando una exportación de sus datos.</string>
<string name="importVoucherVaultMessage">Seleccione su <i>vouchervault.json</i> exportado desde Voucher Vault para importarlo.
\nCréalo pulsando primero Exportar en Voucher Vault.</string>
@@ -131,7 +132,6 @@
<string name="frontImageDescription">Imagen frontal</string>
<string name="copy_to_clipboard_multiple_toast">Códigos copiados al portapapeles</string>
<string name="wrongValueForBarcodeType">El valor no es válido para el tipo de código de barras seleccionado</string>
<string name="unsupportedBarcodeType">Este tipo de código de barras todavía no se puede visualizar. Es posible que se admita en una futura versión de la aplicación.</string>
<string name="setBarcodeId">Establecer valor de código de barra</string>
<string name="sameAsCardId">Igual que el código</string>
<string name="barcodeId">Valor de código de barra</string>
@@ -145,7 +145,7 @@
\n
\nNINGÚN DATO SE RECOPILA, cualquiera puede confirmar ya que nuestra aplicación es software libre.</string>
<string name="privacy_policy">Política de privacidad</string>
<string name="app_loyalty_card_keychain">Llavero con tarjeta de fidelización</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="chooseImportType">Importar datos de</string>
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> no tendría un saldo válido.</string>
<string name="currency">Moneda</string>
@@ -157,9 +157,8 @@
<string name="settings_disable_lockscreen_while_viewing_card">Evitar el bloqueo de pantalla</string>
<string name="settings_keep_screen_on">Mantener pantalla encendida</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> seleccionada</item>
<item quantity="many"><xliff:g>%d</xliff:g> seleccionadas</item>
<item quantity="other"><xliff:g>%d</xliff:g> seleccionadas</item>
<item quantity="one"><xliff:g>%d</xliff:g> tarjeta seleccionada</item>
<item quantity="other"><xliff:g>%d</xliff:g> tarjetas seleccionadas</item>
</plurals>
<string name="deleteTitle">Eliminar la tarjeta</string>
<string name="deleteConfirmation">¿Quiere eliminar permanentemente esta tarjeta\?</string>
@@ -225,7 +224,7 @@
<string name="selectColor">Seleccione el color</string>
<string name="setIcon">Establecer icono</string>
<string name="sort">Ordenar</string>
<string name="swipeToSwitchImages">Desliza para cambiar de imagen, mantén presionado para abrir la imagen en la aplicación de la galería</string>
<string name="swipeToSwitchImages">Desliza el dedo o haz una pulsación larga para cambiar de imagen</string>
<string name="sort_by">Ordenar por</string>
<string name="rate_this_app">Califica esta aplicación</string>
<string name="options">Opciones</string>
@@ -258,21 +257,4 @@
<item quantity="many"><xliff:g>%1$d</xliff:g> tarjetas (archivadas)<xliff:g id="archivedCount">%2$d</xliff:g></item>
<item quantity="other"><xliff:g>%1$d</xliff:g> tarjetas (archivadas)<xliff:g id="archivedCount">%2$d</xliff:g></item>
</plurals>
<string name="starred">Estrellado</string>
<string name="failedToRetrieveImageFile">No se pudo recuperar el archivo de imagen</string>
<string name="barcodeLongPressMessage">Solo se pueden abrir imágenes en la aplicación de galería</string>
<string name="updateBalanceTitle">¿Cuánto has gastado\?</string>
<string name="currentBalanceSentence">Saldo actual: <xliff:g>%s</xliff:g></string>
<string name="noCameraPermissionDirectToSystemSetting">Para escanear códigos de barras, Catima necesitará acceso a su cámara. Toque aquí para cambiar la configuración de sus permisos.</string>
<string name="updateBalanceHint">Introduzca el importe</string>
<string name="newBalanceSentence">Nuevo balance: <xliff:g>%s</xliff:g></string>
<string name="cameraPermissionDeniedTitle">No se pudo acceder a la cámara</string>
<string name="updateBalance">Actualizar el balance</string>
<string name="importCards">Importar tarjetas</string>
<string name="storageReadPermissionRequired">Se necesita permiso para leer el almacenamiento para esta acción…</string>
<string name="cameraPermissionRequired">Se necesita permiso para acceder a la cámara para esta acción…</string>
<string name="anyDate">Cualquier fecha</string>
<string name="validFromDate">Válido desde</string>
<string name="chooseValidFromDate">Elija una fecha válida desde</string>
<string name="validFromSentence">Válido desde: <xliff:g>%s</xliff:g></string>
</resources>

View File

@@ -1,27 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="noExternalStoragePermissionError">Salli käyttöoikeus ulkoisen tallennustilan käyttöön voidaksesi tuoda tai viedä tietoja</string>
<string name="no">Ei</string>
<string name="yes">Kyllä</string>
<string name="updateBarcodeQuestionText">Vaihdoit ID-tunnuksen. Haluatko päivittää myös viivakoodin käyttämään samaa arvoa\?</string>
<string name="updateBarcodeQuestionTitle">Päivitetäänkö viivakoodin arvo\?</string>
<string name="updateBarcodeQuestionTitle">Päivitä viivakoodin arvo\?</string>
<string name="intent_import_card_from_url_share_multiple_text">Haluan jakaa joitain kortteja kanssasi</string>
<string name="copy_to_clipboard_multiple_toast">ID-tunnukset kopioitu leikepöydälle</string>
<string name="wrongValueForBarcodeType">Arvo ei ole kelvollinen valitulle viivakoodityypille</string>
<string name="unsupportedBarcodeType">Tätä viivakoodityyppiä ei voi vielä näyttää. Sitä saatetaan tukea sovelluksen myöhemmässä versiossa.</string>
<string name="setBarcodeId">Aseta viivakoodin arvo</string>
<string name="sameAsCardId">Sama kuin ID-tunnus</string>
<string name="barcodeId">Viivakoodin arvo</string>
<string name="importVoucherVaultMessage">Valitse tuotava <i>vouchervault.json</i>-vienti Voucher Vaultista.
\nLuo se painamalla ensin Vie Voucher Vaultissa.</string>
<string name="importVoucherVault">Tuo Voucher Vault -varmuuskopiotiedostosta</string>
<string name="importVoucherVault">Tuo Voucher Vault varmuuskopiotiedostosta</string>
<string name="importLoyaltyCardKeychainMessage">Valitse tuotava <i>LoyaltyCardKeychain.csv</i>-vientitiedostosi Loyalty Card Keychainista.
\nTai luo se Loyalty Card Keychainin Import/Export-valikosta painamalla ensin Export (Vie).</string>
<string name="importLoyaltyCardKeychain">Tuo Loyalty Card Keychain varmuuskopiotiedostosta</string>
<string name="importFidmeMessage">Valitse tuotava <i>fidme-export-request-xxxxxxxx.zip</i>-vientitiedostosi FidMe:stä ja valitse viivakoodityypit manuaalisesti sen jälkeen.
\nLuo se FidMe-profiilistasi valitsemalla Tietosuoja ja painamalla ensin Extract my data.</string>
<string name="importFidme">Tuo FidMe-varmuuskopiotiedostosta</string>
<string name="importFidme">Tuo FidMe varmuuskopiotiedostosta</string>
<string name="importCatimaMessage">Valitse tuotava <i>catima.zip</i>-vientitiedostosi Catimasta.
\nLuo se Catima-sovelluksen Tuo/vie-valikosta painamalla siellä ensin Vie.</string>
\nLuo se Catima-sovelluksen Tuo/Vie-valikosta painamalla siellä ensin Vie.</string>
<string name="importCatima">Tuo Catima varmuuskopiotiedostosta</string>
<string name="accept">Hyväksy</string>
<string name="privacy_policy_popup_text">Tietosuojaseloste (joidenkin sovelluskauppojen vaatimus):
@@ -50,18 +50,18 @@
<string name="groupsList">Ryhmät: <xliff:g>%s</xliff:g></string>
<string name="addFromImage">Valitse kuva galleriasta</string>
<string name="addManually">Anna ID-tunnus manuaalisesti</string>
<string name="leaveWithoutSaveConfirmation">Poistutaanko tallentamatta\?</string>
<string name="leaveWithoutSaveConfirmation">Poistu tallentamatta\?</string>
<string name="leaveWithoutSaveTitle">Poistu</string>
<string name="moveDown">Siirrä alaspäin</string>
<string name="moveUp">Siirrä ylöspäin</string>
<string name="failedOpeningFileManager">Asenna ensin tiedostonhallintaohjelma.</string>
<string name="deleteConfirmationGroup">Poistetaanko ryhmä\?</string>
<string name="deleteConfirmationGroup">Poista ryhmä\?</string>
<string name="all">Kaikki</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> kortti</item>
<item quantity="other"><xliff:g>%d</xliff:g> korttia</item>
<item quantity="other"><xliff:g>%d</xliff:g> kortit</item>
</plurals>
<string name="noGroups">Napsauta plus-painiketta (+) lisätäksesi ryhmät luokittelua varten.</string>
<string name="noGroups">Napsauta + plus-painiketta lisätäksesi ryhmät luokittelua varten.</string>
<string name="groups">Ryhmät</string>
<string name="enter_group_name">Anna ryhmän nimi</string>
<string name="exportSuccessful">Tiedot viety</string>
@@ -77,18 +77,18 @@
<string name="settings_theme">Teema</string>
<string name="settings_category_title_ui">Käyttöliittymä</string>
<string name="settings">Asetukset</string>
<string name="starImage">Suosikkitähti</string>
<string name="starImage">Suosikki tähti</string>
<string name="thumbnailDescription">Pienoiskuva</string>
<string name="copy_to_clipboard_toast">ID-tunnus kopioitu leikepöydälle</string>
<string name="enterBarcodeInstructions">Syötä ID-tunnus ja valitse sen viivakoodityyppi, tai valitse \"Ei viivakoodia\".</string>
<string name="selectBarcodeTitle">Valitse viivakoodi</string>
<string name="app_resources">Vapaat kolmannen osapuolen resurssit: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Vapaat kolmannen osapuolen kirjastot: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_libraries">Kolmannen osapuolen Libre-kirjastot: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_revision_fmt">Muutostiedot: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="debug_version_fmt">Versio: <xliff:g id="version">%s</xliff:g></string>
<string name="about_title_fmt">Tietoja <xliff:g id="app_name">%s</xliff:g></string>
<string name="app_license">Copyleft (käyttäjänoikeus) - vapaa ohjelmisto, lisenssi GPLv3+</string>
<string name="app_copyright_old">Perustuu Loyalty Card Keychain -sovellukseen
<string name="app_copyright_old">Perustuu Loyalty Card Keychain sovellukseen
\ntekijänoikeus © 20162020 Branden Archer</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">Tekijänoikeus © 2019<xliff:g>%d</xliff:g> Sylvia van Os</string>
<string name="about">Tietoja</string>
@@ -109,7 +109,7 @@
<string name="importSuccessfulTitle">Tuonti valmis</string>
<string name="importExportHelp">Tietojesi varmuuskopiointi mahdollistaa niiden siirtämisen toiseen laitteeseen.</string>
<string name="exportName">Vie</string>
<string name="importExport">Tuo/vie</string>
<string name="importExport">Tuo/Vie</string>
<string name="failedParsingImportUriError">Tuonnin URI: n jäsentäminen epäonnistui</string>
<string name="noCardExistsError">Tätä korttia ei löytynyt</string>
<string name="noCardIdError">ID-tunnusta ei annettu</string>
@@ -138,7 +138,7 @@
<string name="note">Lisätieto</string>
<string name="storeName">Nimi</string>
<string name="noMatchingGiftCards">Ei hakutuloksia, kokeile toisella hakutermillä.</string>
<string name="noGiftCards">Lisää kortti napsauttamalla plus-painiketta (+), tai mene ⋮-valikkoon tuodaksesi.</string>
<string name="noGiftCards">Lisää kortti napsauttamalla + plus-painiketta, tai mene ⋮ valikkoon tuodaksesi varmuuskopiosta.</string>
<string name="action_add">Lisää</string>
<string name="action_search">Hae</string>
<string name="takePhoto">Ota valokuva</string>
@@ -148,32 +148,32 @@
<string name="photos">Valokuvat</string>
<string name="backImageDescription">Takakuva</string>
<string name="frontImageDescription">Etukuva</string>
<string name="deleteConfirmation">Poistetaanko tämä kortti\?</string>
<string name="deleteConfirmation">Poista tämä kortti\?</string>
<string name="deleteTitle">Poista kortti</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">Poistetaanko tämä <xliff:g>%d</xliff:g> kortti pysyvästi\?</item>
<item quantity="other">Poistetaanko nämä <xliff:g>%d</xliff:g> korttia pysyvästi\?</item>
<item quantity="one">Poista tämä <xliff:g>%d</xliff:g> kortti pysyvästi\?</item>
<item quantity="other">Poista tämä <xliff:g>%d</xliff:g> kortit pysyvästi\?</item>
</plurals>
<plurals name="deleteCardsTitle">
<item quantity="one">Poista <xliff:g>%d</xliff:g>kortti</item>
<item quantity="other">Poista <xliff:g>%d</xliff:g>korttia</item>
<item quantity="other">Poista <xliff:g>%d</xliff:g>kortit</item>
</plurals>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> valittu</item>
<item quantity="other"><xliff:g>%d</xliff:g> valittu</item>
<item quantity="other"><xliff:g>%d</xliff:g> valitut</item>
</plurals>
<string name="importStocard">Tuo Stocardista</string>
<string name="importStocardMessage">Valitse tuotava <i>***.zip</i>-vienti Stocardista.
<string name="importStocardMessage">Valitse tuotava <i>***-sync.zip</i>-vienti Stocardista.
\nHanki se lähettämällä sähköpostia osoitteeseen support@stocardapp.com ja pyytämällä tietojesi vientiä.</string>
<string name="passwordRequired">Ole hyvä ja syötä salasana</string>
<string name="failedGeneratingShareURL">Jaettavaa URL-osoitetta ei voitu luoda. Ilmoita tästä.</string>
<string name="turn_flashlight_on">ytä taskulamppua</string>
<string name="failedGeneratingShareURL">Ei pystynyt luomaan jaettavaa URL-osoitetta. Ilmoita tästä.</string>
<string name="turn_flashlight_on">Sytytä taskulamppu</string>
<string name="turn_flashlight_off">Sammuta salamavalo</string>
<string name="app_contributors">Mahdollistanut: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="settings_brown_theme">Ruskea</string>
<string name="settings_grey_theme">Harmaa</string>
<string name="settings_green_theme">Vihreä</string>
<string name="settings_sky_blue_theme">Taivaansininen</string>
<string name="settings_sky_blue_theme">Taivaan sininen</string>
<string name="settings_blue_theme">Siniset</string>
<string name="settings_violet_theme">Violetti</string>
<string name="settings_magenta_theme">Magenta</string>
@@ -194,7 +194,7 @@
</plurals>
<string name="settings_oled_dark">Musta tausta tummalle teemalle</string>
<string name="setIcon">Aseta kuvake</string>
<string name="swipeToSwitchImages">Vaihda kuvia pyyhkäisemällä, paina kuvaa pitkään avataksesi galleriasovelluksessa</string>
<string name="swipeToSwitchImages">Vaihda kuvia pyyhkäisemällä tai painamalla pitkään</string>
<string name="sort_by_name">Nimi</string>
<string name="sort_by_most_recently_used">Viimeksi käytetyt</string>
<string name="sort_by_expiry">Viimeinen voimassaoloaika</string>
@@ -226,10 +226,10 @@
<string name="selectColor">Valitse väri</string>
<string name="rate_this_app">Arvostele tämä sovellus</string>
<string name="noGiftCardsGroup">Lisää kortteja ja lisää ne ryhmään täällä.</string>
<string name="barcodeImageDescriptionWithType">Kuva <xliff:g>%s</xliff:g> viivakoodi</string>
<string name="barcodeImageDescriptionWithType">Image <xliff:g>%s</xliff:g> viivakoodi</string>
<string name="settings_follow_system_orientation">Seuraa järjestelmää</string>
<string name="settings_portrait_orientation">Pysty</string>
<string name="settings_landscape_orientation">Vaaka</string>
<string name="settings_portrait_orientation">Muotokuva</string>
<string name="settings_landscape_orientation">Maisema</string>
<string name="unarchived">Kortti on poistettu arkistosta</string>
<string name="noUnarchivedCardsMessage">Arkistoimattomia kortteja ei ole olemassa</string>
<string name="unarchive">Poista arkistosta</string>
@@ -241,7 +241,7 @@
<item quantity="one"><xliff:g>%1$d</xliff:g> kortti (<xliff:g id="archivedCount">%2$d</xliff:g> arkistoitu)</item>
<item quantity="other"><xliff:g>%1$d</xliff:g> korttia (<xliff:g id="archivedCount">%2$d</xliff:g> arkistoitu)</item>
</plurals>
<string name="duplicateCard">Kaksoiskappale</string>
<string name="duplicateCard">Duplikaatti</string>
<string name="previousCard">Edellinen</string>
<string name="nextCard">Seuraava</string>
<string name="failedToOpenUrl">Asenna ensin verkkoselain</string>
@@ -250,22 +250,4 @@
<item quantity="one">Näytä arkisto (<xliff:g>%1$d</xliff:g> kortti)</item>
<item quantity="other">Näytä arkisto (<xliff:g>%1$d</xliff:g> korttia)</item>
</plurals>
<string name="updateBalanceTitle">Kuinka paljon kulutit\?</string>
<string name="updateBalanceHint">Syötä summa</string>
<string name="barcodeLongPressMessage">Vain kuvia on mahdollista avata galleriasovelluksessa</string>
<string name="archive">Arkistoi</string>
<string name="importCards">Tuo kortteja</string>
<string name="failedToRetrieveImageFile">Kuvatiedoston noutaminen epäonnistui</string>
<string name="currentBalanceSentence">Nykyinen saldo: <xliff:g>%s</xliff:g></string>
<string name="newBalanceSentence">Uusi saldo: <xliff:g>%s</xliff:g></string>
<string name="cameraPermissionDeniedTitle">Ei pääsyä kameraan</string>
<string name="settings_lock_on_opening_orientation">Lukitse suunta, kun korttia avataan</string>
<string name="noCameraPermissionDirectToSystemSetting">Viivakoodien lukeminen vaatii, että Catimalla on käyttöoikeus kameraan. Napauta tästä muuttaaksesi oikeuksia.</string>
<string name="updateBalance">Päivitä saldo</string>
<string name="cameraPermissionRequired">Tämä toiminto vaatii oikeuden käyttää kameraa…</string>
<string name="storageReadPermissionRequired">Tämä toiminto vaatii oikeuden lukea tallennustilaa…</string>
<string name="validFromDate">Kelvollinen alkaen</string>
<string name="anyDate">Mikä tahansa päivämäärä</string>
<string name="chooseValidFromDate">Valitse kelvollinen päivämäärä</string>
<string name="validFromSentence">Kelvollinen alkaen: <xliff:g>%s</xliff:g></string>
</resources>

View File

@@ -32,6 +32,7 @@
<string name="exportFailed">Impossible deffectuer lexportation</string>
<string name="importing">Import …</string>
<string name="exporting">Export …</string>
<string name="noExternalStoragePermissionError">Accorder au stockage externe lautorisation dimporter ou dexporter des données</string>
<string name="importOptionFilesystemTitle">Importer depuis le système de fichiers</string>
<string name="importOptionFilesystemExplanation">Choisissez le fichier à importer.</string>
<string name="importOptionFilesystemButton">Système de fichiers</string>
@@ -130,7 +131,6 @@
<string name="sameAsCardId">Identique à lidentifiant</string>
<string name="barcodeId">Valeur du code-barres</string>
<string name="settings_max_font_size_scale">Taille max. de la police</string>
<string name="unsupportedBarcodeType">Ce type de code-barres ne peut pas encore être affiché. Il sera peut-être pris en charge dans une version ultérieure de lapplication.</string>
<string name="wrongValueForBarcodeType">La valeur nest pas valide pour le type de code-barres sélectionné</string>
<string name="app_resources">Ressources tierces libres : <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="app_libraries">Bibliothèques tierces libres : <xliff:g id="app_libraries_list">%s</xliff:g></string>
@@ -150,7 +150,7 @@
<string name="backImageDescription">Image du verso</string>
<string name="frontImageDescription">Image du recto</string>
<string name="passwordRequired">Veuillez entrer le mot de passe</string>
<string name="importStocardMessage">Sélectionnez votre exportation <i>***.zip</i> de Stocard pour limporter.
<string name="importStocardMessage">Sélectionnez votre exportation <i>***-sync.zip</i> de Stocard pour limporter.
\nVous pouvez lobtenir en envoyant un courriel à support@stocardapp.com pour demander une exportation de vos données.</string>
<string name="importStocard">Importer depuis Stocard</string>
<string name="turn_flashlight_off">Éteindre la lampe de poche</string>
@@ -188,7 +188,7 @@
<string name="app_contributors">Rendu possible par : <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="noGroupCards">Ce groupe est vide</string>
<string name="barcodeImageDescriptionWithType">Image <xliff:g>%s</xliff:g> code-barres</string>
<string name="swipeToSwitchImages">Balayez pour changer dimage, maintenez appuyé pour ouvrir limage dans la galerie</string>
<string name="swipeToSwitchImages">Balayez ou appuyez longuement pour changer dimage</string>
<string name="sort">Trier</string>
<string name="sort_by">Trier par</string>
<string name="reverse"> dans lordre inverse</string>
@@ -259,20 +259,4 @@
<item quantity="other">Voir les archives (<xliff:g>%1$d</xliff:g> cartes)</item>
</plurals>
<string name="welcome">Bienvenue dans Catima</string>
<string name="barcodeLongPressMessage">Seules les images peuvent être ouvertes dans lapplication galerie</string>
<string name="failedToRetrieveImageFile">Impossible de récupérer le fichier image</string>
<string name="currentBalanceSentence">Solde actuel : <xliff:g>%s</xliff:g></string>
<string name="updateBalanceHint">Entrez le montant</string>
<string name="cameraPermissionDeniedTitle">Impossible d\'accéder à la caméra</string>
<string name="noCameraPermissionDirectToSystemSetting">Pour scanner les codes-barres, Catima doit avoir accès à votre caméra. Tapez ici pour modifier vos paramètres d\'autorisation.</string>
<string name="updateBalance">Mettre à jour le solde</string>
<string name="updateBalanceTitle">Combien avez-vous dépensé \?</string>
<string name="newBalanceSentence">Nouveau solde : <xliff:g>%s</xliff:g></string>
<string name="importCards">Importer des cartes</string>
<string name="storageReadPermissionRequired">L\'autorisation de lire le stockage est nécessaire pour cette action…</string>
<string name="cameraPermissionRequired">L\'autorisation d\'accéder à la caméra est nécessaire pour cette action…</string>
<string name="validFromDate">Valide à partir de</string>
<string name="validFromSentence">Valide à partir de : <xliff:g>%s</xliff:g></string>
<string name="anyDate">Nimporte quelle date</string>
<string name="chooseValidFromDate">Choisissez la date de début de validité</string>
</resources>

View File

@@ -1,153 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="storeName">नाम</string>
<string name="note">नोट</string>
<string name="cardId">कार्ड आईडी</string>
<string name="star">पसंदीदा में जोड़ें</string>
<string name="unstar">पसंदीदा से निकाले</string>
<string name="cancel">रद्द करना</string>
<string name="save">बचाना</string>
<string name="delete">मिटाना</string>
<string name="deleteConfirmation">इस कार्ड को स्थायी रूप से हटाएं\?</string>
<plurals name="deleteCardsConfirmation">
<item quantity="one">इस <xliff:g>%d</xliff:g> कार्ड को स्थायी रूप से हटाएं\?</item>
<item quantity="other">ये <xliff:g>%d</xliff:g> कार्ड स्थायी रूप से हटाएं\?</item>
</plurals>
<string name="ok">ठीक</string>
<string name="share">बाँटें</string>
<string name="sendLabel">भेजें…</string>
<string name="editCardTitle">कार्ड संपादित करें</string>
<string name="addCardTitle">कार्ड जोड़ें</string>
<string name="noCardsMessage">पहले एक कार्ड जोड़ें</string>
<string name="card_ids_copied">कॉपी की गई आईडी</string>
<string name="noStoreError">कोई नाम दर्ज नहीं</string>
<string name="noCardIdError">कोई पहचान पत्र (आईडी दर्ज नहीं) की गई</string>
<string name="noCardExistsError">वह कार्ड नहीं मिला</string>
<string name="importExport">आयात / निर्यात</string>
<string name="exportName">निर्यात</string>
<string name="importSuccessfulTitle">आयातित</string>
<string name="importFailed">आयात नहीं कर सका</string>
<string name="action_search">खोज</string>
<string name="noGiftCardsGroup">कुछ कार्ड बनाएँ, और फिर उन्हें यहाँ समूह करें।</string>
<string name="noMatchingGiftCards">कोई परिणाम नहीं। अपनी खोज बदलने का प्रयास करें।</string>
<string name="deleteTitle">कार्ड हटाएं</string>
<plurals name="deleteCardsTitle">
<item quantity="one">कार्ड <xliff:g>%d</xliff:g> हटाएं</item>
<item quantity="other"><xliff:g>%d</xliff:g> इन कार्डों को हटाएं</item>
</plurals>
<string name="importFailedTitle">आयात विफल</string>
<string name="exportOptionExplanation">डाटा आपके मनचाहे स्थान पर लिखा जाएगा.</string>
<string name="importOptionFilesystemExplanation">फाईल सिस्टम से एक फाईल चुनें.</string>
<string name="app_copyright_old">लोयलटी कार्ड कीचैंन पर आधारित
\nकौपीराईट © 20162020 ब्रांडन आर्चर</string>
<string name="action_add">जोड़</string>
<string name="edit">बदलें</string>
<string name="confirm">पक्का करें</string>
<string name="failedParsingImportUriError">दी गई URL का विक्ष्लेषण नही हो सका</string>
<string name="exportSuccessfulTitle">निर्यात सफल</string>
<string name="exportFailedTitle">निर्यात असफल</string>
<string name="exportFailed">निर्यात नही हो सका</string>
<string name="importing">आयात चल रहा है…</string>
<string name="exporting">निर्यात चल रहा है…</string>
<string name="importOptionFilesystemTitle">फाईल सिस्टम से आयात करें</string>
<string name="importOptionFilesystemButton">फाईल सिस्टम से</string>
<string name="importOptionApplicationTitle">दूसरा एप्प इस्तमाल करें</string>
<string name="importOptionApplicationButton">दूसरा एप्प इस्तमाल करें</string>
<string name="about">बारे में</string>
<string name="starImage">पसंदीदा स्टार</string>
<string name="settings_light_theme">उजालित</string>
<string name="settings_dark_theme">अँधेरा</string>
<string name="settings">व्यवस्थाएं (सेटिंगें)</string>
<string name="settings_system_theme">तंत्र</string>
<string name="settings_card_orientation">बारकोड का अनुकूलन(ओरिएंटेशन)</string>
<string name="settings_landscape_orientation">आयत रूप(लैंडस्केप)</string>
<string name="settings_follow_system_orientation">सिस्टम का पालन करें</string>
<string name="settings_portrait_orientation">पोट्रैट</string>
<string name="settings_max_font_size_scale">अक्षर का अधिकतम नाप</string>
<string name="settings_display_barcode_max_brightness">बारकोड का दृश्य उजालित(ब्राइट) करें</string>
<string name="settings_keep_screen_on">स्क्रीन को चालू रखें</string>
<string name="cameraPermissionDeniedTitle">हम कैमरा तक पहुँच नहीं सकते</string>
<string name="noCameraPermissionDirectToSystemSetting">बारकोड स्कैन करने के लिए,को आपके कैमरा का इस्तेमाल करना होगा। इजाज़त कि व्यवस्था (सेटिंग) बदलने के लिए यहाँ दबायें।</string>
<string name="importOptionApplicationExplanation">फाइल खोलने के लिए कोई भी ऐप या अपना पसंदिता फाइल मैनेजर का इस्तेमाल करे।</string>
<string name="settings_category_title_ui">उपयोक्‍ता अंतरापृष्‍ठ (यूजर इंटरफ़ेस)</string>
<string name="settings_theme">विषय</string>
<string name="barcodeType">बारकोड का प्रकार</string>
<string name="barcodeNoBarcode">कोई बारकोड नहीं है</string>
<string name="noBarcode">बारकोड नहीं है</string>
<string name="copy_to_clipboard">पहचान पत्र(आई डी) को क्लिपबोर्ड में कॉपी करें</string>
<string name="scanCardBarcode">बारकोड स्कैन करें</string>
<string name="cardShortcut">कार्ड का सरल उपाय (शॉर्टकट)</string>
<string name="noGiftCards">\"+\" बटन दबाके कार्ड जोड़ें वा मेन्यू से कार्ड आयात करें</string>
<string name="importExportHelp">तथ्य (डाटा) को बैकअप करना हमें उसे दूसरे डिवाइस में भेजने देता है|</string>
<string name="barcodeImageDescriptionWithType"><xliff:g>%s</xliff:g> का बारकोड</string>
<string name="settings_disable_lockscreen_while_viewing_card">स्क्रीन को लॉक होने से रोकें</string>
<string name="enterBarcodeInstructions">अपनी आई डी लिखें तथा या तो बारकोड प्रकार चुनें, या फिर कोई बारकोड नहीं है चुनें|</string>
<string name="settings_lock_on_opening_orientation">कार्ड खोलते समय प्रयोग करि गयी अवस्था को प्रतिबन्ध करें</string>
<string name="intent_import_card_from_url_share_text">मैं तुम्हें एक कार्ड भेजना चाहता हूँ</string>
<string name="selectBarcodeTitle">बारकोड चुनें</string>
<string name="thumbnailDescription">छोटा चित्र</string>
<string name="noGroups">+ दबा कर समूहों को भागीकरण के लिए चुनें|</string>
<string name="sameAsCardId">आई डी से निरंतर</string>
<string name="intent_import_card_from_url_share_multiple_text">में तुम्हें कुछ कार्ड्स भेजना चाहता हूँ</string>
<string name="importSuccessful">जानकारी प्राप्त हुई</string>
<string name="enter_group_name">समूह का नाम लिखें</string>
<string name="group_edit">समूह बदलें</string>
<string name="noGroupCards">यह समूह खाली है</string>
<string name="group_name_already_in_use">समूह का नाम पहले ही प्रयोग में है</string>
<string name="group_name_is_empty">समूह का नाम खाली नहीं हो सकता</string>
<string name="all">सब</string>
<string name="deleteConfirmationGroup">समूह को मिटा दिया जाए\?</string>
<string name="moveUp">ऊपर की और बढ़ें</string>
<string name="moveDown">नीचे की और बढ़ें</string>
<string name="leaveWithoutSaveTitle">निकास करें</string>
<string name="addFromImage">गैलरी से चित्र चुनें</string>
<string name="card">कार्ड</string>
<string name="barcode">बारकोड</string>
<string name="editBarcode">बारकोड में बदलाव करें</string>
<string name="expiryDate">समाप्ति दिन</string>
<string name="never">कभी नहीं</string>
<string name="chooseExpiryDate">समाप्ति दिन चुनें</string>
<string name="moveBarcodeToTopOfScreen">बार कोड को स्क्रीन के ऊपरी भाग पर लाएं</string>
<string name="moveBarcodeToCenterOfScreen">बार कोड को स्क्रीन के बीच में लाएं</string>
<string name="noBarcodeFound">कोई बारकोड नहीं पाया गया</string>
<string name="errorReadingImage">चित्र को पढ़ना संभव नहीं हुआ</string>
<string name="balance">संतुलन</string>
<string name="currency">मुद्रा</string>
<string name="accept">स्वीकार</string>
<string name="exportSuccessful">डेटा निर्यात किया गया</string>
<string name="groupsList">समूह: <xliff:g>%s</xliff:g></string>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> अंक</item>
<item quantity="other"><xliff:g>%s</xliff:g> अंक</item>
</plurals>
<string name="privacy_policy_popup_text">निजता नीति नोटिस (कुछ ऐप स्टोर के लिए आवश्यक):
\n
\nकोई भी डेटा एकत्र नहीं किया जाता है, जिसकी पुष्टि कोई भी कर सकता है क्योंकि हमारा ऐप मुफ्त सॉफ्टवेयर है।</string>
<string name="importCatimaMessage">आयात करने के लिए, <i>catima.zip</i> फाइल को चुने जो की Catima से निर्यात किया गया था.
\nदूसरे Catima ऍप के आयात/निर्यात मेनू से निर्यात बटन दबाकर, पहले catima.zip फाइल को बनाये.</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> चयनित</item>
<item quantity="other"><xliff:g>%d</xliff:g> चयनित.</item>
</plurals>
<string name="groups">समूहों</string>
<string name="app_copyright_fmt" tools:ignore="PluralsCandidate">कॉपीराइट © 2019<xliff:g>%d</xliff:g> Sylvia van Os</string>
<plurals name="groupCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> कार्ड</item>
<item quantity="other"><xliff:g>%d</xliff:g> कार्डों</item>
</plurals>
<string name="group_updated">समूह अपडेट किया गया</string>
<string name="failedOpeningFileManager">पहले एक फाइल मैनेजर इनस्टॉल करें.</string>
<string name="leaveWithoutSaveConfirmation">बिना सुरक्षित किये छोड़े\?</string>
<string name="addManually">मैन्युअली आई डी दर्ज करें</string>
<string name="editGroup">समूह संपादन: <xliff:g>%s</xliff:g></string>
<string name="points">अंक</string>
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> एक वैध शेष राशि नहीं है.</string>
<string name="chooseImportType">यहाँ से डेटा का आयात करें</string>
<string name="privacy_policy">निजता नीति</string>
<string name="importCatima">कैटिमा से आयात करें</string>
<string name="app_license">कॉपीलेफ्ट लिबर सॉफ्टवेयर, GPLv3+ लाइसेंस प्राप्त</string>
<string name="about_title_fmt"><xliff:g id="app_name">%s</xliff:g> के बारे में</string>
<string name="debug_version_fmt">संस्करण: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt"><xliff:g id="app_revision_url">%s</xliff:g> संशोधन के बारे में</string>
<string name="copy_to_clipboard_toast">आई डी क्लिपबोर्ड पर कॉपी किया गया</string>
</resources>

View File

@@ -22,7 +22,7 @@
<string name="action_add">Dodaj</string>
<string name="storeName">Ime</string>
<string name="note">Napomena</string>
<string name="noMatchingGiftCards">Nema rezultata. Pokušajte promijeniti pretraživanje.</string>
<string name="noMatchingGiftCards">Ništa nisam našao. Pokušajte promijeniti pretraživanje.</string>
<string name="cardId">ID kartice</string>
<string name="barcodeType">Vrsta crtičnog koda</string>
<string name="barcodeNoBarcode">Na ovoj kartici nema crtičnog koda</string>
@@ -44,6 +44,7 @@
<string name="exportOptionExplanation">Podaci će biti zabilježeni na odabranom mjestu.</string>
<string name="exportFailedTitle">Izvoz nije uspio</string>
<string name="exporting">Opskrba…</string>
<string name="noExternalStoragePermissionError">Najprije odobrite dopuštenje za vanjsku pohranu za uvoz ili izvoz kartica</string>
<string name="importOptionFilesystemExplanation">Odaberite određenu datoteku iz datotečnog sustava.</string>
<string name="importOptionApplicationTitle">Koristite drugu aplikaciju</string>
<string name="settings">Postavke</string>
@@ -79,5 +80,4 @@
<string name="leaveWithoutSaveTitle">Izlaz</string>
<string name="card">Karta</string>
<string name="leaveWithoutSaveConfirmation">Otići bez očuvanja\?</string>
<string name="noGiftCardsGroup">Izradite neke kartice, a zatim ih dodijelite grupi ovdje.</string>
</resources>

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
<string name="noGiftCards">Kattintson a + plusz gombra egy kártya hozzáadásához, vagy importáljon a ⋮ menül.</string>
<string name="noGiftCards">Nyomja meg a + jelet a kártya hozzáadásához, vagy importáljon a ⋮ menüvel.</string>
<string name="action_add">Hozzáadás</string>
<string name="action_search">Keresés</string>
<plurals name="selectedCardCount">
<item quantity="one"><xliff:g>%d</xliff:g> kiválasztott</item>
<item quantity="other"><xliff:g>%d</xliff:g> kiválasztottak</item>
<item quantity="one"><xliff:g>%d</xliff:g> kiválasztva</item>
<item quantity="other"><xliff:g>%d</xliff:g> kiválasztva</item>
</plurals>
<string name="noCardsMessage">Először adjon hozzá egy kártyát</string>
<string name="noGiftCardsGroup">Hozzon létre kártyákat, és rendelje hozzá a megfelelő csoporthoz.</string>
@@ -22,10 +22,10 @@
<string name="save">Mentés</string>
<string name="edit">Szerkesztés</string>
<string name="delete">Törlés</string>
<string name="confirm">Jóváhagy</string>
<string name="confirm">Alkalmaz</string>
<plurals name="deleteCardsTitle">
<item quantity="one">Törölje az <xliff:g>%d</xliff:g> kártyát</item>
<item quantity="other">Törölje az <xliff:g>%d</xliff:g> kártyákat</item>
<item quantity="one"><xliff:g>%d</xliff:g> törlése</item>
<item quantity="other"><xliff:g>%d</xliff:g> törlése</item>
</plurals>
<string name="deleteConfirmation">Véglegesen törli a kártyát\?</string>
<string name="ok">Rendben</string>
@@ -55,7 +55,7 @@
<string name="barcodeImageDescriptionWithType"><xliff:g>%s</xliff:g> vonalkód képe</string>
<string name="noCardIdError">Nincs azonosító megadva</string>
<string name="noCardExistsError">Kártya nem található</string>
<string name="importStocardMessage">Válassza ki a <i>***.zip</i> Stocard exportot.
<string name="importStocardMessage">Válassza ki a <i>***-sync.zip</i> Stocard exportot.
\nAdatinak exportját kérheti e-mailben a support@stocardapp.com címre írva.</string>
<string name="importVoucherVault">Importálás Voucher Vault-ból</string>
<string name="wrongValueForBarcodeType">Ez az érték meg megfelelő a vonalkód típushoz</string>
@@ -81,6 +81,7 @@
<string name="exportFailed">Exportálás sikertelen</string>
<string name="importing">Importálás…</string>
<string name="exporting">Exportálás…</string>
<string name="noExternalStoragePermissionError">Külső háttértárhoz való hozzáférés engedélyezése adat import vagy export miatt</string>
<string name="exportOptionExplanation">Az adatokat a kiválasztott helyre fogjuk menteni.</string>
<string name="importOptionFilesystemTitle">Importálás fájlrendszerből</string>
<string name="importOptionFilesystemButton">A fájlrendszerből</string>
@@ -136,7 +137,7 @@
<string name="currency">Valuta</string>
<string name="points">Pontok</string>
<string name="parsingBalanceFailed">A <xliff:g>%s</xliff:g> nem tűnik valós egyenlegnek.</string>
<string name="app_loyalty_card_keychain">Hűségkártya kulcstartó</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="privacy_policy">Adatvédelmi Szabályzat</string>
<string name="accept">Elfogad</string>
<string name="importCatima">Importálás Catimából</string>
@@ -150,7 +151,6 @@
<string name="barcodeId">Vonalkód érték</string>
<string name="sameAsCardId">Ugyanaz, mint az azonosító</string>
<string name="setBarcodeId">Vonalkód érték beállítása</string>
<string name="unsupportedBarcodeType">Ez a vonalkód típus még nem megjeleníthető. Feltehetően támogatva lesz egy későbbi verzióban.</string>
<string name="copy_to_clipboard_multiple_toast">Azonosítók a vágólapra másolva</string>
<string name="intent_import_card_from_url_share_multiple_text">Meg akarok veled osztani pár kártyát</string>
<string name="frontImageDescription">Előlapi kép</string>
@@ -178,7 +178,7 @@
<string name="app_contributors">Lehetővé tette: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="showMoreInfo">Infó mutatása</string>
<string name="hideMoreInfo">Infó elrejtése</string>
<string name="swipeToSwitchImages">Csúsztassa ujját a képek váltásához, tartsa lenyomva a kép megnyitásához a Galéria alkalmazásban</string>
<string name="swipeToSwitchImages">Húzza oldalra vagy tartsa nyomva a kép megváltoztatásáért</string>
<string name="sort_by_balance">Egyenleg</string>
<string name="reverse">...fordított sorrendben</string>
<string name="sort_by">Rendezés mint</string>
@@ -199,7 +199,7 @@
</plurals>
<plurals name="balancePoints">
<item quantity="one"><xliff:g>%s</xliff:g> pont</item>
<item quantity="other"><xliff:g>%s</xliff:g> pontok</item>
<item quantity="other"><xliff:g>%s</xliff:g> pont</item>
</plurals>
<string name="chooseImportType">Adat importálása innen</string>
<string name="thumbnailDescription">Előkép</string>
@@ -246,26 +246,4 @@
<string name="archived">Kártya archiválása</string>
<string name="archiveList">Archivál</string>
<string name="duplicateCard">Másolat</string>
<string name="currentBalanceSentence">Jelenlegi egyenleg: <xliff:g>%s</xliff:g></string>
<string name="updateBalanceHint">Adja meg az összeget</string>
<string name="importCards">Kártyák importálása</string>
<string name="updateBalanceTitle">Mennyit költöttél\?</string>
<string name="newBalanceSentence">Új egyenleg: <xliff:g>%s</xliff:g></string>
<string name="cameraPermissionDeniedTitle">Nem sikerült hozzáférni a kamerához</string>
<string name="failedToRetrieveImageFile">Nem sikerült lekérni a képfájlt</string>
<string name="barcodeLongPressMessage">Csak képek nyithatók meg a galéria alkalmazásban</string>
<string name="unarchived">Archiválatlan kártya</string>
<string name="welcome">Üdvözöljük Catimában</string>
<plurals name="viewArchivedCardsWithCount">
<item quantity="one">Archívum megtekintése (<xliff:g>%1$d</xliff:g> kártya)</item>
<item quantity="other">Archívum megtekintése (<xliff:g>%1$d</xliff:g> kártyák)</item>
</plurals>
<string name="updateBalance">Egyenleg frissítése</string>
<string name="noCameraPermissionDirectToSystemSetting">A vonalkódok beolvasásához a Catimának hozzá kell férnie a kamerához. Koppintson ide az engedélybeállítások módosításához.</string>
<string name="validFromDate">Érvényesség kezdete</string>
<string name="anyDate">Bármely dátum</string>
<string name="chooseValidFromDate">Válassza ki az érvényesség kezdeti dátumot</string>
<string name="validFromSentence">Érvényesség kezdete: <xliff:g>%s</xliff:g></string>
<string name="storageReadPermissionRequired">A művelethez tároló olvasási engedély szükséges…</string>
<string name="cameraPermissionRequired">A kamerához való hozzáférés engedélye szükséges ehhez a művelethez…</string>
</resources>

View File

@@ -7,8 +7,8 @@
<string name="edit">Ubah</string>
<string name="save">Simpan</string>
<string name="deleteTitle">Hapus kartu</string>
<string name="cardId">ID Kartu</string>
<string name="barcodeType">Jenis barcode</string>
<string name="cardId">Kartu ID</string>
<string name="barcodeType">Tipe barcode</string>
<string name="star">Tambahkan ke favorit</string>
<string name="unstar">Hapus dari favorit</string>
<string name="action_add">Tambah</string>
@@ -30,13 +30,13 @@
<string name="scanCardBarcode">Pindai Barcode</string>
<string name="barcodeNoBarcode">Tidak ada barcode</string>
<string name="cancel">Batalkan</string>
<string name="importExport">Impor/Ekspor</string>
<string name="importExport">Import/Ekspor</string>
<string name="settings_category_title_ui">Tampilan Pengguna</string>
<string name="settings_theme">Tema</string>
<string name="all">Semua</string>
<string name="leaveWithoutSaveTitle">Keluar</string>
<string name="card">Kartu</string>
<string name="barcode">barcode</string>
<string name="barcode">Barcode</string>
<string name="chooseExpiryDate">Pilih masa berlaku</string>
<string name="noBarcodeFound">Barcode tidak ditemukan</string>
<string name="errorReadingImage">Tidak dapat membaca gambar</string>
@@ -76,17 +76,17 @@
<string name="removeImage">Hapus gambar</string>
<string name="setBackImage">Atur gambar bagian belakang</string>
<string name="intent_import_card_from_url_share_multiple_text">Saya ingin berbagi kartu dengan Anda</string>
<string name="noGiftCards">Tap tanda tombol plus ( + ) untuk menambahkan kartu, atau mengimpor nya melalui menu ( ⋮ ).</string>
<string name="noMatchingGiftCards">Tidak menemukan apapun. Cobalah untuk mengubah pencarian anda.</string>
<string name="noGiftCards">Klik tanda + tombol tambah untuk menambahkan kartu, atau mengimpor beberapa kartu melalui menu.</string>
<string name="noMatchingGiftCards">Tidak menemukan apapun. Coba untuk mengubah pencarian anda.</string>
<string name="noBarcode">Bukan barcode</string>
<string name="confirm">Konfirmasi</string>
<string name="copy_to_clipboard">Salin ID ke papan klip</string>
<string name="sendLabel">Kirim…</string>
<string name="noCardsMessage">Tambah kartu terlebih dahulu</string>
<string name="noStoreError">Nama masih kosong</string>
<string name="noCardIdError">Tidak ada ID yang dimasukkan</string>
<string name="noCardIdError">Kartu ID masih kosong</string>
<string name="noCardExistsError">Tidak dapat menemukan kartu</string>
<string name="failedParsingImportUriError">Tidak dapat memparsing impor URI</string>
<string name="failedParsingImportUriError">Tidak dapat menguraikan alamat impor situs web</string>
<string name="exportName">Ekspor</string>
<string name="importSuccessfulTitle">Sudah diimpor</string>
<string name="importFailedTitle">Impor gagal</string>
@@ -94,12 +94,13 @@
<string name="exportSuccessfulTitle">Sudah diekspor</string>
<string name="exportFailedTitle">Ekspor gagal</string>
<string name="exportFailed">Tidak dapat mengekspor</string>
<string name="importing">Sedang mengimpor…</string>
<string name="exporting">Sedang mengekspor…</string>
<string name="importing">Mengimpor…</string>
<string name="exporting">Mengekspor…</string>
<string name="noExternalStoragePermissionError">Berikan izin penyimpanan eksternal untuk mengimpor atau mengekspor data</string>
<string name="exportOptionExplanation">Data akan ditulis ke lokasi pilihan Anda.</string>
<string name="importOptionFilesystemTitle">Impor dari pengelola file bawaan</string>
<string name="importOptionFilesystemExplanation">Pilih file dari pengelola file bawaan.</string>
<string name="importOptionFilesystemButton">Dari pengelola file bawaan</string>
<string name="importOptionFilesystemTitle">Impor dari sistem</string>
<string name="importOptionFilesystemExplanation">Pilih file dari sistem.</string>
<string name="importOptionFilesystemButton">Dari sistem</string>
<string name="importOptionApplicationTitle">Gunakan aplikasi lain</string>
<string name="importOptionApplicationExplanation">Gunakan aplikasi lain atau pengelola file favorit anda untuk membuka file.</string>
<string name="importOptionApplicationButton">Gunakan aplikasi lain</string>
@@ -122,7 +123,7 @@
<string name="settings_max_font_size_scale">Ukuran maksimal huruf</string>
<string name="settings_display_barcode_max_brightness">Terangkan tampilan barcode</string>
<string name="settings_keep_screen_on">Biarkan layar menyala</string>
<string name="settings_disable_lockscreen_while_viewing_card">Cegah kunci layar</string>
<string name="settings_disable_lockscreen_while_viewing_card">Mencegah layar menyala</string>
<string name="intent_import_card_from_url_share_text">Saya ingin berbagi kartu dengan anda</string>
<string name="importSuccessful">Data terimpor</string>
<string name="exportSuccessful">Data terekspor</string>
@@ -148,7 +149,7 @@
<string name="moveBarcodeToCenterOfScreen">Pusatkan barcode pada layar</string>
<string name="points">Poin</string>
<string name="parsingBalanceFailed"><xliff:g>%s</xliff:g> sepertinya bukan saldo yang valid.</string>
<string name="app_loyalty_card_keychain">Gantungan kunci kartu kesetiaan</string>
<string name="app_loyalty_card_keychain">Loyalty Card Keychain</string>
<string name="privacy_policy">Kebijakan Privasi</string>
<string name="privacy_policy_popup_text">Pemberitahuan kebijakan privasi (diperlukan oleh beberapa toko aplikasi):
\n
@@ -161,12 +162,11 @@
<string name="importLoyaltyCardKeychainMessage">Pilih ekspor <i>LoyaltyCardKeychain.csv</i> Anda dari Loyalty Card Keychain untuk diimpor.
\nBuat dari menu Import/Export di Loyalty Card Keychain dengan menekan Export terlebih dahulu.</string>
<string name="importStocard">Impor dari Stocard</string>
<string name="importStocardMessage">Pilih ekspor <i>***.zip</i> Anda dari Stocard untuk diimpor.
<string name="importStocardMessage">Pilih ekspor <i>***-sync.zip</i> Anda dari Stocard untuk diimpor.
\nDapatkan dengan mengirim email ke support@stocardapp.com untuk meminta ekspor data Anda.</string>
<string name="importVoucherVault">Impor dari Voucher Vault</string>
<string name="importVoucherVaultMessage">Pilih ekspor <i>vouchervault.json</i> Anda dari Vault Voucher untuk diimpor.
\nBuat dengan menekan Ekspor di Vault Voucher terlebih dahulu.</string>
<string name="unsupportedBarcodeType">Jenis barcode ini belum dapat ditampilkan. Ini mungkin didukung di versi aplikasi yang lebih baru.</string>
<string name="wrongValueForBarcodeType">Nilai tidak berlaku untuk jenis barcode yang dipilih</string>
<string name="copy_to_clipboard_multiple_toast">ID disalin ke papan klip</string>
<string name="frontImageDescription">Gambar depan</string>
@@ -177,18 +177,18 @@
<string name="exportPassword">Tetapkan kata sandi untuk melindungi ekspor anda (opsional)</string>
<string name="failedGeneratingShareURL">Tidak dapat membuat alamat berbagi. Mohon laporkan ini.</string>
<string name="app_contributors">Pengembangan dibantu oleh: <xliff:g id="app_contributors">%s</xliff:g></string>
<string name="swipeToSwitchImages">Geser untuk beralih gambar, tahan untuk membuka gambar di aplikasi galeri</string>
<string name="reverse">…dalam urutan terbalik</string>
<string name="swipeToSwitchImages">Geser atau tekan yang lama untuk mengganti gambar</string>
<string name="reverse">Ubah urutan</string>
<string name="version_history">Riwayat Versi</string>
<string name="help_translate_this_app">Bantu terjemahkan aplikasi ini</string>
<string name="source_repository">Sumber Repositori</string>
<string name="on_github">di GitHub</string>
<string name="and_data_usage">dan penggunaan data</string>
<string name="on_google_play">di Google Play</string>
<string name="cardShortcut">Pintasan Kartu</string>
<string name="card_ids_copied">ID(s) yang disalin</string>
<string name="cardShortcut">Pintasan kartu</string>
<string name="card_ids_copied">ID kartu yang tersalin</string>
<string name="barcodeImageDescriptionWithType">Gambar <xliff:g>%s</xliff:g> barcode</string>
<string name="importExportHelp">Mencadangkan data anda akan memungkinkan memindahkannya ke perangkat lain.</string>
<string name="importExportHelp">Mencadangkan data anda memungkinkan memindahkannya ke perangkat lain.</string>
<plurals name="selectedCardCount">
<item quantity="other"><xliff:g>%d</xliff:g> kartu dipilih</item>
</plurals>
@@ -241,26 +241,4 @@
<string name="archive">Arsip</string>
<string name="archived">Kartu diarsipkan</string>
<string name="archiveList">Arsip</string>
<string name="failedToRetrieveImageFile">Gagal mengambil file gambar</string>
<string name="barcodeLongPressMessage">Hanya gambar yang dapat dibuka di aplikasi galeri</string>
<string name="unarchive">Ekstrak</string>
<string name="unarchived">Kartu tidak diarsipkan</string>
<string name="noUnarchivedCardsMessage">Tidak ada kartu yang diekstrak</string>
<string name="options">Opsi</string>
<string name="starred">Berbintang</string>
<string name="importCards">Import kartu</string>
<string name="newBalanceSentence">Saldo baru: <xliff:g>%s</xliff:g></string>
<string name="cameraPermissionDeniedTitle">Tidak dapat mengakses kamera</string>
<string name="updateBalance">Perbarui saldo</string>
<string name="updateBalanceHint">Masukkan jumlah</string>
<string name="currentBalanceSentence">Saldo saat ini: <xliff:g>%s</xliff:g></string>
<string name="updateBalanceTitle">Berapa banyak yang telah kamu habiskan \?</string>
<string name="noCameraPermissionDirectToSystemSetting">Untuk memindai barcode, Catima membutuhkan akses ke kamera mu. Tap disini untuk mengganti pengaturan perizinan mu.</string>
<string name="cameraPermissionRequired">Berikan izin untuk mengakses kamera anda…</string>
<string name="storageReadPermissionRequired">Berikan izin untuk membaca penyimpanan anda…</string>
<string name="validFromDate">Valid dari</string>
<string name="validFromSentence">Valid dari:
\n<xliff:g>%s</xliff:g></string>
<string name="anyDate">Kapan pun</string>
<string name="chooseValidFromDate">Pilih valid dari tanggal</string>
</resources>
</resources>

Some files were not shown because too many files have changed in this diff Show More