Compare commits
5 commits
master
...
wip/swipe_
Author | SHA1 | Date | |
---|---|---|---|
|
d1776c9c20 | ||
|
8ea083418a | ||
|
9e1d5a0fb7 | ||
|
3e252f9f5b | ||
|
83dbc828f5 |
17 changed files with 644 additions and 61 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-alpha6'
|
||||
classpath 'com.android.tools.build:gradle:2.0.0-alpha9'
|
||||
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'
|
||||
|
|
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.**
|
||||
|
|
@ -34,7 +34,7 @@ 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");
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class FixedPassListPassStore implements PassStore {
|
||||
|
||||
|
@ -46,10 +48,6 @@ public class FixedPassListPassStore implements PassStore {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(PassSortOrder order) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pass getCurrentPass() {
|
||||
return actPass;
|
||||
|
@ -60,6 +58,11 @@ public class FixedPassListPassStore implements PassStore {
|
|||
actPass = pass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PassClassifier getClassifier() {
|
||||
return new PassClassifier(new HashMap<String, Set<String>>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePassWithId(String id) {
|
||||
return false;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,8 +11,9 @@ 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class AndroidFileSystemPassStore implements PassStore {
|
||||
|
||||
|
@ -22,12 +22,14 @@ 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();
|
||||
passClassifier = new PassClassifier(new HashMap<String, Set<String>>());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,11 +126,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 +136,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,128 @@
|
|||
package org.ligi.passandroid.model;
|
||||
|
||||
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";
|
||||
|
||||
private final HashMap<String, Set<String>> pass_id_list_by_topic;
|
||||
private final HashMap<String, String> topic_by_id = new HashMap<>();
|
||||
|
||||
public PassClassifier(HashMap<String, Set<String>> pass_id_list_by_topic) {
|
||||
this.pass_id_list_by_topic = pass_id_list_by_topic;
|
||||
|
||||
processDataChange();
|
||||
}
|
||||
|
||||
private void processDataChange() {
|
||||
calculateReverseMapping();
|
||||
removeEmpty();
|
||||
makeSureDefaultTopicExists();
|
||||
|
||||
for (OnClassificationChangeListener onClassificationChangeListener : onClassificationChangeListeners) {
|
||||
onClassificationChangeListener.OnClassificationChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateReverseMapping() {
|
||||
topic_by_id.clear();
|
||||
for (Map.Entry<String, Set<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, Set<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 PassClassifier(final Context context) {
|
||||
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
deleted_passes = Arrays.asList(sharedPreferences.getString("deleted_passes", "").split(";;"));
|
||||
//deleted_passes
|
||||
//deleted_passes.co
|
||||
}*/
|
||||
|
||||
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 Set<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();
|
||||
}
|
||||
|
||||
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>());
|
||||
}
|
||||
|
||||
pass_id_list_by_topic.get(newTopic).add(pass.getId());
|
||||
}
|
||||
|
||||
public Set<String> getTopics() {
|
||||
return pass_id_list_by_topic.keySet();
|
||||
}
|
||||
|
||||
public boolean isPassOnTopic(FiledPass pass, String topic) {
|
||||
final boolean passIsClassified = topic_by_id.containsKey(pass.getId());
|
||||
|
||||
if (passIsClassified) {
|
||||
return topic_by_id.get(pass.getId()).equals(topic);
|
||||
}
|
||||
|
||||
if (topic.equals(DEFAULT_TOPIC)) {
|
||||
topic_by_id.put(pass.getId(), DEFAULT_TOPIC);
|
||||
if (pass_id_list_by_topic.get(DEFAULT_TOPIC) == null) {
|
||||
pass_id_list_by_topic.put(DEFAULT_TOPIC, new HashSet<String>());
|
||||
}
|
||||
final String id = pass.getId();
|
||||
final Set<String> strings = pass_id_list_by_topic.get(DEFAULT_TOPIC);
|
||||
if (id!=null) {
|
||||
strings.add(id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -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().isPassOnTopic(filedPass, topic)) {
|
||||
newPassList.add(filedPass);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(newPassList, passSortOrder.toComparator());
|
||||
|
||||
passList = newPassList;
|
||||
}
|
||||
}
|
|
@ -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,13 @@ 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.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -31,6 +34,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 +44,18 @@ 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.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
|
||||
|
||||
@Bind(R.id.view_pager)
|
||||
ViewPager viewPager;
|
||||
|
||||
@Bind(R.id.drawer_layout)
|
||||
DrawerLayout drawer;
|
||||
|
@ -59,6 +66,8 @@ 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();
|
||||
|
@ -86,7 +95,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)
|
||||
|
@ -131,12 +139,9 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
public void run() {
|
||||
|
||||
passStore.refreshPassesList();
|
||||
passStore.sort(settings.getSortOrder());
|
||||
|
||||
AXT.at(emptyView).setVisibility(passStore.getPassList().isEmpty());
|
||||
|
||||
passAdapter.notifyDataSetChanged();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -157,10 +162,6 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
|
||||
AXT.at(openFileFAB).setVisibility(Build.VERSION.SDK_INT >= VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK);
|
||||
|
||||
final LinearLayoutManager llm = new LinearLayoutManager(this);
|
||||
llm.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
recyclerView.setLayoutManager(llm);
|
||||
|
||||
// don't want too many windows in worst case - so check for errors first
|
||||
if (TraceDroid.getStackTraceFiles().length > 0) {
|
||||
tracker.trackEvent("ui_event", "send", "stacktraces", null);
|
||||
|
@ -185,18 +186,23 @@ 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
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
@ -222,6 +228,9 @@ public class PassListActivity extends PassAndroidActivity {
|
|||
|
||||
bus.register(this);
|
||||
refreshPasses();
|
||||
|
||||
adapter.notifyDataSetChanged();
|
||||
passStore.getClassifier().onClassificationChangeListeners.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -245,8 +254,54 @@ 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();
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
}
|
||||
|
||||
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().toArray(new String[passClassifier.getTopics().size()]);
|
||||
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,113 @@
|
|||
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 org.ligi.passandroid.App;
|
||||
import org.ligi.passandroid.R;
|
||||
import org.ligi.passandroid.model.FiledPass;
|
||||
import org.ligi.passandroid.model.PassClassifier;
|
||||
import org.ligi.passandroid.model.PassStore;
|
||||
import org.ligi.passandroid.model.PassStoreProjection;
|
||||
import org.ligi.passandroid.model.Settings;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class PassListFragment extends Fragment implements PassClassifier.OnClassificationChangeListener {
|
||||
|
||||
private static final String BUNDLE_KEY_TOPIC = "topic";
|
||||
private PassStoreProjection passStoreProjection;
|
||||
private PassAdapter adapter;
|
||||
|
||||
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;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
final View inflate = inflater.inflate(R.layout.pass_recycler, container, false);
|
||||
final RecyclerView 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()));
|
||||
|
||||
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.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());
|
||||
|
||||
if (passStore.getClassifier().isPassOnTopic(pass, "TRASH")) {
|
||||
passStore.getClassifier().moveToTopic(pass, PassClassifier.DEFAULT_TOPIC);
|
||||
} else {
|
||||
passStore.getClassifier().moveToTopic(pass, "TRASH");
|
||||
}
|
||||
|
||||
|
||||
//Remove swiped item from list and notify the RecyclerView
|
||||
/*final Pass passbookAt = passStoreProjection.getPassList().get(viewHolder.getAdapterPosition());
|
||||
passStore.deletePassWithId(passbookAt.getId());
|
||||
adapter.notifyItemRemoved(viewHolder.getAdapterPosition());
|
||||
Snackbar.make(getView(),"Deleted", Snackbar.LENGTH_LONG)
|
||||
.setAction("Undo", new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
}
|
||||
}).show();
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
|
||||
passStore.getClassifier().onClassificationChangeListeners.add(this);
|
||||
return inflate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
passStore.getClassifier().onClassificationChangeListeners.remove(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnClassificationChange() {
|
||||
passStoreProjection.refresh();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
|
@ -21,13 +21,23 @@
|
|||
android:singleLine="false"
|
||||
android:text="@string/empty_text_view" />
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/content_list"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@null"
|
||||
android:listSelector="@android:color/transparent" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/view_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu
|
||||
android:id="@+id/fam"
|
||||
|
|
8
android/src/main/res/layout/pass_recycler.xml
Normal file
8
android/src/main/res/layout/pass_recycler.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/pass_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@null"
|
||||
android:listSelector="@android:color/transparent" />
|
|
@ -39,7 +39,7 @@ The app is offline usable once you downloaded the pass!
|
|||
<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>
|
||||
<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!
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
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, Set<String>>());
|
||||
|
||||
assertThat(tested.isPassOnTopic(getPassWithId(ID_1), PassClassifier.DEFAULT_TOPIC)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatOnlyNonDefaultTopicInTopicListWhenOnePassWithNonDefaultTopic() {
|
||||
final PassClassifier tested = new PassClassifier(new HashMap<String, Set<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, Set<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, Set<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 testThatPassIsNotActiveWhenInTopic() {
|
||||
final PassClassifier tested = new PassClassifier(new HashMap<String, Set<String>>() {
|
||||
{
|
||||
put(TOPIC_1, Sets.newSet(ID_1));
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(tested.isPassOnTopic(getPassWithId(ID_1), PassClassifier.DEFAULT_TOPIC)).isFalse();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testThatPassIsInTopicAsExpected() {
|
||||
final PassClassifier tested = new PassClassifier(new HashMap<String, Set<String>>() {
|
||||
{
|
||||
put(TOPIC_1, Sets.newSet(ID_1));
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(tested.isPassOnTopic(getPassWithId(ID_1), TOPIC_1)).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHasAtLeastOneTopic() {
|
||||
final PassClassifier tested = new PassClassifier(new HashMap<String, Set<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