diff --git a/NEWS.mkd b/NEWS.mkd
index 6c89929..285a04e 100644
--- a/NEWS.mkd
+++ b/NEWS.mkd
@@ -1,6 +1,10 @@
-### version 1.2
+### version 1.3.0
+* No more pauses when scrolling through the history
+* Fixed an error where the wrong razor could be shown on the modify screen
+
+### version 1.2.0
* new feature: keep track of multiple razors!
* update to the latest libraries
* adjustments have been made to make for a more pleasant experience
diff --git a/bladeReminder/build.gradle b/bladeReminder/build.gradle
index 2ba8ddc..93326f0 100644
--- a/bladeReminder/build.gradle
+++ b/bladeReminder/build.gradle
@@ -93,14 +93,15 @@ preBuild.dependsOn myPrebuildTask
dependencies {
compile 'org.florescu.android.rangeseekbar:rangeseekbar-library:0.3.0'
- compile 'com.android.support:appcompat-v7:23.3.0'
- compile 'com.android.support:design:23.3.0'
- compile 'com.android.support:support-v4:23.3.0'
- compile 'com.android.support:support-annotations:23.3.0'
- compile 'com.android.support:preference-v7:23.3.0'
+ compile 'com.android.support:appcompat-v7:24.0.0'
+ compile 'com.android.support:design:24.0.0'
+ compile 'com.android.support:support-v4:24.0.0'
+ compile 'com.android.support:support-annotations:24.0.0'
+ compile 'com.android.support:preference-v7:23.4.0'
+ compile 'com.android.support:recyclerview-v7:24.0.0'
compile 'com.google.guava:guava:19.0'
- compile 'com.jakewharton:butterknife:8.0.1'
- apt 'com.jakewharton:butterknife-compiler:8.0.1'
+ compile 'com.jakewharton:butterknife:8.1.0'
+ apt 'com.jakewharton:butterknife-compiler:8.1.0'
compile 'com.jakewharton.timber:timber:4.1.2'
compile 'com.opencsv:opencsv:3.7'
// Testing-only dependencies
diff --git a/bladeReminder/src/main/AndroidManifest.xml b/bladeReminder/src/main/AndroidManifest.xml
index 3b80336..02cc308 100644
--- a/bladeReminder/src/main/AndroidManifest.xml
+++ b/bladeReminder/src/main/AndroidManifest.xml
@@ -2,8 +2,8 @@
+ android:versionCode="130"
+ android:versionName="1.3.0" >
extends RecyclerView.Adapter implements Filterable,
+ CursorFilter.CursorFilterClient {
+
+ /**
+ * Call when bind view with the cursor
+ *
+ * @param holder RecyclerView.ViewHolder
+ * @param cursor The cursor from which to get the data. The cursor is already
+ * moved to the correct position.
+ * @param position the position.
+ */
+ public abstract void onBindViewHolder(VH holder, Cursor cursor, int position);
+
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected boolean mDataValid;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected Cursor mCursor;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected int mRowIDColumn;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected ChangeObserver mChangeObserver;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected DataSetObserver mDataSetObserver;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected CursorFilter mCursorFilter;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected FilterQueryProvider mFilterQueryProvider;
+
+ /**
+ * If set the adapter will register a content observer on the cursor and will call
+ * {@link #onContentChanged()} when a notification comes in. Be careful when
+ * using this flag: you will need to unset the current Cursor from the adapter
+ * to avoid leaks due to its registered observers. This flag is not needed
+ * when using a CursorAdapter with a
+ * {@link android.content.CursorLoader}.
+ */
+ public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
+
+ /**
+ * Constructor that flags always FLAG_REGISTER_CONTENT_OBSERVER.
+ *
+ * @param c The cursor from which to get the data.
+ * @param context The context
+ * This option is discouraged, as it results in Cursor queries
+ * being performed on the application's UI thread and thus can cause poor
+ * responsiveness or even Application Not Responding errors. As an alternative,
+ * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
+ */
+ public BaseAbstractRecycleCursorAdapter(Context context, Cursor c) {
+ this(context, c, FLAG_REGISTER_CONTENT_OBSERVER);
+ }
+
+ /**
+ * Recommended constructor.
+ *
+ * @param c The cursor from which to get the data.
+ * @param context The context
+ * @param flags Flags used to determine the behavior of the adapter;
+ * Currently it accept {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
+ */
+ public BaseAbstractRecycleCursorAdapter(Context context, Cursor c, int flags) {
+ init(context, c, flags);
+ }
+
+ void init(Context context, Cursor c, int flags) {
+ boolean cursorPresent = c != null;
+ mCursor = c;
+ mDataValid = cursorPresent;
+ mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
+ if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
+ mChangeObserver = new ChangeObserver();
+ mDataSetObserver = new MyDataSetObserver();
+ } else {
+ mChangeObserver = null;
+ mDataSetObserver = null;
+ }
+
+ if (cursorPresent) {
+ if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
+ if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
+ }
+
+ setHasStableIds(true);//这个地方要注意一下,需要将表关联ID设置为true
+ }
+
+ /**
+ * Returns the cursor.
+ *
+ * @return the cursor.
+ */
+ public Cursor getCursor() {
+ return mCursor;
+ }
+
+ /**
+ * @see android.support.v7.widget.RecyclerView.Adapter#getItemCount()
+ */
+ @Override
+ public int getItemCount() {
+ if (mDataValid && mCursor != null) {
+ return mCursor.getCount();
+ } else {
+ return 0;
+ }
+ }
+
+ public Object getItem(int position) {
+ if (mDataValid && mCursor != null) {
+ mCursor.moveToPosition(position);
+ return mCursor;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @param position Adapter position to query
+ * @see android.support.v7.widget.RecyclerView.Adapter#getItemId(int)
+ */
+ @Override
+ public long getItemId(int position) {
+ if (mDataValid && mCursor != null) {
+ if (mCursor.moveToPosition(position)) {
+ return mCursor.getLong(mRowIDColumn);
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(VH holder, int position) {
+ if (!mDataValid) {
+ throw new IllegalStateException("this should only be called when the cursor is valid");
+ }
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+ onBindViewHolder(holder, mCursor, position);
+ }
+
+ /**
+ * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
+ * closed.
+ *
+ * @param cursor The new cursor to be used
+ */
+ public void changeCursor(Cursor cursor) {
+ Cursor old = swapCursor(cursor);
+ if (old != null) {
+ old.close();
+ }
+ }
+
+ /**
+ * Swap in a new Cursor, returning the old Cursor. Unlike
+ * {@link #changeCursor(Cursor)}, the returned old Cursor is not
+ * closed.
+ *
+ * @param newCursor The new cursor to be used.
+ * @return Returns the previously set Cursor, or null if there wasa not one.
+ * If the given new Cursor is the same instance is the previously set
+ * Cursor, null is also returned.
+ */
+ public Cursor swapCursor(Cursor newCursor) {
+ if (newCursor == mCursor) {
+ return null;
+ }
+ Cursor oldCursor = mCursor;
+ if (oldCursor != null) {
+ if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
+ if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
+ }
+ mCursor = newCursor;
+ if (newCursor != null) {
+ if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
+ if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
+ mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
+ mDataValid = true;
+ // notify the observers about the new cursor
+ notifyDataSetChanged();
+ } else {
+ mRowIDColumn = -1;
+ mDataValid = false;
+ // notify the observers about the lack of a data set
+ //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
+ notifyDataSetChanged();
+ }
+ return oldCursor;
+ }
+
+ /**
+ * Converts the cursor into a CharSequence. Subclasses should override this
+ * method to convert their results. The default implementation returns an
+ * empty String for null values or the default String representation of
+ * the value.
+ *
+ * @param cursor the cursor to convert to a CharSequence
+ * @return a CharSequence representing the value
+ */
+ public CharSequence convertToString(Cursor cursor) {
+ return cursor == null ? "" : cursor.toString();
+ }
+
+ /**
+ * Runs a query with the specified constraint. This query is requested
+ * by the filter attached to this adapter.
+ *
+ * The query is provided by a
+ * {@link android.widget.FilterQueryProvider}.
+ * If no provider is specified, the current cursor is not filtered and returned.
+ *
+ * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
+ * and the previous cursor is closed.
+ *
+ * This method is always executed on a background thread, not on the
+ * application's main thread (or UI thread.)
+ *
+ * Contract: when constraint is null or empty, the original results,
+ * prior to any filtering, must be returned.
+ *
+ * @param constraint the constraint with which the query must be filtered
+ * @return a Cursor representing the results of the new query
+ * @see #getFilter()
+ * @see #getFilterQueryProvider()
+ * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
+ */
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ if (mFilterQueryProvider != null) {
+ return mFilterQueryProvider.runQuery(constraint);
+ }
+
+ return mCursor;
+ }
+
+ public Filter getFilter() {
+ if (mCursorFilter == null) {
+ mCursorFilter = new CursorFilter(this);
+ }
+ return mCursorFilter;
+ }
+
+ /**
+ * Returns the query filter provider used for filtering. When the
+ * provider is null, no filtering occurs.
+ *
+ * @return the current filter query provider or null if it does not exist
+ * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
+ * @see #runQueryOnBackgroundThread(CharSequence)
+ */
+ public FilterQueryProvider getFilterQueryProvider() {
+ return mFilterQueryProvider;
+ }
+
+ /**
+ * Sets the query filter provider used to filter the current Cursor.
+ * The provider's
+ * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
+ * method is invoked when filtering is requested by a client of
+ * this adapter.
+ *
+ * @param filterQueryProvider the filter query provider or null to remove it
+ * @see #getFilterQueryProvider()
+ * @see #runQueryOnBackgroundThread(CharSequence)
+ */
+ public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
+ mFilterQueryProvider = filterQueryProvider;
+ }
+
+ /**
+ * Called when the {@link android.database.ContentObserver} on the cursor receives a change notification.
+ * The default implementation provides the auto-requery logic, but may be overridden by
+ * sub classes.
+ *
+ * @see android.database.ContentObserver#onChange(boolean)
+ */
+ protected void onContentChanged() {
+
+ }
+
+ private class ChangeObserver extends ContentObserver {
+ public ChangeObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onContentChanged();
+ }
+ }
+
+ private class MyDataSetObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ mDataValid = true;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ mDataValid = false;
+ //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
+ notifyDataSetChanged();
+ }
+ }
+}
diff --git a/bladeReminder/src/main/java/com/frankzhu/recyclerviewdemo/adapter/CursorFilter.java b/bladeReminder/src/main/java/com/frankzhu/recyclerviewdemo/adapter/CursorFilter.java
new file mode 100644
index 0000000..aa8d8cf
--- /dev/null
+++ b/bladeReminder/src/main/java/com/frankzhu/recyclerviewdemo/adapter/CursorFilter.java
@@ -0,0 +1,59 @@
+package com.frankzhu.recyclerviewdemo.adapter;
+
+import android.database.Cursor;
+import android.widget.Filter;
+
+/**
+ * Author: ZhuWenWu
+ * Version V1.0
+ * Date: 2014/12/30 17:15.
+ * Description: 数据库Cursor筛选
+ * Modification History:
+ * Date Author Version Description
+ * -----------------------------------------------------------------------------------
+ * 2014/12/30 ZhuWenWu 1.0 1.0
+ * Why & What is modified:
+ */
+public class CursorFilter extends Filter {
+ CursorFilterClient mClient;
+
+ interface CursorFilterClient {
+ CharSequence convertToString(Cursor cursor);
+ Cursor runQueryOnBackgroundThread(CharSequence constraint);
+ Cursor getCursor();
+ void changeCursor(Cursor cursor);
+ }
+
+ CursorFilter(CursorFilterClient client) {
+ mClient = client;
+ }
+
+ @Override
+ public CharSequence convertResultToString(Object resultValue) {
+ return mClient.convertToString((Cursor) resultValue);
+ }
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ Cursor cursor = mClient.runQueryOnBackgroundThread(constraint);
+
+ FilterResults results = new FilterResults();
+ if (cursor != null) {
+ results.count = cursor.getCount();
+ results.values = cursor;
+ } else {
+ results.count = 0;
+ results.values = null;
+ }
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ Cursor oldCursor = mClient.getCursor();
+
+ if (results.values != null && results.values != oldCursor) {
+ mClient.changeCursor((Cursor) results.values);
+ }
+ }
+}
diff --git a/bladeReminder/src/main/java/com/github/gist/ssins/EndlessRecyclerOnScrollListener.java b/bladeReminder/src/main/java/com/github/gist/ssins/EndlessRecyclerOnScrollListener.java
new file mode 100644
index 0000000..f27d80c
--- /dev/null
+++ b/bladeReminder/src/main/java/com/github/gist/ssins/EndlessRecyclerOnScrollListener.java
@@ -0,0 +1,45 @@
+package com.github.gist.ssins;
+
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+
+public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
+
+ private int mPreviousTotal = 0; // The total number of items in the dataset after the last load
+ private boolean mLoading = true; // True if we are still waiting for the last set of data to load.
+ private int mVisibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more.
+ int mFirstVisibleItem, mVisibleItemCount, mTotalItemCount;
+
+ private int mCurrentPage = 1;
+
+ private final LinearLayoutManager mLinearLayoutManager;
+
+ public EndlessRecyclerOnScrollListener(LinearLayoutManager linearLayoutManager) {
+ mLinearLayoutManager = linearLayoutManager;
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+
+ mVisibleItemCount = recyclerView.getChildCount();
+ mTotalItemCount = mLinearLayoutManager.getItemCount();
+ mFirstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
+
+ if (mLoading && mTotalItemCount > mPreviousTotal) {
+ mLoading = false;
+ mPreviousTotal = mTotalItemCount;
+ }
+ if (!mLoading &&
+ (mTotalItemCount - mVisibleItemCount) <= (mFirstVisibleItem + mVisibleThreshold)) {
+ // End has been reached
+ // Do something
+ mCurrentPage++;
+ onLoadMore(mCurrentPage);
+ mLoading = true;
+ }
+ }
+
+ public abstract void onLoadMore(int mCurrentPage);
+}
+
diff --git a/bladeReminder/src/main/java/es/quirk/bladereminder/activities/ModifyShaveActivity.java b/bladeReminder/src/main/java/es/quirk/bladereminder/activities/ModifyShaveActivity.java
index 0c44b28..635ec2a 100644
--- a/bladeReminder/src/main/java/es/quirk/bladereminder/activities/ModifyShaveActivity.java
+++ b/bladeReminder/src/main/java/es/quirk/bladereminder/activities/ModifyShaveActivity.java
@@ -109,7 +109,7 @@ public void onClick(View arg0) {
String [] ra = new String[size];
mRazorChoice.setDisplayedValues(razors.toArray(ra));
try {
- mRazorChoice.setValue(Integer.parseInt(originalEntry.getRazor()) - 1);
+ mRazorChoice.setValue(dataSource.getRazorPosition(Integer.parseInt(originalEntry.getRazor())));
} catch (NumberFormatException ex) {
// can't deal..
setRazorChoiceVisibility(View.GONE);
diff --git a/bladeReminder/src/main/java/es/quirk/bladereminder/adapter/ShaveEntryAdapter.java b/bladeReminder/src/main/java/es/quirk/bladereminder/adapter/ShaveEntryAdapter.java
new file mode 100644
index 0000000..fce5255
--- /dev/null
+++ b/bladeReminder/src/main/java/es/quirk/bladereminder/adapter/ShaveEntryAdapter.java
@@ -0,0 +1,119 @@
+package es.quirk.bladereminder.adapter;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import android.widget.TextView;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import com.frankzhu.recyclerviewdemo.adapter.BaseAbstractRecycleCursorAdapter;
+
+import es.quirk.bladereminder.R;
+import es.quirk.bladereminder.ShaveEntry;
+import es.quirk.bladereminder.database.DataSource;
+import es.quirk.bladereminder.widgets.DateLabel;
+import es.quirk.bladereminder.widgets.UsesView;
+
+public class ShaveEntryAdapter extends BaseAbstractRecycleCursorAdapter {
+
+ private final LayoutInflater mLayoutInflater;
+ private final IClickListener mClickListener;
+ private int mSelectedPosition = 0;
+
+ public ShaveEntryAdapter(Context context, IClickListener clickListener) {
+ super(context, null);
+ mLayoutInflater = LayoutInflater.from(context);
+ mClickListener = clickListener;
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ if (position > 0) {
+ // this ends up calling the (holder, Cursor, position) version
+ super.onBindViewHolder(holder, position - 1);
+ } else {
+ // do stuff with header here
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, Cursor cursor, int position) {
+ if (holder instanceof NormalTextViewHolder) {
+ ShaveEntry entry = ShaveEntry.fromCursor(cursor);
+ holder.itemView.setSelected(mSelectedPosition == position);
+ ((NormalTextViewHolder) holder).fillFrom(entry);
+ }
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (viewType == 0) {
+ return new HeaderViewHolder(mLayoutInflater.inflate(R.layout.view_list_item_header, parent, false));
+ } else {
+ return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.shaveentry, parent, false));
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position;
+ }
+
+ public interface IClickListener {
+ public void onItemClick(long id);
+ public int getRazorId();
+ }
+
+ public class NormalTextViewHolder extends RecyclerView.ViewHolder {
+ @BindView(R.id.date_label) DateLabel mDateLabel;
+ @BindView(R.id.count_label) UsesView mCountLabel;
+ @BindView(R.id.comment) TextView mComment;
+ long mShaveId;
+
+ NormalTextViewHolder(View view) {
+ super(view);
+ ButterKnife.bind(this, view);
+ }
+
+ public void fillFrom(ShaveEntry entry) {
+ int count = entry.getCount();
+ mDateLabel.setText(entry.getDate());
+ mCountLabel.setText(Integer.toString(count));
+ mComment.setText(entry.getComment());
+ mShaveId = entry.getID();
+
+ int razorId = mClickListener.getRazorId();
+ try {
+ razorId = Integer.parseInt(entry.getRazor());
+ } catch (NumberFormatException ex) {
+ }
+ boolean isEnabled = count == 0 || razorId == mClickListener.getRazorId();
+ mDateLabel.setEnabled(isEnabled);
+ mCountLabel.setEnabled(isEnabled);
+ mComment.setEnabled(isEnabled);
+ }
+
+ @OnClick(R.id.linear_container)
+ void onItemClick() {
+ mSelectedPosition = getLayoutPosition();
+ notifyItemChanged(mSelectedPosition);
+ mClickListener.onItemClick(mShaveId);
+ }
+ }
+
+ public class HeaderViewHolder extends RecyclerView.ViewHolder {
+ @BindView(R.id.column_header1) View mDateLabel;
+ @BindView(R.id.column_header2) View mCountLabel;
+ @BindView(R.id.column_header3) View mComment;
+
+ HeaderViewHolder(View view) {
+ super(view);
+ ButterKnife.bind(this, view);
+ }
+ }
+}
diff --git a/bladeReminder/src/main/java/es/quirk/bladereminder/database/DataSource.java b/bladeReminder/src/main/java/es/quirk/bladereminder/database/DataSource.java
index ae0de55..396845e 100644
--- a/bladeReminder/src/main/java/es/quirk/bladereminder/database/DataSource.java
+++ b/bladeReminder/src/main/java/es/quirk/bladereminder/database/DataSource.java
@@ -216,6 +216,11 @@ public void toCSV(@NonNull FileOutputStream outstream) throws IOException {
countStr = Integer.toString(count);
cursor.moveToNext();
+ // avoid spitting out a razor name for count
+ if (count == 0) {
+ razorName = "";
+ countStr = "-";
+ }
}
outputData[0] = thisDay;
outputData[1] = countStr;
@@ -456,6 +461,41 @@ public void editRazor(String position, String newName) {
}
}
+ /**
+ * Given an ID, get the index in the table of this ID.
+ */
+ public int getRazorPosition(int id) {
+ // since _ID is random, just the the 1st, 2nd, 3rd... from the list
+ int result = 0;
+ boolean didOpen = openIfNeeded();
+ try {
+ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ queryBuilder.setTables(Contract.Razors.TABLE_NAME);
+ String[] projection = { Contract.Razors._ID };
+ Cursor cursor = queryBuilder.query(mDatabase, projection, null,
+ null, null, null, null);
+ cursor.moveToFirst();
+ boolean found = false;
+ while (!cursor.isAfterLast()) {
+ int cursorId = cursor.getInt(0);
+ cursor.moveToNext();
+ if (cursorId == id) {
+ found = true;
+ break;
+ }
+ result++;
+ }
+ if (!found) {
+ result = 0;
+ }
+ cursor.close();
+ } finally {
+ if (didOpen)
+ close();
+ }
+ return result;
+ }
+
public int getRazorId(int position) {
// since _ID is random, just the the 1st, 2nd, 3rd... from the list
int result = 1;
diff --git a/bladeReminder/src/main/java/es/quirk/bladereminder/fragments/ShaveListFragment.java b/bladeReminder/src/main/java/es/quirk/bladereminder/fragments/ShaveListFragment.java
index f7daca7..2b29a65 100644
--- a/bladeReminder/src/main/java/es/quirk/bladereminder/fragments/ShaveListFragment.java
+++ b/bladeReminder/src/main/java/es/quirk/bladereminder/fragments/ShaveListFragment.java
@@ -24,25 +24,29 @@
import android.support.v4.content.Loader;
import android.support.v7.view.ActionMode;
import android.support.v7.view.ActionMode.Callback;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import com.google.common.base.Optional;
+import com.github.gist.ssins.EndlessRecyclerOnScrollListener;
+
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import butterknife.BindView;
import butterknife.ButterKnife;
-import butterknife.OnItemClick;
import butterknife.Unbinder;
+
+import timber.log.Timber;
+
import es.quirk.bladereminder.EditShaveMenu;
import es.quirk.bladereminder.FabIconUpdater;
import es.quirk.bladereminder.NewShaveMenu;
@@ -51,20 +55,22 @@
import es.quirk.bladereminder.SoundHelper;
import es.quirk.bladereminder.Utils;
import es.quirk.bladereminder.activities.BladeReminderActivity;
+import es.quirk.bladereminder.adapter.ShaveEntryAdapter;
import es.quirk.bladereminder.contentprovider.ShaveEntryContentProvider;
import es.quirk.bladereminder.database.Contract;
import es.quirk.bladereminder.database.Contract.Shaves;
import es.quirk.bladereminder.database.DataSource;
import es.quirk.bladereminder.tasks.BackFiller;
+import es.quirk.bladereminder.widgets.DividerItemDecoration;
import es.quirk.bladereminder.widgets.TextDrawable;
import es.quirk.bladereminder.widgets.TextDrawableFactory;
-import timber.log.Timber;
/**
* Fragment that holds the main list and floating action button.
*/
public class ShaveListFragment extends Fragment
- implements OnScrollListener,
+ implements
+ ShaveEntryAdapter.IClickListener,
INotesEditorListener,
IAddRazorListener,
LoaderCallbacks {
@@ -74,16 +80,12 @@ public class ShaveListFragment extends Fragment
private final static int LIST_THRESHOLD = 2;
private final static String[] PROJECTION = { Shaves._ID, Shaves.DATE, Shaves.COUNT, Shaves.COMMENT, Shaves.RAZOR };
private static final int DIALOG_FRAGMENT = 1;
- private int mListViewLastCount;
- private View mHeaderView;
- @BindView(R.id.shaveList) ListView mListView;
+ @BindView(R.id.shaveList) RecyclerView mRecyclerView;
@BindView(R.id.action_button) FloatingActionButton mFAB;
@BindView(R.id.coordinator_layout) CoordinatorLayout mCoordLayout;
- private int mPreviousVisibleItem;
- private final View [] mColumnHeaders = new View[3];
@Nullable
private BladeReminderActivity mMainActivity;
- private SimpleCursorAdapter mAdapter;
+ private ShaveEntryAdapter mAdapter;
private DataSource mDataSource;
private final Callback mEditModeCallback = new NewShaveMenu(this);
private final Callback mEditEntryCallback = new EditShaveMenu(this);
@@ -107,11 +109,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
View rootView = inflater.inflate(R.layout.fragment_shave_list, container, false);
mUnbinder = ButterKnife.bind(this, rootView);
mRootView = rootView;
- mHeaderView = inflater.inflate(R.layout.view_list_item_header, mListView, false);
- mColumnHeaders[0] = ButterKnife.findById(mHeaderView, R.id.column_header1);
- mColumnHeaders[1] = ButterKnife.findById(mHeaderView, R.id.column_header2);
- mColumnHeaders[2] = ButterKnife.findById(mHeaderView, R.id.column_header3);
- mListView.addHeaderView(mHeaderView, null, false);
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
updateFabIcon();
FabIconUpdater.register(this);
@@ -126,53 +123,38 @@ public void onClick(View v) {
}
});
- String[] columns = new String[] { Shaves.DATE, Shaves.COUNT, Shaves.COMMENT, Shaves.RAZOR};
- // Fields on the UI to which we map
- int[] layoutIds = new int[] {
- R.id.date_label,
- R.id.count_label,
- R.id.comment,
- R.id.razor,
- };
getLoaderManager().initLoader(0, null, this);
- mAdapter = new SimpleCursorAdapter(getActivity(), R.layout.shaveentry, null, columns,
- layoutIds, 0) {
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- super.bindView(view, context, cursor);
- String countStr = cursor.getString(2);
- String razorStr = cursor.getString(4);
- view.setEnabled(true);
-
- if (!(view instanceof LinearLayout))
- return;
- LinearLayout layout = (LinearLayout) view;
- for (int i = 0; i < layout.getChildCount(); i++) {
- View child = layout.getChildAt(i);
- child.setEnabled(true);
- }
- try {
- int count = Integer.parseInt(countStr);
- int razorId = Integer.parseInt(razorStr);
- if (count > 0 && razorId != mRazorId) {
- Timber.d(" view.setBackgroundColor WHITE! %s", view.toString());
- for (int i = 0; i < layout.getChildCount(); i++) {
- View child = layout.getChildAt(i);
- child.setEnabled(false);
- }
- }
- } catch (NumberFormatException ex) {
- }
- }
- };
- mListView.setAdapter(mAdapter);
- mListView.setOnScrollListener(this);
backfill();
mCurrentPage = 0;
mSoundHelper = new SoundHelper(rootView.getContext());
return rootView;
}
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ LinearLayoutManager llm = new LinearLayoutManager(getActivity());
+ mRecyclerView.setLayoutManager(llm);
+ mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity()));
+ mAdapter = new ShaveEntryAdapter(getActivity(), this);
+ mRecyclerView.setAdapter(mAdapter);
+ mRecyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(llm) {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ if (dy > 0)
+ mFAB.setVisibility(View.INVISIBLE);
+ else
+ mFAB.setVisibility(View.VISIBLE);
+ }
+ @Override
+ public void onLoadMore(int currentPage) {
+ mCurrentPage = currentPage - 1;
+ getLoaderManager().restartLoader(0, null, ShaveListFragment.this);
+ }
+ });
+ }
+
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -288,8 +270,8 @@ protected void onPostExecute(Optional maybeSelected) {
}
}
- @OnItemClick(R.id.shaveList)
- void onItemClick(long id) {
+ @Override
+ public void onItemClick(long id) {
if (mActionMode.isPresent()) {
mActionMode.get().finish();
}
@@ -297,6 +279,11 @@ void onItemClick(long id) {
new ItemClickTask().execute();
}
+ @Override
+ public int getRazorId() {
+ return mRazorId;
+ }
+
private void heroaction() {
// find newest entry in database
// "select id from shaves order by date DESC LIMIT 1";
@@ -554,45 +541,6 @@ private void doUndo(@NonNull ShaveEntry lastDeleted, int lastDeletedPosition) {
new SaveNew(lastDeletedPosition, getActivity().getContentResolver()).execute(lastDeleted);
}
- private void updateFloatingActionButton(int firstVisibleItem) {
- // go up = vis, down = hidden
- if (firstVisibleItem > mPreviousVisibleItem) {
- mFAB.setVisibility(View.INVISIBLE);
- } else if (firstVisibleItem < mPreviousVisibleItem) {
- mFAB.setVisibility(View.VISIBLE);
- }
- mPreviousVisibleItem = firstVisibleItem;
- }
-
- /**
- * Required for the OnScrollListener interface.
- */
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- updateFloatingActionButton(firstVisibleItem);
- final float top = -mHeaderView.getTop();
- float height = mHeaderView.getHeight();
- if (top > height)
- return;
- for (View textView : mColumnHeaders) {
- textView.setTranslationY(top / 2f);
- }
-
- }
-
- @Override
- public void onScrollStateChanged(AbsListView listView, int scrollState) {
- int count = mListView.getCount();
- if (scrollState == SCROLL_STATE_IDLE) {
- if (count > mListViewLastCount &&
- mListView.getLastVisiblePosition() >= (count - 1 - LIST_THRESHOLD)) {
- mCurrentPage++;
- getLoaderManager().restartLoader(0, null, this);
- mListViewLastCount = count;
- Timber.d("mCurrentPage = %d", mCurrentPage);
- }
- }
- }
public void setPagerAdapter(FragmentStatePagerAdapter adapter) {
mPagerAdapter = adapter;
@@ -678,8 +626,8 @@ public Loader onCreateLoader(int id, Bundle args) {
}
@Override
- public void onLoadFinished(Loader loader, Cursor data) {
- mAdapter.swapCursor(data);
+ public void onLoadFinished(Loader loader, Cursor cursor) {
+ mAdapter.changeCursor(cursor);
if (mMainActivity != null)
mMainActivity.stop();
}
@@ -687,6 +635,6 @@ public void onLoadFinished(Loader loader, Cursor data) {
@Override
public void onLoaderReset(Loader loader) {
// data is not available anymore, delete reference
- mAdapter.swapCursor(null);
+ mAdapter.changeCursor(null);
}
}
diff --git a/bladeReminder/src/main/java/es/quirk/bladereminder/widgets/DividerItemDecoration.java b/bladeReminder/src/main/java/es/quirk/bladereminder/widgets/DividerItemDecoration.java
new file mode 100644
index 0000000..d923b8d
--- /dev/null
+++ b/bladeReminder/src/main/java/es/quirk/bladereminder/widgets/DividerItemDecoration.java
@@ -0,0 +1,46 @@
+package es.quirk.bladereminder.widgets;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+// Taken from https://stackoverflow.com/a/27037230
+
+public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+
+ private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
+
+ private Drawable mDivider;
+
+ /**
+ * Default divider will be used
+ */
+ public DividerItemDecoration(Context context) {
+ final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
+ mDivider = styledAttributes.getDrawable(0);
+ styledAttributes.recycle();
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ int left = parent.getPaddingLeft();
+ int right = parent.getWidth() - parent.getPaddingRight();
+
+ int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parent.getChildAt(i);
+
+ RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
+
+ int top = child.getBottom() + params.bottomMargin;
+ int bottom = top + mDivider.getIntrinsicHeight();
+
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+}
diff --git a/bladeReminder/src/main/res/layout/fragment_shave_list.xml b/bladeReminder/src/main/res/layout/fragment_shave_list.xml
index 2bc4f68..a66b993 100644
--- a/bladeReminder/src/main/res/layout/fragment_shave_list.xml
+++ b/bladeReminder/src/main/res/layout/fragment_shave_list.xml
@@ -15,7 +15,7 @@
android:layout_height="match_parent"
android:orientation="vertical"
>
-
+
+ - RecyclerView Code by Frank Zhu
+
+
+Copyright 2014 Frank Zhu
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+