diff --git a/app/.classpath b/app/.classpath
index 8c6d17f..090b9c4 100644
--- a/app/.classpath
+++ b/app/.classpath
@@ -3,6 +3,8 @@
+
+
diff --git a/app/.project b/app/.project
index 486ce22..31e737f 100644
--- a/app/.project
+++ b/app/.project
@@ -30,4 +30,17 @@
com.android.ide.eclipse.adt.AndroidNature
org.eclipse.jdt.core.javanature
+
+
+ photoview
+ 2
+ SUBMODULES/PhotoView/library/src
+
+
+
+
+ SUBMODULES
+ $%7BPARENT-1-PROJECT_LOC%7D/submodules
+
+
diff --git a/app/project.properties b/app/project.properties
index 2ac0eb5..670ca8a 100644
--- a/app/project.properties
+++ b/app/project.properties
@@ -9,10 +9,10 @@
# Project target.
target=android-17
-android.library.reference.1=../submodules/PhotoView/library
-android.library.reference.2=../submodules/facebook-android-sdk/facebook
-android.library.reference.3=../submodules/Android-Feather
-android.library.reference.4=../submodules/HoloEverywhere/library
-android.library.reference.5=../submodules/HoloEverywhere/addons/preferences
-android.library.reference.6=../submodules/HoloEverywhere/addons/slider
-android.library.reference.7=../submodules/google-play-services_lib
+android.library.reference.1=../submodules/facebook-android-sdk/facebook
+android.library.reference.2=../submodules/Android-Feather
+android.library.reference.3=../submodules/HoloEverywhere/library
+android.library.reference.4=../submodules/HoloEverywhere/addons/preferences
+android.library.reference.5=../submodules/HoloEverywhere/addons/slider
+android.library.reference.6=../submodules/google-play-services_lib
+android.library.reference.7=../submodules/StickyGridHeaders/Library
diff --git a/app/res/layout/fragment_sync_select_photos.xml b/app/res/layout/fragment_sync_select_photos.xml
index 96c6a33..fcaff74 100644
--- a/app/res/layout/fragment_sync_select_photos.xml
+++ b/app/res/layout/fragment_sync_select_photos.xml
@@ -32,7 +32,7 @@
android:text="@string/sync_next_button" />
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/com/trovebox/android/app/SyncImageSelectionFragment.java b/app/src/com/trovebox/android/app/SyncImageSelectionFragment.java
index 529efd2..334eb24 100644
--- a/app/src/com/trovebox/android/app/SyncImageSelectionFragment.java
+++ b/app/src/com/trovebox/android/app/SyncImageSelectionFragment.java
@@ -19,23 +19,24 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;
-import android.util.TypedValue;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
-import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.GridView;
import android.widget.ImageView;
+import android.widget.TextView;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
+import com.tonicartos.widget.stickygridheaders.StickyGridHeadersGridView;
+import com.tonicartos.widget.stickygridheaders.StickyGridHeadersSimpleAdapter;
import com.trovebox.android.app.bitmapfun.util.ImageCache;
import com.trovebox.android.app.bitmapfun.util.ImageFileSystemFetcher;
import com.trovebox.android.app.bitmapfun.util.ImageResizer;
@@ -60,7 +61,7 @@ public class SyncImageSelectionFragment extends CommonRefreshableFragmentWithIma
private int mImageThumbSize;
private int mImageThumbSpacing;
private int mImageThumbBorder;
- private GridView photosGrid;
+ private StickyGridHeadersGridView photosGrid;
ViewTreeObserver.OnGlobalLayoutListener photosGridListener;
NextStepFlow nextStepFlow;
InitTask initTask = null;
@@ -146,7 +147,7 @@ public void onDetach() {
super.onDetach();
if (mAdapter != null)
{
- mAdapter.setNumColumns(0);
+ mAdapter.mItemHeight = 0;
}
}
@@ -159,7 +160,8 @@ public void onSaveInstanceState(Bundle outState) {
public void init(View v)
{
- photosGrid = (GridView) v.findViewById(R.id.grid_photos);
+ photosGrid = (StickyGridHeadersGridView) v.findViewById(R.id.grid_photos);
+ photosGrid.setAdapter(new DummyImageAdapter());
// This listener is used to get the final width of the GridView and then
// calculate the
@@ -173,7 +175,7 @@ public void init(View v)
@Override
public void onGlobalLayout()
{
- if (mAdapter.getNumColumns() == 0)
+ if (mAdapter.mItemHeight == 0)
{
final int numColumns = (int) Math.floor(
photosGrid.getWidth()
@@ -184,7 +186,6 @@ public void onGlobalLayout()
final int columnWidth =
(photosGrid.getWidth() / numColumns)
- mImageThumbSpacing;
- mAdapter.setNumColumns(numColumns);
mAdapter.setItemHeight(columnWidth, columnWidth
- 2 * mImageThumbBorder);
if (BuildConfig.DEBUG)
@@ -252,10 +253,12 @@ public void onCheckedChanged(CompoundButton buttonView,
if (isDataLoaded())
{
photosGrid.setAdapter(mAdapter);
- }
- if (photosGrid.getAdapter() == null && initTask == null)
+ } else
{
- refresh(v);
+ if (initTask == null)
+ {
+ refresh(v);
+ }
}
}
@@ -425,12 +428,41 @@ public static class ImageData implements Parcelable
{
public long id;
public String data;
+ public String folder;
public ImageData(long id, String data)
{
super();
this.id = id;
this.data = data;
+ folder = getFolderFromPath(data);
+ }
+
+ /**
+ * Get the parent folder name for the specified path
+ *
+ * @param path
+ * @return
+ */
+ public String getFolderFromPath(String path)
+ {
+ if (path == null)
+ {
+ return null;
+ }
+ int p = path.lastIndexOf("/");
+ String result = "";
+ if (p > 0)
+ {
+ int p2 = path.lastIndexOf("/", p - 1);
+ if (p2 != -1)
+ {
+ result = path.substring(p2 + 1, p);
+ }
+ }
+ CommonUtils.debug(TAG, "getFolderFromPath: fileName '%1$s; folderName '%2$s'", path,
+ result);
+ return result;
}
@Override
@@ -451,6 +483,7 @@ public int describeContents() {
public void writeToParcel(Parcel out, int flags) {
out.writeLong(id);
out.writeString(data);
+ out.writeString(folder);
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@@ -468,6 +501,7 @@ public ImageData[] newArray(int size) {
private ImageData(Parcel in) {
id = in.readLong();
data = in.readString();
+ folder = in.readString();
}
}
@@ -497,7 +531,7 @@ protected void onPreExecute()
{
super.onPreExecute();
loadingControl.startLoading();
- photosGrid.setAdapter(null);
+ photosGrid.setAdapter(new DummyImageAdapter());
customImageWorkerAdapter = null;
mImageWorker.setAdapter(null);
selectionController.clearSelection();
@@ -604,8 +638,33 @@ private SelectionController(Parcel in) {
}
}
- private class CustomImageAdapter extends ImageAdapter
+ private class DummyImageAdapter extends BaseAdapter
{
+
+ @Override
+ public int getCount() {
+ return 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return null;
+ }
+
+ }
+
+ private class CustomImageAdapter extends ImageAdapter
+ implements StickyGridHeadersSimpleAdapter {
SelectionController selectionController;
public CustomImageAdapter(Context context, ImageResizer imageWorker,
@@ -620,44 +679,47 @@ public View getViewAdditional(int position, View convertView,
ViewGroup container)
{
// Now handle the main ImageView thumbnails
- View view;
+ final ViewHolder holder;
if (convertView == null)
{ // if it's not recycled, instantiate and initialize
final LayoutInflater layoutInflater = (LayoutInflater) getActivity()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- view = layoutInflater.inflate(
+ convertView = layoutInflater.inflate(
R.layout.item_sync_image, null);
- view.setLayoutParams(mImageViewLayoutParams);
+ convertView.setLayoutParams(mImageViewLayoutParams);
+ holder = new ViewHolder();
+ holder.selectedOverlay = convertView
+ .findViewById(R.id.selection_overlay);
+ holder.uploadedOverlay = convertView
+ .findViewById(R.id.uploaded_overlay);
+ holder.imageContainer = convertView.findViewById(R.id.imageContainer);
+ holder.imageView = (ImageView) convertView.findViewById(R.id.image);
+ convertView.setTag(holder);
} else
{ // Otherwise re-use the converted view
- view = convertView;
+ holder = (ViewHolder) convertView.getTag();
}
// Check the height matches our calculated column width
- if (view.getLayoutParams().height != mItemHeight)
+ if (convertView.getLayoutParams().height != mItemHeight)
{
- view.setLayoutParams(mImageViewLayoutParams);
+ convertView.setLayoutParams(mImageViewLayoutParams);
}
- final View selectedOverlay = view
- .findViewById(R.id.selection_overlay);
ImageData value = (ImageData) getItem(position);
final long id = value.id;
- selectedOverlay.setVisibility(selectionController.isSelected(id) ?
+ holder.selectedOverlay.setVisibility(selectionController.isSelected(id) ?
View.VISIBLE : View.INVISIBLE);
boolean isProcessed = customImageWorkerAdapter
.isProcessedValue(value);
- final View uploadedOverlay = view
- .findViewById(R.id.uploaded_overlay);
- uploadedOverlay.setVisibility(isProcessed ?
+ holder.uploadedOverlay.setVisibility(isProcessed ?
View.VISIBLE : View.INVISIBLE);
- View imageContainer = view.findViewById(R.id.imageContainer);
if (isProcessed)
{
- imageContainer.setOnClickListener(null);
+ holder.imageContainer.setOnClickListener(null);
} else
{
- imageContainer.setOnClickListener(new OnClickListener()
+ holder.imageContainer.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
@@ -672,20 +734,58 @@ public void onClick(View v)
{
selectionController.addToSelected(id);
}
- selectedOverlay.setVisibility(selectionController.isSelected(id) ?
+ holder.selectedOverlay.setVisibility(selectionController.isSelected(id) ?
View.VISIBLE : View.INVISIBLE);
}
});
}
- ImageView imageView = (ImageView) view.findViewById(R.id.image);
// Finally load the image asynchronously into the ImageView, this
// also takes care of
// setting a placeholder image while the background thread runs
- mImageWorker.loadImage(position - mNumColumns, imageView);
- return view;
+ mImageWorker.loadImage(position, holder.imageView);
+ return convertView;
+ }
+
+ @Override
+ public long getHeaderId(int position) {
+ ImageData imageData = (ImageData) getItem(position);
+ return imageData == null ? -1 : imageData.folder.hashCode();
}
+ @Override
+ public View getHeaderView(int position, View convertView, ViewGroup parent) {
+ HeaderViewHolder holder;
+ if (convertView == null) {
+ convertView = inflater.inflate(R.layout.sync_category_separator, parent, false);
+ holder = new HeaderViewHolder();
+ holder.textView = (TextView) convertView.findViewById(android.R.id.text1);
+ convertView.setTag(holder);
+ } else {
+ holder = (HeaderViewHolder) convertView.getTag();
+ }
+
+ ImageData imageData = (ImageData) getItem(position);
+ if (imageData == null)
+ {
+ return null;
+ }
+ // set header text as first char in string
+ holder.textView.setText(imageData.folder);
+
+ return convertView;
+ }
+
+ protected class ViewHolder
+ {
+ View selectedOverlay;
+ View uploadedOverlay;
+ View imageContainer;
+ ImageView imageView;
+ }
+ protected class HeaderViewHolder {
+ public TextView textView;
+ }
}
/**
@@ -694,21 +794,20 @@ public void onClick(View v)
* empty views as we use a transparent ActionBar and don't want the real top
* row of images to start off covered by it.
*/
- private static class ImageAdapter extends BaseAdapter
- {
+ private static class ImageAdapter extends BaseAdapter {
protected final Context mContext;
protected int mItemHeight = 0;
- protected int mNumColumns = 0;
- protected int mActionBarHeight = 0;
protected GridView.LayoutParams mImageViewLayoutParams;
private ImageResizer mImageWorker;
+ LayoutInflater inflater;
public ImageAdapter(Context context, ImageResizer imageWorker)
{
super();
mContext = context;
this.mImageWorker = imageWorker;
+ this.inflater = LayoutInflater.from(context);
mImageViewLayoutParams = new GridView.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@@ -716,36 +815,19 @@ public ImageAdapter(Context context, ImageResizer imageWorker)
@Override
public int getCount()
{
- // Size of adapter + number of columns for top empty row
- return mImageWorker.getAdapter().getSize() + mNumColumns;
+ return mImageWorker.getAdapter().getSize();
}
@Override
public Object getItem(int position)
{
- return position < mNumColumns ?
- null : mImageWorker.getAdapter().getItem(
- position - mNumColumns);
+ return mImageWorker.getAdapter().getItem(position);
}
@Override
public long getItemId(int position)
{
- return position < mNumColumns ? -1 : position - mNumColumns;
- }
-
- @Override
- public int getViewTypeCount()
- {
- // Two types of views, the normal ImageView and the top row of empty
- // views
- return 2;
- }
-
- @Override
- public int getItemViewType(int position)
- {
- return (position < mNumColumns) ? 1 : 0;
+ return position;
}
@Override
@@ -758,37 +840,6 @@ public boolean hasStableIds()
public final View getView(int position, View convertView,
ViewGroup container)
{
- // First check if this is the top row
- if (position < mNumColumns)
- {
- if (convertView == null)
- {
- convertView = new View(mContext);
- }
- // Calculate ActionBar height
- if (mActionBarHeight < 0)
- {
- TypedValue tv = new TypedValue();
- if (mContext.getTheme().resolveAttribute(
- android.R.attr.actionBarSize, tv, true))
- {
- mActionBarHeight = TypedValue
- .complexToDimensionPixelSize(
- tv.data, mContext.getResources()
- .getDisplayMetrics());
- } else
- {
- // No ActionBar style (pre-Honeycomb or ActionBar not in
- // theme)
- mActionBarHeight = 0;
- }
- }
- // Set empty view with height of ActionBar
- convertView.setLayoutParams(new AbsListView.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, mActionBarHeight));
- return convertView;
- }
-
return getViewAdditional(position, convertView, container);
}
@@ -815,7 +866,7 @@ public View getViewAdditional(int position, View convertView,
// Finally load the image asynchronously into the ImageView, this
// also takes care of
// setting a placeholder image while the background thread runs
- mImageWorker.loadImage(position - mNumColumns, imageView);
+ mImageWorker.loadImage(position, imageView);
return imageView;
}
@@ -838,16 +889,6 @@ public void setItemHeight(int height, int imageHeight)
mImageWorker.setImageSize(imageHeight);
notifyDataSetChanged();
}
-
- public void setNumColumns(int numColumns)
- {
- mNumColumns = numColumns;
- }
-
- public int getNumColumns()
- {
- return mNumColumns;
- }
}
private class CustomImageFileSystemFetcher extends ImageFileSystemFetcher
@@ -1014,13 +1055,30 @@ void sort()
@Override
public int compare(ImageData lhs, ImageData rhs)
{
- boolean leftProcessed = isProcessedValue(lhs);
- boolean rightProcessed = isProcessedValue(rhs);
- if (leftProcessed == rightProcessed)
+ int result;
+ if (lhs.folder == null)
+ {
+ result = -1;
+ } else if (rhs.folder == null)
+ {
+ result = 1;
+ } else
+ {
+ result = lhs.folder.toLowerCase().compareTo(rhs.folder.toLowerCase());
+ }
+ if (result == 0)
{
- return 0;
+ boolean leftProcessed = isProcessedValue(lhs);
+ boolean rightProcessed = isProcessedValue(rhs);
+ if (leftProcessed == rightProcessed)
+ {
+ result = 0;
+ } else
+ {
+ result = leftProcessed ? -1 : 1;
+ }
}
- return leftProcessed ? -1 : 1;
+ return result;
}
});
}
diff --git a/submodules/StickyGridHeaders b/submodules/StickyGridHeaders
new file mode 160000
index 0000000..b751c89
--- /dev/null
+++ b/submodules/StickyGridHeaders
@@ -0,0 +1 @@
+Subproject commit b751c89fb4ad084b4813a4d9018271943d3106cb