Merge branch 'master' into feature/dark_mode

This commit is contained in:
Branden Archer
2019-12-02 23:30:32 -08:00
committed by GitHub
17 changed files with 266 additions and 20 deletions

View File

@@ -44,7 +44,17 @@
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateHidden"
android:exported="true"/>
android:exported="true">
<intent-filter android:label="@string/intent_import_card_from_url">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "https://github.com/brarcher/loyalty-card-locker/” -->
<data android:scheme="https"
android:host="@string/intent_import_card_from_url_host"
android:pathPrefix="@string/intent_import_card_from_url_path_prefix" />
</intent-filter>
</activity>
<activity
android:name=".BarcodeSelectorActivity"
android:label="@string/selectBarcodeTitle"

View File

@@ -0,0 +1,79 @@
package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import java.io.InvalidObjectException;
public class ImportURIHelper {
private static final String STORE = DBHelper.LoyaltyCardDbIds.STORE;
private static final String NOTE = DBHelper.LoyaltyCardDbIds.NOTE;
private static final String CARD_ID = DBHelper.LoyaltyCardDbIds.CARD_ID;
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
private static final String HEADER_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_COLOR;
private static final String HEADER_TEXT_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR;
private final Context context;
private final String host;
private final String path;
private final String shareText;
public ImportURIHelper(Context context) {
this.context = context;
host = context.getResources().getString(R.string.intent_import_card_from_url_host);
path = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix);
shareText = context.getResources().getString(R.string.intent_import_card_from_url_share_text);
}
private boolean isImportUri(Uri uri) {
return uri.getHost().equals(host) && uri.getPath().equals(path);
}
public LoyaltyCard parse(Uri uri) throws InvalidObjectException {
if(!isImportUri(uri)) {
throw new InvalidObjectException("Not an import URI");
}
try {
String store = uri.getQueryParameter(STORE);
String note = uri.getQueryParameter(NOTE);
String cardId = uri.getQueryParameter(CARD_ID);
String barcodeType = uri.getQueryParameter(BARCODE_TYPE);
Integer headerColor = Integer.parseInt(uri.getQueryParameter(HEADER_COLOR));
Integer headerTextColor = Integer.parseInt(uri.getQueryParameter(HEADER_TEXT_COLOR));
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor);
} catch (NullPointerException | NumberFormatException ex) {
throw new InvalidObjectException("Not a valid import URI");
}
}
// Protected for usage in tests
protected Uri toUri(LoyaltyCard loyaltyCard) {
Uri.Builder uriBuilder = new Uri.Builder();
uriBuilder.scheme("https");
uriBuilder.authority(host);
uriBuilder.path(path);
uriBuilder.appendQueryParameter(STORE, loyaltyCard.store);
uriBuilder.appendQueryParameter(NOTE, loyaltyCard.note);
uriBuilder.appendQueryParameter(CARD_ID, loyaltyCard.cardId);
uriBuilder.appendQueryParameter(BARCODE_TYPE, loyaltyCard.barcodeType);
uriBuilder.appendQueryParameter(HEADER_COLOR, loyaltyCard.headerColor.toString());
uriBuilder.appendQueryParameter(HEADER_TEXT_COLOR, loyaltyCard.headerTextColor.toString());
return uriBuilder.build();
}
private void startShareIntent(Uri uri) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText + "\n" + uri.toString());
sendIntent.setType("text/plain");
Intent shareIntent = Intent.createChooser(sendIntent, null);
context.startActivity(shareIntent);
}
public void startShareIntent(LoyaltyCard loyaltyCard) {
startShareIntent(toUri(loyaltyCard));
}
}

View File

