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