Introduce Topics/Classification and Swipe to move
This commit is contained in:
parent
7cdb10f8f7
commit
f76072a244
39 changed files with 1396 additions and 183 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,7 +4,6 @@ build
|
|||
#assets
|
||||
bin
|
||||
gen
|
||||
proguard-project.txt
|
||||
project.properties
|
||||
gradle.properties
|
||||
local.properties
|
||||
|
|
|
@ -6,7 +6,7 @@ buildscript {
|
|||
}
|
||||
|
||||
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 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||
|
@ -82,6 +82,10 @@ android {
|
|||
|
||||
// hack for instrumentation testing :-(
|
||||
exclude 'LICENSE.txt'
|
||||
|
||||
exclude 'META-INF/maven/com.google.guava/guava/pom.properties'
|
||||
exclude 'META-INF/maven/com.google.guava/guava/pom.xml'
|
||||
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
|
@ -119,6 +123,8 @@ dependencies {
|
|||
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.assertj:assertj-android:1.1.1'
|
||||
|
||||
|
@ -145,6 +151,7 @@ dependencies {
|
|||
compile 'com.squareup.okhttp:okhttp:2.7.4'
|
||||
compile 'com.larswerkman:HoloColorPicker:1.5'
|
||||
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'
|
||||
|
||||
|
|
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(TheBarCodeEditFragment theBarCodeEditFragment);
|
||||
|
||||
void inject(ThePassListSwiping thePassListSwiping);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.ligi.passandroid.model.comparator.PassSortOrder;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
|
@ -29,10 +30,11 @@ public class TestModule {
|
|||
|
||||
public TestModule() {
|
||||
passList = new ArrayList<>();
|
||||
final PassImpl object = new PassImpl();
|
||||
object.setDescription("description");
|
||||
object.setBarCode(new BarCode(BarcodeFormat.AZTEC, "messageprobe"));
|
||||
passList.add(object);
|
||||
final PassImpl pass = new PassImpl();
|
||||
pass.setId(UUID.randomUUID().toString());
|
||||
pass.setDescription("description");
|
||||
pass.setBarCode(new BarCode(BarcodeFormat.AZTEC, "messageprobe"));
|
||||
passList.add(pass);
|
||||
|
||||
}
|
||||
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.matcher.ViewMatchers.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
|
||||
|
||||
public class TheEmptyPassList extends BaseIntegration<PassListActivity> {
|
||||
|
||||
|
@ -38,7 +39,8 @@ public class TheEmptyPassList extends BaseIntegration<PassListActivity> {
|
|||
@MediumTest
|
||||
public void testHelpGoesToHelp() {
|
||||
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.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.isDisplayed;
|
||||
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.Matchers.allOf;
|
||||
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
|
||||
|
||||
|
||||
public class TheHelpActivity extends BaseIntegration<HelpActivity> {
|
||||
|
@ -31,7 +29,9 @@ public class TheHelpActivity extends BaseIntegration<HelpActivity> {
|
|||
|
||||
@SmallTest
|
||||
public void testHelpIsThere() {
|
||||
onView(withId(R.id.help_tv)).check(matches(isDisplayed()));
|
||||
|
||||
checkThatHelpIsThere();
|
||||
|
||||
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.withId;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
|
||||
|
||||
@TargetApi(14)
|
||||
public class ThePassListActivity extends BaseIntegration<PassListActivity> {
|
||||
|
@ -34,14 +35,15 @@ public class ThePassListActivity extends BaseIntegration<PassListActivity> {
|
|||
@MediumTest
|
||||
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");
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testHelpMenuBringsUsToHelp() {
|
||||
onView(withId(R.id.menu_help)).perform(click());
|
||||
onView(withId(R.id.help_tv)).check(matches(isDisplayed()));
|
||||
|
||||
checkThatHelpIsThere();
|
||||
}
|
||||
|
||||
@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.Pass;
|
||||
import org.ligi.passandroid.model.PassClassifier;
|
||||
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;
|
||||
|
||||
public class FixedPassListPassStore implements PassStore {
|
||||
|
||||
private final List<FiledPass> passes;
|
||||
private Pass actPass;
|
||||
private PassClassifier passClassifier;
|
||||
|
||||
public FixedPassListPassStore(List<FiledPass> passes) {
|
||||
this.passes = passes;
|
||||
|
@ -46,10 +49,6 @@ public class FixedPassListPassStore implements PassStore {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(PassSortOrder order) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pass getCurrentPass() {
|
||||
return actPass;
|
||||
|
@ -60,6 +59,14 @@ public class FixedPassListPassStore implements PassStore {
|
|||
actPass = pass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PassClassifier getClassifier() {
|
||||
if (passClassifier == null) {
|
||||
passClassifier = new PassClassifier(new HashMap<String, Collection<String>>());
|
||||
}
|
||||
return passClassifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePassWithId(String id) {
|
||||
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")));
|
||||
}
|
||||
|
||||
}
|
|
@ -22,17 +22,21 @@
|
|||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<service android:name=".ui.SearchPassesIntentService"/>
|
||||
<service android:name=".ui.SearchPassesIntentService" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.PassListActivity"
|
||||
android:label="@string/app_name">
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppBaseThemeNoActionbar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.HelpActivity" />
|
||||
<activity
|
||||
android:name=".ui.HelpActivity"
|
||||
android:theme="@style/AppBaseThemeNoActionbar" />
|
||||
<activity
|
||||
android:name=".ui.PassEditActivity"
|
||||
android:screenOrientation="sensorPortrait" />
|
||||
|
@ -697,7 +701,7 @@
|
|||
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.quirk_fix.OpenIphoneWebView"/>
|
||||
<activity android:name=".ui.quirk_fix.OpenIphoneWebView" />
|
||||
|
||||
<activity android:name=".ui.quirk_fix.USAirwaysLoadActivity">
|
||||
<intent-filter>
|
||||
|
@ -743,6 +747,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".ui.PassViewActivity"
|
||||
android:theme="@style/AppBaseThemeNoActionbar"
|
||||
android:label="@string/app_name"
|
||||
android:parentActivityName=".ui.PassListActivity">
|
||||
<meta-data
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.ligi.passandroid.ui.PassAndroidActivity;
|
|||
import org.ligi.passandroid.ui.PassEditActivity;
|
||||
import org.ligi.passandroid.ui.PassImportActivity;
|
||||
import org.ligi.passandroid.ui.PassListActivity;
|
||||
import org.ligi.passandroid.ui.PassListFragment;
|
||||
import org.ligi.passandroid.ui.PassMenuOptions;
|
||||
import org.ligi.passandroid.ui.PassViewActivityBase;
|
||||
import org.ligi.passandroid.ui.SearchPassesIntentService;
|
||||
|
@ -53,6 +54,8 @@ public interface AppComponent {
|
|||
|
||||
void inject(PassAndroidActivity passAndroidActivity);
|
||||
|
||||
void inject(PassListFragment passListFragment);
|
||||
|
||||
PassStore passStore();
|
||||
|
||||
Tracker tracker();
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.squareup.otto.Bus;
|
|||
import com.squareup.otto.ThreadEnforcer;
|
||||
|
||||
import org.ligi.passandroid.model.AndroidFileSystemPassStore;
|
||||
import org.ligi.passandroid.model.AndroidSettings;
|
||||
import org.ligi.passandroid.model.PassStore;
|
||||
import org.ligi.passandroid.model.Settings;
|
||||
|
||||
|
@ -33,7 +34,7 @@ public class AppModule {
|
|||
@Singleton
|
||||
@Provides
|
||||
Settings provideSettings() {
|
||||
return new Settings(app);
|
||||
return new AndroidSettings(app);
|
||||
}
|
||||
|
||||
@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.passandroid.helper.DirectoryFileFilter;
|
||||
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
||||
import org.ligi.passandroid.reader.AppleStylePassReader;
|
||||
import org.ligi.passandroid.reader.PassReader;
|
||||
import org.ligi.tracedroid.logging.Log;
|
||||
|
@ -12,7 +11,6 @@ import org.ligi.tracedroid.logging.Log;
|
|||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class AndroidFileSystemPassStore implements PassStore {
|
||||
|
@ -22,12 +20,15 @@ public class AndroidFileSystemPassStore implements PassStore {
|
|||
|
||||
private List<FiledPass> passList = new ArrayList<>();
|
||||
private Pass actPass;
|
||||
private final PassClassifier passClassifier;
|
||||
|
||||
public AndroidFileSystemPassStore(final Context context, final Settings settings) {
|
||||
this.context = context;
|
||||
path = settings.getPassesDir();
|
||||
|
||||
refreshPassesList();
|
||||
final File classificationFile = new File(settings.getStateDir(), "classification_state.json");
|
||||
passClassifier = new FileBackedPassClassifier(classificationFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,11 +125,6 @@ public class AndroidFileSystemPassStore implements PassStore {
|
|||
return getCachedPassOrLoad(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(final PassSortOrder order) {
|
||||
Collections.sort(passList, order.toComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pass getCurrentPass() {
|
||||
return actPass;
|
||||
|
@ -139,9 +135,18 @@ public class AndroidFileSystemPassStore implements PassStore {
|
|||
actPass = pass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PassClassifier getClassifier() {
|
||||
return passClassifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
|
|
|
@ -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 org.ligi.passandroid.model.comparator.PassSortOrder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PassStore {
|
||||
|
||||
boolean deletePassWithId(String id);
|
||||
|
||||
String getPathForID(String id);
|
||||
|
||||
|
||||
List<FiledPass> getPassList();
|
||||
|
||||
void preCachePassesList();
|
||||
|
||||
void deleteCacheForId(String id);
|
||||
|
||||
void refreshPassesList();
|
||||
|
||||
Pass getPassbookForId(String id);
|
||||
|
||||
boolean deletePassWithId(String id);
|
||||
|
||||
void sort(PassSortOrder order);
|
||||
String getPathForID(String id);
|
||||
|
||||
void preCachePassesList();
|
||||
|
||||
List<FiledPass> getPassList();
|
||||
|
||||
@Nullable
|
||||
Pass getCurrentPass();
|
||||
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Environment;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.ligi.passandroid.model.comparator.PassSortOrder;
|
||||
|
||||
public class Settings {
|
||||
public static final String ORDER_KEY = "order";
|
||||
public final Context context;
|
||||
import java.io.File;
|
||||
|
||||
private final SharedPreferences sharedPreferences;
|
||||
public interface Settings {
|
||||
|
||||
public Settings(Context context) {
|
||||
this.context = context;
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
void setSortOrder(PassSortOrder order);
|
||||
|
||||
public void setSortOrder(PassSortOrder order) {
|
||||
sharedPreferences.edit().putInt(ORDER_KEY, order.getInt()).apply();
|
||||
}
|
||||
PassSortOrder getSortOrder();
|
||||
|
||||
public PassSortOrder getSortOrder() {
|
||||
int id = sharedPreferences.getInt(ORDER_KEY, 0);
|
||||
for (PassSortOrder order : PassSortOrder.values()) {
|
||||
if (order.getInt() == id) {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
return PassSortOrder.DATE;
|
||||
}
|
||||
boolean doTraceDroidEmailSend();
|
||||
|
||||
public boolean doTraceDroidEmailSend() {
|
||||
// will be overridden in test-module
|
||||
return true;
|
||||
}
|
||||
String getPassesDir();
|
||||
|
||||
public String getPassesDir() {
|
||||
return context.getFilesDir().getAbsolutePath() + "/passes";
|
||||
}
|
||||
File getStateDir();
|
||||
|
||||
public String getShareDir() {
|
||||
return Environment.getExternalStorageDirectory() + "/tmp/passbook_share_tmp/";
|
||||
}
|
||||
String getShareDir();
|
||||
|
||||
}
|
||||
|
|
|
@ -2,19 +2,23 @@ package org.ligi.passandroid.ui;
|
|||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
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.ButterKnife;
|
||||
import org.ligi.passandroid.R;
|
||||
|
||||
public class HelpActivity extends AppCompatActivity {
|
||||
|
||||
@Bind(R.id.help_tv)
|
||||
TextView helpTextView;
|
||||
@Bind(R.id.help_webview)
|
||||
WebView helpWebView;
|
||||
|
||||
@Bind(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -22,11 +26,13 @@ public class HelpActivity extends AppCompatActivity {
|
|||
setContentView(R.layout.activity_help);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v7.widget.CardView;
|
||||
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.Pass;
|
||||
import org.ligi.passandroid.model.PassStore;
|
||||
import org.ligi.passandroid.model.PassStoreProjection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -25,10 +27,13 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
|||
@Inject
|
||||
PassStore passStore;
|
||||
|
||||
protected final PassListActivity passListActivity;
|
||||
protected final AppCompatActivity passListActivity;
|
||||
private final PassStoreProjection passStoreProjection;
|
||||
|
||||
ActionMode actionMode;
|
||||
|
||||
public PassAdapter(PassListActivity passListActivity) {
|
||||
public PassAdapter(AppCompatActivity passListActivity, PassStoreProjection passStoreProjection) {
|
||||
this.passStoreProjection = passStoreProjection;
|
||||
App.component().inject(this);
|
||||
this.passListActivity = passListActivity;
|
||||
}
|
||||
|
@ -42,10 +47,10 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PassViewHolder viewHolder, final int longClickedCardPosition) {
|
||||
final Pass pass = passStore.getPassList().get(longClickedCardPosition);
|
||||
public void onBindViewHolder(final PassViewHolder viewHolder, int longClickedCardPosition) {
|
||||
final Pass pass = passStoreProjection.getPassList().get(longClickedCardPosition);
|
||||
|
||||
viewHolder.apply(pass,passListActivity);
|
||||
viewHolder.apply(pass, passListActivity);
|
||||
|
||||
final CardView root = viewHolder.root;
|
||||
|
||||
|
@ -62,7 +67,7 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
|||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
|
||||
final Pass pass = getList().get(longClickedCardPosition);
|
||||
final Pass pass = getList().get(viewHolder.getAdapterPosition());
|
||||
|
||||
if (actionMode != null) {
|
||||
final boolean clickedOnDifferentItem = actionMode.getTag() == null || !actionMode.getTag().equals(pass);
|
||||
|
@ -88,7 +93,7 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
|||
|
||||
@Override
|
||||
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();
|
||||
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));
|
||||
|
||||
|
@ -119,8 +124,8 @@ public class PassAdapter extends RecyclerView.Adapter<PassViewHolder> {
|
|||
return position;
|
||||
}
|
||||
|
||||
public List<FiledPass> getList() {
|
||||
return passStore.getPassList();
|
||||
private List<FiledPass> getList() {
|
||||
return passStoreProjection.getPassList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,10 +8,14 @@ import android.content.res.Configuration;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
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.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -31,6 +35,7 @@ import org.ligi.passandroid.events.SortOrderChangeEvent;
|
|||
import org.ligi.passandroid.events.TypeFocusEvent;
|
||||
import org.ligi.passandroid.helper.PassUtil;
|
||||
import org.ligi.passandroid.model.FiledPass;
|
||||
import org.ligi.passandroid.model.PassClassifier;
|
||||
import org.ligi.snackengage.SnackEngage;
|
||||
import org.ligi.snackengage.snacks.DefaultRateSnack;
|
||||
import org.ligi.tracedroid.TraceDroid;
|
||||
|
@ -40,15 +45,20 @@ import butterknife.Bind;
|
|||
import butterknife.ButterKnife;
|
||||
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 PassAdapter passAdapter;
|
||||
|
||||
private ActionBarDrawerToggle drawerToggle;
|
||||
|
||||
@Bind(R.id.content_list)
|
||||
RecyclerView recyclerView;
|
||||
@Bind(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@Bind(R.id.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
|
||||
@Bind(R.id.view_pager)
|
||||
ViewPager viewPager;
|
||||
|
||||
@Bind(R.id.drawer_layout)
|
||||
DrawerLayout drawer;
|
||||
|
@ -59,11 +69,14 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
@Bind(R.id.fam)
|
||||
FloatingActionsMenu floatingActionsMenu;
|
||||
|
||||
private PassTopicFragmentPagerAdapter adapter;
|
||||
|
||||
@OnClick(R.id.fab_action_create_pass)
|
||||
void onFABClick() {
|
||||
final FiledPass pass = PassUtil.createEmptyPass();
|
||||
|
||||
passStore.setCurrentPass(pass);
|
||||
passStore.getClassifier().moveToTopic(pass,adapter.getPageTitle(tabLayout.getSelectedTabPosition()).toString());
|
||||
pass.save(passStore);
|
||||
AXT.at(this).startCommonIntent().activityFromClass(PassEditActivity.class);
|
||||
floatingActionsMenu.collapse();
|
||||
|
@ -86,7 +99,6 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
|
||||
@Bind(R.id.fab_action_open_file)
|
||||
FloatingActionButton openFileFAB;
|
||||
|
||||
public final static int VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK = 19;
|
||||
|
||||
@OnClick(R.id.fab_action_open_file)
|
||||
|
@ -120,7 +132,6 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
|
||||
@Subscribe
|
||||
public void typeFocus(TypeFocusEvent typeFocusEvent) {
|
||||
scrollToType(typeFocusEvent.type);
|
||||
drawer.closeDrawers();
|
||||
}
|
||||
|
||||
|
@ -131,12 +142,9 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
public void run() {
|
||||
|
||||
passStore.refreshPassesList();
|
||||
passStore.sort(settings.getSortOrder());
|
||||
|
||||
AXT.at(emptyView).setVisibility(passStore.getPassList().isEmpty());
|
||||
|
||||
passAdapter.notifyDataSetChanged();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -155,11 +163,9 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
|
||||
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);
|
||||
llm.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
recyclerView.setLayoutManager(llm);
|
||||
AXT.at(openFileFAB).setVisibility(Build.VERSION.SDK_INT >= VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK);
|
||||
|
||||
// don't want too many windows in worst case - so check for errors first
|
||||
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
|
||||
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) {
|
||||
|
@ -185,19 +191,13 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
if (getSupportActionBar() != null) {
|
||||
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
|
||||
|
@ -222,6 +222,9 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
|
||||
bus.register(this);
|
||||
refreshPasses();
|
||||
|
||||
adapter.notifyDataSetChanged();
|
||||
passStore.getClassifier().onClassificationChangeListeners.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -245,8 +248,77 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
passStore.getClassifier().onClassificationChangeListeners.remove(this);
|
||||
bus.unregister(this);
|
||||
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.v4.app.NavUtils;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Html;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.Menu;
|
||||
|
@ -77,6 +78,9 @@ public class PassViewActivity extends PassViewActivityBase {
|
|||
@Bind(R.id.barcode_alt_text)
|
||||
TextView barcodeAlternativeText;
|
||||
|
||||
@Bind(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@OnClick(R.id.zoomIn)
|
||||
void zoomIn() {
|
||||
setBarCodeSize(currentBarcodeWidth + getFingerSize());
|
||||
|
@ -125,28 +129,6 @@ public class PassViewActivity extends PassViewActivityBase {
|
|||
|
||||
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
|
||||
protected void refresh() {
|
||||
super.refresh();
|
||||
|
@ -226,14 +208,34 @@ public class PassViewActivity extends PassViewActivityBase {
|
|||
AXT.at(this).disableRotation();
|
||||
|
||||
setContentView(R.layout.activity_pass_view);
|
||||
}
|
||||
|
||||
final ViewGroup extraViewContainer = (ViewGroup) findViewById(R.id.passExtrasContainer);
|
||||
getLayoutInflater().inflate(R.layout.pass_view_extra_data, extraViewContainer);
|
||||
@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);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
configureActionBar();
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
||||
private void addFrontFields(PassFieldList passFields) {
|
||||
for (PassField field : passFields) {
|
||||
|
||||
|
|
|
@ -37,11 +37,6 @@ public class PassViewActivityBase extends PassAndroidActivity {
|
|||
protected void onCreate(Bundle 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
|
||||
// ;-)
|
||||
// not having the Actionbar overflow menu also with devices with hardware
|
||||
|
@ -57,7 +52,6 @@ public class PassViewActivityBase extends PassAndroidActivity {
|
|||
} catch (Exception ex) {
|
||||
// Ignore - but at least we tried ;-)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -76,6 +70,16 @@ public class PassViewActivityBase extends PassAndroidActivity {
|
|||
tracker.trackException("pass not present in " + this, false);
|
||||
finish();
|
||||
}
|
||||
|
||||
configureActionBar();
|
||||
|
||||
}
|
||||
|
||||
protected void configureActionBar() {
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected void refresh() {
|
||||
|
|
|
@ -58,7 +58,7 @@ public class PassViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
@OnClick(R.id.addCalendar)
|
||||
void onCalendarClick() {
|
||||
AddToCalendar.tryAddDateToCalendar(pass,activity,getDate());
|
||||
AddToCalendar.tryAddDateToCalendar(pass, activity, getDate());
|
||||
}
|
||||
|
||||
private Pass pass;
|
||||
|
|
|
@ -1,11 +1,38 @@
|
|||
<?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_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_height="wrap_content"
|
||||
android:id="@+id/help_tv"
|
||||
android:padding="16dp" />
|
||||
</ScrollView>
|
||||
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.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_height="match_parent">
|
||||
|
||||
<include layout="@layout/pass_list_item" />
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
</ScrollView>
|
||||
<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" />
|
||||
|
||||
</android.support.v4.widget.NestedScrollView>
|
||||
|
||||
</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"?>
|
||||
<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"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:fab="http://schemas.android.com/apk/res-auto"
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:layout_width="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
|
||||
android:id="@+id/emptyView"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -21,13 +48,6 @@
|
|||
android:singleLine="false"
|
||||
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
|
||||
android:id="@+id/fam"
|
||||
|
@ -36,9 +56,11 @@
|
|||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_gravity="right|bottom"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
|
||||
android:layout_marginRight="16dp"
|
||||
app:layout_behavior=".ui.MyShyFABBehavior"
|
||||
fab:fab_addButtonColorNormal="@color/accent"
|
||||
fab:fab_addButtonColorPressed="@color/icon_green"
|
||||
fab:fab_addButtonPlusIconColor="@android:color/black"
|
||||
|
@ -79,6 +101,7 @@
|
|||
|
||||
|
||||
<net.i2p.android.ext.floatingactionbutton.FloatingActionButton
|
||||
|
||||
android:id="@+id/fab_action_create_pass"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -91,7 +114,7 @@
|
|||
|
||||
</net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu>
|
||||
|
||||
</RelativeLayout>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
<FrameLayout
|
||||
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>
|
||||
</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>
|
||||
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="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="topic_trash">trash</string>
|
||||
<string name="topic_archive">archive</string>
|
||||
<string name="move_to_new_topic">Move to new Topic</string>
|
||||
</resources>
|
|
@ -1,4 +1,11 @@
|
|||
<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">
|
||||
<item name="android:windowBackground">@color/window_bg</item>
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
|
@ -8,6 +15,7 @@
|
|||
<item name="actionModeBackground">@color/secondary</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme" parent="AppBaseTheme" />
|
||||
|
||||
<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