@@ -5,6 +5,7 @@ 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 android.support.design.widget.Snackbar;
@@ -29,6 +30,8 @@ import com.google.zxing.integration.android.IntentResult;
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
import java.io.InvalidObjectException;
public class LoyaltyCardEditActivity extends AppCompatActivity
{
private static final String TAG = "CardLocker";
@@ -54,16 +57,19 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
int loyaltyCardId;
boolean updateLoyaltyCard;
Uri importLoyaltyCardUri = null;
Integer headingColorValue = null;
Integer headingStoreTextColorValue = null;
DBHelper db;
ImportURIHelper importUriHelper;
private void extractIntentFields(Intent intent)
{
final Bundle b = intent.getExtras();
loyaltyCardId = b != null ? b.getInt("id") : 0;
updateLoyaltyCard = b != null && b.getBoolean("update", false);
importLoyaltyCardUri = intent.getData();
Log.d(TAG, "View activity: id=" + loyaltyCardId
+ ", updateLoyaltyCard=" + Boolean.toString(updateLoyaltyCard));
@@ -86,6 +92,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
extractIntentFields(getIntent());
db = new DBHelper(this);
importUriHelper = new ImportURIHelper(this);
storeFieldEdit = findViewById(R.id.storeNameEdit);
noteFieldEdit = findViewById(R.id.noteEdit);
@@ -163,7 +170,6 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
{
headingColorValue = LetterBitmap.getDefaultColor(this, loyaltyCard.store);
}
headingColorSample.setBackgroundColor(headingColorValue);
}
if(headingStoreTextColorValue == null)
@@ -173,11 +179,29 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
{
headingStoreTextColorValue = Color.WHITE;
}
headingStoreTextColorSample.setBackgroundColor(headingStoreTextColorValue);
}
setTitle(R.string.editCardTitle);
}
else if(importLoyaltyCardUri != null)
{
// Try to parse
LoyaltyCard importCard;
try {
importCard = importUriHelper.parse(importLoyaltyCardUri);
} catch (InvalidObjectException ex) {
Toast.makeText(this, R.string.failedParsingImportUriError, Toast.LENGTH_LONG).show();
finish();
return;
}
storeFieldEdit.setText(importCard.store);
noteFieldEdit.setText(importCard.note);
cardIdFieldView.setText(importCard.cardId);
barcodeTypeField.setText(importCard.barcodeType);
headingColorValue = importCard.headerColor;
headingStoreTextColorValue = importCard.headerTextColor;
}
else
{
setTitle(R.string.addCardTitle);
@@ -190,16 +214,14 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
final int color = (int)(Math.random() * colors.length());
headingColorValue = colors.getColor(color, Color.BLACK);
colors.recycle();
headingColorSample.setBackgroundColor(headingColorValue);
}
if(headingStoreTextColorValue == null)
{
if(headingStoreTextColorValue == null) {
headingStoreTextColorValue = Color.WHITE;
headingStoreTextColorSample.setBackgroundColor(headingStoreTextColorValue);
}
headingColorSample.setBackgroundColor(headingColorValue);
headingStoreTextColorSample.setBackgroundColor(headingStoreTextColorValue);
headingColorSelectButton.setOnClickListener(new ColorSelectListener(headingColorValue, true));
headingStoreTextColorSelectButton.setOnClickListener(new ColorSelectListener(headingStoreTextColorValue, false));

View File

@@ -38,8 +38,10 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
ImageView barcodeImage;
View collapsingToolbarLayout;
int loyaltyCardId;
LoyaltyCard loyaltyCard;
boolean rotationEnabled;
DBHelper db;
ImportURIHelper importURIHelper;
Settings settings;
private void extractIntentFields(Intent intent)
@@ -69,6 +71,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
db = new DBHelper(this);
importURIHelper = new ImportURIHelper(this);
cardIdFieldView = findViewById(R.id.cardIdView);
noteView = findViewById(R.id.noteView);
@@ -105,7 +108,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
window.setAttributes(attributes);
}
final LoyaltyCard loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
loyaltyCard = db.getLoyaltyCard(loyaltyCardId);
if(loyaltyCard == null)
{
Log.w(TAG, "Could not lookup loyalty card " + loyaltyCardId);
@@ -221,6 +224,10 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
finish();
break;
case R.id.action_share:
importURIHelper.startShareIntent(loyaltyCard);
return true;
case R.id.action_edit:
Intent intent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
Bundle bundle = new Bundle();

View File

@@ -11,6 +11,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@@ -192,14 +193,23 @@ public class MainActivity extends AppCompatActivity
Cursor cardCursor = (Cursor)listView.getItemAtPosition(info.position);
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
if(card != null && item.getItemId() == R.id.action_clipboard)
if(card != null)
{
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(card.store, card.cardId);
clipboard.setPrimaryClip(clip);
if(item.getItemId() == R.id.action_clipboard)
{
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(card.store, card.cardId);
clipboard.setPrimaryClip(clip);
Toast.makeText(this, R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
return true;
Toast.makeText(this, R.string.copy_to_clipboard_toast, Toast.LENGTH_LONG).show();
return true;
}
else if(item.getItemId() == R.id.action_share)
{
final ImportURIHelper importURIHelper = new ImportURIHelper(this);
importURIHelper.startShareIntent(card);
return true;
}
}
return super.onContextItemSelected(item);

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -71,15 +71,16 @@
<TextView
android:id="@id/noteView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="10.0dip"
android:ellipsize="end"
android:layout_gravity="bottom"
app:layout_constraintTop_toBottomOf="@id/noteViewDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="@dimen/singleCardNoteTextSizeMin"
app:autoSizeMaxTextSize="@dimen/singleCardNoteTextSizeMax"

View File

@@ -6,4 +6,8 @@
android:id="@+id/action_clipboard"
android:title="@string/copy_to_clipboard"
app:showAsAction="always"/>
<item
android:id="@+id/action_share"
android:title="@string/share"
app:showAsAction="always"/>
</menu>

View File

@@ -7,6 +7,11 @@
android:icon="@drawable/ic_lock_open_white_24dp"
android:title="@string/lockScreen"
app:showAsAction="always"/>
<item
android:id="@+id/action_share"
android:icon="@drawable/ic_share_white"
android:title="@string/share"
app:showAsAction="always"/>
<item
android:id="@+id/action_edit"
android:icon="@drawable/ic_mode_edit_white_24dp"

View File

@@ -27,6 +27,7 @@
<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="share">Share</string>
<string name="sendLabel">Send&#8230;</string>
<string name="addedShortcut">Added to Home Screen</string>
@@ -42,6 +43,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="failedParsingImportUriError">Could not parse the import Uri</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="storeNameAndNoteFormat" translatable="false">%1$s - %2$s</string>
@@ -133,4 +135,9 @@
<string name="settings_key_display_barcode_max_brightness" translatable="false">pref_display_card_max_brightness</string>
<string name="settings_lock_barcode_orientation">Lock barcode orientation</string>
<string name="settings_key_lock_barcode_orientation" translatable="false">pref_lock_barcode_orientation</string>
<string name="intent_import_card_from_url">Import loyalty card</string>
<string name="intent_import_card_from_url_share_text">I want to share a loyalty card with you</string>
<string name="intent_import_card_from_url_host" translatable="false">brarcher.github.io</string>
<string name="intent_import_card_from_url_path_prefix" translatable="false">/loyalty-card-locker/share</string>
</resources>

View File

@@ -0,0 +1,66 @@
package protect.card_locker;
import android.app.Activity;
import android.graphics.Color;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.io.InvalidObjectException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static protect.card_locker.DBHelper.LoyaltyCardDbIds;
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class ImportURITest {
private ImportURIHelper importURIHelper;
private DBHelper db;
@Before
public void setUp()
{
Activity activity = Robolectric.setupActivity(MainActivity.class);
importURIHelper = new ImportURIHelper(activity);
db = new DBHelper(activity);
}
@Test
public void ensureNoDataLoss() throws InvalidObjectException
{
// Generate card
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, Color.BLACK, Color.WHITE);
// Get card
LoyaltyCard card = db.getLoyaltyCard(1);
// Card to URI
Uri cardUri = importURIHelper.toUri(card);
// Parse URI
LoyaltyCard parsedCard = importURIHelper.parse(cardUri);
// Compare everything
assertEquals(card.barcodeType, parsedCard.barcodeType);
assertEquals(card.cardId, parsedCard.cardId);
assertEquals(card.headerColor, parsedCard.headerColor);
assertEquals(card.headerTextColor, parsedCard.headerTextColor);
assertEquals(card.note, parsedCard.note);
assertEquals(card.store, parsedCard.store);
}
@Test
public void failToParseInvalidUri()
{
try {
importURIHelper.parse(Uri.parse("https://example.com/test"));
assertTrue(false); // Shouldn't get here
} catch(InvalidObjectException ex) {
// Desired behaviour
}
}
}

View File

@@ -7,6 +7,8 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.widget.TextViewCompat;
@@ -448,10 +450,11 @@ public class LoyaltyCardViewActivityTest
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
// The settings and add button should be present
assertEquals(menu.size(), 2);
// The share, settings and add button should be present
assertEquals(menu.size(), 3);
assertEquals("Block Rotation", menu.findItem(R.id.action_lock_unlock).getTitle().toString());
assertEquals("Share", menu.findItem(R.id.action_share).getTitle().toString());
assertEquals("Edit", menu.findItem(R.id.action_edit).getTitle().toString());
}
@@ -604,4 +607,25 @@ public class LoyaltyCardViewActivityTest
}
}
}
@Test
public void importCard()
{
Uri importUri = Uri.parse("https://brarcher.github.io/loyalty-card-locker/share?store=Example%20Store&note=&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1");
Intent intent = new Intent();
intent.setData(importUri);
ActivityController activityController = Robolectric.buildActivity(LoyaltyCardEditActivity.class).withIntent(intent).create();
activityController.start();
activityController.visible();
activityController.resume();
Activity activity = (Activity)activityController.get();
checkAllFields(activity, ViewMode.ADD_CARD, "Example Store", "", "123456", "AZTEC");
assertEquals(activity.findViewById(R.id.headingColorSample).getBackground(), new ColorDrawable(-416706));
assertEquals(activity.findViewById(R.id.headingStoreTextColorSample).getBackground(), new ColorDrawable(-1));
}
}

1
docs/index.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

10
docs/share.md Normal file
View File

@@ -0,0 +1,10 @@
# Shared Loyalty Card
Someone wants to share a loyalty card with you. To import this loyalty card, you will first need to install the Loyalty Card Locker app. It is free, Open Source and contains no ads.
<a href="https://f-droid.org/repository/browse/?fdid=protect.card_locker" target="_blank">
<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="90"/></a>
<a href="https://play.google.com/store/apps/details?id=protect.card_locker" target="_blank">
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="90"/></a>
After installing the app, just click the link you were given again and choose "Import loyalty card".