Compare commits

...

74 Commits
v0.11 ... v0.15

Author SHA1 Message Date
Branden Archer
7758c61079 Merge pull request #157 from brarcher/pre-v0.15
Pre v0.15
2017-11-25 14:48:27 -05:00
Branden Archer
47b30ca9fb Update for v0.15 2017-11-25 14:41:20 -05:00
Branden Archer
7b4d587b13 Update CHANGELOG for v0.15 2017-11-25 14:40:40 -05:00
Branden Archer
685ee018c6 Merge pull request #155 from brarcher/shortcuts
Add support for shortcuts
2017-11-25 14:40:07 -05:00
Branden Archer
e0915578ba Redraw card info when launched by a shortcut
When a shortcut launches the view activity, if the activity
already exists it will get onNewIntent() called. This needs to
clear out the view items so the next card will be redrawn.
2017-11-25 14:33:59 -05:00
Branden Archer
c733a6c3b9 Start shortcuts with singleTop, but not all launches
This will prevent shortcuts from creating many views, and causing
them to leak. However, do not use singleTop for all launches
of the view activity, else the edit mode cannot be entered.
2017-11-25 14:33:59 -05:00
Branden Archer
ec17255a43 Remove unneeded local variables
The global variables have the same information; no need to look
up the information again.
2017-11-25 14:33:59 -05:00
Branden Archer
1fc7baa5a0 Capture view items in onCreate
Some of these will need to be accessed prior to onResume()
2017-11-25 14:33:59 -05:00
Branden Archer
0df411ee96 Revert "Add widget for directly opening specific card"
This is largely unneeded, as shortcuts are directly supported
now, and the widget was a hacky replacement for shortcuts.

This reverts commit ebe6139a64.
2017-11-25 14:33:59 -05:00
Branden Archer
23178d9694 Optionally add shortcut when creating/editing a card
When a card is being added or created, a checkbox will now
be presented asking if a shortcut should be made. If selected,
when saving a shortcut will be added to the home screen. The
shortcut will directly launch the given card in view mode.
2017-11-25 14:33:59 -05:00
Branden Archer
94accc951d Let insertLoyaltyCard return the new ID
Card IDs for new cards will soon need to be known once they
are created. This change updates this call to return the
new id.
2017-11-25 14:33:59 -05:00
Branden Archer
0207e12aed Revert "Only let one card viewing activity exist at a time"
This prevented the editing of cards, as the same activity
was used for viewing and editing, but the viewing "instance"
was calling finish(). It is simpler to remove the singleTop
option.

This reverts commit 4e02252b75.
2017-11-25 14:33:59 -05:00
Branden Archer
c760465b3e Merge pull request #156 from brarcher/mimetype
Report accurate mime type when sending backup data
2017-11-25 14:33:44 -05:00
Branden Archer
f480bd0c7e Report the correct mime type for exported data
Technically text/plain is correct-ish, but text/csv is more correct.
2017-11-25 14:25:47 -05:00
Branden Archer
5599560258 Remove unneeded imports 2017-11-25 14:25:47 -05:00
Branden Archer
7b4c119d7d Merge pull request #153 from brarcher/content-imports
Support importing/exporting via content providers
2017-11-21 21:24:52 -05:00
Branden Archer
6144353079 List correct file name on imports 2017-11-21 21:15:42 -05:00
Branden Archer
03a5334961 Export backups using FileProvider
On Android 7+ providing another activity a file:// Uri is
discouraged. This changes instead uses a content provider
backed by a FileProvider to give backup data to other
activities.
2017-11-21 21:14:47 -05:00
Branden Archer
04e0a5716e Flatten error handling when importing from an activity 2017-11-21 21:12:21 -05:00
Branden Archer
91e3f9f785 Support importing content URIs and file URIs
When importing backed up settings other activities may provide
data via a content URI. This is especially likely on Android 7+,
where providing a file URI is flagged as a security issue.
To support such activities, this commit enables supporting
content URIs for importing settings.
2017-11-21 18:15:56 -05:00
Branden Archer
2ebc862e27 Remove file arg from TaskCompleteListener
Soon more than files will be imported, as content URIs will
also be supported. This then makes the File argument for
onTaskComplete() not always useful, as there may not
be a direct file used. To this end, removing the File
argument as the caller should know what was passed
to the ImportExport task anyway.
2017-11-21 16:41:25 -05:00
Branden Archer
5d122affce Merge pull request #150 from brarcher/proguard
do not obfuscate using proguard
2017-10-01 14:32:48 -04:00
Branden Archer
9e09b9052a do not obfuscate using proguard 2017-10-01 13:53:11 -04:00
Branden Archer
2753b8826f Merge pull request #149 from brarcher/singletop
Only let one card viewing activity exist at a time
2017-09-28 12:59:19 -04:00
Branden Archer
4e02252b75 Only let one card viewing activity exist at a time
Now that there are shortcuts which can launch card viewing activities
directly, if a user does not back out of the activity then there will
be a pile up of activities over time. To prevent this, only let
one such view activity exist at a time.
2017-09-28 11:31:00 -04:00
Branden Archer
647ce00e72 Merge pull request #148 from brarcher/text
Capitalize first letter in store and note fields
2017-09-28 10:03:30 -04:00
Branden Archer
86a5f2fb50 Capitalize first letter in store and note fields 2017-09-28 09:46:14 -04:00
Branden Archer
dca5129031 Merge pull request #147 from brarcher/proguard
Do not strip line number info with proguard
2017-09-27 12:14:48 -04:00
Branden Archer
e30eb00bf7 Do not strip line number info with proguard 2017-09-27 11:46:31 -04:00
Branden Archer
11e5cb8ec2 Merge pull request #146 from brarcher/pre-v0.14
Update for v0.14
2017-09-26 23:25:48 -04:00
Branden Archer
bdd5c8fbbb Update CHANGELOG 2017-09-26 23:19:06 -04:00
Branden Archer
cd79839748 Update for v0.14 2017-09-26 23:14:47 -04:00
Branden Archer
d6b47914c8 Merge pull request #145 from brarcher/app-shortcuts
Add card shortcuts for most recently used cards
2017-09-26 23:12:00 -04:00
Branden Archer
ce1acb83f0 Add card shortcuts for most recently used cards
This adds app shortcuts for the most recently used cards.
When a new card is accessed, it is added to the shortcuts list.
When the list exceeds its maximum size, the least recently
used shortcut is discarded.

Android limits the maximum number of shortcuts to 5, however
it recommends in its documentation to limit this to 4. This
commit limits this to 3, however, as that is aesthetically pleasing.
2017-09-26 22:52:32 -04:00
Branden Archer
bc360aa06c Merge pull request #142 from brarcher/widget
Add widget for directly opening specific card
2017-09-19 13:49:47 -04:00
Branden Archer
ebe6139a64 Add widget for directly opening specific card 2017-09-19 12:58:48 -04:00
Branden Archer
d354fd1877 Merge pull request #138 from brarcher/transifex
Remove missing translations stubbed to English
2017-08-10 21:49:46 -04:00
Branden Archer
00b612d6c7 Remove missing translations stubbed to English
By request, a project on Transifex was created to better
manage translations. To make things less confusing, instead
of using the MissingTranslations lint error, and thus needing
to add stub translations, the progress of translations is now
handled on Transifex.

transifex.com/na-243/loyalty-card-locker/
2017-08-10 21:25:13 -04:00
Branden Archer
a307511193 Merge pull request #137 from brarcher/readme
README: add translating info
2017-08-10 21:22:41 -04:00
Branden Archer
f7f358c5c3 README: add translating info 2017-08-10 21:15:10 -04:00
Branden Archer
f7b50a72a4 Merge pull request #135 from brarcher/changelog
Update CHANGELOG for v0.11.1, v0.12, v0.13
2017-07-26 18:18:36 -04:00
Branden Archer
68ce1fe9fd Update CHANGELOG for v0.11.1, v0.12, v0.13 2017-07-26 18:12:01 -04:00
Branden Archer
e822ab0b56 Merge pull request #133 from brarcher/pre-v0.13
Update for v0.13
2017-07-25 10:04:44 -04:00
Branden Archer
e33ab682a6 Update for v0.13 2017-07-25 09:58:19 -04:00
Branden Archer
0d50ad6d10 Merge pull request #132 from brarcher/check-for-card
Verify card exists before displaying it
2017-07-23 10:27:06 -04:00
Branden Archer
4143e5c286 Bail if loyalty card could not be found
This case was hit at least once by a user, though the scenario
is not known. If it is hit, post a message and bail gracefully,
until the reason can be determined.
2017-07-23 10:05:05 -04:00
Branden Archer
15425d51aa Add debugging when entering view activity 2017-07-22 23:52:21 -04:00
Branden Archer
3abcb32a75 Merge pull request #130 from brarcher/text-update
Change lock/unlock text to say block/unblock
2017-07-18 22:25:47 -04:00
Branden Archer
93124a88a5 Change lock/unlock text to say block/unblock 2017-07-18 22:18:52 -04:00
Branden Archer
29b00e3b59 Merge pull request #129 from airon90/patch-1
Update strings.xml
2017-07-18 22:06:08 -04:00
Michael Moroni
04174154d6 Update strings.xml
Instead of "Block screen" I translated "Block rotation" because I think it's more intuitive
2017-07-17 12:49:39 +02:00
Branden Archer
bf60976d10 Merge pull request #128 from brarcher/lock-orientation
Add option to lock screen orientation
2017-07-16 21:32:55 -04:00
Branden Archer
fb7e3e12f2 Add menu option to lock screen when viewing card
When passing a phone to a clerk to scan the barcode, if the
phone is rotated and the screen reloads it can be bothersome
or confusion. To avoid this situation, a new option is
added to lock the screen.

