Introduce Topics/Classification and Swipe to move
This commit is contained in:
parent
3b8ecbc14e
commit
03ea344a1c
39 changed files with 1396 additions and 183 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,7 +4,6 @@ build
|
||||||
#assets
|
#assets
|
||||||
bin
|
bin
|
||||||
gen
|
gen
|
||||||
proguard-project.txt
|
|
||||||
project.properties
|
project.properties
|
||||||
gradle.properties
|
gradle.properties
|
||||||
local.properties
|
local.properties
|
||||||
|
|
|
@ -6,7 +6,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.0.0-beta4'
|
classpath 'com.android.tools.build:gradle:2.0.0-beta5'
|
||||||
classpath 'de.felixschulze.gradle:gradle-spoon-plugin:2.6.1'
|
classpath 'de.felixschulze.gradle:gradle-spoon-plugin:2.6.1'
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
||||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||||
|
@ -82,6 +82,10 @@ android {
|
||||||
|
|
||||||
// hack for instrumentation testing :-(
|
// hack for instrumentation testing :-(
|
||||||
exclude 'LICENSE.txt'
|
exclude 'LICENSE.txt'
|
||||||
|
|
||||||
|
exclude 'META-INF/maven/com.google.guava/guava/pom.properties'
|
||||||
|
exclude 'META-INF/maven/com.google.guava/guava/pom.xml'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
|
@ -119,6 +123,8 @@ dependencies {
|
||||||
exclude module: 'recyclerview-v7'
|
exclude module: 'recyclerview-v7'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidTestCompile('com.android.support.test.espresso:espresso-web:2.2.1')
|
||||||
|
|
||||||
androidTestCompile 'com.squareup.spoon:spoon-client:1.3.2'
|
androidTestCompile 'com.squareup.spoon:spoon-client:1.3.2'
|
||||||
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
|
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
|
||||||
|
|
||||||
|
@ -145,6 +151,7 @@ dependencies {
|
||||||
compile 'com.squareup.okhttp:okhttp:2.7.4'
|
compile 'com.squareup.okhttp:okhttp:2.7.4'
|
||||||
compile 'com.larswerkman:HoloColorPicker:1.5'
|
compile 'com.larswerkman:HoloColorPicker:1.5'
|
||||||
compile 'com.google.code.findbugs:jsr305:3.0.1'
|
compile 'com.google.code.findbugs:jsr305:3.0.1'
|
||||||
|
compile 'com.squareup.moshi:moshi:1.1.0'
|
||||||
|
|
||||||
compile 'net.steamcrafted:load-toast:1.0.10'
|
compile 'net.steamcrafted:load-toast:1.0.10'
|
||||||
|
|
||||||
|
|
110
android/proguard-project.txt
Normal file
110
android/proguard-project.txt
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# To enable ProGuard in your project, edit project.properties
|
||||||
|
# to define the proguard.config property as described in that file.
|
||||||
|
#
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the ProGuard
|
||||||
|
# include property in project.properties.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
#http://stackoverflow.com/questions/19274974/android-badparcelableexception-only-with-signed-apk
|
||||||
|
-keep class * implements android.os.Parcelable {
|
||||||
|
public static final android.os.Parcelable$Creator *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# optimize
|
||||||
|
-optimizationpasses 2
|
||||||
|
-optimizations !code/simplification/arithmetic
|
||||||
|
-dontusemixedcaseclassnames
|
||||||
|
-dontskipnonpubliclibraryclasses
|
||||||
|
|
||||||
|
# AppCompat
|
||||||
|
|
||||||
|
-dontwarn android.support.v7.**
|
||||||
|
-keep class android.support.v7.** { *; }
|
||||||
|
-keep interface android.support.v7.** { *; }
|
||||||
|
|
||||||
|
# Keep line numbers to alleviate debugging stack traces
|
||||||
|
|
||||||
|
-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
### for api client
|
||||||
|
|
||||||
|
|
||||||
|
-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
|
||||||
|
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@com.google.api.client.util.Key <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Needed by Guava
|
||||||
|
# See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI
|
||||||
|
|
||||||
|
|
||||||
|
-dontwarn sun.misc.Unsafe
|
||||||
|
-dontwarn com.google.common.collect.MinMaxPriorityQueue
|
||||||
|
|
||||||
|
# Needed by google-http-client-android when linking against an older platform version
|
||||||
|
|
||||||
|
-dontwarn com.google.api.client.extensions.android.**
|
||||||
|
|
||||||
|
# Needed by google-api-client-android when linking against an older platform version
|
||||||
|
|
||||||
|
-dontwarn com.google.api.client.googleapis.extensions.android.**
|
||||||
|
|
||||||
|
|
||||||
|
#### for butterknife
|
||||||
|
-dontwarn butterknife.internal.**
|
||||||
|
-keep class **$$ViewBinder { *; }
|
||||||
|
-keepnames class * { @butterknife.Bind *;}
|
||||||
|
|
||||||
|
#### for support 22
|
||||||
|
-dontwarn android.support.**
|
||||||
|
|
||||||
|
#### for guava
|
||||||
|
-dontwarn javax.annotation.**
|
||||||
|
-dontwarn javax.inject.**
|
||||||
|
-dontwarn sun.misc.Unsafe
|
||||||
|
-dontwarn com.google.common.collect.MinMaxPriorityQueue
|
||||||
|
|
||||||
|
-keep,allowoptimization class com.google.inject.** { *; }
|
||||||
|
-keep,allowoptimization class javax.inject.** { *; }
|
||||||
|
-keep,allowoptimization class javax.annotation.** { *; }
|
||||||
|
-keep,allowoptimization class com.google.inject.Binder
|
||||||
|
|
||||||
|
-keepclasseswithmembers public class * {
|
||||||
|
public static void main(java.lang.String[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers,allowoptimization class com.google.common.* {
|
||||||
|
void finalizeReferent();
|
||||||
|
void startFinalizer(java.lang.Class,java.lang.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@com.google.common.eventbus.Subscribe *;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-dontwarn java.nio.file.Files
|
||||||
|
-dontwarn java.nio.file.Path
|
||||||
|
-dontwarn java.nio.file.OpenOption
|
||||||
|
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||||
|
|
||||||
|
-keep public class com.google.android.gms.**
|
||||||
|
-dontwarn com.google.android.gms.**
|
||||||
|
|
|
@ -16,4 +16,6 @@ public interface TestComponent extends AppComponent {
|
||||||
void inject(ThePastLocationsStore thePastLocationsStore);
|
void inject(ThePastLocationsStore thePastLocationsStore);
|
||||||
|
|
||||||
void inject(TheBarCodeEditFragment theBarCodeEditFragment);
|
void inject(TheBarCodeEditFragment theBarCodeEditFragment);
|
||||||
|
|
||||||
|
void inject(ThePassListSwiping thePassListSwiping);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.ligi.passandroid.model.comparator.PassSortOrder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@ -29,10 +30,11 @@ public class TestModule {
|
||||||
|
|
||||||
public TestModule() {
|
public TestModule() {
|
||||||
passList = new ArrayList<>();
|
passList = new ArrayList<>();
|
||||||
final PassImpl object = new PassImpl();
|
final PassImpl pass = new PassImpl();
|
||||||
object.setDescription("description");
|
pass.setId(UUID.randomUUID().toString());
|
||||||
object.setBarCode(new BarCode(BarcodeFormat.AZTEC, "messageprobe"));
|
pass.setDescription("description");
|
||||||
passList.add(object);
|
pass.setBarCode(new BarCode(BarcodeFormat.AZTEC, "messageprobe"));
|
||||||
|
passList.add(pass);
|
||||||
|
|
||||||
}
|
}
|
||||||
public TestModule(List<FiledPass> passList) {
|
public TestModule(List<FiledPass> passList) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import static android.support.test.espresso.action.ViewActions.click;
|
||||||
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
|
||||||
|
|
||||||
public class TheEmptyPassList extends BaseIntegration<PassListActivity> {
|
public class TheEmptyPassList extends BaseIntegration<PassListActivity> {
|
||||||
|
|
||||||
|
@ -38,7 +39,8 @@ public class TheEmptyPassList extends BaseIntegration<PassListActivity> {
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testHelpGoesToHelp() {
|
public void testHelpGoesToHelp() {
|
||||||
onView(withId(R.id.menu_help)).perform(click());
|
onView(withId(R.id.menu_help)).perform(click());
|
||||||
onView(withId(R.id.help_tv)).check(matches(isDisplayed()));
|
|
||||||
|
checkThatHelpIsThere();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,11 @@ import org.ligi.passandroid.ui.HelpActivity;
|
||||||
|
|
||||||
import static android.support.test.espresso.Espresso.onView;
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
import static android.support.test.espresso.action.ViewActions.click;
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.isClickable;
|
import static android.support.test.espresso.matcher.ViewMatchers.isClickable;
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
|
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.Matchers.allOf;
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
|
||||||
|
|
||||||
|
|
||||||
public class TheHelpActivity extends BaseIntegration<HelpActivity> {
|
public class TheHelpActivity extends BaseIntegration<HelpActivity> {
|
||||||
|
@ -31,7 +29,9 @@ public class TheHelpActivity extends BaseIntegration<HelpActivity> {
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public void testHelpIsThere() {
|
public void testHelpIsThere() {
|
||||||
onView(withId(R.id.help_tv)).check(matches(isDisplayed()));
|
|
||||||
|
checkThatHelpIsThere();
|
||||||
|
|
||||||
Spoon.screenshot(getActivity(), "help");
|
Spoon.screenshot(getActivity(), "help");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import static android.support.test.espresso.contrib.DrawerActions.open;
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
import static org.hamcrest.CoreMatchers.not;
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
|
||||||
|
|
||||||
@TargetApi(14)
|
@TargetApi(14)
|
||||||
public class ThePassListActivity extends BaseIntegration<PassListActivity> {
|
public class ThePassListActivity extends BaseIntegration<PassListActivity> {
|
||||||
|
@ -34,14 +35,15 @@ public class ThePassListActivity extends BaseIntegration<PassListActivity> {
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testListIsThere() {
|
public void testListIsThere() {
|
||||||
|
|
||||||
onView(withId(R.id.content_list)).check(matches(isDisplayed()));
|
onView(withId(R.id.pass_recyclerview)).check(matches(isDisplayed()));
|
||||||
Spoon.screenshot(getActivity(), "list");
|
Spoon.screenshot(getActivity(), "list");
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testHelpMenuBringsUsToHelp() {
|
public void testHelpMenuBringsUsToHelp() {
|
||||||
onView(withId(R.id.menu_help)).perform(click());
|
onView(withId(R.id.menu_help)).perform(click());
|
||||||
onView(withId(R.id.help_tv)).check(matches(isDisplayed()));
|
|
||||||
|
checkThatHelpIsThere();
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
package org.ligi.passandroid;
|
||||||
|
|
||||||
|
import org.ligi.passandroid.model.PassStore;
|
||||||
|
import org.ligi.passandroid.ui.PassListActivity;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
public class ThePassListSwiping extends BaseIntegration<PassListActivity> {
|
||||||
|
|
||||||
|
public static final String CUSTOM_PROBE = "custom";
|
||||||
|
@Inject
|
||||||
|
PassStore passStore;
|
||||||
|
|
||||||
|
public ThePassListSwiping() {
|
||||||
|
super(PassListActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
final TestComponent testComponent = DaggerTestComponent.builder().build();
|
||||||
|
|
||||||
|
testComponent.inject(this);
|
||||||
|
|
||||||
|
App.setComponent(testComponent);
|
||||||
|
getActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
TODO figure out why this is flaky on some devices :-(
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testDialogOpensWhenSwipeRight() {
|
||||||
|
onView(withId(R.id.pass_recyclerview)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.pass_recyclerview)).perform(RecyclerViewActions.actionOnItemAtPosition(0,swipeRight()));
|
||||||
|
|
||||||
|
onView(withText("description")).perform(swipeRight());
|
||||||
|
RecyclerViewActions.actionOnItemAtPosition(0,swipeRight());
|
||||||
|
|
||||||
|
onView(withText(R.string.move_to_new_topic)).check(matches(isDisplayed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testWeCanMoveToTrash() {
|
||||||
|
onView(withId(R.id.pass_recyclerview)).perform(click());
|
||||||
|
onView(withText("description")).perform(swipeRight());
|
||||||
|
|
||||||
|
onView(withId(R.id.suggestion_button_trash)).perform(click());
|
||||||
|
|
||||||
|
onView(withText(R.string.topic_trash)).check(matches(isDisplayed()));
|
||||||
|
assertThat(passStore.getClassifier().getTopics()).containsExactly(getActivity().getString(R.string.topic_trash));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testWeCanMoveToArchive() {
|
||||||
|
onView(withId(R.id.pass_recyclerview)).perform(click());
|
||||||
|
onView(withText("description")).perform(swipeRight());
|
||||||
|
|
||||||
|
onView(withId(R.id.suggestion_button_archive)).perform(click());
|
||||||
|
|
||||||
|
onView(withText(R.string.topic_archive)).check(matches(isDisplayed()));
|
||||||
|
assertThat(passStore.getClassifier().getTopics()).containsExactly(getActivity().getString(R.string.topic_archive));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testWeCanMoveToCustom() {
|
||||||
|
onView(withId(R.id.pass_recyclerview)).perform(click());
|
||||||
|
onView(withText("description")).perform(swipeLeft());
|
||||||
|
|
||||||
|
onView(withId(R.id.new_topic_edit)).perform(typeText(CUSTOM_PROBE));
|
||||||
|
|
||||||
|
onView(withText(android.R.string.ok)).perform(click());
|
||||||
|
|
||||||
|
assertThat(passStore.getClassifier().getTopics()).containsExactly(CUSTOM_PROBE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testDialogOpensWhenSwipeLeft() {
|
||||||
|
onView(withId(R.id.pass_recyclerview)).perform(click());
|
||||||
|
onView(withId(R.id.pass_recyclerview)).perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
|
||||||
|
onView(withId(R.id.pass_recyclerview)).perform(RecyclerViewActions.actionOnItemAtPosition(0, swipeLeft()));
|
||||||
|
|
||||||
|
onView(withText(R.string.move_to_new_topic)).check(matches(isDisplayed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static ViewAction swipeRight() {
|
||||||
|
return actionWithAssertions(new GeneralSwipeAction(Swipe.SLOW,
|
||||||
|
translate(GeneralLocation.CENTER_LEFT, -0.1f, 0),
|
||||||
|
GeneralLocation.CENTER_RIGHT, Press.FINGER));
|
||||||
|
}
|
||||||
|
|
||||||
|
static CoordinatesProvider translate(final CoordinatesProvider coords,
|
||||||
|
final float dx, final float dy) {
|
||||||
|
return new CoordinatesProvider() {
|
||||||
|
@Override
|
||||||
|
public float[] calculateCoordinates(View view) {
|
||||||
|
float xy[] = coords.calculateCoordinates(view);
|
||||||
|
xy[0] += dx * view.getWidth();
|
||||||
|
xy[1] += dy * view.getHeight();
|
||||||
|
return xy;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -2,15 +2,18 @@ package org.ligi.passandroid.injections;
|
||||||
|
|
||||||
import org.ligi.passandroid.model.FiledPass;
|
import org.ligi.passandroid.model.FiledPass;
|
||||||
import org.ligi.passandroid.model.Pass;
|
import org.ligi.passandroid.model.Pass;
|
||||||
|
import org.ligi.passandroid.model.PassClassifier;
|
||||||
import org.ligi.passandroid.model.PassStore;
|
import org.ligi.passandroid.model.PassStore;
|
||||||
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FixedPassListPassStore implements PassStore {
|
public class FixedPassListPassStore implements PassStore {
|
||||||
|
|
||||||
private final List<FiledPass> passes;
|
private final List<FiledPass> passes;
|
||||||
private Pass actPass;
|
private Pass actPass;
|
||||||
|
private PassClassifier passClassifier;
|
||||||
|
|
||||||
public FixedPassListPassStore(List<FiledPass> passes) {
|
public FixedPassListPassStore(List<FiledPass> passes) {
|
||||||
this.passes = passes;
|
this.passes = passes;
|
||||||
|
@ -46,10 +49,6 @@ public class FixedPassListPassStore implements PassStore {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sort(PassSortOrder order) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pass getCurrentPass() {
|
public Pass getCurrentPass() {
|
||||||
return actPass;
|
return actPass;
|
||||||
|
@ -60,6 +59,14 @@ public class FixedPassListPassStore implements PassStore {
|
||||||
actPass = pass;
|
actPass = pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PassClassifier getClassifier() {
|
||||||
|
if (passClassifier == null) {
|
||||||
|
passClassifier = new PassClassifier(new HashMap<String, Collection<String>>());
|
||||||
|
}
|
||||||
|
return passClassifier;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deletePassWithId(String id) {
|
public boolean deletePassWithId(String id) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.ligi.passandroid.steps;
|
||||||
|
|
||||||
|
import android.support.test.espresso.web.assertion.WebViewAssertions;
|
||||||
|
|
||||||
|
import static android.support.test.espresso.web.matcher.DomMatchers.containingTextInBody;
|
||||||
|
import static android.support.test.espresso.web.sugar.Web.onWebView;
|
||||||
|
|
||||||
|
public class HelpSteps {
|
||||||
|
public static void checkThatHelpIsThere() {
|
||||||
|
onWebView().check(WebViewAssertions.webContent(containingTextInBody("Example passes")));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,16 +23,20 @@
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<service android:name=".ui.SearchPassesIntentService" />
|
<service android:name=".ui.SearchPassesIntentService" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.PassListActivity"
|
android:name=".ui.PassListActivity"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/AppBaseThemeNoActionbar">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".ui.HelpActivity" />
|
<activity
|
||||||
|
android:name=".ui.HelpActivity"
|
||||||
|
android:theme="@style/AppBaseThemeNoActionbar" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.PassEditActivity"
|
android:name=".ui.PassEditActivity"
|
||||||
android:screenOrientation="sensorPortrait" />
|
android:screenOrientation="sensorPortrait" />
|
||||||
|
@ -743,6 +747,7 @@
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.PassViewActivity"
|
android:name=".ui.PassViewActivity"
|
||||||
|
android:theme="@style/AppBaseThemeNoActionbar"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:parentActivityName=".ui.PassListActivity">
|
android:parentActivityName=".ui.PassListActivity">
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.ligi.passandroid.ui.PassAndroidActivity;
|
||||||
import org.ligi.passandroid.ui.PassEditActivity;
|
import org.ligi.passandroid.ui.PassEditActivity;
|
||||||
import org.ligi.passandroid.ui.PassImportActivity;
|
import org.ligi.passandroid.ui.PassImportActivity;
|
||||||
import org.ligi.passandroid.ui.PassListActivity;
|
import org.ligi.passandroid.ui.PassListActivity;
|
||||||
|
import org.ligi.passandroid.ui.PassListFragment;
|
||||||
import org.ligi.passandroid.ui.PassMenuOptions;
|
import org.ligi.passandroid.ui.PassMenuOptions;
|
||||||
import org.ligi.passandroid.ui.PassViewActivityBase;
|
import org.ligi.passandroid.ui.PassViewActivityBase;
|
||||||
import org.ligi.passandroid.ui.SearchPassesIntentService;
|
import org.ligi.passandroid.ui.SearchPassesIntentService;
|
||||||
|
@ -53,6 +54,8 @@ public interface AppComponent {
|
||||||
|
|
||||||
void inject(PassAndroidActivity passAndroidActivity);
|
void inject(PassAndroidActivity passAndroidActivity);
|
||||||
|
|
||||||
|
void inject(PassListFragment passListFragment);
|
||||||
|
|
||||||
PassStore passStore();
|
PassStore passStore();
|
||||||
|
|
||||||
Tracker tracker();
|
Tracker tracker();
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.squareup.otto.Bus;
|
||||||
import com.squareup.otto.ThreadEnforcer;
|
import com.squareup.otto.ThreadEnforcer;
|
||||||
|
|
||||||
import org.ligi.passandroid.model.AndroidFileSystemPassStore;
|
import org.ligi.passandroid.model.AndroidFileSystemPassStore;
|
||||||
|
import org.ligi.passandroid.model.AndroidSettings;
|
||||||
import org.ligi.passandroid.model.PassStore;
|
import org.ligi.passandroid.model.PassStore;
|
||||||
import org.ligi.passandroid.model.Settings;
|
import org.ligi.passandroid.model.Settings;
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ public class AppModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
Settings provideSettings() {
|
Settings provideSettings() {
|
||||||
return new Settings(app);
|
return new AndroidSettings(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.ligi.passandroid.helper;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.ligi.passandroid.R;
|
||||||
|
import org.ligi.passandroid.model.Pass;
|
||||||
|
import org.ligi.passandroid.model.PassClassifier;
|
||||||
|
|
||||||
|
public class MoveHelper {
|
||||||
|
public static void moveWithUndoSnackbar(final PassClassifier passClassifier, final Pass pass, String topic, final Activity activity) {
|
||||||
|
final String oldTopic = passClassifier.getTopic(pass);
|
||||||
|
|
||||||
|
Snackbar.make(activity.getWindow().getDecorView().findViewById(R.id.fam), "Pass moved to " + topic, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction("undo", new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
passClassifier.moveToTopic(pass, oldTopic);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
passClassifier.moveToTopic(pass, topic);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import android.content.Context;
|
||||||
|
|
||||||
import org.ligi.axt.AXT;
|
import org.ligi.axt.AXT;
|
||||||
import org.ligi.passandroid.helper.DirectoryFileFilter;
|
import org.ligi.passandroid.helper.DirectoryFileFilter;
|
||||||
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
|
||||||
import org.ligi.passandroid.reader.AppleStylePassReader;
|
import org.ligi.passandroid.reader.AppleStylePassReader;
|
||||||
import org.ligi.passandroid.reader.PassReader;
|
import org.ligi.passandroid.reader.PassReader;
|
||||||
import org.ligi.tracedroid.logging.Log;
|
import org.ligi.tracedroid.logging.Log;
|
||||||
|
@ -12,7 +11,6 @@ import org.ligi.tracedroid.logging.Log;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AndroidFileSystemPassStore implements PassStore {
|
public class AndroidFileSystemPassStore implements PassStore {
|
||||||
|
@ -22,12 +20,15 @@ public class AndroidFileSystemPassStore implements PassStore {
|
||||||
|
|
||||||
private List<FiledPass> passList = new ArrayList<>();
|
private List<FiledPass> passList = new ArrayList<>();
|
||||||
private Pass actPass;
|
private Pass actPass;
|
||||||
|
private final PassClassifier passClassifier;
|
||||||
|
|
||||||
public AndroidFileSystemPassStore(final Context context, final Settings settings) {
|
public AndroidFileSystemPassStore(final Context context, final Settings settings) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
path = settings.getPassesDir();
|
path = settings.getPassesDir();
|
||||||
|
|
||||||
refreshPassesList();
|
refreshPassesList();
|
||||||
|
final File classificationFile = new File(settings.getStateDir(), "classification_state.json");
|
||||||
|
passClassifier = new FileBackedPassClassifier(classificationFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -124,11 +125,6 @@ public class AndroidFileSystemPassStore implements PassStore {
|
||||||
return getCachedPassOrLoad(id);
|
return getCachedPassOrLoad(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sort(final PassSortOrder order) {
|
|
||||||
Collections.sort(passList, order.toComparator());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pass getCurrentPass() {
|
public Pass getCurrentPass() {
|
||||||
return actPass;
|
return actPass;
|
||||||
|
@ -139,9 +135,18 @@ public class AndroidFileSystemPassStore implements PassStore {
|
||||||
actPass = pass;
|
actPass = pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PassClassifier getClassifier() {
|
||||||
|
return passClassifier;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deletePassWithId(final String id) {
|
public boolean deletePassWithId(final String id) {
|
||||||
return AXT.at(new File(getPathForID(id))).deleteRecursive();
|
final boolean result = AXT.at(new File(getPathForID(id))).deleteRecursive();
|
||||||
|
if (result) {
|
||||||
|
refreshPassesList();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPathForID(final String id) {
|
public String getPathForID(final String id) {
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package org.ligi.passandroid.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class AndroidSettings implements Settings {
|
||||||
|
public static final String ORDER_KEY = "order";
|
||||||
|
public final Context context;
|
||||||
|
|
||||||
|
final SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
|
public AndroidSettings(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSortOrder(PassSortOrder order) {
|
||||||
|
sharedPreferences.edit().putInt(ORDER_KEY, order.getInt()).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PassSortOrder getSortOrder() {
|
||||||
|
int id = sharedPreferences.getInt(ORDER_KEY, 0);
|
||||||
|
for (PassSortOrder order : PassSortOrder.values()) {
|
||||||
|
if (order.getInt() == id) {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PassSortOrder.DATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doTraceDroidEmailSend() {
|
||||||
|
// will be overridden in test-module
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassesDir() {
|
||||||
|
return context.getFilesDir().getAbsolutePath() + "/passes";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getStateDir() {
|
||||||
|
return new File(context.getFilesDir(), "state");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getShareDir() {
|
||||||
|
return Environment.getExternalStorageDirectory() + "/tmp/passbook_share_tmp/";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package org.ligi.passandroid.model;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okio.BufferedSink;
|
||||||
|
import okio.Okio;
|
||||||
|
|
||||||
|
public class FileBackedPassClassifier extends PassClassifier {
|
||||||
|
|
||||||
|
private final JsonAdapter<Map> adapter;
|
||||||
|
private final File backed_file;
|
||||||
|
|
||||||
|
public FileBackedPassClassifier(final File backed_file) {
|
||||||
|
super(getBase(backed_file));
|
||||||
|
|
||||||
|
this.backed_file = backed_file;
|
||||||
|
adapter = getAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonAdapter<Map> getAdapter() {
|
||||||
|
final Moshi build = new Moshi.Builder().build();
|
||||||
|
return build.adapter(Map.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Map<String, Collection<String>> getBase(final File backed_file) {
|
||||||
|
|
||||||
|
if (backed_file.exists()) {
|
||||||
|
try {
|
||||||
|
return (Map<String, Collection<String>>) getAdapter().fromJson(Okio.buffer(Okio.source(backed_file)));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
private BufferedSink getBufferedSinkFromMaybeCreatedFile() {
|
||||||
|
try {
|
||||||
|
if (!backed_file.exists()) {
|
||||||
|
final File parentFile = backed_file.getParentFile();
|
||||||
|
if (!parentFile.exists()) {
|
||||||
|
parentFile.mkdirs();
|
||||||
|
}
|
||||||
|
backed_file.createNewFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Okio.buffer(Okio.sink(backed_file));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processDataChange() {
|
||||||
|
super.processDataChange();
|
||||||
|
|
||||||
|
// write
|
||||||
|
if (adapter != null) {
|
||||||
|
final BufferedSink buffer = getBufferedSinkFromMaybeCreatedFile();
|
||||||
|
|
||||||
|
if (buffer != null) {
|
||||||
|
try {
|
||||||
|
adapter.toJson(buffer, pass_id_list_by_topic);
|
||||||
|
buffer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package org.ligi.passandroid.model;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
public class PassClassifier {
|
||||||
|
|
||||||
|
public interface OnClassificationChangeListener {
|
||||||
|
void OnClassificationChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<OnClassificationChangeListener> onClassificationChangeListeners = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
|
public final static String DEFAULT_TOPIC = "active";
|
||||||
|
|
||||||
|
protected final Map<String, Collection<String>> pass_id_list_by_topic;
|
||||||
|
private final Map<String, String> topic_by_id = new HashMap<>();
|
||||||
|
|
||||||
|
public PassClassifier(Map<String, Collection<String>> pass_id_list_by_topic) {
|
||||||
|
this.pass_id_list_by_topic = pass_id_list_by_topic;
|
||||||
|
|
||||||
|
processDataChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processDataChange() {
|
||||||
|
calculateReverseMapping();
|
||||||
|
removeEmpty();
|
||||||
|
makeSureDefaultTopicExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateReverseMapping() {
|
||||||
|
topic_by_id.clear();
|
||||||
|
for (Map.Entry<String, Collection<String>> stringListEntry : pass_id_list_by_topic.entrySet()) {
|
||||||
|
final String topic = stringListEntry.getKey();
|
||||||
|
for (String id : stringListEntry.getValue()) {
|
||||||
|
topic_by_id.put(id, topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeSureDefaultTopicExists() {
|
||||||
|
|
||||||
|
if (pass_id_list_by_topic.isEmpty()) {
|
||||||
|
pass_id_list_by_topic.put(DEFAULT_TOPIC, new TreeSet<String>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeEmpty() {
|
||||||
|
final Set<String> toRemove = new HashSet<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, Collection<String>> stringListEntry : pass_id_list_by_topic.entrySet()) {
|
||||||
|
if (stringListEntry.getValue().isEmpty()) {
|
||||||
|
toRemove.add(stringListEntry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String s : toRemove) {
|
||||||
|
pass_id_list_by_topic.remove(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveToTopic(final Pass pass, final String newTopic) {
|
||||||
|
if (topic_by_id.containsKey(pass.getId())) {
|
||||||
|
final String oldTopic = topic_by_id.get(pass.getId());
|
||||||
|
final Collection<String> idsForOldTopic = pass_id_list_by_topic.get(oldTopic);
|
||||||
|
idsForOldTopic.remove(pass.getId());
|
||||||
|
if (idsForOldTopic.isEmpty()) {
|
||||||
|
pass_id_list_by_topic.remove(oldTopic);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
upsertPassToTopic(pass, newTopic);
|
||||||
|
|
||||||
|
processDataChange();
|
||||||
|
|
||||||
|
notifyDataChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyDataChange() {
|
||||||
|
for (OnClassificationChangeListener onClassificationChangeListener : onClassificationChangeListeners) {
|
||||||
|
onClassificationChangeListener.OnClassificationChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void upsertPassToTopic(Pass pass, String newTopic) {
|
||||||
|
if (!pass_id_list_by_topic.containsKey(newTopic)) {
|
||||||
|
pass_id_list_by_topic.put(newTopic, new TreeSet<String>());
|
||||||
|
}
|
||||||
|
|
||||||
|
final Collection<String> strings = pass_id_list_by_topic.get(newTopic);
|
||||||
|
if (!strings.contains(pass.getId())) {
|
||||||
|
strings.add(pass.getId());
|
||||||
|
processDataChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getTopics() {
|
||||||
|
final Set<String> strings = pass_id_list_by_topic.keySet();
|
||||||
|
return pass_id_list_by_topic.keySet().toArray(new String[strings.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTopic(Pass pass) {
|
||||||
|
if(topic_by_id.containsKey(pass.getId())) {
|
||||||
|
return topic_by_id.get(pass.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
upsertPassToTopic(pass,DEFAULT_TOPIC);
|
||||||
|
|
||||||
|
return DEFAULT_TOPIC;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,33 +2,28 @@ package org.ligi.passandroid.model;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface PassStore {
|
public interface PassStore {
|
||||||
|
|
||||||
boolean deletePassWithId(String id);
|
|
||||||
|
|
||||||
String getPathForID(String id);
|
|
||||||
|
|
||||||
|
|
||||||
List<FiledPass> getPassList();
|
|
||||||
|
|
||||||
void preCachePassesList();
|
|
||||||
|
|
||||||
void deleteCacheForId(String id);
|
void deleteCacheForId(String id);
|
||||||
|
|
||||||
void refreshPassesList();
|
void refreshPassesList();
|
||||||
|
|
||||||
Pass getPassbookForId(String id);
|
Pass getPassbookForId(String id);
|
||||||
|
|
||||||
|
boolean deletePassWithId(String id);
|
||||||
|
|
||||||
void sort(PassSortOrder order);
|
String getPathForID(String id);
|
||||||
|
|
||||||
|
void preCachePassesList();
|
||||||
|
|
||||||
|
List<FiledPass> getPassList();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Pass getCurrentPass();
|
Pass getCurrentPass();
|
||||||
|
|
||||||
void setCurrentPass(@Nullable Pass pass);
|
void setCurrentPass(@Nullable Pass pass);
|
||||||
|
|
||||||
|
PassClassifier getClassifier();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.ligi.passandroid.model;
|
||||||
|
|
||||||
|
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PassStoreProjection {
|
||||||
|
|
||||||
|
private List<FiledPass> passList = new ArrayList<>();
|
||||||
|
|
||||||
|
private final PassStore passStore;
|
||||||
|
private final String topic;
|
||||||
|
private final PassSortOrder passSortOrder;
|
||||||
|
|
||||||
|
public PassStoreProjection(final PassStore passStore, final String topic, PassSortOrder order) {
|
||||||
|
|
||||||
|
this.passStore = passStore;
|
||||||
|
this.topic = topic;
|
||||||
|
this.passSortOrder = order;
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FiledPass> getPassList() {
|
||||||
|
return passList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
ArrayList<FiledPass> newPassList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (FiledPass filedPass : passStore.getPassList()) {
|
||||||
|
if (passStore.getClassifier().getTopic(filedPass).equals(topic)) {
|
||||||
|
newPassList.add(filedPass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(newPassList, passSortOrder.toComparator());
|
||||||
|
|
||||||
|
passList = newPassList;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,48 +1,21 @@
|
||||||
package org.ligi.passandroid.model;
|
package org.ligi.passandroid.model;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
||||||
|
|
||||||
public class Settings {
|
import java.io.File;
|
||||||
public static final String ORDER_KEY = "order";
|
|
||||||
public final Context context;
|
|
||||||
|
|
||||||
private final SharedPreferences sharedPreferences;
|
public interface Settings {
|
||||||
|
|
||||||
public Settings(Context context) {
|
void setSortOrder(PassSortOrder order);
|
||||||
this.context = context;
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSortOrder(PassSortOrder order) {
|
PassSortOrder getSortOrder();
|
||||||
sharedPreferences.edit().putInt(ORDER_KEY, order.getInt()).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PassSortOrder getSortOrder() {
|
boolean doTraceDroidEmailSend();
|
||||||
int id = sharedPreferences.getInt(ORDER_KEY, 0);
|
|
||||||
for (PassSortOrder order : PassSortOrder.values()) {
|
|
||||||
if (order.getInt() == id) {
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return PassSortOrder.DATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean doTraceDroidEmailSend() {
|
String getPassesDir();
|
||||||
// will be overridden in test-module
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassesDir() {
|
File getStateDir();
|
||||||
return context.getFilesDir().getAbsolutePath() + "/passes";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShareDir() {
|
String getShareDir();
|
||||||
return Environment.getExternalStorageDirectory() + "/tmp/passbook_share_tmp/";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,23 @@ package org.ligi.passandroid.ui;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.text.Html;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.text.util.Linkify;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.TextView;
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import org.ligi.passandroid.R;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import org.ligi.passandroid.R;
|
|
||||||
|
|
||||||
public class HelpActivity extends AppCompatActivity {
|
public class HelpActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Bind(R.id.help_tv)
|
@Bind(R.id.help_webview)
|
||||||
TextView helpTextView;
|
WebView helpWebView;
|
||||||
|
|
||||||
|
@Bind(R.id.toolbar)
|
||||||
|
Toolbar toolbar;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -22,11 +26,13 @@ public class HelpActivity extends AppCompatActivity {
|
||||||
setContentView(R.layout.activity_help);
|
setContentView(R.layout.activity_help);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
helpTextView.setText(Html.fromHtml(getString(R.string.help_content)));
|
WebSettings webSettings = helpWebView.getSettings();
|
||||||
|
webSettings.setJavaScriptEnabled(true);
|
||||||
|
webSettings.setStandardFontFamily("Sans-Serif");
|
||||||
|
|
||||||
Linkify.addLinks(helpTextView, Linkify.ALL);
|
helpWebView.loadData(getString(R.string.help_content),"text/html","utf-8");
|
||||||
|
|
||||||
helpTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.ligi.passandroid.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import org.ligi.passandroid.R;
|
||||||
|
import org.ligi.passandroid.helper.MoveHelper;
|
||||||
|
import org.ligi.passandroid.model.Pass;
|
||||||
|
import org.ligi.passandroid.model.PassStore;
|
||||||
|
|
||||||
|
import butterknife.Bind;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
|
||||||
|
class MoveToNewTopicUI {
|
||||||
|
|
||||||
|
private final Activity context;
|
||||||
|
private final PassStore passStore;
|
||||||
|
private final Pass pass;
|
||||||
|
|
||||||
|
@Bind(R.id.new_topic_edit)
|
||||||
|
EditText newTopicEditText;
|
||||||
|
private AlertDialog dialog;
|
||||||
|
|
||||||
|
@OnClick(R.id.suggestion_button_trash)
|
||||||
|
void onTrashClick() {
|
||||||
|
move(context.getString(R.string.topic_trash));
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.suggestion_button_archive)
|
||||||
|
void onArchiveClick() {
|
||||||
|
move(context.getString(R.string.topic_archive));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void move(String topic) {
|
||||||
|
MoveHelper.moveWithUndoSnackbar(passStore.getClassifier(),pass,topic,context);
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveToNewTopicUI(Activity context, PassStore passStore, Pass pass) {
|
||||||
|
this.context = context;
|
||||||
|
this.passStore = passStore;
|
||||||
|
this.pass = pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void showTopicMove() {
|
||||||
|
|
||||||
|
dialog = new AlertDialog.Builder(context)
|
||||||
|
.setTitle(context.getString(R.string.move_to_new_topic))
|
||||||
|
.setView(R.layout.dialog_move_to_new_topic)
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
// empty but needed
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
passStore.getClassifier().notifyDataChange();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (newTopicEditText.getText().toString().isEmpty()) {
|
||||||
|
newTopicEditText.setError("cannot be empty");
|
||||||
|
newTopicEditText.requestFocus();
|
||||||
|
} else {
|
||||||
|
move(newTopicEditText.getText().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ButterKnife.bind(this, dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.ligi.passandroid.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.support.design.widget.AppBarLayout;
|
||||||
|
import android.support.design.widget.CoordinatorLayout;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.view.ViewCompat;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
|
||||||
|
|
||||||
|
import org.ligi.passandroid.R;
|
||||||
|
|
||||||
|
public class MyShyFABBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
|
||||||
|
|
||||||
|
public MyShyFABBehavior() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyShyFABBehavior(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionsMenu child, View dependency) {
|
||||||
|
return dependency instanceof Snackbar.SnackbarLayout || dependency instanceof AppBarLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionsMenu child, View dependency) {
|
||||||
|
if (dependency instanceof Snackbar.SnackbarLayout) {
|
||||||
|
updateFabTranslationForSnackbar(child, dependency);
|
||||||
|
}
|
||||||
|
if (dependency instanceof AppBarLayout) {
|
||||||
|
final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
|
||||||
|
final int fabBottomMargin = lp.bottomMargin;
|
||||||
|
final int distanceToScroll;
|
||||||
|
if (child.isExpanded()) {
|
||||||
|
distanceToScroll = child.getHeight() + fabBottomMargin;
|
||||||
|
} else {
|
||||||
|
distanceToScroll = (int) (child.getContext().getResources().getDimension(R.dimen.fab_size_normal) + 2 * fabBottomMargin);
|
||||||
|
}
|
||||||
|
final float ratio = ViewCompat.getY(dependency) / getToolbarHeight(dependency.getContext());
|
||||||
|
|
||||||
|
ViewCompat.setTranslationY(child, -distanceToScroll * ratio);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFabTranslationForSnackbar(FloatingActionsMenu child, View dependency) {
|
||||||
|
final float translationY = ViewCompat.getTranslationY(dependency) - dependency.getHeight();
|
||||||
|
final float translationYClipped = Math.min(0, translationY);
|
||||||
|
ViewCompat.setTranslationY(child, translationYClipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getToolbarHeight(Context context) {
|
||||||
|
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
|
||||||
|
new int[]{R.attr.actionBarSize});
|
||||||
|
int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
|
||||||
|
styledAttributes.recycle();
|
||||||
|
|
||||||
|
return toolbarHeight;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package org.ligi.passandroid.ui;
|
package org.ligi.passandroid.ui;
|
||||||
|
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.view.ActionMode;
|
import android.support.v7.view.ActionMode;
|
||||||
import android.support.v7.widget.CardView;
|
import android.support.v7.widget.CardView;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
@ -15,6 +16,7 @@ import org.ligi.passandroid.R;
|
||||||
import org.ligi.passandroid.model.FiledPass;
|
import org.ligi.passandroid.model.FiledPass;
|
||||||
import org.ligi.passandroid.model.Pass;
|
import org.ligi.passandroid.model.Pass;
|
||||||
import org.ligi.passandroid.model.PassStore;
|
import org.ligi.passandroid.model.PassStore;
|
||||||
|
import org.ligi.passandroid.model.PassStoreProjection;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -25,10 +27,13 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
||||||
@Inject
|
@Inject
|
||||||
PassStore passStore;
|
PassStore passStore;
|
||||||
|
|
||||||
protected final PassListActivity passListActivity;
|
protected final AppCompatActivity passListActivity;
|
||||||
|
private final PassStoreProjection passStoreProjection;
|
||||||
|
|
||||||
ActionMode actionMode;
|
ActionMode actionMode;
|
||||||
|
|
||||||
public PassAdapter(PassListActivity passListActivity) {
|
public PassAdapter(AppCompatActivity passListActivity, PassStoreProjection passStoreProjection) {
|
||||||
|
this.passStoreProjection = passStoreProjection;
|
||||||
App.component().inject(this);
|
App.component().inject(this);
|
||||||
this.passListActivity = passListActivity;
|
this.passListActivity = passListActivity;
|
||||||
}
|
}
|
||||||
|
@ -42,8 +47,8 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(PassViewHolder viewHolder, final int longClickedCardPosition) {
|
public void onBindViewHolder(final PassViewHolder viewHolder, int longClickedCardPosition) {
|
||||||
final Pass pass = passStore.getPassList().get(longClickedCardPosition);
|
final Pass pass = passStoreProjection.getPassList().get(longClickedCardPosition);
|
||||||
|
|
||||||
viewHolder.apply(pass, passListActivity);
|
viewHolder.apply(pass, passListActivity);
|
||||||
|
|
||||||
|
@ -62,7 +67,7 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(View v) {
|
public boolean onLongClick(View v) {
|
||||||
|
|
||||||
final Pass pass = getList().get(longClickedCardPosition);
|
final Pass pass = getList().get(viewHolder.getAdapterPosition());
|
||||||
|
|
||||||
if (actionMode != null) {
|
if (actionMode != null) {
|
||||||
final boolean clickedOnDifferentItem = actionMode.getTag() == null || !actionMode.getTag().equals(pass);
|
final boolean clickedOnDifferentItem = actionMode.getTag() == null || !actionMode.getTag().equals(pass);
|
||||||
|
@ -88,7 +93,7 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
|
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
|
||||||
if (new PassMenuOptions(passListActivity, getList().get(longClickedCardPosition)).process(menuItem)) {
|
if (new PassMenuOptions(passListActivity, getList().get(viewHolder.getAdapterPosition())).process(menuItem)) {
|
||||||
actionMode.finish();
|
actionMode.finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +108,7 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
actionMode.setTag(getList().get(longClickedCardPosition));
|
actionMode.setTag(getList().get(viewHolder.getAdapterPosition()));
|
||||||
|
|
||||||
root.setCardElevation(v.getContext().getResources().getDimension(R.dimen.card_longclick_elevation));
|
root.setCardElevation(v.getContext().getResources().getDimension(R.dimen.card_longclick_elevation));
|
||||||
|
|
||||||
|
@ -119,8 +124,8 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FiledPass> getList() {
|
private List<FiledPass> getList() {
|
||||||
return passStore.getPassList();
|
return passStoreProjection.getPassList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -8,10 +8,14 @@ import android.content.res.Configuration;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
import android.support.v4.widget.DrawerLayout;
|
import android.support.v4.widget.DrawerLayout;
|
||||||
import android.support.v7.app.ActionBarDrawerToggle;
|
import android.support.v7.app.ActionBarDrawerToggle;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -31,6 +35,7 @@ import org.ligi.passandroid.events.SortOrderChangeEvent;
|
||||||
import org.ligi.passandroid.events.TypeFocusEvent;
|
import org.ligi.passandroid.events.TypeFocusEvent;
|
||||||
import org.ligi.passandroid.helper.PassUtil;
|
import org.ligi.passandroid.helper.PassUtil;
|
||||||
import org.ligi.passandroid.model.FiledPass;
|
import org.ligi.passandroid.model.FiledPass;
|
||||||
|
import org.ligi.passandroid.model.PassClassifier;
|
||||||
import org.ligi.snackengage.SnackEngage;
|
import org.ligi.snackengage.SnackEngage;
|
||||||
import org.ligi.snackengage.snacks.DefaultRateSnack;
|
import org.ligi.snackengage.snacks.DefaultRateSnack;
|
||||||
import org.ligi.tracedroid.TraceDroid;
|
import org.ligi.tracedroid.TraceDroid;
|
||||||
|
@ -40,15 +45,20 @@ import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
|
||||||
public class PassListActivity extends PassAndroidActivity {
|
public class PassListActivity extends PassAndroidActivity implements PassClassifier.OnClassificationChangeListener {
|
||||||
|
|
||||||
private static final int OPEN_FILE_READ_REQUEST_CODE = 1000;
|
private static final int OPEN_FILE_READ_REQUEST_CODE = 1000;
|
||||||
private PassAdapter passAdapter;
|
|
||||||
|
|
||||||
private ActionBarDrawerToggle drawerToggle;
|
private ActionBarDrawerToggle drawerToggle;
|
||||||
|
|
||||||
@Bind(R.id.content_list)
|
@Bind(R.id.toolbar)
|
||||||
RecyclerView recyclerView;
|
Toolbar toolbar;
|
||||||
|
|
||||||
|
@Bind(R.id.tab_layout)
|
||||||
|
TabLayout tabLayout;
|
||||||
|
|
||||||
|
@Bind(R.id.view_pager)
|
||||||
|
ViewPager viewPager;
|
||||||
|
|
||||||
@Bind(R.id.drawer_layout)
|
@Bind(R.id.drawer_layout)
|
||||||
DrawerLayout drawer;
|
DrawerLayout drawer;
|
||||||
|
@ -59,11 +69,14 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
@Bind(R.id.fam)
|
@Bind(R.id.fam)
|
||||||
FloatingActionsMenu floatingActionsMenu;
|
FloatingActionsMenu floatingActionsMenu;
|
||||||
|
|
||||||
|
private PassTopicFragmentPagerAdapter adapter;
|
||||||
|
|
||||||
@OnClick(R.id.fab_action_create_pass)
|
@OnClick(R.id.fab_action_create_pass)
|
||||||
void onFABClick() {
|
void onFABClick() {
|
||||||
final FiledPass pass = PassUtil.createEmptyPass();
|
final FiledPass pass = PassUtil.createEmptyPass();
|
||||||
|
|
||||||
passStore.setCurrentPass(pass);
|
passStore.setCurrentPass(pass);
|
||||||
|
passStore.getClassifier().moveToTopic(pass,adapter.getPageTitle(tabLayout.getSelectedTabPosition()).toString());
|
||||||
pass.save(passStore);
|
pass.save(passStore);
|
||||||
AXT.at(this).startCommonIntent().activityFromClass(PassEditActivity.class);
|
AXT.at(this).startCommonIntent().activityFromClass(PassEditActivity.class);
|
||||||
floatingActionsMenu.collapse();
|
floatingActionsMenu.collapse();
|
||||||
|
@ -86,7 +99,6 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
|
|
||||||
@Bind(R.id.fab_action_open_file)
|
@Bind(R.id.fab_action_open_file)
|
||||||
FloatingActionButton openFileFAB;
|
FloatingActionButton openFileFAB;
|
||||||
|
|
||||||
public final static int VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK = 19;
|
public final static int VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK = 19;
|
||||||
|
|
||||||
@OnClick(R.id.fab_action_open_file)
|
@OnClick(R.id.fab_action_open_file)
|
||||||
|
@ -120,7 +132,6 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void typeFocus(TypeFocusEvent typeFocusEvent) {
|
public void typeFocus(TypeFocusEvent typeFocusEvent) {
|
||||||
scrollToType(typeFocusEvent.type);
|
|
||||||
drawer.closeDrawers();
|
drawer.closeDrawers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,12 +142,9 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
passStore.refreshPassesList();
|
passStore.refreshPassesList();
|
||||||
passStore.sort(settings.getSortOrder());
|
|
||||||
|
|
||||||
AXT.at(emptyView).setVisibility(passStore.getPassList().isEmpty());
|
AXT.at(emptyView).setVisibility(passStore.getPassList().isEmpty());
|
||||||
|
|
||||||
passAdapter.notifyDataSetChanged();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -155,11 +163,9 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
|
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
AXT.at(openFileFAB).setVisibility(Build.VERSION.SDK_INT >= VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
final LinearLayoutManager llm = new LinearLayoutManager(this);
|
AXT.at(openFileFAB).setVisibility(Build.VERSION.SDK_INT >= VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK);
|
||||||
llm.setOrientation(LinearLayoutManager.VERTICAL);
|
|
||||||
recyclerView.setLayoutManager(llm);
|
|
||||||
|
|
||||||
// don't want too many windows in worst case - so check for errors first
|
// don't want too many windows in worst case - so check for errors first
|
||||||
if (TraceDroid.getStackTraceFiles().length > 0) {
|
if (TraceDroid.getStackTraceFiles().length > 0) {
|
||||||
|
@ -170,7 +176,7 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
} else { // if no error - check if there is a new version of the app
|
} else { // if no error - check if there is a new version of the app
|
||||||
tracker.trackEvent("ui_event", "processFile", "updatenotice", null);
|
tracker.trackEvent("ui_event", "processFile", "updatenotice", null);
|
||||||
|
|
||||||
SnackEngage.from(this).withSnack(new DefaultRateSnack()).build().engageWhenAppropriate();
|
SnackEngage.from(floatingActionsMenu).withSnack(new DefaultRateSnack()).build().engageWhenAppropriate();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawerToggle = new ActionBarDrawerToggle(this, drawer, R.string.drawer_open, R.string.drawer_close) {
|
drawerToggle = new ActionBarDrawerToggle(this, drawer, R.string.drawer_open, R.string.drawer_close) {
|
||||||
|
@ -185,19 +191,13 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
if (getSupportActionBar() != null) {
|
if (getSupportActionBar() != null) {
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
passAdapter = new PassAdapter(this);
|
|
||||||
recyclerView.setAdapter(passAdapter);
|
adapter = new PassTopicFragmentPagerAdapter(passStore.getClassifier(), getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scrollToType(String type) {
|
|
||||||
|
|
||||||
for (int i = 0; i < passAdapter.getItemCount(); i++) {
|
|
||||||
if (passStore.getPassList().get(i).getTypeNotNull().equals(type)) {
|
|
||||||
recyclerView.scrollToPosition(i);
|
|
||||||
return; // we are done
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -222,6 +222,9 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
|
|
||||||
bus.register(this);
|
bus.register(this);
|
||||||
refreshPasses();
|
refreshPasses();
|
||||||
|
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
passStore.getClassifier().onClassificationChangeListeners.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -245,8 +248,77 @@ public class PassListActivity extends PassAndroidActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
|
passStore.getClassifier().onClassificationChangeListeners.remove(this);
|
||||||
bus.unregister(this);
|
bus.unregister(this);
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void OnClassificationChange() {
|
||||||
|
refreshPasses();
|
||||||
|
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
setupWithViewPagerIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupWithViewPagerIfNeeded() {
|
||||||
|
if (!areTabLayoutAndViewPagerInSync()) {
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean areTabLayoutAndViewPagerInSync() {
|
||||||
|
if (adapter.getCount() == tabLayout.getTabCount()) {
|
||||||
|
for (int i=0;i<adapter.getCount();i++) {
|
||||||
|
if (!adapter.getPageTitle(i).equals(tabLayout.getTabAt(i).getText())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PassTopicFragmentPagerAdapter extends FragmentStatePagerAdapter {
|
||||||
|
|
||||||
|
private String[] topic_array;
|
||||||
|
private final PassClassifier passClassifier;
|
||||||
|
|
||||||
|
public PassTopicFragmentPagerAdapter(PassClassifier passClassifier, FragmentManager fragmentManager) {
|
||||||
|
super(fragmentManager);
|
||||||
|
this.passClassifier = passClassifier;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyDataSetChanged() {
|
||||||
|
topic_array = passClassifier.getTopics();
|
||||||
|
super.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
return PassListFragment.newInstance(topic_array[position]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemPosition(Object object) {
|
||||||
|
return POSITION_NONE; // TODO - return POSITION_UNCHANGED in some cases
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return topic_array.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
return topic_array[position];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
package org.ligi.passandroid.ui;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.squareup.otto.Bus;
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import org.ligi.passandroid.App;
|
||||||
|
import org.ligi.passandroid.R;
|
||||||
|
import org.ligi.passandroid.events.TypeFocusEvent;
|
||||||
|
import org.ligi.passandroid.helper.MoveHelper;
|
||||||
|
import org.ligi.passandroid.model.FiledPass;
|
||||||
|
import org.ligi.passandroid.model.PassStore;
|
||||||
|
import org.ligi.passandroid.model.PassStoreProjection;
|
||||||
|
import org.ligi.passandroid.model.Settings;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.support.v7.widget.helper.ItemTouchHelper.LEFT;
|
||||||
|
import static android.support.v7.widget.helper.ItemTouchHelper.RIGHT;
|
||||||
|
import static android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback;
|
||||||
|
import static org.ligi.passandroid.model.PassClassifier.OnClassificationChangeListener;
|
||||||
|
|
||||||
|
public class PassListFragment extends Fragment implements OnClassificationChangeListener {
|
||||||
|
|
||||||
|
private static final String BUNDLE_KEY_TOPIC = "topic";
|
||||||
|
private PassStoreProjection passStoreProjection;
|
||||||
|
private PassAdapter adapter;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
|
||||||
|
public static PassListFragment newInstance(final String topic) {
|
||||||
|
PassListFragment myFragment = new PassListFragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(BUNDLE_KEY_TOPIC, topic);
|
||||||
|
myFragment.setArguments(args);
|
||||||
|
|
||||||
|
return myFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PassStore passStore;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
Settings settings;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
Bus bus;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
final View inflate = inflater.inflate(R.layout.pass_recycler, container, false);
|
||||||
|
recyclerView = (RecyclerView) inflate.findViewById(R.id.pass_recyclerview);
|
||||||
|
|
||||||
|
App.component().inject(this);
|
||||||
|
|
||||||
|
passStoreProjection = new PassStoreProjection(passStore, getArguments().getString(BUNDLE_KEY_TOPIC), settings.getSortOrder());
|
||||||
|
adapter = new PassAdapter((AppCompatActivity) getActivity(), passStoreProjection);
|
||||||
|
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
|
|
||||||
|
SimpleCallback simpleItemTouchCallback = new SimpleCallback(0, LEFT | RIGHT) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
|
||||||
|
final FiledPass pass = passStoreProjection.getPassList().get(viewHolder.getAdapterPosition());
|
||||||
|
|
||||||
|
final String nextTopic = calculateNextTopic(swipeDir, pass);
|
||||||
|
|
||||||
|
if (nextTopic != null) {
|
||||||
|
MoveHelper.moveWithUndoSnackbar(passStore.getClassifier(),pass,nextTopic,getActivity());
|
||||||
|
} else {
|
||||||
|
new MoveToNewTopicUI(getActivity(),passStore, pass).showTopicMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String calculateNextTopic(final int swipeDir, final FiledPass pass) {
|
||||||
|
final String[] topics = passStore.getClassifier().getTopics();
|
||||||
|
|
||||||
|
switch (swipeDir) {
|
||||||
|
case LEFT:
|
||||||
|
return getNextTopicLeft(pass, topics);
|
||||||
|
case RIGHT:
|
||||||
|
return getNextTopicRight(pass, topics);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getNextTopicRight(FiledPass pass, String[] topics) {
|
||||||
|
|
||||||
|
boolean nextIsCandidate = false;
|
||||||
|
|
||||||
|
for (String topic : topics) {
|
||||||
|
if (nextIsCandidate) {
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
if (passStore.getClassifier().getTopic(pass).equals(topic)) {
|
||||||
|
nextIsCandidate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getNextTopicLeft(FiledPass pass, String[] topics) {
|
||||||
|
String prev = null;
|
||||||
|
|
||||||
|
for (String topic : topics) {
|
||||||
|
if (passStore.getClassifier().getTopic(pass).equals(topic)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
prev = topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
|
||||||
|
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||||
|
|
||||||
|
passStore.getClassifier().onClassificationChangeListeners.add(this);
|
||||||
|
|
||||||
|
bus.register(this);
|
||||||
|
return inflate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bus.unregister(this);
|
||||||
|
passStore.getClassifier().onClassificationChangeListeners.remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void OnClassificationChange() {
|
||||||
|
passStoreProjection.refresh();
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void typeFocus(TypeFocusEvent typeFocusEvent) {
|
||||||
|
scrollToType(typeFocusEvent.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void scrollToType(String type) {
|
||||||
|
|
||||||
|
for (int i = 0; i < adapter.getItemCount(); i++) {
|
||||||
|
if (passStoreProjection.getPassList().get(i).getTypeNotNull().equals(type)) {
|
||||||
|
recyclerView.scrollToPosition(i);
|
||||||
|
return; // we are done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.NavUtils;
|
import android.support.v4.app.NavUtils;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
@ -77,6 +78,9 @@ public class PassViewActivity extends PassViewActivityBase {
|
||||||
@Bind(R.id.barcode_alt_text)
|
@Bind(R.id.barcode_alt_text)
|
||||||
TextView barcodeAlternativeText;
|
TextView barcodeAlternativeText;
|
||||||
|
|
||||||
|
@Bind(R.id.toolbar)
|
||||||
|
Toolbar toolbar;
|
||||||
|
|
||||||
@OnClick(R.id.zoomIn)
|
@OnClick(R.id.zoomIn)
|
||||||
void zoomIn() {
|
void zoomIn() {
|
||||||
setBarCodeSize(currentBarcodeWidth + getFingerSize());
|
setBarCodeSize(currentBarcodeWidth + getFingerSize());
|
||||||
|
@ -125,28 +129,6 @@ public class PassViewActivity extends PassViewActivityBase {
|
||||||
|
|
||||||
int currentBarcodeWidth;
|
int currentBarcodeWidth;
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
if (optionalPass == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AXT.at(this).disableRotation();
|
|
||||||
|
|
||||||
final View contentView = getLayoutInflater().inflate(R.layout.activity_pass_view, null);
|
|
||||||
setContentView(contentView);
|
|
||||||
|
|
||||||
final ViewGroup extraViewContainer = (ViewGroup) contentView.findViewById(R.id.passExtrasContainer);
|
|
||||||
final View passExtrasView = getLayoutInflater().inflate(R.layout.pass_view_extra_data, extraViewContainer, false);
|
|
||||||
extraViewContainer.addView(passExtrasView);
|
|
||||||
|
|
||||||
ButterKnife.bind(this);
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void refresh() {
|
protected void refresh() {
|
||||||
super.refresh();
|
super.refresh();
|
||||||
|
@ -226,14 +208,34 @@ public class PassViewActivity extends PassViewActivityBase {
|
||||||
AXT.at(this).disableRotation();
|
AXT.at(this).disableRotation();
|
||||||
|
|
||||||
setContentView(R.layout.activity_pass_view);
|
setContentView(R.layout.activity_pass_view);
|
||||||
|
}
|
||||||
|
|
||||||
final ViewGroup extraViewContainer = (ViewGroup) findViewById(R.id.passExtrasContainer);
|
@Override
|
||||||
getLayoutInflater().inflate(R.layout.pass_view_extra_data, extraViewContainer);
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
if (optionalPass == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AXT.at(this).disableRotation();
|
||||||
|
|
||||||
|
final View contentView = getLayoutInflater().inflate(R.layout.activity_pass_view, null);
|
||||||
|
setContentView(contentView);
|
||||||
|
|
||||||
|
final ViewGroup extraViewContainer = (ViewGroup) contentView.findViewById(R.id.passExtrasContainer);
|
||||||
|
final View passExtrasView = getLayoutInflater().inflate(R.layout.pass_view_extra_data, extraViewContainer, false);
|
||||||
|
extraViewContainer.addView(passExtrasView);
|
||||||
|
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
configureActionBar();
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addFrontFields(PassFieldList passFields) {
|
private void addFrontFields(PassFieldList passFields) {
|
||||||
for (PassField field : passFields) {
|
for (PassField field : passFields) {
|
||||||
|
|
||||||
|
|
|
@ -37,11 +37,6 @@ public class PassViewActivityBase extends PassAndroidActivity {
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (getSupportActionBar() != null) {
|
|
||||||
getSupportActionBar().setHomeButtonEnabled(true);
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// a little hack because I strongly disagree with the style guide here
|
// a little hack because I strongly disagree with the style guide here
|
||||||
// ;-)
|
// ;-)
|
||||||
// not having the Actionbar overflow menu also with devices with hardware
|
// not having the Actionbar overflow menu also with devices with hardware
|
||||||
|
@ -57,7 +52,6 @@ public class PassViewActivityBase extends PassAndroidActivity {
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// Ignore - but at least we tried ;-)
|
// Ignore - but at least we tried ;-)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,6 +70,16 @@ public class PassViewActivityBase extends PassAndroidActivity {
|
||||||
tracker.trackException("pass not present in " + this, false);
|
tracker.trackException("pass not present in " + this, false);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configureActionBar();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void configureActionBar() {
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void refresh() {
|
protected void refresh() {
|
||||||
|
|
|
@ -1,11 +1,38 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
|
||||||
|
<android.support.design.widget.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/help_tv"
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||||
|
|
||||||
|
</android.support.design.widget.AppBarLayout>
|
||||||
|
|
||||||
|
<android.support.v4.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/help_webview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
android:padding="16dp"/>
|
android:padding="16dp"/>
|
||||||
</ScrollView>
|
|
||||||
|
</android.support.v4.widget.NestedScrollView>
|
||||||
|
|
||||||
|
</android.support.design.widget.CoordinatorLayout>
|
|
@ -1,8 +1,35 @@
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
|
<android.support.design.widget.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||||
|
|
||||||
|
</android.support.design.widget.AppBarLayout>
|
||||||
|
|
||||||
|
<android.support.v4.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
|
||||||
<include layout="@layout/pass_list_item" />
|
<include layout="@layout/pass_list_item" />
|
||||||
|
|
||||||
|
</android.support.v4.widget.NestedScrollView>
|
||||||
|
|
||||||
</ScrollView>
|
</android.support.design.widget.CoordinatorLayout>
|
||||||
|
|
43
android/src/main/res/layout/dialog_move_to_new_topic.xml
Normal file
43
android/src/main/res/layout/dialog_move_to_new_topic.xml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/new_topic_edit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Title for new topic" />
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Suggestions:" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<Button
|
||||||
|
android:id="@+id/suggestion_button_trash"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="TRASH"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/suggestion_button_archive"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="ARCHIVE"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -1,17 +1,44 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:fab="http://schemas.android.com/apk/res-auto"
|
xmlns:fab="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/drawer_layout"
|
android:id="@+id/drawer_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<android.support.design.widget.CoordinatorLayout
|
||||||
xmlns:fab="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
||||||
|
<android.support.design.widget.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||||
|
|
||||||
|
<android.support.design.widget.TabLayout
|
||||||
|
android:id="@+id/tab_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</android.support.design.widget.AppBarLayout>
|
||||||
|
|
||||||
|
<android.support.v4.view.ViewPager
|
||||||
|
android:id="@+id/view_pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/emptyView"
|
android:id="@+id/emptyView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -21,13 +48,6 @@
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
android:text="@string/empty_text_view" />
|
android:text="@string/empty_text_view" />
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
|
||||||
android:id="@+id/content_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:choiceMode="singleChoice"
|
|
||||||
android:divider="@null"
|
|
||||||
android:listSelector="@android:color/transparent" />
|
|
||||||
|
|
||||||
<net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu
|
<net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu
|
||||||
android:id="@+id/fam"
|
android:id="@+id/fam"
|
||||||
|
@ -36,9 +56,11 @@
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_gravity="right|bottom"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
android:layout_marginRight="16dp"
|
||||||
|
app:layout_behavior=".ui.MyShyFABBehavior"
|
||||||
fab:fab_addButtonColorNormal="@color/accent"
|
fab:fab_addButtonColorNormal="@color/accent"
|
||||||
fab:fab_addButtonColorPressed="@color/icon_green"
|
fab:fab_addButtonColorPressed="@color/icon_green"
|
||||||
fab:fab_addButtonPlusIconColor="@android:color/black"
|
fab:fab_addButtonPlusIconColor="@android:color/black"
|
||||||
|
@ -79,6 +101,7 @@
|
||||||
|
|
||||||
|
|
||||||
<net.i2p.android.ext.floatingactionbutton.FloatingActionButton
|
<net.i2p.android.ext.floatingactionbutton.FloatingActionButton
|
||||||
|
|
||||||
android:id="@+id/fab_action_create_pass"
|
android:id="@+id/fab_action_create_pass"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -91,7 +114,7 @@
|
||||||
|
|
||||||
</net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu>
|
</net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu>
|
||||||
|
|
||||||
</RelativeLayout>
|
</android.support.design.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/left_drawer"
|
android:id="@+id/left_drawer"
|
||||||
|
|
10
android/src/main/res/layout/pass_recycler.xml
Normal file
10
android/src/main/res/layout/pass_recycler.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/pass_recyclerview"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:choiceMode="singleChoice"
|
||||||
|
android:divider="@null"
|
||||||
|
android:listSelector="@android:color/transparent" />
|
|
@ -36,11 +36,6 @@ The app is offline usable once you downloaded the pass!
|
||||||
<li>The new passbook is visible in PassAndroid.</li>
|
<li>The new passbook is visible in PassAndroid.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h2>Why this app?</h2>
|
|
||||||
The tickets for the Chaos Congress were only for print and iPhone passbooks.
|
|
||||||
Ligi - the original author - didn\'t want to print the ticket or buy an Apple device, so he programmed PassAndroid.
|
|
||||||
<a href="https://twitter.com/mr_ligi/status/678708731190841346">Source</a>
|
|
||||||
|
|
||||||
<H1>Credits</H1>
|
<H1>Credits</H1>
|
||||||
Big thanks to <a href="http://ltorrecilla.com/">Luis Torrecilla</a> for the Design & <a href="https://github.com/cketti">Cketti</a> for pull-requests!
|
Big thanks to <a href="http://ltorrecilla.com/">Luis Torrecilla</a> for the Design & <a href="https://github.com/cketti">Cketti</a> for pull-requests!
|
||||||
|
|
||||||
|
@ -123,4 +118,7 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
|
||||||
<string name="pass_updated">Pass Updated</string>
|
<string name="pass_updated">Pass Updated</string>
|
||||||
<string name="expiration_date_to_calendar_warning_message">You are about to add the expiration-date to the calendar - If you think there is a better date in this pass - please send it to me - PassAndroid needs to know where this</string>
|
<string name="expiration_date_to_calendar_warning_message">You are about to add the expiration-date to the calendar - If you think there is a better date in this pass - please send it to me - PassAndroid needs to know where this</string>
|
||||||
<string name="expiration_date_to_calendar_warning_title">Add Expiration-Date</string>
|
<string name="expiration_date_to_calendar_warning_title">Add Expiration-Date</string>
|
||||||
|
<string name="topic_trash">trash</string>
|
||||||
|
<string name="topic_archive">archive</string>
|
||||||
|
<string name="move_to_new_topic">Move to new Topic</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1,4 +1,11 @@
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<style name="AppBaseThemeNoActionbar" parent="AppBaseTheme">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
<item name="android:windowBackground">@color/window_bg</item>
|
<item name="android:windowBackground">@color/window_bg</item>
|
||||||
<item name="colorPrimary">@color/primary</item>
|
<item name="colorPrimary">@color/primary</item>
|
||||||
|
@ -8,6 +15,7 @@
|
||||||
<item name="actionModeBackground">@color/secondary</item>
|
<item name="actionModeBackground">@color/secondary</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="AppTheme" parent="AppBaseTheme" />
|
<style name="AppTheme" parent="AppBaseTheme" />
|
||||||
|
|
||||||
<style name="nav_btn">
|
<style name="nav_btn">
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
package org.ligi.passandroid.unittest;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.ligi.passandroid.model.FiledPass;
|
||||||
|
import org.ligi.passandroid.model.PassClassifier;
|
||||||
|
import org.ligi.passandroid.model.PassImpl;
|
||||||
|
import org.mockito.internal.util.collections.Sets;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class ThePassClassifier {
|
||||||
|
|
||||||
|
public static final String ID_1 = "ID1";
|
||||||
|
public static final String TOPIC_1 = "topic1";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatPassIsInactiveByDefault() {
|
||||||
|
|
||||||
|
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>());
|
||||||
|
|
||||||
|
assertThat(tested.getTopic(getPassWithId(ID_1)).equals(PassClassifier.DEFAULT_TOPIC));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatOnlyNonDefaultTopicInTopicListWhenOnePassWithNonDefaultTopic() {
|
||||||
|
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>() {
|
||||||
|
{
|
||||||
|
put(TOPIC_1, Sets.newSet(ID_1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(tested.getTopics()).containsExactly(TOPIC_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatAfterMovingFromOnlyOneTopicToDefaultTopicOnly() {
|
||||||
|
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>() {
|
||||||
|
{
|
||||||
|
put(TOPIC_1, Sets.newSet(ID_1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tested.moveToTopic(getPassWithId(ID_1), PassClassifier.DEFAULT_TOPIC);
|
||||||
|
assertThat(tested.getTopics()).containsExactly(PassClassifier.DEFAULT_TOPIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatTopicIsGoneAfterMove() {
|
||||||
|
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>() {
|
||||||
|
{
|
||||||
|
put(TOPIC_1, Sets.newSet(ID_1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tested.moveToTopic(getPassWithId(ID_1), PassClassifier.DEFAULT_TOPIC);
|
||||||
|
assertThat(tested.getTopics()).containsExactly(PassClassifier.DEFAULT_TOPIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatPassIsInTopicAsExpected() {
|
||||||
|
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>() {
|
||||||
|
{
|
||||||
|
put(TOPIC_1, Sets.newSet(ID_1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(tested.getTopic(getPassWithId(ID_1)).equals(PassClassifier.DEFAULT_TOPIC));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHasAtLeastOneTopic() {
|
||||||
|
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>());
|
||||||
|
|
||||||
|
assertThat(tested.getTopics()).containsExactly(PassClassifier.DEFAULT_TOPIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FiledPass getPassWithId(String id) {
|
||||||
|
PassImpl result = new PassImpl();
|
||||||
|
result.setId(id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue