Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40d36ec8fb | ||
|
|
99a3849942 | ||
|
|
26a6a786dc | ||
|
|
55a5884852 | ||
|
|
c20ce1e555 | ||
|
|
a90899d41e | ||
|
|
413d479ea0 | ||
|
|
8ff452cfd2 | ||
|
|
54c279ec89 | ||
|
|
000d7722da | ||
|
|
0bb0df21e4 | ||
|
|
0dcfc11c53 | ||
|
|
58f8a6a929 | ||
|
|
6a80c633a8 | ||
|
|
031bfd94e0 | ||
|
|
45443abaf9 | ||
|
|
b81da59bcc | ||
|
|
86f3530d95 | ||
|
|
863a378bf0 | ||
|
|
a9d81b8321 | ||
|
|
46865f349b | ||
|
|
1d1109d665 | ||
|
|
c11e995684 | ||
|
|
184af4d272 | ||
|
|
d7356704ea | ||
|
|
66573fd2da | ||
|
|
36ff2333b7 | ||
|
|
9325f33da6 | ||
|
|
9e40d9b6b3 | ||
|
|
4679051df1 | ||
|
|
27cbad1b44 | ||
|
|
ba24b49009 | ||
|
|
c408beb68c | ||
|
|
5b7590e4e1 | ||
|
|
574832d09b | ||
|
|
8029be7b2b | ||
|
|
390a2e1b5a | ||
|
|
803555f94e | ||
|
|
6488dc0fe3 | ||
|
|
ddc385c39d | ||
|
|
6382c54d1a | ||
|
|
af515d307a | ||
|
|
e1edeb364e | ||
|
|
18b95e96eb | ||
|
|
5960857568 | ||
|
|
6426a99be4 | ||
|
|
1f76809300 | ||
|
|
5346726ae1 | ||
|
|
3828454a26 | ||
|
|
7995c2ba1a | ||
|
|
9ee4184122 | ||
|
|
319ba195e4 | ||
|
|
a21e9f142c | ||
|
|
22fbbd3341 | ||
|
|
191e22db65 | ||
|
|
ceba99d11f | ||
|
|
dd1840ce5c | ||
|
|
b1608d6a9f | ||
|
|
aee03170dc | ||
|
|
419e12c0eb | ||
|
|
6c2ce61ddc | ||
|
|
fc342b4197 | ||
|
|
a6536307ad | ||
|
|
8a517e8161 | ||
|
|
34f314dad8 | ||
|
|
2e49e36e0c | ||
|
|
a7afdae83c | ||
|
|
c705136362 | ||
|
|
afa76d8f54 | ||
|
|
cf20d03fb3 | ||
|
|
bb866819db | ||
|
|
c9dd994120 | ||
|
|
eabd988540 | ||
|
|
af91097559 | ||
|
|
16793d1c0b | ||
|
|
6f4673c9fb | ||
|
|
bb99321209 | ||
|
|
baa42c8c69 | ||
|
|
6c011dd9ac | ||
|
|
9bf245a392 | ||
|
|
412afcda1e | ||
|
|
c8bc0864b2 | ||
|
|
4d59e6c48d | ||
|
|
70cf42f144 | ||
|
|
b3fc66fa58 | ||
|
|
aa7315fafe | ||
|
|
5325f1955e | ||
|
|
ee30180137 | ||
|
|
6bddeb2f75 | ||
|
|
3c69990b0d | ||
|
|
43d5478ae3 | ||
|
|
30a54ae22a | ||
|
|
9ff6b87092 | ||
|
|
9763d9fb73 | ||
|
|
0b92686dad | ||
|
|
cb1c834057 | ||
|
|
98a0487d0c | ||
|
|
2df165061d | ||
|
|
fdd7ca03cf | ||
|
|
29e996a5fe | ||
|
|
3c1c8baaa0 | ||
|
|
6c186c2abb | ||
|
|
9deb0b94af | ||
|
|
97d54e87f2 | ||
|
|
0e329b3c31 | ||
|
|
bd337d6da8 | ||
|
|
dc7f199e0c | ||
|
|
b54e14def2 | ||
|
|
f724e4c164 | ||
|
|
c389a02d69 | ||
|
|
b7db58e7d0 | ||
|
|
eefd26fa06 | ||
|
|
8d4a122cb5 | ||
|
|
13ea10d0b0 | ||
|
|
4366eab380 | ||
|
|
0c61d36cb3 | ||
|
|
6bc5e77bbd | ||
|
|
d8ddee0667 | ||
|
|
5e8312e816 | ||
|
|
8e10022158 | ||
|
|
b209b670a5 | ||
|
|
ecdec0f773 | ||
|
|
5c90107a7b | ||
|
|
6a8a9878d7 | ||
|
|
3400d10cc6 | ||
|
|
05e171170b | ||
|
|
cb967a8d8d | ||
|
|
5835da2931 | ||
|
|
2e9339b8aa | ||
|
|
bbba4b2e11 | ||
|
|
d9659ecc9d | ||
|
|
c558278962 | ||
|
|
c137cb9428 | ||
|
|
1d2f54c69b | ||
|
|
d4a377fe18 | ||
|
|
9844113568 |
10
.github/workflows/android.yml
vendored
@@ -23,5 +23,11 @@ jobs:
|
||||
run: ./gradlew lintRelease
|
||||
- name: Run unit tests
|
||||
run: ./gradlew testReleaseUnitTest
|
||||
- name: FindBugs
|
||||
run: ./gradlew findbugs
|
||||
- name: SpotBugs
|
||||
run: ./gradlew spotbugsRelease
|
||||
- name: Archive test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: test-results
|
||||
path: app/build/reports
|
||||
|
||||
13
.gitignore
vendored
@@ -1,10 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
local.properties
|
||||
.idea/
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
/app/release
|
||||
/app/debug
|
||||
build/
|
||||
captures/
|
||||
**/release
|
||||
**/debug
|
||||
|
||||
62
CHANGELOG.md
@@ -1,5 +1,67 @@
|
||||
# Changelog
|
||||
|
||||
## v1.8 (2021-01-28)
|
||||
|
||||
Changes:
|
||||
|
||||
- Add support for scaling the barcode when moving to top to fit even more small scanners
|
||||
- Fix bottom sheet jumping after switching to fullscreen
|
||||
- Make header in loyalty card view small in landscape mode
|
||||
- Fix cards not staying in group when group gets renamed
|
||||
|
||||
## v1.7.1 (2021-01-18)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix crash on switching to barcode tab in edit view if there is no barcode
|
||||
|
||||
## v1.7.0 (2021-01-18)
|
||||
|
||||
Changes:
|
||||
|
||||
- Separate edit UI in tabs to make it feel more spacious
|
||||
- Add expiry field support
|
||||
|
||||
## v1.6.2 (2021-01-04)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix edit button or more info bottom sheet drawing over barcode ID
|
||||
|
||||
## v1.6.1 (2020-12-16)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix regression causing manual barcode entry to not be saved
|
||||
|
||||
## v1.6.0 (2020-12-15)
|
||||
|
||||
Changes:
|
||||
|
||||
- Automatically focus text field when creating or editing a group
|
||||
- Fix blurry icons (use SVG everywhere)
|
||||
- Always open camera but add manual scan button to camera view
|
||||
|
||||
## v1.5.1 (2020-12-03)
|
||||
|
||||
Changes:
|
||||
|
||||
- Fix bottomsheet background being transparent
|
||||
|
||||
## v1.5.0 (2020-12-03)
|
||||
|
||||
Changes:
|
||||
|
||||
- Improve contrast by always using white text on red buttons
|
||||
- Draggable bottom sheet in loyalty card view
|
||||
|
||||
## v1.4.1 (2020-12-01)
|
||||
|
||||
Changes:
|
||||
|
||||
- Improved translations
|
||||
- Small UI fixes
|
||||
|
||||
## v1.4.0 (2020-11-28)
|
||||
|
||||
Changes:
|
||||
|
||||
1
app/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,77 +1,97 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'findbugs'
|
||||
import com.github.spotbugs.snom.SpotBugsTask
|
||||
|
||||
findbugs {
|
||||
sourceSets = []
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.github.spotbugs'
|
||||
|
||||
spotbugs {
|
||||
ignoreFailures = false
|
||||
effort = 'max'
|
||||
excludeFilter = file("./config/spotbugs/exclude.xml")
|
||||
reportsDir = file("$buildDir/reports/spotbugs/")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "me.hackerchick.catima"
|
||||
minSdkVersion 16
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 48
|
||||
versionName "1.4.0"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionCode 57
|
||||
versionName "1.8"
|
||||
|
||||
vectorDrawables.useSupportLibrary true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
resValue "string", "app_name", "Catima"
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_name", "Catima Debug"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
encoding "UTF-8"
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable "GoogleAppIndexingWarning"
|
||||
disable "ButtonStyle"
|
||||
disable "AlwaysShowAction"
|
||||
disable "MissingTranslation"
|
||||
disable "MissingPrefix"
|
||||
disable "GoogleAppIndexingWarning", "ButtonStyle", "AlwaysShowAction",
|
||||
"MissingTranslation", "MissingPrefix"
|
||||
}
|
||||
|
||||
// Starting with Android Studio 3 Robolectric is unable to find resources.
|
||||
// The following allows it to find the resources.
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
all {
|
||||
testLogging {
|
||||
events 'started', 'passed', 'skipped', 'failed'
|
||||
}
|
||||
}
|
||||
includeAndroidResources true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// AndroidX
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
|
||||
implementation 'com.google.zxing:core:3.3.0'
|
||||
implementation 'org.apache.commons:commons-csv:1.5'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'com.jaredrummler:colorpicker:1.0.2'
|
||||
implementation group: 'com.google.guava', name: 'guava', version: '20.0'
|
||||
implementation "com.vanniktech:vntnumberpickerpreference:1.0.0"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.robolectric:robolectric:4.0.2"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'
|
||||
|
||||
// Third-party
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.1.0@aar'
|
||||
implementation 'com.google.zxing:core:3.4.1'
|
||||
implementation 'org.apache.commons:commons-csv:1.8'
|
||||
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
||||
implementation 'com.google.guava:guava:30.1-jre'
|
||||
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.1'
|
||||
|
||||
// Testing
|
||||
testImplementation 'androidx.test:core:1.0.0'
|
||||
testImplementation 'junit:junit:4.13.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.4'
|
||||
}
|
||||
|
||||
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {
|
||||
tasks.withType(SpotBugsTask) {
|
||||
|
||||
description 'Run findbugs'
|
||||
description 'Run spotbugs'
|
||||
group 'verification'
|
||||
|
||||
classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
|
||||
source = fileTree('src/main/java')
|
||||
classpath = files()
|
||||
|
||||
effort = 'max'
|
||||
|
||||
excludeFilter = file("./config/findbugs/exclude.xml")
|
||||
//classes = fileTree('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes')
|
||||
//source = fileTree('src/main/java')
|
||||
//classpath = files()
|
||||
|
||||
reports {
|
||||
xml.enabled = false
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application>
|
||||
{
|
||||
public ApplicationTest()
|
||||
{
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="protect.card_locker"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"/>
|
||||
@@ -16,6 +17,8 @@
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
|
||||
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
|
||||
|
||||
<application
|
||||
android:name=".LoyaltyCardLockerApplication"
|
||||
android:allowBackup="true"
|
||||
@@ -46,7 +49,6 @@
|
||||
<activity
|
||||
android:name=".LoyaltyCardViewActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:label=""
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:exported="true"/>
|
||||
<activity
|
||||
@@ -67,6 +69,10 @@
|
||||
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_old" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ScanActivity"
|
||||
android:label="@string/scanCardBarcode"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<activity
|
||||
android:name=".BarcodeSelectorActivity"
|
||||
android:label="@string/selectBarcodeTitle"
|
||||
|
||||
@@ -1,33 +1,19 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.w3c.dom.Text;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
@@ -55,12 +41,12 @@ public class AboutActivity extends AppCompatActivity
|
||||
.put("ZXing", "https://github.com/zxing/zxing")
|
||||
.put("ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded")
|
||||
.put("Color Picker", "https://github.com/jaredrummler/ColorPicker")
|
||||
.put("VNTNumberPickerPreference", "https://github.com/vanniktech/VNTNumberPickerPreference")
|
||||
.put("NumberPickerPreference", "https://github.com/invissvenska/NumberPickerPreference")
|
||||
.build();
|
||||
|
||||
final Map<String, String> USED_ASSETS = ImmutableMap.of
|
||||
(
|
||||
"Save by Bernar Novalyi", "https://thenounproject.com/term/save/716011"
|
||||
"Android icons", "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
);
|
||||
|
||||
StringBuilder libs = new StringBuilder().append("<br/>");
|
||||
|
||||
@@ -3,7 +3,6 @@ package protect.card_locker;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -191,14 +190,7 @@ public class BarcodeSelectorActivity extends AppCompatActivity
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth());
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
{
|
||||
image.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
image.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
|
||||
Log.d(TAG, "Generating barcode for type " + formatType);
|
||||
BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format, text);
|
||||
|
||||
@@ -49,6 +49,7 @@ public class CsvDatabaseExporter implements DatabaseExporter
|
||||
printer.printRecord(DBHelper.LoyaltyCardDbIds.ID,
|
||||
DBHelper.LoyaltyCardDbIds.STORE,
|
||||
DBHelper.LoyaltyCardDbIds.NOTE,
|
||||
DBHelper.LoyaltyCardDbIds.EXPIRY,
|
||||
DBHelper.LoyaltyCardDbIds.CARD_ID,
|
||||
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
|
||||
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
|
||||
@@ -63,6 +64,7 @@ public class CsvDatabaseExporter implements DatabaseExporter
|
||||
printer.printRecord(card.id,
|
||||
card.store,
|
||||
card.note,
|
||||
card.expiry != null ? card.expiry.getTime() : "",
|
||||
card.cardId,
|
||||
card.headerColor,
|
||||
card.barcodeType,
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -47,6 +48,8 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
default:
|
||||
throw new FormatException(String.format("No code to parse version %s", version));
|
||||
}
|
||||
|
||||
bufferedReader.close();
|
||||
}
|
||||
|
||||
public void parseV1(DBHelper db, BufferedReader input) throws IOException, FormatException, InterruptedException
|
||||
@@ -131,7 +134,6 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void parseV2Groups(DBHelper db, SQLiteDatabase database, String data) throws IOException, FormatException, InterruptedException
|
||||
{
|
||||
// Parse groups
|
||||
@@ -145,10 +147,10 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
groupParser.close();
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
} finally {
|
||||
groupParser.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,10 +167,10 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardParser.close();
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
} finally {
|
||||
cardParser.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,10 +187,10 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
cardGroupParser.close();
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw new FormatException("Issue parsing CSV data", e);
|
||||
} finally {
|
||||
cardGroupParser.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +250,36 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a long 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, or the data is not a valid
|
||||
* int, a FormatException is thrown.
|
||||
*/
|
||||
private Long extractLong(String key, CSVRecord record, boolean nullIsOk)
|
||||
throws FormatException
|
||||
{
|
||||
if(record.isMapped(key) == false)
|
||||
{
|
||||
throw new FormatException("Field not used but expected: " + key);
|
||||
}
|
||||
|
||||
String value = record.get(key);
|
||||
if(value.isEmpty() && nullIsOk)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Long.parseLong(record.get(key));
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
throw new FormatException("Failed to parse field: " + key, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single loyalty card into the database using the given
|
||||
* session.
|
||||
@@ -264,6 +296,10 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
}
|
||||
|
||||
String note = extractString(DBHelper.LoyaltyCardDbIds.NOTE, record, "");
|
||||
Date expiry = null;
|
||||
try {
|
||||
expiry = new Date(extractLong(DBHelper.LoyaltyCardDbIds.EXPIRY, record, true));
|
||||
} catch (NullPointerException | FormatException e) { }
|
||||
|
||||
String cardId = extractString(DBHelper.LoyaltyCardDbIds.CARD_ID, record, "");
|
||||
if(cardId.isEmpty())
|
||||
@@ -288,7 +324,7 @@ public class CsvDatabaseImporter implements DatabaseImporter
|
||||
// We catch this exception so we can still import old backups
|
||||
}
|
||||
if (starStatus != 1) starStatus = 0;
|
||||
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, starStatus);
|
||||
helper.insertLoyaltyCard(database, id, store, note, expiry, cardId, barcodeType, headerColor, starStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,9 +4,11 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Color;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -14,7 +16,7 @@ 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 = 6;
|
||||
public static final int DATABASE_VERSION = 7;
|
||||
|
||||
static class LoyaltyCardDbGroups
|
||||
{
|
||||
@@ -28,6 +30,7 @@ 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 EXPIRY = "expiry";
|
||||
public static final String NOTE = "note";
|
||||
public static final String HEADER_COLOR = "headercolor";
|
||||
public static final String HEADER_TEXT_COLOR = "headertextcolor";
|
||||
@@ -61,6 +64,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
|
||||
LoyaltyCardDbIds.STORE + " TEXT not null," +
|
||||
LoyaltyCardDbIds.NOTE + " TEXT not null," +
|
||||
LoyaltyCardDbIds.EXPIRY + " INTEGER," +
|
||||
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
|
||||
LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER," +
|
||||
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
|
||||
@@ -118,16 +122,23 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbGroups.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbGroups.ORDER + " INTEGER DEFAULT '0'");
|
||||
}
|
||||
|
||||
if(oldVersion < 7 && newVersion >= 7)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
|
||||
+ " ADD COLUMN " + LoyaltyCardDbIds.EXPIRY + " INTEGER");
|
||||
}
|
||||
}
|
||||
|
||||
public long insertLoyaltyCard(final String store, final String note, final String cardId,
|
||||
final String barcodeType, final Integer headerColor,
|
||||
final int starStatus)
|
||||
public long insertLoyaltyCard(final String store, final String note, final Date expiry,
|
||||
final String cardId, final String barcodeType,
|
||||
final Integer headerColor, final int starStatus)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
@@ -138,7 +149,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
}
|
||||
|
||||
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
|
||||
final String note, final String cardId,
|
||||
final String note, final Date expiry, final String cardId,
|
||||
final String barcodeType, final Integer headerColor,
|
||||
final int starStatus)
|
||||
{
|
||||
@@ -146,6 +157,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
contentValues.put(LoyaltyCardDbIds.ID, id);
|
||||
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
@@ -156,13 +168,14 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
}
|
||||
|
||||
public boolean updateLoyaltyCard(final int id, final String store, final String note,
|
||||
final String cardId, final String barcodeType,
|
||||
final Integer headerColor)
|
||||
final Date expiry, final String cardId,
|
||||
final String barcodeType, final Integer headerColor)
|
||||
{
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbIds.STORE, store);
|
||||
contentValues.put(LoyaltyCardDbIds.NOTE, note);
|
||||
contentValues.put(LoyaltyCardDbIds.EXPIRY, expiry != null ? expiry.getTime() : null);
|
||||
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
|
||||
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
|
||||
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
|
||||
@@ -215,6 +228,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
List<Group> groups = new ArrayList<>();
|
||||
|
||||
if (!data.moveToFirst()) {
|
||||
data.close();
|
||||
return groups;
|
||||
}
|
||||
|
||||
@@ -224,6 +238,8 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
groups.add(Group.toGroup(data));
|
||||
}
|
||||
|
||||
data.close();
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
@@ -395,6 +411,7 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
List<Group> groups = new ArrayList<>();
|
||||
|
||||
if (!data.moveToFirst()) {
|
||||
data.close();
|
||||
return groups;
|
||||
}
|
||||
|
||||
@@ -404,6 +421,8 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
groups.add(Group.toGroup(data));
|
||||
}
|
||||
|
||||
data.close();
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
@@ -514,33 +533,66 @@ public class DBHelper extends SQLiteOpenHelper
|
||||
{
|
||||
if (newName.isEmpty()) return false;
|
||||
|
||||
boolean success = false;
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(LoyaltyCardDbGroups.ID, newName);
|
||||
ContentValues groupContentValues = new ContentValues();
|
||||
groupContentValues.put(LoyaltyCardDbGroups.ID, newName);
|
||||
|
||||
ContentValues lookupContentValues = new ContentValues();
|
||||
lookupContentValues.put(LoyaltyCardDbIdsGroups.groupID, newName);
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
int rowsUpdated = db.update(LoyaltyCardDbGroups.TABLE, contentValues,
|
||||
// Update group name
|
||||
int groupsChanged = db.update(LoyaltyCardDbGroups.TABLE, groupContentValues,
|
||||
LoyaltyCardDbGroups.ID + "=?",
|
||||
new String[]{groupName});
|
||||
return (rowsUpdated == 1);
|
||||
} catch (android.database.sqlite.SQLiteConstraintException _e) {
|
||||
return false;
|
||||
|
||||
// Also update lookup tables
|
||||
db.update(LoyaltyCardDbIdsGroups.TABLE, lookupContentValues,
|
||||
LoyaltyCardDbIdsGroups.groupID + "=?",
|
||||
new String[]{groupName});
|
||||
|
||||
if (groupsChanged == 1) {
|
||||
db.setTransactionSuccessful();
|
||||
success = true;
|
||||
}
|
||||
} catch (SQLiteException e) {
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public boolean deleteGroup(final String groupName)
|
||||
{
|
||||
boolean success = false;
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
// Delete group
|
||||
int rowsDeleted = db.delete(LoyaltyCardDbGroups.TABLE,
|
||||
LoyaltyCardDbGroups.ID + " = ? ",
|
||||
new String[]{groupName});
|
||||
|
||||
// And delete lookup table entries associated with this group
|
||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||
LoyaltyCardDbIdsGroups.groupID + " = ? ",
|
||||
new String[]{groupName});
|
||||
db.beginTransaction();
|
||||
try {
|
||||
// Delete group
|
||||
int groupsDeleted = db.delete(LoyaltyCardDbGroups.TABLE,
|
||||
LoyaltyCardDbGroups.ID + " = ? ",
|
||||
new String[]{groupName});
|
||||
|
||||
return (rowsDeleted == 1);
|
||||
// And delete lookup table entries associated with this group
|
||||
db.delete(LoyaltyCardDbIdsGroups.TABLE,
|
||||
LoyaltyCardDbIdsGroups.groupID + " = ? ",
|
||||
new String[]{groupName});
|
||||
|
||||
if (groupsDeleted == 1) {
|
||||
db.setTransactionSuccessful();
|
||||
success = true;
|
||||
}
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public int getGroupCardCount(final String groupName)
|
||||
|
||||
@@ -43,9 +43,12 @@ class GroupCursorAdapter extends CursorAdapter
|
||||
// Extract properties from cursor
|
||||
Group group = Group.toGroup(cursor);
|
||||
|
||||
Integer groupCardCount = db.getGroupCardCount(group._id);
|
||||
|
||||
// Populate fields with extracted properties
|
||||
nameField.setText(group._id);
|
||||
countField.setText(String.format(context.getString(R.string.groupCardCount), db.getGroupCardCount(group._id)));
|
||||
|
||||
countField.setText(context.getResources().getQuantityString(R.plurals.groupCardCount, groupCardCount, groupCardCount));
|
||||
|
||||
nameField.setTextSize(settings.getCardTitleListFontSize());
|
||||
countField.setTextSize(settings.getCardNoteListFontSize());
|
||||
|
||||
@@ -2,11 +2,9 @@ package protect.card_locker;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
@@ -30,7 +28,6 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class ImportExportActivity extends AppCompatActivity
|
||||
{
|
||||
@@ -287,7 +284,7 @@ public class ImportExportActivity extends AppCompatActivity
|
||||
}
|
||||
catch (ActivityNotFoundException e)
|
||||
{
|
||||
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG);
|
||||
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, "No activity found to handle intent", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import java.io.InvalidObjectException;
|
||||
import java.util.Date;
|
||||
|
||||
public class ImportURIHelper {
|
||||
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
|
||||
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
|
||||
private static final String EXPIRY = DBHelper.LoyaltyCardDbIds.EXPIRY;
|
||||
private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID;
|
||||
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
|
||||
|
||||
@@ -40,6 +42,7 @@ public class ImportURIHelper {
|
||||
|
||||
try {
|
||||
// These values are allowed to be null
|
||||
Date expiry = null;
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
@@ -49,13 +52,18 @@ public class ImportURIHelper {
|
||||
String barcodeType = uri.getQueryParameter(BARCODE_TYPE);
|
||||
if (store == null || note == null || cardId == null || barcodeType == null) throw new InvalidObjectException("Not a valid import URI");
|
||||
|
||||
String unparsedExpiry = uri.getQueryParameter(EXPIRY);
|
||||
if(unparsedExpiry != null && !unparsedExpiry.equals(""))
|
||||
{
|
||||
expiry = new Date(Long.parseLong(unparsedExpiry));
|
||||
}
|
||||
String unparsedHeaderColor = uri.getQueryParameter(HEADER_COLOR);
|
||||
if(unparsedHeaderColor != null)
|
||||
{
|
||||
headerColor = Integer.parseInt(unparsedHeaderColor);
|
||||
}
|
||||
|
||||
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor, 0);
|
||||
return new LoyaltyCard(-1, store, note, expiry, cardId, barcodeType, headerColor, headerTextColor, 0);
|
||||
} catch (NullPointerException | NumberFormatException ex) {
|
||||
throw new InvalidObjectException("Not a valid import URI");
|
||||
}
|
||||
@@ -69,6 +77,9 @@ public class ImportURIHelper {
|
||||
uriBuilder.path(path);
|
||||
uriBuilder.appendQueryParameter(STORE, loyaltyCard.store);
|
||||
uriBuilder.appendQueryParameter(NOTE, loyaltyCard.note);
|
||||
if (loyaltyCard.expiry != null) {
|
||||
uriBuilder.appendQueryParameter(EXPIRY, String.valueOf(loyaltyCard.expiry.getTime()));
|
||||
}
|
||||
uriBuilder.appendQueryParameter(CARD_ID, loyaltyCard.cardId);
|
||||
uriBuilder.appendQueryParameter(BARCODE_TYPE, loyaltyCard.barcodeType);
|
||||
if(loyaltyCard.headerColor != null)
|
||||
|
||||
@@ -2,6 +2,9 @@ package protect.card_locker;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class LoyaltyCard
|
||||
@@ -9,6 +12,7 @@ public class LoyaltyCard
|
||||
public final int id;
|
||||
public final String store;
|
||||
public final String note;
|
||||
public final Date expiry;
|
||||
public final String cardId;
|
||||
public final String barcodeType;
|
||||
|
||||
@@ -20,13 +24,14 @@ public class LoyaltyCard
|
||||
|
||||
public final int starStatus;
|
||||
|
||||
public LoyaltyCard(final int id, final String store, final String note, final String cardId,
|
||||
public LoyaltyCard(final int id, final String store, final String note, final Date expiry, final String cardId,
|
||||
final String barcodeType, final Integer headerColor, final Integer headerTextColor,
|
||||
final int starStatus)
|
||||
{
|
||||
this.id = id;
|
||||
this.store = store;
|
||||
this.note = note;
|
||||
this.expiry = expiry;
|
||||
this.cardId = cardId;
|
||||
this.barcodeType = barcodeType;
|
||||
this.headerColor = headerColor;
|
||||
@@ -39,17 +44,23 @@ public class LoyaltyCard
|
||||
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 expiryLong = cursor.getLong(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXPIRY));
|
||||
String cardId = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.CARD_ID));
|
||||
String barcodeType = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE));
|
||||
int starred = cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.STAR_STATUS));
|
||||
|
||||
|
||||
int headerColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR);
|
||||
int headerTextColorColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR);
|
||||
|
||||
Date expiry = null;
|
||||
Integer headerColor = null;
|
||||
Integer headerTextColor = null;
|
||||
|
||||
if(expiryLong > 0)
|
||||
{
|
||||
expiry = new Date(expiryLong);
|
||||
}
|
||||
|
||||
if(cursor.isNull(headerColorColumn) == false)
|
||||
{
|
||||
headerColor = cursor.getInt(headerColorColumn);
|
||||
@@ -60,6 +71,6 @@ public class LoyaltyCard
|
||||
headerTextColor = cursor.getInt(headerTextColorColumn);
|
||||
}
|
||||
|
||||
return new LoyaltyCard(id, store, note, cardId, barcodeType, headerColor, headerTextColor, starred);
|
||||
return new LoyaltyCard(id, store, note, expiry, cardId, barcodeType, headerColor, headerTextColor, starred);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,27 +2,26 @@ package protect.card_locker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
class LoyaltyCardCursorAdapter extends CursorAdapter
|
||||
{
|
||||
Settings settings;
|
||||
boolean darkModeEnabled;
|
||||
|
||||
public LoyaltyCardCursorAdapter(Context context, Cursor cursor)
|
||||
{
|
||||
super(context, cursor, 0);
|
||||
settings = new Settings(context);
|
||||
darkModeEnabled= MainActivity.isDarkModeEnabled(context);
|
||||
|
||||
}
|
||||
|
||||
// The newView method is used to inflate a new view and return it,
|
||||
@@ -42,13 +41,9 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
|
||||
ImageView thumbnail = view.findViewById(R.id.thumbnail);
|
||||
TextView storeField = view.findViewById(R.id.store);
|
||||
TextView noteField = view.findViewById(R.id.note);
|
||||
TextView expiryField = view.findViewById(R.id.expiry);
|
||||
ImageView star = view.findViewById(R.id.star);
|
||||
|
||||
if(darkModeEnabled)
|
||||
{
|
||||
star.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
|
||||
// Extract properties from cursor
|
||||
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(cursor);
|
||||
|
||||
@@ -68,6 +63,22 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
|
||||
noteField.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(loyaltyCard.expiry != null)
|
||||
{
|
||||
expiryField.setVisibility(View.VISIBLE);
|
||||
int expiryString = R.string.expiryStateSentence;
|
||||
if(Utils.hasExpired(loyaltyCard.expiry)) {
|
||||
expiryString = R.string.expiryStateSentenceExpired;
|
||||
expiryField.setTextColor(context.getResources().getColor(R.color.alert));
|
||||
}
|
||||
expiryField.setText(context.getString(expiryString, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry)));
|
||||
expiryField.setTextSize(settings.getCardNoteListFontSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
expiryField.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (loyaltyCard.starStatus!=0) star.setVisibility(View.VISIBLE);
|
||||
else star.setVisibility(View.GONE);
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.android.material.chip.Chip;
|
||||
@@ -17,6 +17,7 @@ import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
@@ -29,32 +30,40 @@ import android.view.ViewTreeObserver;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.CalendarView;
|
||||
import android.widget.DatePicker;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
TabLayout tabs;
|
||||
|
||||
ImageView thumbnail;
|
||||
EditText storeFieldEdit;
|
||||
EditText noteFieldEdit;
|
||||
ChipGroup groupsChips;
|
||||
AutoCompleteTextView expiryField;
|
||||
View cardAndBarcodeLayout;
|
||||
TextView cardIdFieldView;
|
||||
View barcodeTypeView;
|
||||
AutoCompleteTextView barcodeTypeField;
|
||||
ImageView barcodeImage;
|
||||
View barcodeImageLayout;
|
||||
@@ -111,13 +120,14 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
db = new DBHelper(this);
|
||||
importUriHelper = new ImportURIHelper(this);
|
||||
|
||||
tabs = findViewById(R.id.tabs);
|
||||
thumbnail = findViewById(R.id.thumbnail);
|
||||
storeFieldEdit = findViewById(R.id.storeNameEdit);
|
||||
noteFieldEdit = findViewById(R.id.noteEdit);
|
||||
groupsChips = findViewById(R.id.groupChips);
|
||||
expiryField = findViewById(R.id.expiryField);
|
||||
cardAndBarcodeLayout = findViewById(R.id.cardAndBarcodeLayout);
|
||||
cardIdFieldView = findViewById(R.id.cardIdView);
|
||||
barcodeTypeView = findViewById(R.id.barcodeTypeView);
|
||||
barcodeTypeField = findViewById(R.id.barcodeTypeField);
|
||||
barcodeImage = findViewById(R.id.barcode);
|
||||
barcodeImageLayout = findViewById(R.id.barcodeLayout);
|
||||
@@ -140,6 +150,39 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
public void afterTextChanged(Editable s) { }
|
||||
});
|
||||
|
||||
expiryField.addTextChangedListener(new TextWatcher() {
|
||||
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) {
|
||||
hasChanged = true;
|
||||
|
||||
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(expiryField);
|
||||
datePickerFragment.show(getSupportFragmentManager(), "datePicker");
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
});
|
||||
|
||||
cardIdFieldView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
|
||||
@@ -183,6 +226,27 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) { }
|
||||
});
|
||||
|
||||
tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
showPart(tab.getText().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
showPart(tab.getText().toString());
|
||||
}
|
||||
});
|
||||
|
||||
tabs.selectTab(tabs.getTabAt(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -196,6 +260,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
// Reset these fields, so they are re-populated in onResume().
|
||||
storeFieldEdit.setText("");
|
||||
noteFieldEdit.setText("");
|
||||
expiryField.setTag(null);
|
||||
expiryField.setText("");
|
||||
cardIdFieldView.setText("");
|
||||
barcodeTypeField.setText("");
|
||||
}
|
||||
@@ -228,6 +294,16 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
noteFieldEdit.setText(loyaltyCard.note);
|
||||
}
|
||||
|
||||
if(expiryField.getText().length() == 0)
|
||||
{
|
||||
expiryField.setTag(loyaltyCard.expiry);
|
||||
if (loyaltyCard.expiry == null) {
|
||||
expiryField.setText(getString(R.string.never));
|
||||
} else {
|
||||
expiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry));
|
||||
}
|
||||
}
|
||||
|
||||
if(cardIdFieldView.getText().length() == 0)
|
||||
{
|
||||
cardIdFieldView.setText(loyaltyCard.cardId);
|
||||
@@ -263,6 +339,12 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
|
||||
storeFieldEdit.setText(importCard.store);
|
||||
noteFieldEdit.setText(importCard.note);
|
||||
expiryField.setTag(importCard.expiry);
|
||||
if (importCard.expiry != null) {
|
||||
expiryField.setText(DateFormat.getDateInstance(DateFormat.LONG).format(importCard.expiry));
|
||||
} else {
|
||||
expiryField.setText(R.string.never);
|
||||
}
|
||||
cardIdFieldView.setText(importCard.cardId);
|
||||
barcodeTypeField.setText(importCard.barcodeType);
|
||||
headingColorValue = importCard.headerColor;
|
||||
@@ -270,6 +352,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
else
|
||||
{
|
||||
setTitle(R.string.addCardTitle);
|
||||
expiryField.setTag(null);
|
||||
expiryField.setText(getString(R.string.never));
|
||||
hideBarcode();
|
||||
}
|
||||
|
||||
@@ -356,17 +440,6 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
enterButton.setOnClickListener(new EditCardIdAndBarcode());
|
||||
barcodeImage.setOnClickListener(new EditCardIdAndBarcode());
|
||||
|
||||
if(cardIdFieldView.getText().length() > 0)
|
||||
{
|
||||
cardAndBarcodeLayout.setVisibility(View.VISIBLE);
|
||||
enterButton.setText(R.string.editCard);
|
||||
}
|
||||
else
|
||||
{
|
||||
cardAndBarcodeLayout.setVisibility(View.GONE);
|
||||
enterButton.setText(R.string.enterCard);
|
||||
}
|
||||
|
||||
ArrayList<String> barcodeList = new ArrayList<>(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
|
||||
barcodeList.add(0, getString(R.string.noBarcode));
|
||||
ArrayAdapter<String> barcodeAdapter = new ArrayAdapter<>(this, android.R.layout.select_dialog_item, barcodeList);
|
||||
@@ -421,7 +494,12 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Utils.createSetBarcodeDialog(LoyaltyCardEditActivity.this, LoyaltyCardEditActivity.this, true, cardIdFieldView.getText().toString());
|
||||
|
||||
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
|
||||
final Bundle b = new Bundle();
|
||||
b.putString("cardId", cardIdFieldView.getText().toString());
|
||||
i.putExtras(b);
|
||||
startActivityForResult(i, Utils.BARCODE_SCAN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,14 +534,62 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
// Nothing to do, no change made
|
||||
}
|
||||
});
|
||||
dialog.show(getFragmentManager(), "color-picker-dialog");
|
||||
dialog.show(getSupportFragmentManager(), "color-picker-dialog");
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatePickerFragment extends DialogFragment
|
||||
implements DatePickerDialog.OnDateSetListener {
|
||||
|
||||
final EditText expiryFieldEdit;
|
||||
|
||||
DatePickerFragment(EditText expiryFieldEdit) {
|
||||
this.expiryFieldEdit = expiryFieldEdit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
// Use the current date as the default date in the picker
|
||||
final Calendar c = Calendar.getInstance();
|
||||
|
||||
Date date = (Date) expiryFieldEdit.getTag();
|
||||
if (date != null) {
|
||||
c.setTime(date);
|
||||
}
|
||||
|
||||
int year = c.get(Calendar.YEAR);
|
||||
int month = c.get(Calendar.MONTH);
|
||||
int day = c.get(Calendar.DAY_OF_MONTH);
|
||||
|
||||
// Create a new instance of DatePickerDialog and return it
|
||||
return new DatePickerDialog(getActivity(), this, year, month, day);
|
||||
}
|
||||
|
||||
public void onDateSet(DatePicker view, int year, int month, int day) {
|
||||
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, 0);
|
||||
c.set(Calendar.MINUTE, 0);
|
||||
c.set(Calendar.SECOND, 0);
|
||||
c.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
long unixTime = c.getTimeInMillis();
|
||||
|
||||
Date date = new Date(unixTime);
|
||||
|
||||
expiryFieldEdit.setTag(date);
|
||||
expiryFieldEdit.setText(DateFormat.getDateInstance(DateFormat.LONG).format(date));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void doSave()
|
||||
{
|
||||
String store = storeFieldEdit.getText().toString();
|
||||
String note = noteFieldEdit.getText().toString();
|
||||
Date expiry = (Date) expiryField.getTag();
|
||||
String cardId = cardIdFieldView.getText().toString();
|
||||
String barcodeType = barcodeTypeField.getText().toString();
|
||||
|
||||
@@ -495,12 +621,12 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
|
||||
if(updateLoyaltyCard)
|
||||
{ //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
|
||||
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType, headingColorValue);
|
||||
db.updateLoyaltyCard(loyaltyCardId, store, note, expiry, cardId, barcodeType, headingColorValue);
|
||||
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
|
||||
}
|
||||
else
|
||||
{
|
||||
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType, headingColorValue, 0);
|
||||
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, expiry, cardId, barcodeType, headingColorValue, 0);
|
||||
}
|
||||
|
||||
db.setLoyaltyCardGroups(loyaltyCardId, selectedGroups);
|
||||
@@ -601,11 +727,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
barcodeImage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
} else {
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardId, barcodeFormat).execute();
|
||||
@@ -636,4 +758,30 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
|
||||
|
||||
thumbnail.setMinimumWidth(thumbnail.getHeight());
|
||||
}
|
||||
|
||||
private void showPart(String part) {
|
||||
View cardPart = findViewById(R.id.cardPart);
|
||||
View barcodePart = findViewById(R.id.barcodePart);
|
||||
|
||||
if (getString(R.string.card).equals(part)) {
|
||||
cardPart.setVisibility(View.VISIBLE);
|
||||
barcodePart.setVisibility(View.GONE);
|
||||
|
||||
// Explicitly hide barcode (fixes blurriness on redraw)
|
||||
hideBarcode();
|
||||
} else if (getString(R.string.barcode).equals(part)) {
|
||||
cardPart.setVisibility(View.GONE);
|
||||
barcodePart.setVisibility(View.VISIBLE);
|
||||
|
||||
// Redraw barcode due to size change (Visibility.GONE sets it to 0)
|
||||
String formatString = barcodeTypeField.getText().toString();
|
||||
if (formatString.isEmpty() || formatString.equals(getString(R.string.noBarcode))) {
|
||||
hideBarcode();
|
||||
} else {
|
||||
generateBarcode(cardIdFieldView.getText().toString(), BarcodeFormat.valueOf(formatString));
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,23 @@ package protect.card_locker;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.Guideline;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
@@ -23,13 +28,20 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.preferences.Settings;
|
||||
|
||||
public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
@@ -37,11 +49,18 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
TextView cardIdFieldView;
|
||||
BottomSheetBehavior behavior;
|
||||
View bottomSheet;
|
||||
ImageView bottomSheetButton;
|
||||
TextView noteView;
|
||||
View noteViewDivider;
|
||||
TextView groupsView;
|
||||
TextView expiryView;
|
||||
TextView storeName;
|
||||
ImageButton maximizeButton;
|
||||
ImageView barcodeImage;
|
||||
ImageButton minimizeButton;
|
||||
View collapsingToolbarLayout;
|
||||
AppBarLayout appBarLayout;
|
||||
int loyaltyCardId;
|
||||
LoyaltyCard loyaltyCard;
|
||||
boolean rotationEnabled;
|
||||
@@ -52,10 +71,14 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
String cardIdString;
|
||||
BarcodeFormat format;
|
||||
|
||||
FloatingActionButton editButton;
|
||||
|
||||
Guideline centerGuideline;
|
||||
SeekBar barcodeScaler;
|
||||
|
||||
boolean starred;
|
||||
boolean backgroundNeedsDarkIcons;
|
||||
boolean barcodeIsFullscreen = false;
|
||||
ViewGroup.LayoutParams barcodeImageState;
|
||||
|
||||
private void extractIntentFields(Intent intent)
|
||||
{
|
||||
@@ -67,6 +90,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
private Drawable getIcon(int icon, boolean dark)
|
||||
{
|
||||
Drawable unwrappedIcon = AppCompatResources.getDrawable(this, icon);
|
||||
assert unwrappedIcon != null;
|
||||
Drawable wrappedIcon = DrawableCompat.wrap(unwrappedIcon);
|
||||
if(dark)
|
||||
{
|
||||
@@ -91,39 +115,101 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
setContentView(R.layout.loyalty_card_view_layout);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
db = new DBHelper(this);
|
||||
importURIHelper = new ImportURIHelper(this);
|
||||
|
||||
cardIdFieldView = findViewById(R.id.cardIdView);
|
||||
bottomSheet = findViewById(R.id.bottom_sheet);
|
||||
bottomSheetButton = findViewById(R.id.bottomSheetButton);
|
||||
noteView = findViewById(R.id.noteView);
|
||||
noteViewDivider = findViewById(R.id.noteViewDivider);
|
||||
groupsView = findViewById(R.id.groupsView);
|
||||
expiryView = findViewById(R.id.expiryView);
|
||||
storeName = findViewById(R.id.storeName);
|
||||
maximizeButton = findViewById(R.id.maximizeButton);
|
||||
barcodeImage = findViewById(R.id.barcode);
|
||||
minimizeButton = findViewById(R.id.minimizeButton);
|
||||
collapsingToolbarLayout = findViewById(R.id.collapsingToolbarLayout);
|
||||
appBarLayout = findViewById(R.id.app_bar_layout);
|
||||
|
||||
centerGuideline = findViewById(R.id.centerGuideline);
|
||||
centerGuideline.setGuidelinePercent(0.5f);
|
||||
barcodeScaler = findViewById(R.id.barcodeScaler);
|
||||
barcodeScaler.setProgress(100);
|
||||
barcodeScaler.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
Log.d(TAG, "Progress is " + progress);
|
||||
Log.d(TAG, "Max is " + barcodeScaler.getMax());
|
||||
float scale = (float) progress / (float) barcodeScaler.getMax();
|
||||
Log.d(TAG, "Scaling to " + scale);
|
||||
|
||||
redrawBarcodeAfterResize();
|
||||
centerGuideline.setGuidelinePercent(0.5f * scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
rotationEnabled = true;
|
||||
|
||||
// Allow making barcode fullscreen on tap
|
||||
barcodeImage.setOnClickListener(new View.OnClickListener() {
|
||||
maximizeButton.setOnClickListener(v -> setFullscreen(true));
|
||||
barcodeImage.setOnClickListener(view -> {
|
||||
if (barcodeIsFullscreen)
|
||||
{
|
||||
setFullscreen(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFullscreen(true);
|
||||
}
|
||||
});
|
||||
minimizeButton.setOnClickListener(v -> setFullscreen(false));
|
||||
|
||||
editButton = findViewById(R.id.fabEdit);
|
||||
editButton.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCardId);
|
||||
bundle.putBoolean("update", true);
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
|
||||
behavior = BottomSheetBehavior.from(bottomSheet);
|
||||
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(barcodeIsFullscreen)
|
||||
{
|
||||
setFullscreen(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFullscreen(true);
|
||||
public void onStateChanged(@NonNull View bottomSheet, int newState) {
|
||||
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
|
||||
editButton.hide();
|
||||
} else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_down_24);
|
||||
editButton.hide();
|
||||
} else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
bottomSheetButton.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24);
|
||||
editButton.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(@NonNull View bottomSheet, float slideOffset) { }
|
||||
});
|
||||
|
||||
bottomSheetButton.setOnClickListener(v -> {
|
||||
if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
} else {
|
||||
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,15 +229,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
Log.i(TAG, "To view card: " + loyaltyCardId);
|
||||
|
||||
if(barcodeIsFullscreen)
|
||||
{
|
||||
// Completely reset state
|
||||
//
|
||||
// This prevents the barcode from taking up the entire screen
|
||||
// on resume and thus being stretched out of proportion.
|
||||
recreate();
|
||||
}
|
||||
|
||||
// The brightness value is on a scale from [0, ..., 1], where
|
||||
// '1' is the brightest. We attempt to maximize the brightness
|
||||
// to help barcode readers scan the barcode.
|
||||
@@ -172,6 +249,8 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
return;
|
||||
}
|
||||
|
||||
setupOrientation();
|
||||
|
||||
String formatString = loyaltyCard.barcodeType;
|
||||
format = !formatString.isEmpty() ? BarcodeFormat.valueOf(formatString) : null;
|
||||
cardIdString = loyaltyCard.cardId;
|
||||
@@ -183,15 +262,49 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
if(loyaltyCard.note.length() > 0)
|
||||
{
|
||||
noteView.setVisibility(View.VISIBLE);
|
||||
noteView.setText(loyaltyCard.note);
|
||||
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(noteView,
|
||||
getResources().getInteger(R.integer.settings_card_note_min_font_size_sp)-1,
|
||||
settings.getCardNoteFontSize(), 1, TypedValue.COMPLEX_UNIT_SP);
|
||||
}
|
||||
else
|
||||
{
|
||||
noteView.setVisibility(View.GONE);
|
||||
noteViewDivider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
List<Group> loyaltyCardGroups = db.getLoyaltyCardGroups(loyaltyCardId);
|
||||
|
||||
if(loyaltyCardGroups.size() > 0) {
|
||||
StringBuilder groupsString = new StringBuilder();
|
||||
for (Group group : loyaltyCardGroups) {
|
||||
groupsString.append(group._id);
|
||||
groupsString.append(" ");
|
||||
}
|
||||
|
||||
groupsView.setVisibility(View.VISIBLE);
|
||||
groupsView.setText(getString(R.string.groupsList, groupsString.toString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
groupsView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(loyaltyCard.expiry != null) {
|
||||
expiryView.setVisibility(View.VISIBLE);
|
||||
|
||||
int expiryString = R.string.expiryStateSentence;
|
||||
if(Utils.hasExpired(loyaltyCard.expiry)) {
|
||||
expiryString = R.string.expiryStateSentenceExpired;
|
||||
expiryView.setTextColor(getResources().getColor(R.color.alert));
|
||||
}
|
||||
expiryView.setText(getString(expiryString, DateFormat.getDateInstance(DateFormat.LONG).format(loyaltyCard.expiry)));
|
||||
}
|
||||
else
|
||||
{
|
||||
expiryView.setVisibility(View.GONE);
|
||||
}
|
||||
expiryView.setTag(loyaltyCard.expiry);
|
||||
|
||||
if (!barcodeIsFullscreen) {
|
||||
makeBottomSheetVisibleIfUseful();
|
||||
}
|
||||
|
||||
storeName.setText(loyaltyCard.store);
|
||||
@@ -208,6 +321,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
collapsingToolbarLayout.setBackgroundColor(backgroundHeaderColor);
|
||||
appBarLayout.setBackgroundColor(backgroundHeaderColor);
|
||||
|
||||
int textColor;
|
||||
if(Utils.needsDarkForeground(backgroundHeaderColor))
|
||||
@@ -219,6 +333,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
textColor = Color.WHITE;
|
||||
}
|
||||
storeName.setTextColor(textColor);
|
||||
((Toolbar) findViewById(R.id.toolbar_landscape)).setTitleTextColor(textColor);
|
||||
|
||||
// If the background is very bright, we should use dark icons
|
||||
backgroundNeedsDarkIcons = Utils.needsDarkForeground(backgroundHeaderColor);
|
||||
@@ -243,49 +358,31 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
|
||||
if(format != null)
|
||||
{
|
||||
findViewById(R.id.barcode).setVisibility(View.VISIBLE);
|
||||
if (!barcodeIsFullscreen) {
|
||||
maximizeButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
barcodeImage.setVisibility(View.VISIBLE);
|
||||
if(barcodeImage.getHeight() == 0)
|
||||
{
|
||||
Log.d(TAG, "ImageView size is not known known at start, waiting for load");
|
||||
// The size of the ImageView is not yet available as it has not
|
||||
// yet been drawn. Wait for it to be drawn so the size is available.
|
||||
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener()
|
||||
{
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
});
|
||||
redrawBarcodeAfterResize();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.d(TAG, "ImageView size known known, creating barcode");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
|
||||
// Force redraw fullscreen state
|
||||
setFullscreen(barcodeIsFullscreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
findViewById(R.id.barcode).setVisibility(View.GONE);
|
||||
maximizeButton.setVisibility(View.GONE);
|
||||
barcodeImage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
FloatingActionButton editButton = findViewById(R.id.fabEdit);
|
||||
editButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("id", loyaltyCardId);
|
||||
bundle.putBoolean("update", true);
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -297,7 +394,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -374,7 +470,40 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setupOrientation()
|
||||
{
|
||||
Toolbar portraitToolbar = findViewById(R.id.toolbar);
|
||||
Toolbar landscapeToolbar = findViewById(R.id.toolbar_landscape);
|
||||
|
||||
int orientation = getResources().getConfiguration().orientation;
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
Log.d(TAG, "Detected landscape mode");
|
||||
|
||||
setTitle(loyaltyCard.store);
|
||||
|
||||
collapsingToolbarLayout.setVisibility(View.GONE);
|
||||
portraitToolbar.setVisibility(View.GONE);
|
||||
landscapeToolbar.setVisibility(View.VISIBLE);
|
||||
|
||||
setSupportActionBar(landscapeToolbar);
|
||||
} else {
|
||||
Log.d(TAG, "Detected portrait mode");
|
||||
|
||||
setTitle("");
|
||||
|
||||
collapsingToolbarLayout.setVisibility(View.VISIBLE);
|
||||
portraitToolbar.setVisibility(View.VISIBLE);
|
||||
landscapeToolbar.setVisibility(View.GONE);
|
||||
|
||||
setSupportActionBar(portraitToolbar);
|
||||
}
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
private void setOrientatonLock(MenuItem item, boolean lock)
|
||||
{
|
||||
if(lock)
|
||||
@@ -392,6 +521,33 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void makeBottomSheetVisibleIfUseful()
|
||||
{
|
||||
if (noteView.getVisibility() == View.VISIBLE || groupsView.getVisibility() == View.VISIBLE || expiryView.getVisibility() == View.VISIBLE) {
|
||||
bottomSheet.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
bottomSheet.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void redrawBarcodeAfterResize()
|
||||
{
|
||||
if (format != null) {
|
||||
barcodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
|
||||
Log.d(TAG, "ImageView size now known");
|
||||
new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* When enabled, hides the status bar and moves the barcode to the top of the screen.
|
||||
*
|
||||
@@ -401,10 +557,16 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
private void setFullscreen(boolean enable)
|
||||
{
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(enable && !barcodeIsFullscreen)
|
||||
if(enable)
|
||||
{
|
||||
// Save previous barcodeImage state
|
||||
barcodeImageState = barcodeImage.getLayoutParams();
|
||||
Log.d(TAG, "Move into of fullscreen");
|
||||
// Prepare redraw after size change
|
||||
redrawBarcodeAfterResize();
|
||||
|
||||
// Hide maximize and show minimize button and scaler
|
||||
maximizeButton.setVisibility(View.GONE);
|
||||
minimizeButton.setVisibility(View.VISIBLE);
|
||||
barcodeScaler.setVisibility(View.VISIBLE);
|
||||
|
||||
// Hide actionbar
|
||||
if(actionBar != null)
|
||||
@@ -412,55 +574,73 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
|
||||
actionBar.hide();
|
||||
}
|
||||
|
||||
// Hide collapsingToolbar
|
||||
// Hide toolbars
|
||||
//
|
||||
// Appbar needs to be invisible and have padding removed
|
||||
// Or the barcode will be centered instead of on top of the screen
|
||||
// Don't ask me why...
|
||||
appBarLayout.setVisibility(View.INVISIBLE);
|
||||
appBarLayout.setPadding(0, 0, 0, 0);
|
||||
collapsingToolbarLayout.setVisibility(View.GONE);
|
||||
findViewById(R.id.toolbar_landscape).setVisibility(View.GONE);
|
||||
|
||||
// Hide other UI elements
|
||||
cardIdFieldView.setVisibility(View.GONE);
|
||||
bottomSheet.setVisibility(View.GONE);
|
||||
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
editButton.hide();
|
||||
|
||||
// Set Android to fullscreen mode
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// Make barcode take all space
|
||||
barcodeImage.setLayoutParams(new ConstraintLayout.LayoutParams(
|
||||
ConstraintLayout.LayoutParams.MATCH_PARENT,
|
||||
ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
|
||||
// Move barcode to top
|
||||
barcodeImage.setScaleType(ImageView.ScaleType.FIT_START);
|
||||
|
||||
// Prevent centering
|
||||
barcodeImage.setAdjustViewBounds(false);
|
||||
|
||||
// Set current state
|
||||
barcodeIsFullscreen = true;
|
||||
}
|
||||
else if(!enable && barcodeIsFullscreen)
|
||||
else if(!enable)
|
||||
{
|
||||
Log.d(TAG, "Move out of fullscreen");
|
||||
|
||||
// Reset center guideline
|
||||
barcodeScaler.setProgress(100);
|
||||
|
||||
// Prepare redraw after size change
|
||||
redrawBarcodeAfterResize();
|
||||
|
||||
// Show maximize and hide minimize button and scaler
|
||||
maximizeButton.setVisibility(View.VISIBLE);
|
||||
minimizeButton.setVisibility(View.GONE);
|
||||
barcodeScaler.setVisibility(View.GONE);
|
||||
|
||||
// Show actionbar
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.show();
|
||||
}
|
||||
|
||||
// Show collapsingToolbar
|
||||
collapsingToolbarLayout.setVisibility(View.VISIBLE);
|
||||
// Show appropriate toolbar
|
||||
// And restore 24dp paddingTop for appBarLayout
|
||||
appBarLayout.setVisibility(View.VISIBLE);
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
appBarLayout.setPadding(0, (int) Math.ceil(metrics.density * 24), 0, 0);
|
||||
setupOrientation();
|
||||
|
||||
// Show other UI elements
|
||||
cardIdFieldView.setVisibility(View.VISIBLE);
|
||||
makeBottomSheetVisibleIfUseful();
|
||||
editButton.show();
|
||||
|
||||
// Unset fullscreen mode
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
getWindow().getDecorView().getSystemUiVisibility()
|
||||
& ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
& ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// Turn barcode back to normal
|
||||
barcodeImage.setLayoutParams(barcodeImageState);
|
||||
|
||||
// Fix barcode centering
|
||||
barcodeImage.setAdjustViewBounds(true);
|
||||
|
||||
// Set current state
|
||||
barcodeIsFullscreen = false;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.SearchManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
@@ -17,7 +15,6 @@ import androidx.appcompat.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -29,10 +26,8 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.preferences.SettingsActivity;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener
|
||||
@@ -125,13 +120,13 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
|
||||
Object group = null;
|
||||
|
||||
if (groupsTabLayout.getTabCount() != 0) {
|
||||
TabLayout.Tab tab = groupsTabLayout.getTabAt(0);
|
||||
|
||||
if (selectedTab < groupsTabLayout.getTabCount()) {
|
||||
tab = groupsTabLayout.getTabAt(selectedTab);
|
||||
TabLayout.Tab tab = groupsTabLayout.getTabAt(selectedTab);
|
||||
if (tab == null) {
|
||||
tab = groupsTabLayout.getTabAt(0);
|
||||
}
|
||||
|
||||
groupsTabLayout.selectTab(tab);
|
||||
assert tab != null;
|
||||
group = tab.getTag();
|
||||
}
|
||||
updateLoyaltyCardList(filter, group);
|
||||
@@ -141,7 +136,8 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
|
||||
addButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Utils.createSetBarcodeDialog(MainActivity.this, MainActivity.this, false, null);
|
||||
Intent i = new Intent(getApplicationContext(), ScanActivity.class);
|
||||
startActivityForResult(i, Utils.BARCODE_SCAN);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -365,8 +361,13 @@ public class MainActivity extends AppCompatActivity implements GestureDetector.O
|
||||
filter = newText;
|
||||
|
||||
TabLayout groupsTabLayout = findViewById(R.id.groups);
|
||||
TabLayout.Tab currentTab = groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition());
|
||||
|
||||
updateLoyaltyCardList(
|
||||
newText,
|
||||
currentTab != null ? currentTab.getTag() : null
|
||||
);
|
||||
|
||||
updateLoyaltyCardList(newText, groupsTabLayout.getTabCount() > 0 ? groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition()).getTag() : null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
@@ -26,7 +27,6 @@ public class ManageGroupsActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private AlertDialog newGroupDialog;
|
||||
private final DBHelper db = new DBHelper(this);
|
||||
|
||||
@Override
|
||||
@@ -42,8 +42,6 @@ public class ManageGroupsActivity extends AppCompatActivity
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
newGroupDialog = createNewGroupDialog();
|
||||
|
||||
updateGroupList();
|
||||
}
|
||||
|
||||
@@ -57,7 +55,7 @@ public class ManageGroupsActivity extends AppCompatActivity
|
||||
addButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
newGroupDialog.show();
|
||||
createGroup();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -147,6 +145,8 @@ public class ManageGroupsActivity extends AppCompatActivity
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
input.requestFocus();
|
||||
}
|
||||
|
||||
public void deleteGroup(View view) {
|
||||
@@ -175,7 +175,7 @@ public class ManageGroupsActivity extends AppCompatActivity
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private AlertDialog createNewGroupDialog() {
|
||||
private void createGroup() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.enter_group_name);
|
||||
final EditText input = new EditText(this);
|
||||
@@ -196,8 +196,9 @@ public class ManageGroupsActivity extends AppCompatActivity
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
return dialog;
|
||||
dialog.show();
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
input.requestFocus();
|
||||
}
|
||||
|
||||
private String getGroupname(View view) {
|
||||
|
||||
@@ -31,32 +31,23 @@ public class MultiFormatImporter
|
||||
break;
|
||||
}
|
||||
|
||||
if(importer != null)
|
||||
if (importer != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
importer.importData(db, input);
|
||||
return true;
|
||||
}
|
||||
catch(IOException e)
|
||||
catch(IOException | FormatException | InterruptedException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to input data", e);
|
||||
}
|
||||
catch(FormatException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to input data", e);
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
Log.e(TAG, "Failed to input data", e);
|
||||
Log.e(TAG, "Failed to import data", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.e(TAG, "Unsupported data format imported: " + format.name());
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
157
app/src/main/java/protect/card_locker/ScanActivity.java
Normal file
@@ -0,0 +1,157 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.client.android.Intents;
|
||||
import com.journeyapps.barcodescanner.BarcodeCallback;
|
||||
import com.journeyapps.barcodescanner.BarcodeResult;
|
||||
import com.journeyapps.barcodescanner.CaptureManager;
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
/**
|
||||
* Custom Scannner Activity extending from Activity to display a custom layout form scanner view.
|
||||
*
|
||||
* Based on https://github.com/journeyapps/zxing-android-embedded/blob/0fdfbce9fb3285e985bad9971c5f7c0a7a334e7b/sample/src/main/java/example/zxing/CustomScannerActivity.java
|
||||
* originally licensed under Apache 2.0
|
||||
*/
|
||||
public class ScanActivity extends AppCompatActivity {
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private CaptureManager capture;
|
||||
private DecoratedBarcodeView barcodeScannerView;
|
||||
|
||||
private String cardId;
|
||||
|
||||
private void extractIntentFields(Intent intent) {
|
||||
final Bundle b = intent.getExtras();
|
||||
cardId = b != null ? b.getString("cardId") : null;
|
||||
Log.d(TAG, "Scan activity: id=" + cardId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.scan_activity);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
extractIntentFields(getIntent());
|
||||
|
||||
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
|
||||
capture = new CaptureManager(this, barcodeScannerView);
|
||||
Intent captureIntent = new Intent();
|
||||
Bundle captureIntentBundle = new Bundle();
|
||||
captureIntentBundle.putBoolean(Intents.Scan.BEEP_ENABLED, false);
|
||||
captureIntent.putExtras(captureIntentBundle);
|
||||
capture.initializeFromIntent(captureIntent, savedInstanceState);
|
||||
|
||||
barcodeScannerView.decodeSingle(new BarcodeCallback() {
|
||||
@Override
|
||||
public void barcodeResult(BarcodeResult result) {
|
||||
Intent scanResult = new Intent();
|
||||
Bundle scanResultBundle = new Bundle();
|
||||
scanResultBundle.putString(BarcodeSelectorActivity.BARCODE_CONTENTS, result.getText());
|
||||
scanResultBundle.putString(BarcodeSelectorActivity.BARCODE_FORMAT, result.getBarcodeFormat().toString());
|
||||
scanResult.putExtras(scanResultBundle);
|
||||
ScanActivity.this.setResult(RESULT_OK, scanResult);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void possibleResultPoints(List<ResultPoint> resultPoints) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
capture.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
capture.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
capture.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
capture.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
if (item.getItemId() == android.R.id.home)
|
||||
{
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent)
|
||||
{
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
|
||||
BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent);
|
||||
|
||||
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());
|
||||
manualResult.putExtras(manualResultBundle);
|
||||
ScanActivity.this.setResult(RESULT_OK, manualResult);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void addManually(View view) {
|
||||
Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class);
|
||||
if (cardId != null) {
|
||||
final Bundle b = new Bundle();
|
||||
b.putString("initialCardId", cardId);
|
||||
i.putExtras(b);
|
||||
}
|
||||
startActivityForResult(i, Utils.SELECT_BARCODE_REQUEST);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,24 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
public class Utils {
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
// Barcode config dialog
|
||||
private static AlertDialog setBarcodeDialog;
|
||||
|
||||
// Activity request codes
|
||||
public static final int MAIN_REQUEST = 1;
|
||||
public static final int SELECT_BARCODE_REQUEST = 2;
|
||||
public static final int BARCODE_SCAN = 3;
|
||||
|
||||
static final double LUMINANCE_MIDPOINT = 0.5;
|
||||
|
||||
@@ -48,83 +42,19 @@ public class Utils {
|
||||
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
|
||||
}
|
||||
|
||||
static public void startCameraBarcodeScan(Context context, Activity activity) {
|
||||
IntentIntegrator integrator = new IntentIntegrator(activity);
|
||||
integrator.setDesiredBarcodeFormats(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES);
|
||||
|
||||
String prompt = context.getResources().getString(R.string.scanCardBarcode);
|
||||
integrator.setPrompt(prompt);
|
||||
integrator.setBeepEnabled(false);
|
||||
integrator.initiateScan();
|
||||
}
|
||||
|
||||
static public void createSetBarcodeDialog(final Context context, final Activity activity, boolean isUpdate, final String initialCardId) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
// Get the layout inflater
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
|
||||
// Inflate and set the layout for the dialog
|
||||
// Pass null as the parent view because its going in the dialog layout
|
||||
builder.setView(inflater.inflate(R.layout.dialog_create, null));
|
||||
|
||||
if (isUpdate) {
|
||||
builder.setTitle(context.getString(R.string.editCardTitle));
|
||||
} else {
|
||||
builder.setTitle(context.getString(R.string.addCardTitle));
|
||||
}
|
||||
|
||||
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
setBarcodeDialog.cancel();
|
||||
}
|
||||
});
|
||||
setBarcodeDialog = builder.create();
|
||||
setBarcodeDialog.show();
|
||||
|
||||
View addFromCamera = setBarcodeDialog.getWindow().findViewById(R.id.add_from_camera);
|
||||
View addManually = setBarcodeDialog.getWindow().findViewById(R.id.add_manually);
|
||||
|
||||
addFromCamera.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Utils.startCameraBarcodeScan(context, activity);
|
||||
|
||||
setBarcodeDialog.hide();
|
||||
}
|
||||
});
|
||||
|
||||
addManually.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(context, BarcodeSelectorActivity.class);
|
||||
if (initialCardId != null) {
|
||||
final Bundle b = new Bundle();
|
||||
b.putString("initialCardId", initialCardId);
|
||||
i.putExtras(b);
|
||||
}
|
||||
activity.startActivityForResult(i, Utils.SELECT_BARCODE_REQUEST);
|
||||
|
||||
setBarcodeDialog.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
String contents = null;
|
||||
String format = null;
|
||||
|
||||
IntentResult result =
|
||||
IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
if (result != null)
|
||||
if (resultCode == Activity.RESULT_OK)
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from capture");
|
||||
contents = result.getContents();
|
||||
format = result.getFormatName();
|
||||
}
|
||||
|
||||
if(requestCode == Utils.SELECT_BARCODE_REQUEST && resultCode == Activity.RESULT_OK)
|
||||
{
|
||||
Log.i(TAG, "Received barcode information from typing it");
|
||||
if (requestCode == Utils.BARCODE_SCAN) {
|
||||
Log.i(TAG, "Received barcode information from camera");
|
||||
} else if (requestCode == Utils.SELECT_BARCODE_REQUEST) {
|
||||
Log.i(TAG, "Received barcode information from typing it");
|
||||
} else {
|
||||
return new BarcodeValues(null, null);
|
||||
}
|
||||
|
||||
contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS);
|
||||
format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT);
|
||||
@@ -135,4 +65,16 @@ public class Utils {
|
||||
|
||||
return new BarcodeValues(format, contents);
|
||||
}
|
||||
|
||||
static public Boolean hasExpired(Date expiryDate) {
|
||||
// today
|
||||
Calendar date = new GregorianCalendar();
|
||||
// reset hour, minutes, seconds and millis
|
||||
date.set(Calendar.HOUR_OF_DAY, 0);
|
||||
date.set(Calendar.MINUTE, 0);
|
||||
date.set(Calendar.SECOND, 0);
|
||||
date.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
return expiryDate.before(date.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package protect.card_locker.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.annotation.IntegerRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
@@ -81,11 +81,6 @@ public class Settings
|
||||
return getInt(R.string.settings_key_card_id_font_size, R.integer.settings_card_id_font_size_sp);
|
||||
}
|
||||
|
||||
public int getCardNoteFontSize()
|
||||
{
|
||||
return getInt(R.string.settings_key_card_note_font_size, R.integer.settings_card_note_font_size_sp);
|
||||
}
|
||||
|
||||
public boolean useMaxBrightnessDisplayingBarcode()
|
||||
{
|
||||
return getBoolean(R.string.settings_key_display_barcode_max_brightness, true);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package protect.card_locker.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import nl.invissvenska.numberpickerpreference.NumberDialogPreference;
|
||||
import nl.invissvenska.numberpickerpreference.NumberPickerPreferenceDialogFragment;
|
||||
import protect.card_locker.R;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity
|
||||
@@ -20,11 +24,11 @@ public class SettingsActivity extends AppCompatActivity
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar != null)
|
||||
{
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
// Display the fragment as the main content.
|
||||
getFragmentManager().beginTransaction()
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
}
|
||||
@@ -43,17 +47,18 @@ public class SettingsActivity extends AppCompatActivity
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat
|
||||
{
|
||||
private static final String DIALOG_FRAGMENT_TAG = "SettingsFragment";
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState)
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
findPreference(getResources().getString(R.string.settings_key_theme)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
Preference themePreference = findPreference(getResources().getString(R.string.settings_key_theme));
|
||||
assert themePreference != null;
|
||||
themePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o)
|
||||
@@ -71,11 +76,36 @@ public class SettingsActivity extends AppCompatActivity
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
}
|
||||
|
||||
getActivity().recreate();
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity != null) {
|
||||
activity.recreate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference)
|
||||
{
|
||||
if (preference instanceof NumberDialogPreference)
|
||||
{
|
||||
NumberDialogPreference dialogPreference = (NumberDialogPreference) preference;
|
||||
DialogFragment dialogFragment = NumberPickerPreferenceDialogFragment
|
||||
.newInstance(
|
||||
dialogPreference.getKey(),
|
||||
dialogPreference.getMinValue(),
|
||||
dialogPreference.getMaxValue(),
|
||||
dialogPreference.getUnitText()
|
||||
);
|
||||
dialogFragment.setTargetFragment(this, 0);
|
||||
dialogFragment.show(getParentFragmentManager(), DIALOG_FRAGMENT_TAG);
|
||||
}
|
||||
else
|
||||
{
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 139 B |
|
Before Width: | Height: | Size: 140 B |
|
Before Width: | Height: | Size: 327 B |
|
Before Width: | Height: | Size: 161 B |
|
Before Width: | Height: | Size: 163 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 219 B |
|
Before Width: | Height: | Size: 582 B |
|
Before Width: | Height: | Size: 554 B |
|
Before Width: | Height: | Size: 336 B |
|
Before Width: | Height: | Size: 439 B |
|
Before Width: | Height: | Size: 368 B |
|
Before Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 155 B |
|
Before Width: | Height: | Size: 107 B |
|
Before Width: | Height: | Size: 110 B |
|
Before Width: | Height: | Size: 217 B |
|
Before Width: | Height: | Size: 115 B |
|
Before Width: | Height: | Size: 135 B |
|
Before Width: | Height: | Size: 132 B |
|
Before Width: | Height: | Size: 200 B |
|
Before Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 165 B |
|
Before Width: | Height: | Size: 410 B |
|
Before Width: | Height: | Size: 397 B |
|
Before Width: | Height: | Size: 234 B |
|
Before Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 97 B |
|
Before Width: | Height: | Size: 225 B |
|
Before Width: | Height: | Size: 139 B |
|
Before Width: | Height: | Size: 147 B |
|
Before Width: | Height: | Size: 375 B |
|
Before Width: | Height: | Size: 151 B |
|
Before Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 202 B |
|
Before Width: | Height: | Size: 354 B |
|
Before Width: | Height: | Size: 343 B |
|
Before Width: | Height: | Size: 239 B |
|
Before Width: | Height: | Size: 783 B |
|
Before Width: | Height: | Size: 758 B |
|
Before Width: | Height: | Size: 421 B |
|
Before Width: | Height: | Size: 564 B |
|
Before Width: | Height: | Size: 466 B |
|
Before Width: | Height: | Size: 97 B |
|
Before Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 169 B |
|
Before Width: | Height: | Size: 176 B |
|
Before Width: | Height: | Size: 523 B |
|
Before Width: | Height: | Size: 194 B |
|
Before Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 512 B |
|
Before Width: | Height: | Size: 494 B |
|
Before Width: | Height: | Size: 302 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 829 B |
|
Before Width: | Height: | Size: 809 B |
|
Before Width: | Height: | Size: 606 B |
|
Before Width: | Height: | Size: 102 B |
|
Before Width: | Height: | Size: 243 B |
|
Before Width: | Height: | Size: 328 B |
|
Before Width: | Height: | Size: 665 B |
|
Before Width: | Height: | Size: 651 B |
|
Before Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 758 B |
5
app/src/main/res/drawable/ic_add_white_24dp.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
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="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_arrow_back_white.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
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="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
||||
</vector>
|
||||