A menu icon is now added which defaults as unlocked. When
touched the app sets its orientation to the "natural" orientation
of the device. When touched again the sensor dictates the
orientation of the device.
2017-07-16 21:26:40 -04:00
Branden Archer
641d29a6f8 Remove usage of depreated location of ActivityController 2017-07-16 21:26:40 -04:00
Branden Archer
0f2ee296b4 Remove container class for intro slides
Even with passing a bundle with the layout ID sometimes
some users will encounter a situation where the _layout
variable is null. To avoid further issues, creating a
class for each intro slide, so the layout is encoded
into the class itself.
2017-07-16 21:26:40 -04:00
Branden Archer
ecd2ecd517 Merge pull request #127 from brarcher/pre-v0.12
Update for v0.12
2017-07-12 22:55:31 -04:00
Branden Archer
f3e7d8f5c1 Update for v0.12 2017-07-12 22:47:29 -04:00
Branden Archer
f4522fb17d Merge pull request #126 from brarcher/scale-barcode
Scale barcode height to match reduced width
2017-07-12 22:14:03 -04:00
Branden Archer
824a72b156 Scale height of barcode to match ratio when reducing width
When reducing the pixel size of the width, if the height is not
scaled to match then 1D barcodes end up being squished and are
too narrow to scan. To avoid this, scale the height to match.
Then, when the barcode is loaded into a Bitmap it will scale up
to the correct size.

It was found that on a Galaxy S4 a barcode width of 400 px started
to see some blurriness, but 500 px still looked sharp. Reducing
the maximum width to 500 px.
2017-07-11 23:16:26 -04:00
Branden Archer
116a44fd1c Let BarcodeImageWriterTask's constructor be package private 2017-07-11 23:13:46 -04:00
Branden Archer
b6190d3250 Merge pull request #125 from arno-github/patch-3
French update
2017-07-03 15:55:48 -04:00
arno-github
82c40408f7 French update 2017-07-03 21:10:21 +02:00
Branden Archer
c6c9fb763a Merge pull request #124 from arno-github/patch-2
Update french translation
2017-07-02 22:36:30 -04:00
arno-github
dda5dcc652 Update french translation
Cosmetic changes
2017-07-02 20:00:21 +02:00
Branden Archer
d039ad9fa4 Merge pull request #122 from Entze/master
add & refine german translation
2017-06-29 21:44:02 -04:00
Lukas Grassauer
5c70c98eb9 Merge branch 'master' of github.com:brarcher/loyalty-card-locker 2017-06-29 16:28:17 +02:00
Lukas Grassauer
2face21985 add missing and revise de translations 2017-06-29 16:23:10 +02:00
Branden Archer
b38ec7b753 Merge pull request #121 from brarcher/pre-v0.11.1
Update for v0.11.1
2017-06-29 10:13:07 -04:00
Branden Archer
32d5aec76b Update for v0.11.1 2017-06-28 22:49:44 -04:00
Branden Archer
bdbcaf4b1e Merge pull request #120 from brarcher/handleConfigChanges
handle orientation and resize config change for IntroActivity
2017-06-28 22:48:50 -04:00
Branden Archer
f54905f218 handle orientation and resize config change for IntroActivity
If orientation and resize are not handled if the screen is
rotate or resized the IntroSlide fragment loses its layout,
and when it tries inflates the layout it fails to do so.
2017-06-28 22:42:02 -04:00
Branden Archer
2d3bd4a375 Merge pull request #118 from brarcher/credit
Add credit for save icon & update copyright string format
2017-06-26 22:56:36 -04:00
Branden Archer
de4ab95437 Add credit for save icon in about dialog 2017-06-26 22:10:09 -04:00
Branden Archer
09fba71710 Fix string format for copyright year
The year is an integer, but the format was for a string.
2017-06-26 22:10:09 -04:00
46 changed files with 901 additions and 258 deletions

View File

@@ -1,3 +1,37 @@
## v0.15 (2017-11-25)
Changes:
- Add support for adding shortcuts to home screen when adding or editing a card. (https://github.com/brarcher/loyalty-card-locker/pull/155)
- Remove widget, as it was a poor substitute for shortcuts. (https://github.com/brarcher/loyalty-card-locker/pull/155)
- Fix exporting backups on Android 7+. (https://github.com/brarcher/loyalty-card-locker/pull/153)
- Report more accurate mime type when exporting backup data. (https://github.com/brarcher/loyalty-card-locker/pull/156)
- Fix bug where a card could not be edited. (https://github.com/brarcher/loyalty-card-locker/pull/155)
## v0.14 (2017-10-26)
Changes:
- Add support for app shortcuts (Android 7.1+), where the most recently used cards will appear as shortcuts. (https://github.com/brarcher/loyalty-card-locker/pull/145)
- Add a widget which works like a pinned app shortcut, to support devices which run below Android 7.1. (https://github.com/brarcher/loyalty-card-locker/pull/142)
## v0.13 (2017-07-25)
Changes:
- Add screen rotation lock menu option when displaying a card. If locked, the screen will transition to its "natural" orientation and further screen rotation will be blocked. (https://github.com/brarcher/loyalty-card-locker/pull/128)
- If a card is selected from the main screen but cannot be loaded, the application fails gracefully and posts a message. (https://github.com/brarcher/loyalty-card-locker/pull/132)
- Fix case where layout IDs for intro wizard could not be found. (https://github.com/brarcher/loyalty-card-locker/pull/128)
## v0.12 (2017-07-16)
Changes:
- A change in v0.11 reduced the memory usage of barcode drawing, but affected the barcode dimensions. This is now changed to maintain the barcode dimensions while reducing memory usage. (https://github.com/brarcher/loyalty-card-locker/pull/126)
- Update German and French translations. (https://github.com/brarcher/loyalty-card-locker/pull/122, https://github.com/brarcher/loyalty-card-locker/pull/124, https://github.com/brarcher/loyalty-card-locker/pull/125)
## v0.11.1 (2017-06-29)
Changes:
- Prevent a crash when rotation the screen in the first run intro wizard.
## v0.11 (2017-06-26)
Improvements:

View File

@@ -48,6 +48,10 @@ Windows:
./gradlew.bat build
```
# Translating
If you are interested in translating this application to another language, create a pull request with changes or find the project listing on [Transifex](https://www.transifex.com/na-243/loyalty-card-locker).
# Thanks
This application uses the following image:

View File

@@ -13,9 +13,9 @@ android {
defaultConfig {
applicationId "protect.card_locker"
minSdkVersion 17
targetSdkVersion 23
versionCode 11
versionName "0.11"
targetSdkVersion 25
versionCode 16
versionName "0.15"
}
buildTypes {
release {
@@ -27,6 +27,7 @@ android {
disable "GoogleAppIndexingWarning"
disable "ButtonStyle"
disable "AlwaysShowAction"
disable "MissingTranslation"
}
// This is for Robolectric support for SDK 23

View File

@@ -15,3 +15,10 @@
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
-keepattributes SourceFile,LineNumberTable
# This keep the class and method names the same, for debugging stack traces
-dontobfuscate

View File

@@ -8,6 +8,8 @@
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission
android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-feature
android:name="android.hardware.camera"
@@ -36,7 +38,8 @@
android:name=".LoyaltyCardViewActivity"
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateHidden"/>
android:windowSoftInputMode="stateHidden"
android:exported="true"/>
<activity
android:name=".BarcodeSelectorActivity"
android:label="@string/selectBarcodeTitle"
@@ -49,9 +52,19 @@
android:configChanges="orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".IntroActivity"
android:name=".intro.IntroActivity"
android:label=""
android:configChanges="orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar"/>
<provider
android:name="android.support.v4.content.FileProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="${applicationId}">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/>
</provider>
</application>
</manifest>

View File

@@ -21,7 +21,7 @@ import java.lang.ref.WeakReference;
class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
{
private static final String TAG = "LoyaltyCardLocker";
private static final int MAX_WIDTH = 600;
private static final int MAX_WIDTH = 500;
private final WeakReference<ImageView> imageViewReference;
private final String cardId;
@@ -29,7 +29,7 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
private final int imageHeight;
private final int imageWidth;
public BarcodeImageWriterTask(ImageView imageView, String cardIdString,
BarcodeImageWriterTask(ImageView imageView, String cardIdString,
BarcodeFormat barcodeFormat)
{
// Use a WeakReference to ensure the ImageView can be garbage collected
@@ -37,11 +37,19 @@ class BarcodeImageWriterTask extends AsyncTask<Void, Void, Bitmap>
cardId = cardIdString;
format = barcodeFormat;
imageHeight = imageView.getHeight();
// No matter how long the window is, there is only so much space
// needed for the barcode. Put a limit on it to reduce memory usage
imageWidth = Math.min(imageView.getWidth(), MAX_WIDTH);
if(imageView.getWidth() < MAX_WIDTH)
{
imageHeight = imageView.getHeight();
imageWidth = imageView.getWidth();
}
else
{
// Scale down the image to reduce the memory needed to produce it
imageWidth = MAX_WIDTH;
double ratio = (double)MAX_WIDTH / (double)imageView.getWidth();
imageHeight = (int)(imageView.getHeight() * ratio);
}
}
public Bitmap doInBackground(Void... params)

View File

@@ -51,7 +51,7 @@ public class DBHelper extends SQLiteOpenHelper
}
}
public boolean insertLoyaltyCard(final String store, final String note, final String cardId,
public long insertLoyaltyCard(final String store, final String note, final String cardId,
final String barcodeType)
{
SQLiteDatabase db = getWritableDatabase();
@@ -61,7 +61,7 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.CARD_ID, cardId);
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
return newId;
}
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,

View File

@@ -7,12 +7,15 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.provider.OpenableColumns;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@@ -24,6 +27,9 @@ import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
public class ImportExportActivity extends AppCompatActivity
@@ -132,24 +138,34 @@ public class ImportExportActivity extends AppCompatActivity
@Override
public void onClick(View v)
{
startImport(exportFile);
Uri uri = Uri.fromFile(exportFile);
try
{
FileInputStream stream = new FileInputStream(exportFile);
startImport(stream, uri);
}
catch(FileNotFoundException e)
{
Log.e(TAG, "Could not import file " + exportFile.getAbsolutePath(), e);
onImportComplete(false, uri);
}
}
});
}
private void startImport(File target)
private void startImport(final InputStream target, final Uri targetUri)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success, File file)
public void onTaskComplete(boolean success)
{
onImportComplete(success, file);
onImportComplete(success, targetUri);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
true, DataFormat.CSV, target, listener);
DataFormat.CSV, target, listener);
importExporter.execute();
}
@@ -158,14 +174,14 @@ public class ImportExportActivity extends AppCompatActivity
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success, File file)
public void onTaskComplete(boolean success)
{
onExportComplete(success, file);
onExportComplete(success, exportFile);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
false, DataFormat.CSV, exportFile, listener);
DataFormat.CSV, exportFile, listener);
importExporter.execute();
}
@@ -220,7 +236,34 @@ public class ImportExportActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
private void onImportComplete(boolean success, File path)
private String fileNameFromUri(Uri uri)
{
if("file".equals(uri.getScheme()))
{
return uri.getPath();
}
Cursor returnCursor =
getContentResolver().query(uri, null, null, null, null);
if(returnCursor == null)
{
return null;
}
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if(returnCursor.moveToFirst() == false)
{
returnCursor.close();
return null;
}
String name = returnCursor.getString(nameIndex);
returnCursor.close();
return name;
}
private void onImportComplete(boolean success, Uri path)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -236,7 +279,15 @@ public class ImportExportActivity extends AppCompatActivity
int messageId = success ? R.string.importedFrom : R.string.importFailed;
final String template = getResources().getString(messageId);
final String message = String.format(template, path.getAbsolutePath());
// Get the filename of the file being imported
String filename = fileNameFromUri(path);
if(filename == null)
{
filename = "(unknown)";
}
final String message = String.format(template, filename);
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
@@ -286,10 +337,13 @@ public class ImportExportActivity extends AppCompatActivity
@Override
public void onClick(DialogInterface dialog, int which)
{
Uri outputUri = Uri.fromFile(path);
Uri outputUri = FileProvider.getUriForFile(ImportExportActivity.this, BuildConfig.APPLICATION_ID, path);
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, outputUri);
sendIntent.setType("text/plain");
sendIntent.setType("text/csv");
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
@@ -344,34 +398,28 @@ public class ImportExportActivity extends AppCompatActivity
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == CHOOSE_EXPORT_FILE)
{
String path = null;
Uri uri = data.getData();
if(uri != null && uri.toString().startsWith("/"))
{
uri = Uri.parse("file://" + uri.toString());
}
if(uri != null)
{
path = uri.getPath();
}
if(path != null)
{
Log.e(TAG, "Starting file import with: " + uri.toString());
startImport(new File(path));
}
else
{
Log.e(TAG, "Fail to make sense of URI returned from activity: " + (uri != null ? uri.toString() : "null"));
}
}
else
if (resultCode != RESULT_OK || requestCode != CHOOSE_EXPORT_FILE)
{
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
return;
}
Uri uri = data.getData();
if(uri == null)
{
Log.e(TAG, "Activity returned a NULL URI");
return;
}
try
{
InputStream reader = getContentResolver().openInputStream(uri);
Log.e(TAG, "Starting file import with: " + uri.toString());
startImport(reader, uri);
}
catch (FileNotFoundException e)
{
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
}
}
}

View File

@@ -12,6 +12,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
@@ -24,29 +25,46 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
private boolean doImport;
private DataFormat format;
private File target;
private InputStream inputStream;
private TaskCompleteListener listener;
private ProgressDialog progress;
public ImportExportTask(Activity activity, boolean doImport, DataFormat format, File target,
/**
* Constructor which will setup a task for exporting to the given file
*/
ImportExportTask(Activity activity, DataFormat format, File target,
TaskCompleteListener listener)
{
super();
this.activity = activity;
this.doImport = doImport;
this.doImport = false;
this.format = format;
this.target = target;
this.listener = listener;
}
private boolean performImport(File importFile, DBHelper db)
/**
* Constructor which will setup a task for importing from the given InputStream.
*/
ImportExportTask(Activity activity, DataFormat format, InputStream input,
TaskCompleteListener listener)
{
super();
this.activity = activity;
this.doImport = true;
this.format = format;
this.inputStream = input;
this.listener = listener;
}
private boolean performImport(InputStream stream, DBHelper db)
{
boolean result = false;
try
{
FileInputStream fileReader = new FileInputStream(importFile);
InputStreamReader reader = new InputStreamReader(fileReader, Charset.forName("UTF-8"));
InputStreamReader reader = new InputStreamReader(stream, Charset.forName("UTF-8"));
result = MultiFormatImporter.importData(db, reader, format);
reader.close();
}
@@ -55,7 +73,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
Log.e(TAG, "Unable to import file", e);
}
Log.i(TAG, "Import of '" + importFile.getAbsolutePath() + "' result: " + result);
Log.i(TAG, "Import result: " + result);
return result;
}
@@ -105,7 +123,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
if(doImport)
{
result = performImport(target, db);
result = performImport(inputStream, db);
}
else
{
@@ -117,7 +135,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
protected void onPostExecute(Boolean result)
{
listener.onTaskComplete(result, target);
listener.onTaskComplete(result);
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
@@ -130,7 +148,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
}
interface TaskCompleteListener
{
void onTaskComplete(boolean success, File file);
void onTaskComplete(boolean success);
}
}

View File

@@ -1,43 +0,0 @@
package protect.card_locker;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v4.app.Fragment;
import com.github.paolorotolo.appintro.AppIntro;
public class IntroActivity extends AppIntro
{
@Override
public void init(Bundle savedInstanceState)
{
addIntroSlide(R.layout.intro1_layout);
addIntroSlide(R.layout.intro2_layout);
addIntroSlide(R.layout.intro3_layout);
addIntroSlide(R.layout.intro4_layout);
addIntroSlide(R.layout.intro5_layout);
addIntroSlide(R.layout.intro6_layout);
}
private void addIntroSlide(@LayoutRes int layout)
{
Fragment slide = new IntroSlide();
Bundle args = new Bundle();
args.putInt("layout", layout);
slide.setArguments(args);
addSlide(slide);
}
@Override
public void onSkipPressed(Fragment fragment) {
finish();
}
@Override
public void onDonePressed(Fragment fragment) {
finish();
}
}

View File

@@ -2,8 +2,11 @@ package protect.card_locker;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
@@ -19,9 +22,11 @@ import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.integration.android.IntentIntegrator;
@@ -38,6 +43,9 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
TextView storeFieldView;
EditText noteFieldEdit;
TextView noteFieldView;
CheckBox shortcutCheckbox;
View shortcutBorder;
View shortcutTablerow;
TextView cardIdFieldView;
View cardIdDivider;
View cardIdTableRow;
@@ -53,8 +61,22 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
boolean updateLoyaltyCard;
boolean viewLoyaltyCard;
boolean rotationEnabled;
DBHelper db;
private void extractIntentFields(Intent intent)
{
final Bundle b = intent.getExtras();
loyaltyCardId = b != null ? b.getInt("id") : 0;
updateLoyaltyCard = b != null && b.getBoolean("update", false);
viewLoyaltyCard = b != null && b.getBoolean("view", false);
Log.d(TAG, "View activity: id=" + loyaltyCardId
+ ", updateLoyaltyCard=" + Boolean.toString(updateLoyaltyCard)
+ ", viewLoyaltyCard=" + Boolean.toString(viewLoyaltyCard));
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -69,12 +91,40 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
actionBar.setDisplayHomeAsUpEnabled(true);
}
final Bundle b = getIntent().getExtras();
loyaltyCardId = b != null ? b.getInt("id") : 0;
updateLoyaltyCard = b != null && b.getBoolean("update", false);
viewLoyaltyCard = b != null && b.getBoolean("view", false);
extractIntentFields(getIntent());
db = new DBHelper(this);
storeFieldEdit = (EditText) findViewById(R.id.storeNameEdit);
storeFieldView = (TextView) findViewById(R.id.storeNameView);
noteFieldEdit = (EditText) findViewById(R.id.noteEdit);
noteFieldView = (TextView) findViewById(R.id.noteView);
shortcutCheckbox = (CheckBox) findViewById(R.id.shortcutCheckbox);
shortcutBorder = findViewById(R.id.shortcutBorder);
shortcutTablerow = findViewById(R.id.shortcutTablerow);
cardIdFieldView = (TextView) findViewById(R.id.cardIdView);
cardIdDivider = findViewById(R.id.cardIdDivider);
cardIdTableRow = findViewById(R.id.cardIdTableRow);
barcodeTypeField = (TextView) findViewById(R.id.barcodeType);
barcodeImage = (ImageView) findViewById(R.id.barcode);
barcodeImageLayout = findViewById(R.id.barcodeLayout);
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
captureButton = (Button) findViewById(R.id.captureButton);
enterButton = (Button) findViewById(R.id.enterButton);
}
@Override
public void onNewIntent(Intent intent)
{
Log.i(TAG, "Received new intent");
extractIntentFields(intent);
// Reset these fields, so they are re-populated in onResume().
storeFieldEdit.setText("");
noteFieldEdit.setText("");
cardIdFieldView.setText("");
barcodeTypeField.setText("");
}
@Override
@@ -98,24 +148,16 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
}
storeFieldEdit = (EditText) findViewById(R.id.storeNameEdit);
storeFieldView = (TextView) findViewById(R.id.storeNameView);
noteFieldEdit = (EditText) findViewById(R.id.noteEdit);
noteFieldView = (TextView) findViewById(R.id.noteView);
cardIdFieldView = (TextView) findViewById(R.id.cardIdView);
cardIdDivider = findViewById(R.id.cardIdDivider);
cardIdTableRow = findViewById(R.id.cardIdTableRow);
barcodeTypeField = (TextView) findViewById(R.id.barcodeType);
barcodeImage = (ImageView) findViewById(R.id.barcode);
barcodeImageLayout = findViewById(R.id.barcodeLayout);
barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout);
captureButton = (Button) findViewById(R.id.captureButton);
enterButton = (Button) findViewById(R.id.enterButton);
if(updateLoyaltyCard || viewLoyaltyCard)
{
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
if(loyaltyCard == null)
{
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
Toast.makeText(this, R.string.noCardExistsError, Toast.LENGTH_LONG).show();
finish();
return;
}
if(storeFieldEdit.getText().length() == 0)
{
@@ -164,6 +206,9 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
noteFieldView.setVisibility(View.GONE);
}
shortcutBorder.setVisibility(viewLoyaltyCard ? View.GONE : View.VISIBLE);
shortcutTablerow.setVisibility(viewLoyaltyCard ? View.GONE : View.VISIBLE);
if(cardIdFieldView.getText().length() > 0 && barcodeTypeField.getText().length() > 0)
{
String formatString = barcodeTypeField.getText().toString();
@@ -257,6 +302,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
{
String store = storeFieldEdit.getText().toString();
String note = noteFieldEdit.getText().toString();
boolean shouldAddShortcut = shortcutCheckbox.isChecked();
String cardId = cardIdFieldView.getText().toString();
String barcodeType = barcodeTypeField.getText().toString();
@@ -279,19 +325,45 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
else
{
db.insertLoyaltyCard(store, note, cardId, barcodeType);
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType);
}
if(shouldAddShortcut)
{
addShortcut(loyaltyCardId, store);
}
finish();
}
private void addShortcut(int id, String name)
{
Intent shortcutIntent = new Intent(this, LoyaltyCardViewActivity.class);
shortcutIntent.setAction(Intent.ACTION_MAIN);
// Prevent instances of the view activity from piling up; if one exists let this
// one replace it.
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Bundle bundle = new Bundle();
bundle.putInt("id", id);
bundle.putBoolean("view", true);
shortcutIntent.putExtras(bundle);
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource
.fromContext(this, R.mipmap.ic_launcher));
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
// Do not duplicate the shortcut if it is already there
intent.putExtra("duplicate", false);
getApplicationContext().sendBroadcast(intent);
Toast.makeText(this, R.string.addedShortcut, Toast.LENGTH_LONG).show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
final Bundle b = getIntent().getExtras();
final boolean updateLoyaltyCard = b != null && b.getBoolean("update", false);
final boolean viewLoyaltyCard = b != null && b.getBoolean("view", false);
if(viewLoyaltyCard)
{
getMenuInflater().inflate(R.menu.card_view_menu, menu);
@@ -305,6 +377,8 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
getMenuInflater().inflate(R.menu.card_add_menu, menu);
}
rotationEnabled = true;
return super.onCreateOptionsMenu(menu);
}
@@ -313,9 +387,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
{
int id = item.getItemId();
final Bundle b = getIntent().getExtras();
final int loyaltyCardId = b != null ? b.getInt("id") : 0;
switch(id)
{
case android.R.id.home:
@@ -335,6 +406,9 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
DBHelper db = new DBHelper(LoyaltyCardViewActivity.this);
db.deleteLoyaltyCard(loyaltyCardId);
ShortcutHelper.removeShortcut(LoyaltyCardViewActivity.this, loyaltyCardId);
finish();
dialog.dismiss();
}
@@ -361,6 +435,22 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
finish();
return true;
case R.id.action_lock_unlock:
if(rotationEnabled)
{
item.setIcon(R.drawable.ic_lock_outline_white_24dp);
item.setTitle(R.string.unlockScreen);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
}
else
{
item.setIcon(R.drawable.ic_lock_open_white_24dp);
item.setTitle(R.string.lockScreen);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
rotationEnabled = !rotationEnabled;
return true;
case R.id.action_save:
doSave();
return true;

View File

@@ -29,6 +29,8 @@ import com.google.common.collect.ImmutableMap;
import java.util.Calendar;
import java.util.Map;
import protect.card_locker.intro.IntroActivity;
public class MainActivity extends AppCompatActivity
{
private static final String TAG = "LoyaltyCardLocker";
@@ -91,10 +93,14 @@ public class MainActivity extends AppCompatActivity
LoyaltyCard loyaltyCard = LoyaltyCard.toLoyaltyCard(selected);
Intent i = new Intent(view.getContext(), LoyaltyCardViewActivity.class);
i.setAction("");
final Bundle b = new Bundle();
b.putInt("id", loyaltyCard.id);
b.putBoolean("view", true);
i.putExtras(b);
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i);
startActivity(i);
}
});
@@ -185,6 +191,11 @@ public class MainActivity extends AppCompatActivity
"AppIntro", "https://github.com/apl-devs/AppIntro"
);
final Map<String, String> USED_ASSETS = ImmutableMap.of
(
"Save by Bernar Novalyi", "https://thenounproject.com/term/save/716011"
);
StringBuilder libs = new StringBuilder().append("<ul>");
for (Map.Entry<String, String> entry : USED_LIBRARIES.entrySet())
{
@@ -192,6 +203,13 @@ public class MainActivity extends AppCompatActivity
}
libs.append("</ul>");
StringBuilder resources = new StringBuilder().append("<ul>");
for (Map.Entry<String, String> entry : USED_ASSETS.entrySet())
{
resources.append("<li><a href=\"").append(entry.getValue()).append("\">").append(entry.getKey()).append("</a></li>");
}
resources.append("</ul>");
String appName = getString(R.string.app_name);
int year = Calendar.getInstance().get(Calendar.YEAR);
@@ -229,7 +247,9 @@ public class MainActivity extends AppCompatActivity
"</p><hr/><p>" +
getString(R.string.app_license) +
"</p><hr/><p>" +
String.format(getString(R.string.app_libraries), appName, libs.toString());
String.format(getString(R.string.app_libraries), appName, libs.toString()) +
"</p><hr/><p>" +
String.format(getString(R.string.app_resources), appName, resources.toString());
wv.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html", "utf-8", null);
new AlertDialog.Builder(this)

View File

@@ -0,0 +1,153 @@
package protect.card_locker;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.os.Build;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
class ShortcutHelper
{
// Android documentation says that no more than 5 shortcuts
// are supported. However, that may be too many, as not all
// launcher will show all 5. Instead, the number is limited
// to 3 here, so that the most recent shortcut has a good
// chance of being shown.
private static final int MAX_SHORTCUTS = 3;
/**
* Add a card to the app shortcuts, and maintain a list of the most
* recently used cards. If there is already a shortcut for the card,
* the card is marked as the most recently used card. If adding this
* card exceeds the max number of shortcuts, then the least recently
* used card shortcut is discarded.
*/
@TargetApi(25)
static void updateShortcuts(Context context, LoyaltyCard card, Intent intent)
{
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1)
{
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
LinkedList<ShortcutInfo> list = new LinkedList<>(shortcutManager.getDynamicShortcuts());
String shortcutId = Integer.toString(card.id);
// Sort the shortcuts by rank, so working with the relative order will be easier.
// This sorts so that the lowest rank is first.
Collections.sort(list, new Comparator<ShortcutInfo>()
{
@Override
public int compare(ShortcutInfo o1, ShortcutInfo o2)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
{
return o1.getRank() - o2.getRank();
}
else
{
return 0;
}
}
});
Integer foundIndex = null;
for(int index = 0; index < list.size(); index++)
{
if(list.get(index).getId().equals(shortcutId))
{
// Found the item already
foundIndex = index;
break;
}
}
if(foundIndex != null)
{
// If the item is already found, then the list needs to be
// reordered, so that the selected item now has the lowest
// rank, thus letting it survive longer.
ShortcutInfo found = list.remove(foundIndex.intValue());
list.addFirst(found);
}
else
{
// The item is new to the list. First, we need to trim the list
// until it is able to accept a new item, then the item is
// inserted.
while(list.size() >= MAX_SHORTCUTS)
{
list.pollLast();
}
ShortcutInfo shortcut = new ShortcutInfo.Builder(context, Integer.toString(card.id))
.setShortLabel(card.store)
.setLongLabel(card.store)
.setIntent(intent)
.build();
list.addFirst(shortcut);
}
LinkedList<ShortcutInfo> finalList = new LinkedList<>();
// The ranks are now updated; the order in the list is the rank.
for(int index = 0; index < list.size(); index++)
{
ShortcutInfo prevShortcut = list.get(index);
Intent shortcutIntent = prevShortcut.getIntent();
// Prevent instances of the view activity from piling up; if one exists let this
// one replace it.
shortcutIntent.setFlags(shortcutIntent.getFlags() | Intent.FLAG_ACTIVITY_SINGLE_TOP);
ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(context, prevShortcut.getId())
.setShortLabel(prevShortcut.getShortLabel())
.setLongLabel(prevShortcut.getLongLabel())
.setIntent(shortcutIntent)
.setIcon(Icon.createWithResource(context, R.drawable.circle))
.setRank(index)
.build();
finalList.addLast(updatedShortcut);
}
shortcutManager.setDynamicShortcuts(finalList);
}
}
/**
* Remove the given card id from the app shortcuts, if such a
* shortcut exists.
*/
@TargetApi(25)
static void removeShortcut(Context context, int cardId)
{
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1)
{
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
List<ShortcutInfo> list = shortcutManager.getDynamicShortcuts();
String shortcutId = Integer.toString(cardId);
for(int index = 0; index < list.size(); index++)
{
if(list.get(index).getId().equals(shortcutId))
{
list.remove(index);
break;
}
}
shortcutManager.setDynamicShortcuts(list);
}
}
}

View File

@@ -0,0 +1,33 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import com.github.paolorotolo.appintro.AppIntro;
public class IntroActivity extends AppIntro
{
@Override
public void init(Bundle savedInstanceState)
{
addSlide(new IntroSlide1());
addSlide(new IntroSlide2());
addSlide(new IntroSlide3());
addSlide(new IntroSlide4());
addSlide(new IntroSlide5());
addSlide(new IntroSlide6());
}
@Override
public void onSkipPressed(Fragment fragment) {
finish();
}
@Override
public void onDonePressed(Fragment fragment) {
finish();
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide1 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro1_layout, container, false);
return v;
}
}

View File

@@ -1,4 +1,4 @@
package protect.card_locker;
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@@ -6,20 +6,15 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class IntroSlide extends Fragment
{
int _layout;
import protect.card_locker.R;
@Override
public void setArguments(Bundle bundle)
{
_layout = bundle.getInt("layout");
}
public class IntroSlide2 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(_layout, container, false);
View v = inflater.inflate(R.layout.intro2_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide3 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro3_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide4 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro4_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide5 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro5_layout, container, false);
return v;
}
}

View File

@@ -0,0 +1,19 @@
package protect.card_locker.intro;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import protect.card_locker.R;
public class IntroSlide6 extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.intro6_layout, container, false);
return v;
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

View File

@@ -65,6 +65,7 @@
<EditText
android:id="@+id/storeNameEdit"
android:inputType="textCapWords"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
@@ -121,6 +122,7 @@
<EditText
android:id="@+id/noteEdit"
android:inputType="textCapSentences"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
@@ -144,6 +146,55 @@
android:background="@color/inputBorder" />
</TableRow>
<!-- Add Shortcut -->
<View
android:id="@+id/shortcutBorder"
android:layout_height="@dimen/inputBorderThickness"
android:layout_width="match_parent"
android:background="@color/inputBorder" />
<TableRow
android:id="@+id/shortcutTablerow"
android:background="@color/inputBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:gravity="start"
android:layout_height="match_parent"
android:layout_width="@dimen/inputBorderThickness"
android:background="@color/inputBorder" />
<RelativeLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="@dimen/inputPadding"
android:paddingEnd="@dimen/inputPadding">
<TextView
android:id="@+id/shortcutField"
android:text="@string/addShortcut"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:textSize="@dimen/inputSize"
android:padding="@dimen/inputPadding"
android:layout_alignParentStart="true"/>
<CheckBox
android:id="@+id/shortcutCheckbox"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="@dimen/inputPadding"
android:textSize="@dimen/inputSize"
android:layout_toEndOf="@id/shortcutField"/>
</RelativeLayout>
<View
android:gravity="end"
android:layout_height="match_parent"
android:layout_width="@dimen/inputBorderThickness"
android:background="@color/inputBorder" />
</TableRow>
<!-- Card ID -->
<View
android:id="@+id/cardIdDivider"

View File

@@ -2,6 +2,11 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_lock_unlock"
android:icon="@drawable/ic_lock_open_white_24dp"
android:title="@string/lockScreen"
app:showAsAction="always"/>
<item
android:id="@+id/action_edit"
android:icon="@drawable/ic_mode_edit_white_24dp"

View File

@@ -15,10 +15,11 @@
<string name="save">Uložit</string>
<string name="capture">Naskenovat kartu</string>
<string name="enterCard">Vložit vlastnoručně</string>
<!-- NEEDS TRANSLATED --><string name="editCard">Edit Card</string>
<string name="edit">Editovat</string>
<string name="delete">Smazat</string>
<string name="confirm">Potvrdit</string>
<string name="deleteTitle">Odstzranit věrnostní kartu</string>
<string name="deleteConfirmation">Opravdu chcete smazat tuto věrnostní kartu?</string>
<string name="ok">Ano</string>
@@ -34,6 +35,7 @@
<string name="noStoreError">Nebyl zadán Obchod</string>
<string name="noCardIdError">Nebylo zadáno ID karty</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Import/Export</string>
@@ -49,7 +51,7 @@
<string name="noExternalStoragePermissionError">Nelze importovat nebo exportovat karty bez přístupu k externímu uložišti</string>
<string name="about">O aplikaci</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licensed under the GPLv3.</string>
<string name="about_title_fmt">O aplikaci <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Verze: <xliff:g id="version">%s</xliff:g></string>
@@ -77,17 +79,4 @@
<string name="importOptionFixedButton">Použít složku exportu</string>
<string name="sendLabel">Odeslat&#8230;</string>
<!-- NEEDS TRANSLATED --><string name="startIntro">Start Intro</string>
<!-- NEEDS TRANSLATED --><string name="intro1Title">Welcome to Loyalty Card Keychain\n</string>
<!-- NEEDS TRANSLATED --><string name="intro1Description">Manage your barcode-based store/loyalty cards on your phone!\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro2Title">Adding Cards\n</string>
<!-- NEEDS TRANSLATED --><string name="intro2Description">Add a new card by touching the plus from the card list.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro3Title">Adding Cards\n</string>
<!-- NEEDS TRANSLATED --><string name="intro3Description">To add the barcode, either capture with the camera or type in manually.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro4Title">Show Card\n</string>
<!-- NEEDS TRANSLATED --><string name="intro4Description">To display a card, click on the store name from the main screen\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro5Title">Backup\n</string>
<!-- NEEDS TRANSLATED --><string name="intro5Description">The cards can be backed-up. To export or import card data touch Import/Export in the menu on the main page.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro6Title">Feedback\n</string>
<!-- NEEDS TRANSLATED --><string name="intro6Description">This app is free, ad-free, and open source. See details by touching About in the menu on the main page.\n\nPlease leave feedback in the app store! (:</string>
</resources>

View File

@@ -5,17 +5,18 @@
<string name="about_title_fmt">Über <xliff:g id="app_name">%s</xliff:g></string>
<string name="action_add">Neu</string>
<string name="addCardTitle">Neue Kundenkarte</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Lizensiert unter der GPLv3.</string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> benutzt die folgenden Fremdbibliotheken: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> verwendet folgenden Dritt-Ressourcen: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="viewCardTitle">Kundenkarte anzeigen</string>
<string name="ok">Ok</string>
<string name="note">Notiz</string>
<string name="save">Speichern</string>
<string name="scanCardBarcode">Barcode scannen</string>
<string name="selectBarcodeTitle">Barcode auswählen</string>
<string name="storeName">Firma</string>
<string name="noStoreError">Keine Firma angegeben</string>
<string name="storeName">Geschäft</string>
<string name="noStoreError">Kein Geschäft angegeben</string>
<string name="exportName">Exportieren</string>
<string name="exportedTo">Exportiert nach: %1$s</string>
<string name="fileMissing">Datei fehlt: %1$s</string>
@@ -24,27 +25,29 @@
<string name="importName">Import</string>
<string name="importedFrom">Importiert von: %1$s</string>
<string name="noCardIdError">Keine Kartennummer angegeben</string>
<string name="noExternalStoragePermissionError">Ohne die Berechtigung für den externen Speicher kann kein Import oder Export erfolgen.</string>
<string name="noGiftCards">Du hast noch keine Kundenkarte angelegt. Über den "+" Button oben rechts, kannst du welche anlegen.\n\nDiese App ermöglicht es dir, deine Kundenkarten immer mit dir zu führen.</string>
<string name="cancel">Abrechen</string>
<string name="capture">Karte aufnehmen</string>
<string name="noGiftCards">Sie haben noch keine Kundenkarte angelegt. Über den "+" Button oben rechts, können welche angelegt werden.\n\nDiese App ermöglicht es, Kundenkarten immer mit zu führen.</string>
<string name="cancel">Abbrechen</string>
<string name="capture">Karte erfassen</string>
<string name="cardId">Kartennummer</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="confirm">Bestätigen</string>
<string name="copy_to_clipboard">Kopiere die Nummer in die Zwischenablage</string>
<string name="delete">Löschen</string>
<string name="deleteConfirmation">Bitte bestätige, dass du die Karte löschen möchtest.</string>
<string name="deleteTitle">Lösche die Kundenkarte</string>
<string name="deleteConfirmation">Bitte bestätigen Sie, dass diese Karte gelöscht werden soll.</string>
<string name="edit">Bearbeiten</string>
<string name="editCardTitle">Kundenkarte bearbeiten</string>
<string name="enterCard">Karte einfügen</string>
<!-- NEEDS TRANSLATED --><string name="editCard">Edit Card</string>
<string name="editCard">Karte bearbeiten</string>
<string name="exportFailed">Export fehlgeschlagen: %1$s</string>
<string name="barcodeType">Barcodeart</string>
<string name="barcodeImageDescription">Bild des Barcodes</string>
<string name="copy_to_clipboard_toast">Nummer in die Zwischenablage kopiert</string>
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
<string name="enterBarcodeInstructions">Füge die Kundennummer ein, anschließend wähle die korrekte Barcodeart aus.</string>
<string name="enterBarcodeInstructions">Fügen Sie die Kundennummer ein, anschließend wählen Sie die korrekte Barcodeart aus.</string>
<string name="app_revision_fmt">Versions Information: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="importing">Importiere…</string>
<string name="exporting">Exportiere…</string>
@@ -54,29 +57,29 @@
<string name="importFailedTitle">Import fehlgeschlagen</string>
<string name="exportSuccessfulTitle">Export erfolgreich</string>
<string name="exportFailedTitle">Export fehlgeschlagen</string>
<string name="exportOptionExplanation">Die Datei wird ins Wurzelverzeichnis des externen Speichers geschrieben.</string>
<string name="exportOptionExplanation">Die Datei wird ins Rootverzeichnis des externen Speichers geschrieben.</string>
<string name="importOptionFilesystemTitle">Importiere vom Dateisystem</string>
<string name="importOptionFilesystemExplanation">Wähle eine Datei im Speicher aus.</string>
<string name="importOptionFilesystemButton">Vom Dateisystem</string>
<string name="importOptionApplicationTitle">Nutze eine externe App</string>
<string name="importOptionApplicationExplanation">Wähle eine Datei aus einer App wie Dropbox, Google Drive, oder deinem bevorzugten Dateisystem aus.</string>
<string name="importOptionApplicationExplanation">Wählen Sie eine Datei aus einer App wie Dropbox, Google Drive, oder Ihrem bevorzugten Dateisystem aus.</string>
<string name="importOptionApplicationButton">Nutze eine externe App</string>
<string name="importOptionFixedTitle">Importiere vom Export Ort</string>
<string name="importOptionFixedExplanation">Importiere von derselben Stelle, wo die exportiere Datei liegen würde.</string>
<string name="importOptionFixedButton">Verwende die exportierte Stelle</string>
<string name="importOptionFixedTitle">Importiere vom Export-Pfad</string>
<string name="importOptionFixedExplanation">Importiere vom Export-Pfad.</string>
<string name="importOptionFixedButton">Verwende den Export-Pfad.</string>
<string name="sendLabel">Senden&#8230;</string>
<!-- NEEDS TRANSLATED --><string name="startIntro">Start Intro</string>
<!-- NEEDS TRANSLATED --><string name="intro1Title">Welcome to Loyalty Card Keychain\n</string>
<!-- NEEDS TRANSLATED --><string name="intro1Description">Manage your barcode-based store/loyalty cards on your phone!\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro2Title">Adding Cards\n</string>
<!-- NEEDS TRANSLATED --><string name="intro2Description">Add a new card by touching the plus from the card list.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro3Title">Adding Cards\n</string>
<!-- NEEDS TRANSLATED --><string name="intro3Description">To add the barcode, either capture with the camera or type in manually.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro4Title">Show Card\n</string>
<!-- NEEDS TRANSLATED --><string name="intro4Description">To display a card, click on the store name from the main screen\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro5Title">Backup\n</string>
<!-- NEEDS TRANSLATED --><string name="intro5Description">The cards can be backed-up. To export or import card data touch Import/Export in the menu on the main page.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro6Title">Feedback\n</string>
<!-- NEEDS TRANSLATED --><string name="intro6Description">This app is free, ad-free, and open source. See details by touching About in the menu on the main page.\n\nPlease leave feedback in the app store! (:</string>
</resources>
<string name="startIntro">Starte Einführung</string>
<string name="intro1Title">Willkommen zu Loyalty Card Keychain\n</string>
<string name="intro1Description">Verwalten Sie Ihre Barcode-Kundenkarten auf Ihrem Smartphone!\n\n</string>
<string name="intro2Title">Karten hinzufügen\n</string>
<string name="intro2Description">Fügen Sie neue Karten hinzu indem Sie das "+" Symbol in der Liste berühren.\n\n</string>
<string name="intro3Title">Karten hinzufügen\n</string>
<string name="intro3Description">Um einen Barcode hinzuzufügen, verwenden Sie die Kamera oder geben Sie den Code manuell ein.\n\n</string>
<string name="intro4Title">Karte anzeigen\n</string>
<string name="intro4Description">Um eine Karte anzuzeigen, den entsprechenen Namen in der Hauptansicht anwählen.\n\n</string>
<string name="intro5Title">Backup\n</string>
<string name="intro5Description">Sie können selbstverständlich Backups anlegen. Um Karten zu exportieren oder importieren wählen Sie Import/Export im Menü auf dem Hauptbildschirm.\n\n</string>
<string name="intro6Title">Feedback\n</string>
<string name="intro6Description">Diese App enthält keine Werbung, und ist freie und quelloffene Software. Für Details berühren Sie Über im Menü auf der Hauptseite.\n\nHinterlassen Sie uns ein Feedback im App-Store (:</string>
</resources>

View File

@@ -18,6 +18,7 @@
<string name="editCard">Modifier</string>
<string name="edit">Modifier</string>
<string name="delete">Supprimer</string>
<string name="confirm">Confirmer</string>
<string name="deleteTitle">Supprimer la carte de fidélité</string>
<string name="deleteConfirmation">Confirmez que vous souhaitez supprimer cette carte</string>
@@ -49,12 +50,13 @@
<string name="noExternalStoragePermissionError">Impossible d\'importer ou d\'exporter les données sans l\'autorisation d\'accès au stockage externe</string>
<string name="about">À propos</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licence GPLv3.</string>
<string name="about_title_fmt">À propos de <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Version : <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Notes sur les versions : <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> utilise les bibliothèques-tierces suivantes : <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> utilise les ressources-tierces suivantes : <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Choisissez le code-barre</string>
<string name="enterBarcodeInstructions">Saisissez les chiffres du code-barres et sélectionnez l\'image qui le représente</string>
@@ -78,11 +80,11 @@
<string name="importOptionFixedButton">Utiliser l\'emplacement de l\'export</string>
<string name="sendLabel">Envoyer&#8230;</string>
<string name="startIntro">Démarrer la présentation</string>
<string name="intro1Title">Bienvenue dans Loyalty Card Keychain\n</string>
<string name="intro1Description">Gérez vos cartes de fidélité à codes-barres sur votre téléphone !\n\n</string>
<string name="startIntro">Présentation</string>
<string name="intro1Title">Bienvenue dans\nLoyalty Card Keychain\n</string>
<string name="intro1Description">Gérez vos cartes de fidélité\nsur votre téléphone !\n\n</string>
<string name="intro2Title">Ajouter une carte\n</string>
<string name="intro2Description">Touchez le signe \'+\' en haut de la liste des cartes pour en ajouter une.\n\n</string>
<string name="intro2Description">Touchez le signe \'+\' en haut de l\'écran pour ajouter une carte.\n\n</string>
<string name="intro3Title">Ajouter une carte\n</string>
<string name="intro3Description">Pour enregistrer le code-barres, utilisez l\'appareil-photo ou entrez-le manuellement.\n\n</string>
<string name="intro4Title">Afficher une carte\n</string>

View File

@@ -26,6 +26,7 @@
<string name="noStoreError">Nessun negozio inserito</string>
<string name="noCardIdError">Nessun codice carta inserito</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="note">Note</string>
@@ -41,12 +42,13 @@
<string name="exporting">Esportazione in corso&#8230;</string>
<string name="noExternalStoragePermissionError">Impossibile importare o esportare i dati senza il permesso per l\'uso della memoria esterna.</string>
<string name="about">Informazioni</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Pubblicato sotto licenza GPLv3.</string>
<string name="about_title_fmt">Informazioni su <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versione: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Informazione sulla revisione: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> usa le seguenti librerie di terze parti: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> usa le seguenti risorse di terze parti: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="ok">Ok</string>
<string name="enterCard">Inserisci carta</string>
<string name="editCard">Modifica carta</string>
@@ -55,6 +57,8 @@
<string name="copy_to_clipboard">Copia ID negli appunti</string>
<string name="copy_to_clipboard_toast">ID della carta copiato negli appunti</string>
<string name="confirm">Conferma</string>
<string name="lockScreen">Blocca rotazione</string>
<string name="unlockScreen">Sblocca rotazione</string>
<string name="deleteTitle">Rimuovi carta fedeltà</string>
<string name="deleteConfirmation">Conferma che vuoi eliminare questa carta.</string>
<string name="importExportHelp">Fare il backup dei dati ti permette di spostare le tue tessere da un dispositivo ad un altro.</string>

View File

@@ -15,10 +15,11 @@
<string name="save">Išsaugoti</string>
<string name="capture">Nufotografuoti kortelę</string>
<string name="enterCard">Įvesti kortelę</string>
<!-- NEEDS TRANSLATED --><string name="editCard">Edit Card</string>
<string name="edit">Redaguoti</string>
<string name="delete">Ištrinti</string>
<string name="confirm">Patvirtinti</string>
<string name="deleteTitle">Panaikinti lojalumo kortelę</string>
<string name="deleteConfirmation">Prašome patvirtinti jog Jūs norite panaikinti šią lojalumo kortelę.</string>
<string name="ok">Gerai</string>
@@ -34,6 +35,7 @@
<string name="noStoreError">Parduotuvė neįvesta</string>
<string name="noCardIdError">Neįvestas kortelės ID</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Importuoti/Exportuoti</string>
@@ -49,7 +51,7 @@
<string name="noExternalStoragePermissionError">Negalima importuoti/eksportuoti kortelių be išorinės atminties leidimo</string>
<string name="about">Apie</string>
<string name="app_copyright_fmt">Visos teisės saugomos 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Visos teisės saugomos 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licenzijuota pagal GPLv3.</string>
<string name="about_title_fmt">Apie <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versija: <xliff:g id="version">%s</xliff:g></string>
@@ -61,34 +63,4 @@
<string name="copy_to_clipboard_toast">Kortelės ID nukopijuota į iškarpinę</string>
<!-- needs translated --><string name="importExportHelp">Backed up data can allow you to move your cards to another device.</string>
<!-- needs translated --><string name="importSuccessfulTitle">Import successful</string>
<!-- needs translated --><string name="importFailedTitle">Import failed</string>
<!-- needs translated --><string name="exportSuccessfulTitle">Export successful</string>
<!-- needs translated --><string name="exportFailedTitle">Export failed</string>
<!-- needs translated --><string name="exportOptionExplanation">Data is written to the top directory in external storage.</string>
<!-- needs translated --><string name="importOptionFilesystemTitle">Import from filesystem</string>
<!-- needs translated --><string name="importOptionFilesystemExplanation">Choose a specific file from the filesystem.</string>
<!-- needs translated --><string name="importOptionFilesystemButton">From filesystem</string>
<!-- needs translated --><string name="importOptionApplicationTitle">Use external application</string>
<!-- needs translated --><string name="importOptionApplicationExplanation">Use an external application like Dropbox, Google Drive, or your favorite file manager to open a file.</string>
<!-- needs translated --><string name="importOptionApplicationButton">Use external application</string>
<!-- needs translated --><string name="importOptionFixedTitle">Import from export location</string>
<!-- needs translated --><string name="importOptionFixedExplanation">Import from the same location on the filesystem that is written to on export.</string>
<!-- needs translated --><string name="importOptionFixedButton">Use export location</string>
<!-- needs translated --> <string name="sendLabel">Send&#8230;</string>
<!-- NEEDS TRANSLATED --><string name="startIntro">Start Intro</string>
<!-- NEEDS TRANSLATED --><string name="intro1Title">Welcome to Loyalty Card Keychain\n</string>
<!-- NEEDS TRANSLATED --><string name="intro1Description">Manage your barcode-based store/loyalty cards on your phone!\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro2Title">Adding Cards\n</string>
<!-- NEEDS TRANSLATED --><string name="intro2Description">Add a new card by touching the plus from the card list.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro3Title">Adding Cards\n</string>
<!-- NEEDS TRANSLATED --><string name="intro3Description">To add the barcode, either capture with the camera or type in manually.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro4Title">Show Card\n</string>
<!-- NEEDS TRANSLATED --><string name="intro4Description">To display a card, click on the store name from the main screen\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro5Title">Backup\n</string>
<!-- NEEDS TRANSLATED --><string name="intro5Description">The cards can be backed-up. To export or import card data touch Import/Export in the menu on the main page.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro6Title">Feedback\n</string>
<!-- NEEDS TRANSLATED --><string name="intro6Description">This app is free, ad-free, and open source. See details by touching About in the menu on the main page.\n\nPlease leave feedback in the app store! (:</string>
</resources>

View File

@@ -41,15 +41,16 @@
<string name="exporting">Exporterende&#8230;</string>
<string name="noExternalStoragePermissionError">Niet mogelijk te importeren of exporteren zonder rechten op externe opslag</string>
<string name="about">Over</string>
<string name="app_copyright_fmt">Auteursrecht 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Auteursrecht 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Gelicenseerd met GPLv3.</string>
<string name="about_title_fmt">Over <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Versie: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Revisieïnformatie: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> gebruikt de volgende bibliotheken van derden: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="ok">Oké</string>
<string name="enterCard">Voer kaart in</string>
<!-- NEEDS TRANSLATED --><string name="editCard">Edit Card</string>
<string name="selectBarcodeTitle">Selecteer barcode</string>
<string name="enterBarcodeInstructions">Voer de waarde van de barcode in en kies daarna de afbeelding die de barcode die je wil gebruiken representeert</string>
<string name="copy_to_clipboard">Kopieer het ID naar het klembord</string>
@@ -75,17 +76,4 @@
<string name="importOptionFixedButton">gebruik exporteerlocaite</string>
<string name="sendLabel">Verzend&#8230;</string>
<!-- NEEDS TRANSLATED --><string name="startIntro">Start Intro</string>
<!-- NEEDS TRANSLATED --><string name="intro1Title">Welcome to Loyalty Card Keychain\n</string>
<!-- NEEDS TRANSLATED --><string name="intro1Description">Manage your barcode-based store/loyalty cards on your phone!\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro2Title">Adding Cards\n</string>
<!-- NEEDS TRANSLATED --><string name="intro2Description">Add a new card by touching the plus from the card list.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro3Title">Adding Cards\n</string>
<!-- NEEDS TRANSLATED --><string name="intro3Description">To add the barcode, either capture with the camera or type in manually.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro4Title">Show Card\n</string>
<!-- NEEDS TRANSLATED --><string name="intro4Description">To display a card, click on the store name from the main screen\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro5Title">Backup\n</string>
<!-- NEEDS TRANSLATED --><string name="intro5Description">The cards can be backed-up. To export or import card data touch Import/Export in the menu on the main page.\n\n</string>
<!-- NEEDS TRANSLATED --><string name="intro6Title">Feedback\n</string>
<!-- NEEDS TRANSLATED --><string name="intro6Description">This app is free, ad-free, and open source. See details by touching About in the menu on the main page.\n\nPlease leave feedback in the app store! (:</string>
</resources>

View File

@@ -8,6 +8,7 @@
<string name="storeName">Store</string>
<string name="note">Note</string>
<string name="addShortcut">Add Shortcut</string>
<string name="cardId">Card ID</string>
<string name="barcodeType">Barcode Type</string>
@@ -19,11 +20,14 @@
<string name="edit">Edit</string>
<string name="delete">Delete</string>
<string name="confirm">Confirm</string>
<string name="lockScreen">Block Rotation</string>
<string name="unlockScreen">Unblock Rotation</string>
<string name="deleteTitle">Remove Loyalty Card</string>
<string name="deleteConfirmation">Please confirm that you want to delete this card.</string>
<string name="ok">OK</string>
<string name="copy_to_clipboard">Copy ID to clipboard</string>
<string name="sendLabel">Send&#8230;</string>
<string name="addedShortcut">Added shortcut</string>
<string name="editCardTitle">Edit Loyalty Card</string>
<string name="addCardTitle">Add Loyalty Card</string>
@@ -34,6 +38,7 @@
<string name="noStoreError">No Store entered</string>
<string name="noCardIdError">No Card ID entered</string>
<string name="noCardExistsError">Could not lookup loyalty card</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="storeNameAndNoteFormat" translatable="false">%1$s - %2$s</string>
@@ -66,12 +71,13 @@
<string name="importOptionFixedButton">Use export location</string>
<string name="about">About</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%d</xliff:g> Branden Archer</string>
<string name="app_license">Licensed under the GPLv3.</string>
<string name="about_title_fmt">About <xliff:g id="app_name">%s</xliff:g></string>
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
<string name="app_revision_fmt">Revision Information: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="app_libraries"><xliff:g id="app_name">%s</xliff:g> uses the following third-party libraries: <xliff:g id="app_libraries_list">%s</xliff:g></string>
<string name="app_resources"><xliff:g id="app_name">%s</xliff:g> uses the following third-party resources: <xliff:g id="app_resources_list">%s</xliff:g></string>
<string name="selectBarcodeTitle">Select Barcode</string>
<string name="enterBarcodeInstructions">Enter the barcode value then select the image which represents the barcode you want to use</string>

View File

@@ -0,0 +1,3 @@
<paths>
<external-path name="exportPath" path="/" />
</paths>

View File

@@ -36,7 +36,8 @@ public class DatabaseTest
public void addRemoveOneGiftCard()
{
assertEquals(0, db.getLoyaltyCardCount());
boolean result = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -56,7 +57,8 @@ public class DatabaseTest
@Test
public void updateGiftCard()
{
boolean result = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString());
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -86,7 +88,8 @@ public class DatabaseTest
@Test
public void emptyGiftCardValues()
{
boolean result = db.insertLoyaltyCard("", "", "", "");
long id = db.insertLoyaltyCard("", "", "", "");
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -107,8 +110,9 @@ public class DatabaseTest
// that they are sorted
for(int index = CARDS_TO_ADD-1; index >= 0; index--)
{
boolean result = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
long id = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
BarcodeFormat.UPC_A.toString());
boolean result = (id != -1);
assertTrue(result);
}

View File

@@ -17,6 +17,8 @@ import org.robolectric.annotation.Config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
@@ -63,7 +65,8 @@ public class ImportExportTest
{
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
boolean result = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE);
long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE);
boolean result = (id != -1);
assertTrue(result);
}
@@ -211,17 +214,15 @@ public class ImportExportTest
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener
{
Boolean success;
File file;
public void onTaskComplete(boolean success, File file)
public void onTaskComplete(boolean success)
{
this.success = success;
this.file = file;
}
}
@Test
public void useImportExportTask()
public void useImportExportTask() throws FileNotFoundException
{
final int NUM_CARDS = 10;
@@ -235,7 +236,7 @@ public class ImportExportTest
TestTaskCompleteListener listener = new TestTaskCompleteListener();
// Export to the file
ImportExportTask task = new ImportExportTask(activity, false, format, exportFile, listener);
ImportExportTask task = new ImportExportTask(activity, format, exportFile, listener);
task.execute();
// Actually run the task to completion
@@ -244,8 +245,6 @@ public class ImportExportTest
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertNotNull(listener.file);
assertEquals(exportFile, listener.file);
clearDatabase();
@@ -253,7 +252,9 @@ public class ImportExportTest
listener = new TestTaskCompleteListener();
task = new ImportExportTask(activity, true, format, exportFile, listener);
FileInputStream fileStream = new FileInputStream(exportFile);
task = new ImportExportTask(activity, format, fileStream, listener);
task.execute();
// Actually run the task to completion
@@ -262,8 +263,6 @@ public class ImportExportTest
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertNotNull(listener.file);
assertEquals(exportFile, listener.file);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());

View File

@@ -6,8 +6,10 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
@@ -24,12 +26,14 @@ import org.robolectric.annotation.Config;
import org.robolectric.res.builder.RobolectricPackageManager;
import org.robolectric.shadows.ShadowActivity;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.util.ActivityController;
import org.robolectric.android.controller.ActivityController;
import java.io.IOException;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
@RunWith(RobolectricTestRunner.class)
@@ -85,6 +89,7 @@ public class LoyaltyCardViewActivityTest
*/
private void saveLoyaltyCardWithArguments(final Activity activity,
final String store, final String note,
final boolean addShortcut,
final String cardId,
final String barcodeType,
boolean creatingNewCard)
@@ -101,18 +106,25 @@ public class LoyaltyCardViewActivityTest
final EditText storeField = (EditText) activity.findViewById(R.id.storeNameEdit);
final EditText noteField = (EditText) activity.findViewById(R.id.noteEdit);
final CheckBox shortcutCheckbox = (CheckBox) activity.findViewById(R.id.shortcutCheckbox);
final TextView cardIdField = (TextView) activity.findViewById(R.id.cardIdView);
final TextView barcodeTypeField = (TextView) activity.findViewById(R.id.barcodeType);
storeField.setText(store);
noteField.setText(note);
shortcutCheckbox.setChecked(addShortcut);
cardIdField.setText(cardId);
barcodeTypeField.setText(barcodeType);
ShortcutAddedReceiver shortcutAddedReceiver = new ShortcutAddedReceiver();
shortcutAddedReceiver.registerReceiver(activity);
assertEquals(false, activity.isFinishing());
shadowOf(activity).clickMenuItem(R.id.action_save);
assertEquals(true, activity.isFinishing());
shortcutAddedReceiver.unregisterReceiver(activity);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -120,6 +132,28 @@ public class LoyaltyCardViewActivityTest
assertEquals(note, card.note);
assertEquals(cardId, card.cardId);
assertEquals(barcodeType, card.barcodeType);
Intent shortcutRequest = shortcutAddedReceiver.lastRequest();
if(addShortcut)
{
assertNotNull(shortcutRequest);
String name = shortcutRequest.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
assertEquals(card.store, name);
Intent startIntent = shortcutRequest.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
assertNotNull(startIntent);
Bundle startBundle = startIntent.getExtras();
assertNotNull(startBundle);
assertEquals(card.id, startBundle.getInt("id", -1));
assertEquals(true, startBundle.getBoolean("view", false));
}
else
{
assertNull(shortcutRequest);
}
}
/**
@@ -182,6 +216,8 @@ public class LoyaltyCardViewActivityTest
checkFieldProperties(activity, R.id.storeNameEdit, editVisibility, store);
checkFieldProperties(activity, R.id.storeNameView, viewVisibility, store);
checkFieldProperties(activity, R.id.noteEdit, editVisibility, note);
checkFieldProperties(activity, R.id.shortcutBorder, editVisibility, null);
checkFieldProperties(activity, R.id.shortcutTablerow, editVisibility, null);
checkFieldProperties(activity, R.id.noteView, viewVisibility, note);
checkFieldProperties(activity, R.id.cardIdView, View.VISIBLE, cardId);
checkFieldProperties(activity, R.id.cardIdDivider, cardId.isEmpty() ? View.GONE : View.VISIBLE, null);
@@ -272,7 +308,30 @@ public class LoyaltyCardViewActivityTest
checkAllFields(activity, ViewMode.ADD_CARD, "", "", BARCODE_DATA, BARCODE_TYPE);
// Save and check the gift card
saveLoyaltyCardWithArguments(activity, "store", "note", BARCODE_DATA, BARCODE_TYPE, true);
saveLoyaltyCardWithArguments(activity, "store", "note", false, BARCODE_DATA, BARCODE_TYPE, true);
}
@Test
public void startWithoutParametersCaptureBarcodeCreateLoyaltyCardSaveShortcut() throws IOException
{
registerMediaStoreIntentHandler();
ActivityController activityController = Robolectric.buildActivity(LoyaltyCardViewActivity.class).create();
activityController.start();
activityController.visible();
activityController.resume();
Activity activity = (Activity)activityController.get();
checkAllFields(activity, ViewMode.ADD_CARD, "", "", "", "");
// Complete barcode capture successfully
captureBarcodeWithResult(activity, R.id.captureButton, true);
checkAllFields(activity, ViewMode.ADD_CARD, "", "", BARCODE_DATA, BARCODE_TYPE);
// Save and check the gift card
saveLoyaltyCardWithArguments(activity, "store", "note", true, BARCODE_DATA, BARCODE_TYPE, true);
}
@Test
@@ -414,4 +473,47 @@ public class LoyaltyCardViewActivityTest
shadowOf(activity).clickMenuItem(android.R.id.home);
assertEquals(true, activity.isFinishing());
}
@Test
public void checkMenu() throws IOException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE);
activityController.start();
activityController.visible();
activityController.resume();
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
// The settings and add button should be present
assertEquals(menu.size(), 2);
assertEquals("Block Rotation", menu.findItem(R.id.action_lock_unlock).getTitle().toString());
assertEquals("Edit", menu.findItem(R.id.action_edit).getTitle().toString());
}
@Test
public void startWithMissingLoyaltyCard() throws IOException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
activityController.start();
activityController.visible();
activityController.resume();
// The activity should find that the card is missing and shut down
assertTrue(activity.isFinishing());
// Make sure the activity can close down
activityController.pause();
activityController.stop();
activityController.destroy();
}
}

View File

@@ -21,7 +21,7 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
import org.robolectric.android.controller.ActivityController;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -131,7 +131,7 @@ public class MainActivityTest
ComponentName componentName = next.getComponent();
String name = componentName.flattenToShortString();
assertEquals("protect.card_locker/.IntroActivity", name);
assertEquals("protect.card_locker/.intro.IntroActivity", name);
Bundle extras = next.getExtras();
assertNull(extras);

View File

@@ -0,0 +1,39 @@
package protect.card_locker;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
public class ShortcutAddedReceiver extends BroadcastReceiver
{
public static final String SHORTCUT_ADD_REQUEST = "com.android.launcher.action.INSTALL_SHORTCUT";
private Intent _request = null;
@Override
public void onReceive(Context context, Intent intent)
{
_request = intent;
}
public void registerReceiver(Context context)
{
context.registerReceiver(this, new IntentFilter(SHORTCUT_ADD_REQUEST));
}
public void unregisterReceiver(Context context)
{
context.unregisterReceiver(this);
}
public Intent lastRequest()
{
return _request;
}
public void reset()
{
_request = null;
}
}