From d4de9cf7881ad1e8c724aa8cc685501af5eb663c Mon Sep 17 00:00:00 2001 From: kollerlukas Date: Sun, 19 Feb 2017 23:30:43 -0800 Subject: [PATCH] - Bug fixes: fixed crashes with big albums + crashed with removable storage - added support for removable storage (view, share, ...) - added removable storage to fileExplorer - added option to directly print images - added option to share multiple item at once - minor ui changes - code clean up - StorageLoader speed improvements - FileExplorer speed improvements --- .../us/koller/cameraroll/IntentReceiver.java | 7 +- .../adapter/album/RecyclerViewAdapter.java | 4 +- .../album/ViewHolder/GifViewHolder.java | 3 - .../album/ViewHolder/PhotoViewHolder.java | 4 - .../album/ViewHolder/VideoViewHolder.java | 5 - .../fileExplorer/RecyclerViewAdapter.java | 64 ++- .../adapter/main/RecyclerViewAdapter.java | 7 +- .../adapter/main/ViewHolder/AlbumHolder.java | 9 +- .../java/us/koller/cameraroll/data/Album.java | 15 +- .../us/koller/cameraroll/data/AlbumItem.java | 17 +- .../us/koller/cameraroll/data/File_POJO.java | 5 + .../data/FilesLoader/FilesLoader.java | 246 -------- .../java/us/koller/cameraroll/data/Gif.java | 2 +- .../MediaLoader/Loader/StorageLoader.java | 258 --------- .../java/us/koller/cameraroll/data/Photo.java | 2 +- .../data/Provider/FilesProvider.java | 52 ++ .../data/Provider/ItemLoader/AlbumLoader.java | 56 ++ .../data/Provider/ItemLoader/FileLoader.java | 78 +++ .../data/Provider/ItemLoader/ItemLoader.java | 41 ++ .../MediaProvider.java} | 88 ++- .../cameraroll/data/Provider/Provider.java | 18 + .../Retriever/MediaStoreRetriever.java} | 53 +- .../data/Provider/Retriever/Retriever.java | 12 + .../Provider/Retriever/StorageRetriever.java | 529 ++++++++++++++++++ .../java/us/koller/cameraroll/data/Video.java | 2 +- .../koller/cameraroll/ui/AboutActivity.java | 5 +- .../koller/cameraroll/ui/AlbumActivity.java | 178 ++++-- .../cameraroll/ui/FileExplorerActivity.java | 221 ++++++-- .../us/koller/cameraroll/ui/ItemActivity.java | 84 ++- .../us/koller/cameraroll/ui/MainActivity.java | 62 +- .../koller/cameraroll/util/ItemViewUtil.java | 2 +- .../us/koller/cameraroll/util/MediaType.java | 10 +- .../cameraroll/util/RemovableStorageUtil.java | 69 +++ .../cameraroll/util/SizedColorDrawable.java | 30 - .../us/koller/cameraroll/util/SortUtil.java | 8 +- .../java/us/koller/cameraroll/util/Util.java | 6 + .../ic_content_paste_white_24dp.png | Bin 0 -> 227 bytes .../ic_content_paste_white_24dp.png | Bin 0 -> 157 bytes .../ic_content_paste_white_24dp.png | Bin 0 -> 244 bytes .../ic_content_paste_white_24dp.png | Bin 0 -> 340 bytes .../ic_content_paste_white_24dp.png | Bin 0 -> 460 bytes .../drawable/dark_to_transparent_gradient.xml | 11 + .../res/drawable/ic_folder_white_24dp.png | Bin 0 -> 325 bytes .../main/res/drawable/ic_photo_white_24dp.png | Bin 0 -> 570 bytes .../main/res/drawable/ic_share_white_24dp.png | Bin 938 -> 0 bytes .../main/res/drawable/ic_style_white_24dp.png | Bin 1146 -> 0 bytes .../res/drawable/ic_videocam_white_24dp.png | Bin 0 -> 290 bytes .../res/drawable/ic_wallpaper_white_24dp.png | Bin 0 -> 603 bytes .../drawable/transparent_to_dark_gradient.xml | 11 + .../res/layout/activity_file_explorer.xml | 34 +- app/src/main/res/layout/activity_item.xml | 3 +- app/src/main/res/layout/file_cover.xml | 1 + .../main/res/layout/photo_view_button_bar.xml | 5 +- .../layout/simple_spinner_dropdown_item.xml | 10 + .../main/res/layout/simple_spinner_item.xml | 10 + app/src/main/res/menu/album.xml | 10 + app/src/main/res/menu/item.xml | 7 +- app/src/main/res/values-land/styles.xml | 3 +- app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/provider_paths.xml | 2 +- 60 files changed, 1542 insertions(+), 822 deletions(-) delete mode 100644 app/src/main/java/us/koller/cameraroll/data/FilesLoader/FilesLoader.java delete mode 100644 app/src/main/java/us/koller/cameraroll/data/MediaLoader/Loader/StorageLoader.java create mode 100644 app/src/main/java/us/koller/cameraroll/data/Provider/FilesProvider.java create mode 100644 app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/AlbumLoader.java create mode 100644 app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/FileLoader.java create mode 100644 app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/ItemLoader.java rename app/src/main/java/us/koller/cameraroll/data/{MediaLoader/MediaLoader.java => Provider/MediaProvider.java} (54%) create mode 100644 app/src/main/java/us/koller/cameraroll/data/Provider/Provider.java rename app/src/main/java/us/koller/cameraroll/data/{MediaLoader/Loader/MediaStoreLoader.java => Provider/Retriever/MediaStoreRetriever.java} (79%) create mode 100644 app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/Retriever.java create mode 100644 app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/StorageRetriever.java create mode 100644 app/src/main/java/us/koller/cameraroll/util/RemovableStorageUtil.java delete mode 100644 app/src/main/java/us/koller/cameraroll/util/SizedColorDrawable.java create mode 100644 app/src/main/res/drawable-hdpi/ic_content_paste_white_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_content_paste_white_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_content_paste_white_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_content_paste_white_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_content_paste_white_24dp.png create mode 100644 app/src/main/res/drawable/dark_to_transparent_gradient.xml create mode 100644 app/src/main/res/drawable/ic_folder_white_24dp.png create mode 100644 app/src/main/res/drawable/ic_photo_white_24dp.png delete mode 100644 app/src/main/res/drawable/ic_share_white_24dp.png delete mode 100644 app/src/main/res/drawable/ic_style_white_24dp.png create mode 100644 app/src/main/res/drawable/ic_videocam_white_24dp.png create mode 100644 app/src/main/res/drawable/ic_wallpaper_white_24dp.png create mode 100644 app/src/main/res/drawable/transparent_to_dark_gradient.xml create mode 100644 app/src/main/res/layout/simple_spinner_dropdown_item.xml create mode 100644 app/src/main/res/layout/simple_spinner_item.xml create mode 100644 app/src/main/res/menu/album.xml diff --git a/app/src/main/java/us/koller/cameraroll/IntentReceiver.java b/app/src/main/java/us/koller/cameraroll/IntentReceiver.java index f6423148..aa341421 100644 --- a/app/src/main/java/us/koller/cameraroll/IntentReceiver.java +++ b/app/src/main/java/us/koller/cameraroll/IntentReceiver.java @@ -45,8 +45,11 @@ public void viewPhoto(Intent intent) { return; } - Album album = new Album(); - AlbumItem albumItem = AlbumItem.getInstance(this, uri.toString()); + String path = uri.toString(); + int index = path.lastIndexOf("/"); + + Album album = new Album().setPath(path.substring(0, index)); + AlbumItem albumItem = AlbumItem.getInstance(this, path); if (albumItem == null || albumItem instanceof Video) { Toast.makeText(this, getString(R.string.error), Toast.LENGTH_SHORT).show(); this.finish(); diff --git a/app/src/main/java/us/koller/cameraroll/adapter/album/RecyclerViewAdapter.java b/app/src/main/java/us/koller/cameraroll/adapter/album/RecyclerViewAdapter.java index fb33d577..e693793c 100644 --- a/app/src/main/java/us/koller/cameraroll/adapter/album/RecyclerViewAdapter.java +++ b/app/src/main/java/us/koller/cameraroll/adapter/album/RecyclerViewAdapter.java @@ -10,7 +10,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; import com.michaelflisar.dragselectrecyclerview.DragSelectTouchListener; @@ -133,7 +132,8 @@ public void onClick(View view) { || albumItem instanceof Video) && !albumItem.error) { Intent intent = new Intent(holder.itemView.getContext(), ItemActivity.class); intent.putExtra(ItemActivity.ALBUM_ITEM, albumItem); - intent.putExtra(ItemActivity.ALBUM, album); + //intent.putExtra(ItemActivity.ALBUM, album); + intent.putExtra(ItemActivity.ALBUM_PATH, album.getPath()); intent.putExtra(ItemActivity.ITEM_POSITION, album.getAlbumItems().indexOf(albumItem)); ActivityOptionsCompat options = diff --git a/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/GifViewHolder.java b/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/GifViewHolder.java index 26b7def1..9fa140f4 100644 --- a/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/GifViewHolder.java +++ b/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/GifViewHolder.java @@ -13,8 +13,6 @@ import us.koller.cameraroll.R; import us.koller.cameraroll.data.AlbumItem; -import us.koller.cameraroll.util.SizedColorDrawable; -import us.koller.cameraroll.util.Util; public class GifViewHolder extends AlbumItemHolder { @@ -35,7 +33,6 @@ public void loadImage(final ImageView imageView, final AlbumItem albumItem) { .asGif() .skipMemoryCache(true) .thumbnail(0.1f) - //.placeholder(new SizedColorDrawable(ContextCompat.getColor(context, R.color.white_translucent2), imageDimens)) .listener(new RequestListener() { @Override public boolean onException(Exception e, String model, diff --git a/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/PhotoViewHolder.java b/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/PhotoViewHolder.java index 0ee2a38b..31678f9e 100644 --- a/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/PhotoViewHolder.java +++ b/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/PhotoViewHolder.java @@ -2,8 +2,6 @@ import android.content.Context; import android.graphics.Bitmap; -import android.support.v4.content.ContextCompat; -import android.util.Log; import android.view.View; import android.widget.ImageView; @@ -13,8 +11,6 @@ import us.koller.cameraroll.R; import us.koller.cameraroll.data.AlbumItem; -import us.koller.cameraroll.util.SizedColorDrawable; -import us.koller.cameraroll.util.Util; public class PhotoViewHolder extends AlbumItemHolder { diff --git a/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/VideoViewHolder.java b/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/VideoViewHolder.java index 5beebe0a..1306095b 100644 --- a/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/VideoViewHolder.java +++ b/app/src/main/java/us/koller/cameraroll/adapter/album/ViewHolder/VideoViewHolder.java @@ -1,9 +1,7 @@ package us.koller.cameraroll.adapter.album.ViewHolder; -import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; -import android.support.v4.content.ContextCompat; import android.view.View; import android.widget.ImageView; @@ -13,8 +11,6 @@ import us.koller.cameraroll.R; import us.koller.cameraroll.data.AlbumItem; -import us.koller.cameraroll.util.SizedColorDrawable; -import us.koller.cameraroll.util.Util; public class VideoViewHolder extends AlbumItemHolder { @@ -35,7 +31,6 @@ public void loadImage(ImageView imageView, final AlbumItem albumItem) { .asBitmap() .skipMemoryCache(true) .thumbnail(0.1f) - //.placeholder(new SizedColorDrawable(ContextCompat.getColor(context, R.color.white_translucent2), imageDimens)) .listener(new RequestListener() { @Override public boolean onException(Exception e, String model, diff --git a/app/src/main/java/us/koller/cameraroll/adapter/fileExplorer/RecyclerViewAdapter.java b/app/src/main/java/us/koller/cameraroll/adapter/fileExplorer/RecyclerViewAdapter.java index b13b3290..79e2903a 100644 --- a/app/src/main/java/us/koller/cameraroll/adapter/fileExplorer/RecyclerViewAdapter.java +++ b/app/src/main/java/us/koller/cameraroll/adapter/fileExplorer/RecyclerViewAdapter.java @@ -2,17 +2,22 @@ import android.content.ActivityNotFoundException; import android.content.Intent; +import android.os.Environment; import android.os.Handler; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import java.io.File; + import us.koller.cameraroll.R; import us.koller.cameraroll.adapter.fileExplorer.ViewHolder.FileHolder; import us.koller.cameraroll.data.Album; import us.koller.cameraroll.data.AlbumItem; import us.koller.cameraroll.data.File_POJO; +import us.koller.cameraroll.data.Provider.MediaProvider; +import us.koller.cameraroll.ui.FileExplorerActivity; import us.koller.cameraroll.ui.ItemActivity; public class RecyclerViewAdapter extends RecyclerView.Adapter { @@ -43,7 +48,12 @@ public interface Callback { private Callback callback; - public RecyclerViewAdapter(Callback callback) { + private FileExplorerActivity.OnDirectoryChangeCallback directoryChangeCallback; + + public RecyclerViewAdapter( + FileExplorerActivity.OnDirectoryChangeCallback directoryChangeCallback, + Callback callback) { + this.directoryChangeCallback = directoryChangeCallback; this.callback = callback; } @@ -82,37 +92,42 @@ public void onClick(View view) { String path = file.getPath().substring(0, index); //load Album - final Album album = new Album().setPath(path); - AlbumItem albumItem = AlbumItem.getInstance(holder.itemView.getContext(), file.getPath()); - if (albumItem != null) { - album.getAlbumItems() - .add(albumItem); + final Album album; + AlbumItem albumItem = null; + if (!Environment.isExternalStorageRemovable(new File(file.getPath()))) { + album = MediaProvider.loadAlbum(path); + for (int i = 0; i < album.getAlbumItems().size(); i++) { + if (album.getAlbumItems().get(i).getPath().equals(file.getPath())) { + albumItem = album.getAlbumItems().get(i); + break; + } + } + } else { + album = new Album().setPath(path); + albumItem = AlbumItem.getInstance(holder.itemView.getContext(), file.getPath()); + if (albumItem != null) { + album.getAlbumItems().add(albumItem); + } } - //create intent - Intent intent = new Intent(holder.itemView.getContext(), ItemActivity.class) - .putExtra(ItemActivity.ALBUM_ITEM, albumItem) - .putExtra(ItemActivity.ALBUM, album) - .putExtra(ItemActivity.VIEW_ONLY, true) - .putExtra(ItemActivity.ITEM_POSITION, album.getAlbumItems().indexOf(albumItem)) - .putExtra(ItemActivity.FINISH_AFTER, false); + if (album != null && albumItem != null) { + //create intent + Intent intent = new Intent(holder.itemView.getContext(), ItemActivity.class) + .putExtra(ItemActivity.ALBUM_ITEM, albumItem) + .putExtra(ItemActivity.ALBUM, album) + .putExtra(ItemActivity.ALBUM_PATH, album.getPath()) + .putExtra(ItemActivity.VIEW_ONLY, true) + .putExtra(ItemActivity.ITEM_POSITION, album.getAlbumItems().indexOf(albumItem)) + .putExtra(ItemActivity.FINISH_AFTER, false); - try { holder.itemView.getContext().startActivity(intent); - } catch (ActivityNotFoundException e) { - e.printStackTrace(); } } else { //to keep the ripple animation new Handler().postDelayed(new Runnable() { @Override public void run() { - RecyclerViewAdapter.this.setFiles(file); - RecyclerViewAdapter.this.notifyDataSetChanged(); - - if (callback != null) { - callback.onDataChanged(); - } + directoryChangeCallback.changeDir(file.getPath()); } }, 300); } @@ -212,6 +227,9 @@ public void pickTarget() { @Override public int getItemCount() { - return files.getChildren().size(); + if (files != null) { + return files.getChildren().size(); + } + return 0; } } \ No newline at end of file diff --git a/app/src/main/java/us/koller/cameraroll/adapter/main/RecyclerViewAdapter.java b/app/src/main/java/us/koller/cameraroll/adapter/main/RecyclerViewAdapter.java index 685ff6b0..97c1107f 100644 --- a/app/src/main/java/us/koller/cameraroll/adapter/main/RecyclerViewAdapter.java +++ b/app/src/main/java/us/koller/cameraroll/adapter/main/RecyclerViewAdapter.java @@ -47,7 +47,10 @@ public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) @Override public void onClick(View view) { Intent intent = new Intent(holder.itemView.getContext(), AlbumActivity.class); - intent.putExtra(AlbumActivity.ALBUM, album); + + //intent.putExtra(AlbumActivity.ALBUM, album); + intent.putExtra(AlbumActivity.ALBUM_PATH, album.getPath()); + if (pick_photos) { intent.setAction(MainActivity.PICK_PHOTOS); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, @@ -55,8 +58,10 @@ public void onClick(View view) { } else { intent.setAction(AlbumActivity.VIEW_ALBUM); } + ActivityOptionsCompat options; Activity context = (Activity) holder.itemView.getContext(); + if (!pick_photos) { options = ActivityOptionsCompat.makeSceneTransitionAnimation(context); holder.itemView.getContext().startActivity(intent, options.toBundle()); diff --git a/app/src/main/java/us/koller/cameraroll/adapter/main/ViewHolder/AlbumHolder.java b/app/src/main/java/us/koller/cameraroll/adapter/main/ViewHolder/AlbumHolder.java index a1b65974..0afb964f 100644 --- a/app/src/main/java/us/koller/cameraroll/adapter/main/ViewHolder/AlbumHolder.java +++ b/app/src/main/java/us/koller/cameraroll/adapter/main/ViewHolder/AlbumHolder.java @@ -1,9 +1,7 @@ package us.koller.cameraroll.adapter.main.ViewHolder; -import android.app.Activity; import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -15,6 +13,7 @@ import us.koller.cameraroll.R; import us.koller.cameraroll.data.Album; import us.koller.cameraroll.data.AlbumItem; +import us.koller.cameraroll.data.Provider.MediaProvider; import us.koller.cameraroll.ui.widget.ParallaxImageView; import us.koller.cameraroll.util.ColorFade; @@ -28,6 +27,12 @@ public AlbumHolder(View itemView) { public void setAlbum(Album album) { this.album = album; + + if (album == null) { + //Error album + album = MediaProvider.getErrorAlbum(); + } + ((TextView) itemView.findViewById(R.id.name)).setText(album.getName()); String count = album.getAlbumItems().size() + (album.getAlbumItems().size() > 1 ? diff --git a/app/src/main/java/us/koller/cameraroll/data/Album.java b/app/src/main/java/us/koller/cameraroll/data/Album.java index dcdefd1a..df44a3d5 100644 --- a/app/src/main/java/us/koller/cameraroll/data/Album.java +++ b/app/src/main/java/us/koller/cameraroll/data/Album.java @@ -7,7 +7,7 @@ import java.io.File; import java.util.ArrayList; -import us.koller.cameraroll.data.MediaLoader.MediaLoader; +import us.koller.cameraroll.data.Provider.MediaProvider; import us.koller.cameraroll.util.SortUtil; public class Album @@ -41,10 +41,12 @@ public boolean isHidden() { } else { File dir = new File(getPath()); File[] files = dir.listFiles(); - for (int i = 0; i < files.length; i++) { - if (files[i].getName().equals(MediaLoader.FILE_TYPE_NO_MEDIA)) { - hidden = HIDDEN; - return true; + if (files != null) { + for (int i = 0; i < files.length; i++) { + if (files[i].getName().equals(MediaProvider.FILE_TYPE_NO_MEDIA)) { + hidden = HIDDEN; + return true; + } } } } @@ -58,7 +60,8 @@ public String getPath() { @Override public String getName() { - return new File(path).getName(); + String name = new File(getPath()).getName(); + return name != null ? name : "ERROR"; } @Override diff --git a/app/src/main/java/us/koller/cameraroll/data/AlbumItem.java b/app/src/main/java/us/koller/cameraroll/data/AlbumItem.java index c9727d1e..5ec09851 100644 --- a/app/src/main/java/us/koller/cameraroll/data/AlbumItem.java +++ b/app/src/main/java/us/koller/cameraroll/data/AlbumItem.java @@ -10,7 +10,9 @@ import java.io.File; import us.koller.cameraroll.util.MediaType; +import us.koller.cameraroll.util.RemovableStorageUtil; import us.koller.cameraroll.util.SortUtil; +import us.koller.cameraroll.util.Util; public abstract class AlbumItem implements Parcelable, SortUtil.Sortable { @@ -27,6 +29,8 @@ public abstract class AlbumItem public boolean isSharedElement = false; public boolean hasFadedIn = false; + public String TYPE = "AlbumItem"; + //factory method public static AlbumItem getInstance(Context context, String path) { if (path == null) { @@ -84,15 +88,16 @@ public long getDate(Activity context) { } public Uri getUri(Context context) { - Uri uri; if (!contentUri) { try { File file = new File(getPath()); - uri = FileProvider.getUriForFile(context, + return FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file); - return uri; } catch (IllegalArgumentException e) { e.printStackTrace(); + + //file is probably on removable storage + return RemovableStorageUtil.getContentUriFromFilePath(context, getPath()); } } return Uri.parse(getPath()); @@ -149,4 +154,10 @@ public AlbumItem[] newArray(int i) { return new AlbumItem[i]; } }; + + public static AlbumItem getErrorItem() { + AlbumItem albumItem = new Photo(); + albumItem.setPath("ERROR").setName("ERROR"); + return albumItem; + } } diff --git a/app/src/main/java/us/koller/cameraroll/data/File_POJO.java b/app/src/main/java/us/koller/cameraroll/data/File_POJO.java index 697f41c6..5fabd5c3 100644 --- a/app/src/main/java/us/koller/cameraroll/data/File_POJO.java +++ b/app/src/main/java/us/koller/cameraroll/data/File_POJO.java @@ -39,6 +39,11 @@ public long getDate(Activity context) { return 0; } + @Override + public String toString() { + return getPath(); + } + public String getPath() { return path; } diff --git a/app/src/main/java/us/koller/cameraroll/data/FilesLoader/FilesLoader.java b/app/src/main/java/us/koller/cameraroll/data/FilesLoader/FilesLoader.java deleted file mode 100644 index 5260b808..00000000 --- a/app/src/main/java/us/koller/cameraroll/data/FilesLoader/FilesLoader.java +++ /dev/null @@ -1,246 +0,0 @@ -package us.koller.cameraroll.data.FilesLoader; - -import android.app.Activity; -import android.os.AsyncTask; -import android.os.Environment; -import android.os.Handler; -import android.util.Log; -import android.widget.Toast; - -import java.io.File; -import java.io.FileFilter; -import java.util.ArrayList; -import java.util.Arrays; - -import us.koller.cameraroll.data.File_POJO; -import us.koller.cameraroll.data.MediaLoader.MediaLoader; -import us.koller.cameraroll.util.MediaType; -import us.koller.cameraroll.util.SortUtil; - -public class FilesLoader { - - public interface LoaderCallback { - void onMediaLoaded(File_POJO files); - - void timeout(); - - void needPermission(); - } - - public interface Callback { - void callback(File_POJO files); - } - - //option to set thread count; - //if set to -1 every dir in home dir get its own thread - private static final int THREAD_COUNT = -1; - - private ArrayList threads; - - //for timeout - private Handler handler; - private Runnable timeout; - - public void loadFiles(final Activity context, - final LoaderCallback callback) { - - if (!MediaLoader.checkPermission(context)) { - callback.needPermission(); - return; - } - - final long startTime = System.currentTimeMillis(); - - //handle timeout - handler = new Handler(); - timeout = new Runnable() { - @Override - public void run() { - Toast.makeText(context, "timeout", Toast.LENGTH_SHORT).show(); - callback.timeout(); - } - }; - handler.postDelayed(timeout, 5000); - - //load files from storage - AsyncTask.execute(new Runnable() { - @Override - public void run() { - searchStorage(context, new FilesLoader.Callback() { - @Override - public void callback(File_POJO files) { - //sort files by name - SortUtil.sortFiles(context, files); - - //done loading files from storage - callback.onMediaLoaded(files); - cancelTimeout(); - if (THREAD_COUNT == -1) { - Log.d("FilesLoader", "onMediaLoaded(): " + String.valueOf(System.currentTimeMillis() - startTime) + "; " + files.getChildren()); - } else { - Log.d("FilesLoader", "onMediaLoaded(" + String.valueOf(THREAD_COUNT) - + "): " + String.valueOf(System.currentTimeMillis() - startTime) + "; " + files.getChildren()); - } - } - }); - } - }); - } - - private void cancelTimeout() { - if (handler != null && timeout != null) { - handler.removeCallbacks(timeout); - } - } - - public void onDestroy() { - //cancel all threads when Activity is being destroyed - if (threads != null) { - for (int i = 0; i < threads.size(); i++) { - threads.get(i).cancel(); - } - } - cancelTimeout(); - } - - private void searchStorage(final Activity context, final FilesLoader.Callback callback) { - final File_POJO files = new File_POJO(Environment.getExternalStorageDirectory().toString(), false); - File dir = Environment.getExternalStorageDirectory(); //new File("/storage/emulated/0"); - File[] dirs = dir.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - return !file.getName().equals("Android"); - } - }); - - threads = new ArrayList<>(); - - Thread.Callback threadCallback = new Thread.Callback() { - @Override - public void done(Thread thread, - ArrayList filesToAdd) { - for (int i = 0; i < filesToAdd.size(); i++) { - files.addChild(filesToAdd.get(i)); - } - threads.remove(thread); - thread.cancel(); - if (threads.size() == 0) { - callback.callback(files); - threads = null; - } - } - }; - - if (THREAD_COUNT == -1) { - for (int i = 0; i < dirs.length; i++) { - final File[] threadFiles = {dirs[i]}; - Thread thread = new Thread(context, threadFiles, threadCallback); - thread.start(); - threads.add(thread); - } - } else { - //overhead is to big!! - final File[][] threadDirs = divideDirs(dirs); - - for (int i = 0; i < THREAD_COUNT; i++) { - final File[] threadFiles = threadDirs[i]; - Thread thread = new Thread(context, threadFiles, threadCallback); - thread.start(); - threads.add(thread); - } - } - } - - private File[][] divideDirs(File[] dirs) { - int[] threadDirs_sizes = new int[THREAD_COUNT]; - int rest = dirs.length % THREAD_COUNT; - for (int i = 0; i < threadDirs_sizes.length; i++) { - threadDirs_sizes[i] = dirs.length / THREAD_COUNT; - if (rest > 0) { - threadDirs_sizes[i]++; - rest--; - } - } - - Log.d("FilesLoader", Arrays.toString(threadDirs_sizes)); - - File[][] threadDirs = new File[THREAD_COUNT][dirs.length / THREAD_COUNT + 1]; - int index = 0; - for (int i = 0; i < THREAD_COUNT; i++) { - File[] threadDir = Arrays.copyOfRange(dirs, index, index + threadDirs_sizes[i]); - threadDirs[i] = threadDir; - index = index + threadDirs_sizes[i]; - } - - return threadDirs; - } - - private static class Thread extends java.lang.Thread { - - interface Callback { - void done(Thread thread, ArrayList files); - } - - private Activity context; - private Callback callback; - - private File[] dirs; - - Thread(Activity context, File[] dirs, Callback callback) { - this.context = context; - this.callback = callback; - this.dirs = dirs; - } - - @Override - public void run() { - super.run(); - - final ArrayList files = new ArrayList<>(); - - if (dirs != null) { - for (int i = 0; i < dirs.length; i++) { - File_POJO file_pojo = new File_POJO(dirs[i].getPath(), - MediaType.isMedia(context, dirs[i].getPath())); - recursivelySearchStorage(context, dirs[i], file_pojo); - files.add(file_pojo); - } - } - - if (callback != null) { - callback.done(this, files); - } - } - - private void recursivelySearchStorage(final Activity context, - final File file, - final File_POJO files) { - if (interrupted() || file == null) { - return; - } - - if (file.isFile()) { - return; - } - - java.io.File[] filesToSearch = file.listFiles(); - - for (int i = 0; i < filesToSearch.length; i++) { - boolean isMedia = MediaType.isMedia(context, filesToSearch[i].getPath()); - if (filesToSearch[i].isDirectory() || isMedia) { - File_POJO file_pojo = new File_POJO(filesToSearch[i].getPath(), isMedia); - files.addChild(file_pojo); - if (filesToSearch[i].isDirectory()) { - recursivelySearchStorage(context, filesToSearch[i], file_pojo); - } - } - } - } - - void cancel() { - context = null; - callback = null; - interrupt(); - } - } -} diff --git a/app/src/main/java/us/koller/cameraroll/data/Gif.java b/app/src/main/java/us/koller/cameraroll/data/Gif.java index 4ee1b30a..7088b926 100644 --- a/app/src/main/java/us/koller/cameraroll/data/Gif.java +++ b/app/src/main/java/us/koller/cameraroll/data/Gif.java @@ -5,7 +5,7 @@ public class Gif extends AlbumItem implements Parcelable { Gif() { - + TYPE = "Gif"; } Gif(Parcel parcel) { diff --git a/app/src/main/java/us/koller/cameraroll/data/MediaLoader/Loader/StorageLoader.java b/app/src/main/java/us/koller/cameraroll/data/MediaLoader/Loader/StorageLoader.java deleted file mode 100644 index 518d9584..00000000 --- a/app/src/main/java/us/koller/cameraroll/data/MediaLoader/Loader/StorageLoader.java +++ /dev/null @@ -1,258 +0,0 @@ -package us.koller.cameraroll.data.MediaLoader.Loader; - -import android.app.Activity; -import android.os.AsyncTask; -import android.os.Environment; -import android.os.Handler; -import android.util.Log; -import android.widget.Toast; - -import java.io.File; -import java.io.FileFilter; -import java.util.ArrayList; -import java.util.Arrays; - -import us.koller.cameraroll.data.Album; -import us.koller.cameraroll.data.AlbumItem; -import us.koller.cameraroll.data.MediaLoader.MediaLoader; -import us.koller.cameraroll.util.MediaType; -import us.koller.cameraroll.util.SortUtil; - -//loading media by searching through Storage -//advantage: all items, disadvantage: slower than MediaStore -public class StorageLoader implements MediaLoader.Loader { - - //option to set thread count; - //if set to -1 every dir in home dir get its own thread - private static final int THREAD_COUNT = -1; - - private ArrayList threads; - - //for timeout - private Handler handler; - private Runnable timeout; - - @Override - public void loadAlbums(final Activity context, final boolean hiddenFolders, - final MediaLoader.LoaderCallback callback) { - - final long startTime = System.currentTimeMillis(); - - final ArrayList albums = new ArrayList<>(); - - //handle timeout - handler = new Handler(); - timeout = new Runnable() { - @Override - public void run() { - Toast.makeText(context, "timeout", Toast.LENGTH_SHORT).show(); - callback.timeout(); - } - }; - handler.postDelayed(timeout, 5000); - - //load media from storage - AsyncTask.execute(new Runnable() { - @Override - public void run() { - searchStorage(context, albums, new MediaLoader.Callback() { - @Override - public void callback(ArrayList albums) { - if (!hiddenFolders) { - for (int i = albums.size() - 1; i >= 0; i--) { - if (albums.get(i).isHidden()) { - albums.remove(i); - } - } - } - - //done loading media from storage - SortUtil.sortAlbums(context, albums, SortUtil.BY_NAME); - callback.onMediaLoaded(albums); - cancelTimeout(); - if (THREAD_COUNT == -1) { - Log.d("StorageLoader", "onMediaLoaded(): " + String.valueOf(System.currentTimeMillis() - startTime)); - } else { - Log.d("StorageLoader", "onMediaLoaded(" + String.valueOf(THREAD_COUNT) - + "): " + String.valueOf(System.currentTimeMillis() - startTime)); - } - } - }); - } - }); - } - - private void cancelTimeout() { - if (handler != null && timeout != null) { - handler.removeCallbacks(timeout); - } - } - - @Override - public void onDestroy() { - //cancel all threads when Activity is being destroyed - if (threads != null) { - for (int i = 0; i < threads.size(); i++) { - if (threads.get(i) != null) { - threads.get(i).cancel(); - } - } - } - cancelTimeout(); - } - - private void searchStorage(final Activity context, final ArrayList albums, final MediaLoader.Callback callback) { - File dir = Environment.getExternalStorageDirectory(); //new File("/storage/emulated/0"); - File[] dirs = dir.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - return !file.getName().equals("Android"); - } - }); - - threads = new ArrayList<>(); - - Thread.Callback threadCallback = new Thread.Callback() { - @Override - public void done(Thread thread, - ArrayList albumsToAdd) { - mergeAlbums(albums, albumsToAdd); - threads.remove(thread); - thread.cancel(); - if (threads.size() == 0) { - callback.callback(albums); - threads = null; - } - } - }; - - if (THREAD_COUNT == -1) { - for (int i = 0; i < dirs.length; i++) { - final File[] files = {dirs[i]}; - Thread thread = new Thread(context, files, threadCallback); - thread.start(); - threads.add(thread); - } - } else { - //overhead is to big!! - final File[][] threadDirs = divideDirs(dirs); - - for (int i = 0; i < THREAD_COUNT; i++) { - final File[] files = threadDirs[i]; - Thread thread = new Thread(context, files, threadCallback); - thread.start(); - threads.add(thread); - } - } - } - - private void mergeAlbums(ArrayList albums, ArrayList albumsToAdd) { - for (int i = albumsToAdd.size() - 1; i >= 0; i--) { - for (int k = 0; k < albums.size(); k++) { - if (albumsToAdd.get(i).getPath() - .equals(albums.get(k).getPath())) { - albumsToAdd.remove(i); - break; - } - } - } - albums.addAll(albumsToAdd); - } - - private File[][] divideDirs(File[] dirs) { - int[] threadDirs_sizes = new int[THREAD_COUNT]; - int rest = dirs.length % THREAD_COUNT; - for (int i = 0; i < threadDirs_sizes.length; i++) { - threadDirs_sizes[i] = dirs.length / THREAD_COUNT; - if (rest > 0) { - threadDirs_sizes[i]++; - rest--; - } - } - - Log.d("StorageLoader", Arrays.toString(threadDirs_sizes)); - - File[][] threadDirs = new File[THREAD_COUNT][dirs.length / THREAD_COUNT + 1]; - int index = 0; - for (int i = 0; i < THREAD_COUNT; i++) { - File[] threadDir = Arrays.copyOfRange(dirs, index, index + threadDirs_sizes[i]); - threadDirs[i] = threadDir; - index = index + threadDirs_sizes[i]; - } - - return threadDirs; - } - - private static class Thread extends java.lang.Thread { - - interface Callback { - void done(Thread thread, ArrayList albums); - } - - private Activity context; - private Callback callback; - - private File[] dirs; - - Thread(Activity context, File[] dirs, Callback callback) { - this.context = context; - this.callback = callback; - this.dirs = dirs; - } - - @Override - public void run() { - super.run(); - - final ArrayList albums = new ArrayList<>(); - - if (dirs != null) { - for (int i = 0; i < dirs.length; i++) { - recursivelySearchStorage(context, dirs[i], albums); - } - } - - if (callback != null) { - callback.done(this, albums); - } - } - - private void recursivelySearchStorage(final Activity context, - final File file, - final ArrayList albums) { - if (interrupted() || file == null) { - return; - } - - if (file.isFile()) { - return; - } - - final Album album = new Album().setPath(file.getPath()); - - File[] files = file.listFiles(); - for (int i = 0; i < files.length; i++) { - if (MediaType.isMedia(context, files[i].getPath())) { - AlbumItem albumItem - = AlbumItem.getInstance(context, files[i].getPath()); - if (albumItem != null) { - album.getAlbumItems() - .add(albumItem); - } - } else if (files[i].isDirectory()) { - recursivelySearchStorage(context, files[i], albums); - } - } - - if (album.getAlbumItems().size() > 0) { - albums.add(album); - } - } - - void cancel() { - context = null; - callback = null; - interrupt(); - } - } -} diff --git a/app/src/main/java/us/koller/cameraroll/data/Photo.java b/app/src/main/java/us/koller/cameraroll/data/Photo.java index 90ccb532..79019493 100644 --- a/app/src/main/java/us/koller/cameraroll/data/Photo.java +++ b/app/src/main/java/us/koller/cameraroll/data/Photo.java @@ -9,7 +9,7 @@ public class Photo extends AlbumItem implements Parcelable { private Serializable imageViewSavedState; Photo() { - + TYPE = "Photo"; } Photo(Parcel parcel) { diff --git a/app/src/main/java/us/koller/cameraroll/data/Provider/FilesProvider.java b/app/src/main/java/us/koller/cameraroll/data/Provider/FilesProvider.java new file mode 100644 index 00000000..ebee52fc --- /dev/null +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/FilesProvider.java @@ -0,0 +1,52 @@ +package us.koller.cameraroll.data.Provider; + +import android.app.Activity; + +import us.koller.cameraroll.data.File_POJO; +import us.koller.cameraroll.data.Provider.Retriever.Retriever; +import us.koller.cameraroll.data.Provider.Retriever.StorageRetriever; + +public class FilesProvider extends Provider { + + /*public interface Callback { + void onFilesLoaded(File_POJO files); + + void timeout(); + + void needPermission(); + }*/ + + public interface Callback { + void onDirLoaded(File_POJO dir); + + void timeout(); + + void needPermission(); + } + + private Retriever retriever; + + public FilesProvider() { + retriever = new StorageRetriever(); + } + + /*public void loadFiles(Activity context, final Callback callback) { + + if (!MediaProvider.checkPermission(context)) { + callback.needPermission(); + return; + } + + retriever = new StorageRetriever(); + ((StorageRetriever) retriever).loadFiles(context, callback); + }*/ + + public File_POJO[] getRoots() { + return ((StorageRetriever) retriever).loadRoots(); + } + + public void loadDir(Activity context, String dirPath, + final FilesProvider.Callback callback) { + ((StorageRetriever) retriever).loadDir(context, dirPath, callback); + } +} diff --git a/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/AlbumLoader.java b/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/AlbumLoader.java new file mode 100644 index 00000000..408512c4 --- /dev/null +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/AlbumLoader.java @@ -0,0 +1,56 @@ +package us.koller.cameraroll.data.Provider.ItemLoader; + +import android.app.Activity; + +import java.io.File; +import java.util.ArrayList; + +import us.koller.cameraroll.data.Album; +import us.koller.cameraroll.data.AlbumItem; +import us.koller.cameraroll.util.MediaType; +import us.koller.cameraroll.util.SortUtil; + +public class AlbumLoader extends ItemLoader { + + private ArrayList albums; + + private Album currentAlbum; + + AlbumLoader() { + albums = new ArrayList<>(); + } + + @Override + public void onNewDir(Activity context, File dir) { + currentAlbum = new Album().setPath(dir.getPath()); + } + + @Override + public void onFile(Activity context, File file) { + if (MediaType.isMedia(context, file.getPath())) { + AlbumItem albumItem + = AlbumItem.getInstance(context, file.getPath()); + if (albumItem != null) { + currentAlbum.getAlbumItems() + .add(albumItem); + } + } + } + + @Override + public void onDirDone(Activity context) { + if (currentAlbum != null && currentAlbum.getAlbumItems().size() > 0) { + SortUtil.sortByDate(context, currentAlbum.getAlbumItems()); + albums.add(currentAlbum); + currentAlbum = null; + } + } + + @Override + public Result getResult() { + Result result = new Result(); + result.albums = albums; + albums = new ArrayList<>(); + return result; + } +} diff --git a/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/FileLoader.java b/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/FileLoader.java new file mode 100644 index 00000000..d2725faf --- /dev/null +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/FileLoader.java @@ -0,0 +1,78 @@ +package us.koller.cameraroll.data.Provider.ItemLoader; + +import android.app.Activity; +import android.os.Environment; +import android.util.Log; + +import java.io.File; + +import us.koller.cameraroll.data.File_POJO; +import us.koller.cameraroll.util.MediaType; + +public class FileLoader extends ItemLoader { + + private static File_POJO allFiles; + + private File_POJO dir_pojo; + + FileLoader() { + if (allFiles == null) { + allFiles = new File_POJO(Environment.getExternalStorageDirectory().getPath(), false); + } + } + + @Override + public void onNewDir(Activity context, File dir) { + dir_pojo = new File_POJO(dir.getPath(), + MediaType.isMedia(context, dir.getPath())); + } + + @Override + public void onFile(Activity context, File file) { + File_POJO file_pojo = new File_POJO(file.getPath(), + MediaType.isMedia(context, file.getPath())); + dir_pojo.addChild(file_pojo); + } + + @Override + public void onDirDone(Activity context) { + addFiles(allFiles, dir_pojo); + } + + @Override + public Result getResult() { + Result result = new Result(); + result.files = dir_pojo; + return result; + } + + private static File_POJO addFiles(File_POJO files, File_POJO filesToAdd) { + if (files.getPath().equals(filesToAdd.getPath())) { + files.getChildren().addAll(filesToAdd.getChildren()); + } else if (files.getPath().equals(filesToAdd.getPath() + .replace("/" + filesToAdd.getName(), ""))) { + files.addChild(filesToAdd); + } else { + File_POJO currentFiles = files; + + String[] filesToAddPath = filesToAdd.getPath().split("/"); + for (int i = 0; i < filesToAddPath.length; i++) { + boolean found = false; + for (int k = 0; k < currentFiles.getChildren().size(); k++) { + if (filesToAddPath[i].equals( + currentFiles.getChildren().get(k).getName())) { + found = true; + currentFiles = currentFiles.getChildren().get(k); + } + } + + if (found) { + currentFiles.addChild(filesToAdd); + break; + } + } + } + + return files; + } +} diff --git a/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/ItemLoader.java b/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/ItemLoader.java new file mode 100644 index 00000000..0192feaf --- /dev/null +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/ItemLoader/ItemLoader.java @@ -0,0 +1,41 @@ +package us.koller.cameraroll.data.Provider.ItemLoader; + +import android.app.Activity; + +import java.io.File; +import java.util.ArrayList; + +import us.koller.cameraroll.data.Album; +import us.koller.cameraroll.data.File_POJO; + +public abstract class ItemLoader { + + public class Result { + public ArrayList albums; + public File_POJO files; + } + + public static ItemLoader getInstance(Class c) { + if (c.equals(AlbumLoader.class)) { + return new AlbumLoader(); + } + + if (c.equals(FileLoader.class)) { + return new FileLoader(); + } + + return null; + } + + ItemLoader() { + + } + + public abstract void onNewDir(Activity context, File dir); + + public abstract void onFile(Activity context, File file); + + public abstract void onDirDone(Activity context); + + public abstract Result getResult(); +} diff --git a/app/src/main/java/us/koller/cameraroll/data/MediaLoader/MediaLoader.java b/app/src/main/java/us/koller/cameraroll/data/Provider/MediaProvider.java similarity index 54% rename from app/src/main/java/us/koller/cameraroll/data/MediaLoader/MediaLoader.java rename to app/src/main/java/us/koller/cameraroll/data/Provider/MediaProvider.java index 8f98f823..cbd87afa 100644 --- a/app/src/main/java/us/koller/cameraroll/data/MediaLoader/MediaLoader.java +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/MediaProvider.java @@ -1,4 +1,4 @@ -package us.koller.cameraroll.data.MediaLoader; +package us.koller.cameraroll.data.Provider; import android.Manifest; import android.app.Activity; @@ -12,38 +12,31 @@ import java.util.ArrayList; import us.koller.cameraroll.data.Album; -import us.koller.cameraroll.data.MediaLoader.Loader.MediaStoreLoader; -import us.koller.cameraroll.data.MediaLoader.Loader.StorageLoader; +import us.koller.cameraroll.data.AlbumItem; +import us.koller.cameraroll.data.Provider.Retriever.MediaStoreRetriever; +import us.koller.cameraroll.data.Provider.Retriever.StorageRetriever; import us.koller.cameraroll.ui.MainActivity; -public class MediaLoader { +public class MediaProvider extends Provider { - public interface Loader { - void loadAlbums(final Activity context, final boolean hiddenFolders, final LoaderCallback callback); - void onDestroy(); - } + private static ArrayList albums; - public interface LoaderCallback { + public interface Callback { void onMediaLoaded(ArrayList albums); + void timeout(); void needPermission(); } - public interface Callback { - void callback(ArrayList albums); - } - - public static final int MODE_STORAGE = 1; - public static final int MODE_MEDIASTORE = 2; + private static final int MODE_STORAGE = 1; + private static final int MODE_MEDIASTORE = 2; private static final String MODE_KEY = "MODE_KEY"; public static final String FILE_TYPE_NO_MEDIA = ".nomedia"; public static final int PERMISSION_REQUEST_CODE = 16; - private Loader loader; - - public MediaLoader() { + public MediaProvider() { } @@ -62,9 +55,9 @@ public static boolean checkPermission(Activity context) { public void loadAlbums(Activity context, boolean hiddenFolders, - LoaderCallback callback) { + final Callback callback) { - if (!MediaLoader.checkPermission(context)) { + if (!MediaProvider.checkPermission(context)) { callback.needPermission(); return; } @@ -73,22 +66,65 @@ public void loadAlbums(Activity context, switch (mode) { case MODE_STORAGE: - loader = new StorageLoader(); + retriever = new StorageRetriever(); break; case MODE_MEDIASTORE: - loader = new MediaStoreLoader(); + retriever = new MediaStoreRetriever(); break; } - if (loader != null) { - loader.loadAlbums(context, hiddenFolders, callback); + if (retriever != null) { + retriever.loadAlbums(context, hiddenFolders, + new Callback() { + + @Override + public void onMediaLoaded(ArrayList albums) { + callback.onMediaLoaded(albums); + setAlbums(albums); + } + + @Override + public void timeout() { + callback.timeout(); + } + + @Override + public void needPermission() { + callback.needPermission(); + } + }); } else { callback.onMediaLoaded(null); } } - public void onDestroy() { - loader.onDestroy(); + private static void setAlbums(ArrayList albums) { + MediaProvider.albums = albums; + } + + public static ArrayList getAlbums() { + return albums; + } + + public static Album loadAlbum(String path) { + if (albums == null) { + return getErrorAlbum(); + } + + for (int i = 0; i < albums.size(); i++) { + if (albums.get(i).getPath().equals(path)) { + return albums.get(i); + } + } + + return getErrorAlbum(); + } + + public static Album getErrorAlbum() { + //Error album + Album album = new Album().setPath("ERROR"); + album.getAlbumItems().add(AlbumItem.getErrorItem()); + return album; } public static void toggleMode(Context context) { diff --git a/app/src/main/java/us/koller/cameraroll/data/Provider/Provider.java b/app/src/main/java/us/koller/cameraroll/data/Provider/Provider.java new file mode 100644 index 00000000..907b42b6 --- /dev/null +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/Provider.java @@ -0,0 +1,18 @@ +package us.koller.cameraroll.data.Provider; + +import us.koller.cameraroll.data.Provider.Retriever.Retriever; + +abstract class Provider { + + Retriever retriever; + + Provider() { + + } + + public void onDestroy() { + if (retriever != null) { + retriever.onDestroy(); + } + } +} diff --git a/app/src/main/java/us/koller/cameraroll/data/MediaLoader/Loader/MediaStoreLoader.java b/app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/MediaStoreRetriever.java similarity index 79% rename from app/src/main/java/us/koller/cameraroll/data/MediaLoader/Loader/MediaStoreLoader.java rename to app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/MediaStoreRetriever.java index 36c3253f..ec233a3e 100644 --- a/app/src/main/java/us/koller/cameraroll/data/MediaLoader/Loader/MediaStoreLoader.java +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/MediaStoreRetriever.java @@ -1,4 +1,4 @@ -package us.koller.cameraroll.data.MediaLoader.Loader; +package us.koller.cameraroll.data.Provider.Retriever; import android.app.Activity; import android.database.Cursor; @@ -9,26 +9,26 @@ import android.util.Log; import java.io.File; -import java.io.FileFilter; import java.util.ArrayList; import us.koller.cameraroll.data.Album; import us.koller.cameraroll.data.AlbumItem; -import us.koller.cameraroll.data.MediaLoader.MediaLoader; +import us.koller.cameraroll.data.Provider.MediaProvider; import us.koller.cameraroll.util.MediaType; import us.koller.cameraroll.util.SortUtil; import us.koller.cameraroll.util.Util; //loading media through MediaStore //advantage: speed, disadvantage: might be missing some items -public class MediaStoreLoader implements MediaLoader.Loader { +public class MediaStoreRetriever implements Retriever { private static final String[] projection = new String[]{ MediaStore.Files.FileColumns.DATA, MediaStore.Files.FileColumns.PARENT}; + @Override - public void loadAlbums(final Activity context, boolean hiddenFolders, final MediaLoader.LoaderCallback callback) { + public void loadAlbums(final Activity context, boolean hiddenFolders, final MediaProvider.Callback callback) { final long startTime = System.currentTimeMillis(); @@ -59,7 +59,7 @@ public void loadAlbums(final Activity context, boolean hiddenFolders, final Medi //search hiddenFolders if (hiddenFolders) { - ArrayList hiddenAlbums = loadHiddenFolders(context); + ArrayList hiddenAlbums = checkHiddenFolders(context); albums.addAll(hiddenAlbums); } @@ -98,14 +98,20 @@ public void run() { cursor.close(); //done loading media with content resolver - SortUtil.sortAlbums(context, albums, SortUtil.BY_NAME); + //SortUtil.sortAlbums(context, albums, SortUtil.BY_NAME); + SortUtil.sortByName(albums); callback.onMediaLoaded(albums); - Log.d("MediaStoreLoader", "onMediaLoaded(): " + String.valueOf(System.currentTimeMillis() - startTime)); + Log.d("MediaStoreRetriever", "onMediaLoaded(): " + String.valueOf(System.currentTimeMillis() - startTime)); } }); } - private ArrayList loadHiddenFolders(final Activity context) { + @Override + public void onDestroy() { + + } + + private ArrayList checkHiddenFolders(final Activity context) { ArrayList hiddenAlbums = new ArrayList<>(); @@ -117,7 +123,7 @@ private ArrayList loadHiddenFolders(final Activity context) { String selection = nonMediaCondition + " AND " + MediaStore.Files.FileColumns.TITLE + " LIKE ?"; - String[] params = new String[]{"%" + MediaLoader.FILE_TYPE_NO_MEDIA + "%"}; + String[] params = new String[]{"%" + MediaProvider.FILE_TYPE_NO_MEDIA + "%"}; // make query for non media files with file title contain ".nomedia" as // text on External Media URI @@ -138,22 +144,20 @@ private ArrayList loadHiddenFolders(final Activity context) { do { String path = cursor.getString(pathColumn); - path = path.replace(MediaLoader.FILE_TYPE_NO_MEDIA, ""); - File folder = new File(path); - final Album album = new Album().setPath(folder.getPath()); - //album.isHidden() = true; + path = path.replace(MediaProvider.FILE_TYPE_NO_MEDIA, ""); + File dir = new File(path); + final Album album = new Album().setPath(path); + + File[] files = dir.listFiles(); - folder.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - if (MediaType.isMedia(context, file.getPath())) { - AlbumItem albumItem = AlbumItem.getInstance(context, file.getPath()); + for (int i = 0; i < files.length; i++) { + if (MediaType.isMedia(context, files[i].getPath())) { + AlbumItem albumItem = AlbumItem.getInstance(context, files[i].getPath()); + if (albumItem != null) { album.getAlbumItems().add(albumItem); - return true; } - return false; } - }); + } if (album.getAlbumItems().size() > 0) { hiddenAlbums.add(album); @@ -164,9 +168,4 @@ public boolean accept(File file) { return hiddenAlbums; } - - @Override - public void onDestroy() { - - } } diff --git a/app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/Retriever.java b/app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/Retriever.java new file mode 100644 index 00000000..8d4d7c1f --- /dev/null +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/Retriever.java @@ -0,0 +1,12 @@ +package us.koller.cameraroll.data.Provider.Retriever; + +import android.app.Activity; + +import us.koller.cameraroll.data.Provider.MediaProvider; + +public interface Retriever { + + public void loadAlbums(final Activity context, final boolean hiddenFolders, final MediaProvider.Callback callback); + + public void onDestroy(); +} diff --git a/app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/StorageRetriever.java b/app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/StorageRetriever.java new file mode 100644 index 00000000..d2a45a11 --- /dev/null +++ b/app/src/main/java/us/koller/cameraroll/data/Provider/Retriever/StorageRetriever.java @@ -0,0 +1,529 @@ +package us.koller.cameraroll.data.Provider.Retriever; + +import android.app.Activity; +import android.os.AsyncTask; +import android.os.Environment; +import android.os.Handler; +import android.util.Log; +import android.widget.Toast; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Arrays; + +import us.koller.cameraroll.data.Album; +import us.koller.cameraroll.data.File_POJO; +import us.koller.cameraroll.data.Provider.FilesProvider; +import us.koller.cameraroll.data.Provider.ItemLoader.AlbumLoader; +import us.koller.cameraroll.data.Provider.ItemLoader.FileLoader; +import us.koller.cameraroll.data.Provider.ItemLoader.ItemLoader; +import us.koller.cameraroll.data.Provider.MediaProvider; +import us.koller.cameraroll.util.SortUtil; + +//loading media by searching through Storage +//advantage: all items, disadvantage: slower than MediaStore +public class StorageRetriever implements Retriever { + + interface StorageSearchCallback { + void onPartialResult(ItemLoader.Result result); + + void done(); + } + + //option to set thread count; + //if set to -1 every dir in home dir get its own thread + private static final int THREAD_COUNT = 16; + + //use AdaptableThreads + private static final boolean USE_ADAPTABLE_THREADS = false; + + private ArrayList threads; + + //for timeout + private Handler handler; + private Runnable timeout; + + private Class itemLoaderClass; + + public StorageRetriever() { + + } + + @Override + public void loadAlbums(final Activity context, final boolean hiddenFolders, + final MediaProvider.Callback callback) { + + itemLoaderClass = AlbumLoader.class; + + final long startTime = System.currentTimeMillis(); + + final ArrayList albums = new ArrayList<>(); + + //handle timeout + handler = new Handler(); + timeout = new Runnable() { + @Override + public void run() { + Toast.makeText(context, "timeout", Toast.LENGTH_SHORT).show(); + callback.timeout(); + } + }; + handler.postDelayed(timeout, 5000); + + //load media from storage + AsyncTask.execute(new Runnable() { + @Override + public void run() { + searchStorage(context, + new StorageSearchCallback() { + + @Override + public void onPartialResult(ItemLoader.Result result) { + albums.addAll(result.albums); + } + + @Override + public void done() { + if (!hiddenFolders) { + for (int i = albums.size() - 1; i >= 0; i--) { + if (albums.get(i).isHidden()) { + albums.remove(i); + } + } + } + + //done loading media from storage + //SortUtil.sortAlbums(context, albums, SortUtil.BY_NAME); + SortUtil.sortByName(albums); + callback.onMediaLoaded(albums); + cancelTimeout(); + if (THREAD_COUNT == -1) { + Log.d("StorageRetriever", "onMediaLoaded(): " + String.valueOf(System.currentTimeMillis() - startTime)); + } else { + Log.d("StorageRetriever", "onMediaLoaded(" + String.valueOf(THREAD_COUNT) + + "): " + String.valueOf(System.currentTimeMillis() - startTime)); + } + } + }); + } + }); + } + + public File_POJO[] loadRoots() { + + ArrayList temp = new ArrayList<>(); + + temp.add(new File_POJO(Environment.getExternalStorageDirectory().getPath(), false)); + + File[] removableStorageRoots = getRemovableStorageRoots(); + for (int i = 0; i < removableStorageRoots.length; i++) { + temp.add(new File_POJO(removableStorageRoots[i].getPath(), false)); + } + + File_POJO[] roots = new File_POJO[temp.size()]; + return temp.toArray(roots); + } + + public void loadDir(Activity context, String dirPath, + final FilesProvider.Callback callback) { + + if (new File(dirPath).isFile()) { + callback.onDirLoaded(null); + return; + } + + itemLoaderClass = FileLoader.class; + + threads = new ArrayList<>(); + + AdaptableThread.Callback adaptableThreadCallback = new AdaptableThread.Callback() { + @Override + public void done(AdaptableThread thread, ItemLoader.Result result, + ArrayList filesToSearch) { + File_POJO files = result.files; + SortUtil.sortByName(files.getChildren()); + callback.onDirLoaded(files); + thread.cancel(); + threads = null; + } + + @Override + public File needWork() { + return null; + } + }; + + AdaptableThread thread + = new AdaptableThread(context, new File(dirPath), + adaptableThreadCallback, itemLoaderClass); + thread.start(); + threads.add(thread); + } + + private void cancelTimeout() { + if (handler != null && timeout != null) { + handler.removeCallbacks(timeout); + } + } + + @Override + public void onDestroy() { + cancelTimeout(); + //cancel all threads when Activity is being destroyed + if (threads != null) { + for (int i = 0; i < threads.size(); i++) { + if (threads.get(i) != null) { + threads.get(i).cancel(); + } + } + } + } + + private static File getStorageRoot() { + return new File("/storage"); + } + + private static File[] getDirectoriesToSearch() { + //external Directory + File dir = Environment.getExternalStorageDirectory(); //new File("/storage/emulated/0"); + File[] dirs = dir.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return !file.getPath().equals("/storage/emulated/0/Android"); + } + }); + + + //handle removable storage (e.g. SDCards) + ArrayList temp = new ArrayList<>(); + temp.addAll(Arrays.asList(dirs)); + File[] removableStorageRoots = getRemovableStorageRoots(); + for (int i = 0; i < removableStorageRoots.length; i++) { + Log.d("StorageRetriever", "removableStorageRoot: " + removableStorageRoots[i].getPath()); + File root = removableStorageRoots[i]; + File[] files = root.listFiles(); + if (files != null) { + for (int k = 0; k < files.length; k++) { + temp.add(files[k]); + } + } + } + + dirs = new File[temp.size()]; + + for (int i = 0; i < dirs.length; i++) { + dirs[i] = temp.get(i); + } + + return dirs; + } + + private static File[] getRemovableStorageRoots() { + //look for sd cards + File root = getStorageRoot(); + + return root.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + File[] files1 = file.listFiles(); + return files1 != null && files1.length > 0; + } + }); + } + + private void searchStorage(final Activity context, final StorageSearchCallback callback) { + File[] dirs = getDirectoriesToSearch(); + + threads = new ArrayList<>(); + + Thread.Callback threadCallback = new Thread.Callback() { + @Override + public void done(Thread thread, ItemLoader.Result result) { + callback.onPartialResult(result); + threads.remove(thread); + thread.cancel(); + if (threads.size() == 0) { + callback.done(); + threads = null; + } + } + }; + + if (THREAD_COUNT == -1) { + for (int i = 0; i < dirs.length; i++) { + final File[] files = {dirs[i]}; + Thread thread = new Thread(context, files, + threadCallback, itemLoaderClass); + thread.start(); + threads.add(thread); + } + } else if (!USE_ADAPTABLE_THREADS) { + final File[][] threadDirs = divideDirs(dirs); + + for (int i = 0; i < THREAD_COUNT; i++) { + final File[] files = threadDirs[i]; + Thread thread = new Thread(context, files, + threadCallback, itemLoaderClass); + thread.start(); + threads.add(thread); + } + } else { + final ArrayList queue = new ArrayList<>(Arrays.asList(dirs)); + + AdaptableThread.Callback adaptableThreadCallback = new AdaptableThread.Callback() { + @Override + public void done(AdaptableThread thread, ItemLoader.Result result, + ArrayList filesToSearch) { + callback.onPartialResult(result); + for (int i = 0; i < filesToSearch.size(); i++) { + queue.add(filesToSearch.get(i)); + } + } + + @Override + public File needWork() { + File file = nextDir(); + if (file == null) { + //check if done + AsyncTask.execute(new Runnable() { + @Override + public void run() { + checkIfDone(); + } + }); + } + return file; + } + + private synchronized File nextDir() { + if (queue.size() > 0) { + File fileSearch = queue.get(0); + queue.remove(0); + return fileSearch; + } + return null; + } + + boolean alreadyDone = false; + + private synchronized void checkIfDone() { + if (alreadyDone) { + return; + } + + //check if done with searching + boolean done = true; + for (int i = 0; i < threads.size(); i++) { + if (threads.get(i) != null + && ((AdaptableThread) threads.get(i)).searching) { + done = false; + break; + } + } + + if (done) { + alreadyDone = true; + onDestroy(); + callback.done(); + } + } + }; + + for (int i = 0; i < THREAD_COUNT; i++) { + if (queue.size() > 0) { + AdaptableThread thread + = new AdaptableThread(context, queue.get(0), + adaptableThreadCallback, itemLoaderClass); + //remove file from queue + queue.remove(0); + thread.start(); + threads.add(thread); + } + } + } + } + + private File[][] divideDirs(File[] dirs) { + int[] threadDirs_sizes = new int[THREAD_COUNT]; + int rest = dirs.length % THREAD_COUNT; + for (int i = 0; i < threadDirs_sizes.length; i++) { + threadDirs_sizes[i] = dirs.length / THREAD_COUNT; + if (rest > 0) { + threadDirs_sizes[i]++; + rest--; + } + } + + Log.d("StorageRetriever", Arrays.toString(threadDirs_sizes)); + + File[][] threadDirs = new File[THREAD_COUNT][dirs.length / THREAD_COUNT + 1]; + int index = 0; + for (int i = 0; i < THREAD_COUNT; i++) { + File[] threadDir = Arrays.copyOfRange(dirs, index, index + threadDirs_sizes[i]); + threadDirs[i] = threadDir; + index = index + threadDirs_sizes[i]; + } + + return threadDirs; + } + + private static abstract class AbstractThread extends java.lang.Thread { + + ItemLoader itemLoader; + + AbstractThread(Class itemLoaderClass) { + itemLoader = ItemLoader.getInstance(itemLoaderClass); + } + + abstract void cancel(); + } + + private static class Thread extends AbstractThread { + + interface Callback { + void done(Thread thread, ItemLoader.Result result); + } + + private Activity context; + private Callback callback; + + private File[] dirs; + + Thread(Activity context, File[] dirs, Callback callback, Class itemLoaderClass) { + super(itemLoaderClass); + this.context = context; + this.callback = callback; + this.dirs = dirs; + } + + @Override + public void run() { + super.run(); + + if (dirs != null) { + for (int i = 0; i < dirs.length; i++) { + recursivelySearchStorage(context, dirs[i]); + } + } + + if (callback != null) { + callback.done(this, itemLoader.getResult()); + } + } + + private void recursivelySearchStorage(final Activity context, + final File file) { + if (interrupted() || file == null) { + return; + } + + if (file.isFile()) { + return; + } + + itemLoader.onNewDir(context, file); + + File[] files = file.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + itemLoader.onFile(context, files[i]); + } + itemLoader.onDirDone(context); + + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + recursivelySearchStorage(context, files[i]); + } + } + } + } + + void cancel() { + context = null; + callback = null; + interrupt(); + } + } + + //trying to compensate for bigger directories + private static class AdaptableThread extends AbstractThread { + + interface Callback { + void done(AdaptableThread thread, ItemLoader.Result result, ArrayList filesToSearch); + + File needWork(); + } + + private Activity context; + private Callback callback; + + private ArrayList queue; + + boolean searching = false; + + AdaptableThread(Activity context, File dir, Callback callback, Class itemLoaderClass) { + super(itemLoaderClass); + this.context = context; + this.callback = callback; + + queue = new ArrayList<>(); + queue.add(dir); + } + + @Override + public void run() { + super.run(); + + while (!interrupted()) { + if (queue.size() > 0 && queue.get(0) != null) { + searchDir(context, queue.get(0)); + queue.remove(0); + } else { + File dir = callback.needWork(); + if (dir != null) { + searchDir(context, dir); + } + } + } + } + + private void searchDir(final Activity context, final File file) { + if (interrupted() || file == null) { + return; + } + + if (file.isFile()) { + return; + } + + searching = true; + + itemLoader.onNewDir(context, file); + + final ArrayList filesToSearch = new ArrayList<>(); + + File[] files = file.listFiles(); + for (int i = 0; i < files.length; i++) { + + itemLoader.onFile(context, files[i]); + + if (files[i].isDirectory()) { + filesToSearch.add(files[i]); + } + } + + itemLoader.onDirDone(context); + + callback.done(this, itemLoader.getResult(), filesToSearch); + + searching = false; + } + + void cancel() { + interrupt(); + context = null; + callback = null; + } + } +} diff --git a/app/src/main/java/us/koller/cameraroll/data/Video.java b/app/src/main/java/us/koller/cameraroll/data/Video.java index c5f79859..8f6d7c68 100644 --- a/app/src/main/java/us/koller/cameraroll/data/Video.java +++ b/app/src/main/java/us/koller/cameraroll/data/Video.java @@ -6,7 +6,7 @@ public class Video extends AlbumItem implements Parcelable { Video() { - + TYPE = "Video"; } Video(Parcel parcel) { diff --git a/app/src/main/java/us/koller/cameraroll/ui/AboutActivity.java b/app/src/main/java/us/koller/cameraroll/ui/AboutActivity.java index 46e381e7..a6f71705 100644 --- a/app/src/main/java/us/koller/cameraroll/ui/AboutActivity.java +++ b/app/src/main/java/us/koller/cameraroll/ui/AboutActivity.java @@ -14,7 +14,6 @@ import android.text.method.LinkMovementMethod; import android.transition.Slide; import android.transition.TransitionSet; -import android.util.Log; import android.view.Gravity; import android.view.MenuItem; import android.view.View; @@ -28,7 +27,7 @@ import com.bumptech.glide.Glide; import us.koller.cameraroll.R; -import us.koller.cameraroll.data.MediaLoader.MediaLoader; +import us.koller.cameraroll.data.Provider.MediaProvider; import us.koller.cameraroll.ui.widget.SwipeBackCoordinatorLayout; public class AboutActivity extends AppCompatActivity implements SwipeBackCoordinatorLayout.OnSwipeListener { @@ -162,7 +161,7 @@ public void onSwipeProcess(float percent) { @Override public void run() { if (!consumed) { - MediaLoader.toggleMode(AboutActivity.this); + MediaProvider.toggleMode(AboutActivity.this); consumed = true; } } diff --git a/app/src/main/java/us/koller/cameraroll/ui/AlbumActivity.java b/app/src/main/java/us/koller/cameraroll/ui/AlbumActivity.java index 6b06b3b4..c579145f 100644 --- a/app/src/main/java/us/koller/cameraroll/ui/AlbumActivity.java +++ b/app/src/main/java/us/koller/cameraroll/ui/AlbumActivity.java @@ -12,6 +12,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; import android.provider.MediaStore; import android.support.annotation.NonNull; @@ -21,7 +22,6 @@ import android.support.graphics.drawable.AnimatedVectorDrawableCompat; import android.support.v4.app.SharedElementCallback; import android.support.v4.content.ContextCompat; -import android.support.v4.content.FileProvider; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; @@ -30,6 +30,8 @@ import android.transition.Slide; import android.transition.TransitionSet; import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -38,6 +40,7 @@ import android.widget.Toast; import java.io.File; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -45,18 +48,20 @@ import us.koller.cameraroll.adapter.album.RecyclerViewAdapter; import us.koller.cameraroll.data.Album; import us.koller.cameraroll.data.AlbumItem; -import us.koller.cameraroll.data.MediaLoader.MediaLoader; +import us.koller.cameraroll.data.Provider.MediaProvider; import us.koller.cameraroll.data.Video; import us.koller.cameraroll.ui.widget.GridMarginDecoration; import us.koller.cameraroll.ui.widget.SwipeBackCoordinatorLayout; import us.koller.cameraroll.util.ColorFade; import us.koller.cameraroll.util.MediaType; +import us.koller.cameraroll.util.RemovableStorageUtil; import us.koller.cameraroll.util.Util; public class AlbumActivity extends AppCompatActivity implements SwipeBackCoordinatorLayout.OnSwipeListener, RecyclerViewAdapter.Callback { public static final String ALBUM = "ALBUM"; + public static final String ALBUM_PATH = "ALBUM_PATH"; public static final String VIEW_ALBUM = "VIEW_ALBUM"; public static final String DELETE_ALBUMITEM = "DELETE_ALBUMITEM"; public static final String EXTRA_CURRENT_ALBUM_POSITION = "EXTRA_CURRENT_ALBUM_POSITION"; @@ -99,6 +104,8 @@ public void onMapSharedElements(List names, Map sharedElem private Snackbar snackbar; + private Menu menu; + private boolean refreshMainActivityAfterItemWasDeleted = false; private boolean pick_photos; @@ -112,11 +119,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { pick_photos = getIntent().getAction() != null && getIntent().getAction().equals(MainActivity.PICK_PHOTOS); allowMultiple = getIntent().getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); - if (pick_photos) { - Util.setDarkStatusBarIcons(findViewById(R.id.root_view)); - } - MediaLoader.checkPermission(this); + MediaProvider.checkPermission(this); setExitSharedElementCallback(mCallback); getWindow().setEnterTransition(new TransitionSet() @@ -130,11 +134,19 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { .addTransition(new Fade()) .setInterpolator(new AccelerateDecelerateInterpolator())); - if (savedInstanceState != null && savedInstanceState.containsKey(ALBUM)) { + /*if (savedInstanceState != null && savedInstanceState.containsKey(ALBUM)) { album = savedInstanceState.getParcelable(ALBUM); } else { album = getIntent().getExtras().getParcelable(ALBUM); + }*/ + + String path = ""; + if (savedInstanceState != null && savedInstanceState.containsKey(ALBUM_PATH)) { + path = savedInstanceState.getString(ALBUM_PATH); + } else { + path = getIntent().getStringExtra(ALBUM_PATH); } + album = MediaProvider.loadAlbum(path); if (album == null) { return; @@ -153,6 +165,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { } else { toolbar.setNavigationIcon(R.drawable.ic_clear_black_24dp); toolbar.getNavigationIcon().setTint(ContextCompat.getColor(this, R.color.grey_900_translucent)); + Util.setDarkStatusBarIcons(findViewById(R.id.root_view)); } toolbar.setNavigationOnClickListener(new View.OnClickListener() { @@ -265,7 +278,16 @@ protected void onNewIntent(Intent intent) { super.onNewIntent(intent); if (intent.getAction().equals(DELETE_ALBUMITEM)) { final AlbumItem albumItem = intent.getParcelableExtra(ItemActivity.ALBUM_ITEM); - deleteAlbumItemSnackbar(albumItem, intent.getBooleanExtra(ItemActivity.VIEW_ONLY, false)); + if (intent.getBooleanExtra(ItemActivity.VIEW_ONLY, false)) { + album = MediaProvider.getErrorAlbum(); + album.getAlbumItems().add(albumItem); + AlbumItem[] albumItems = {albumItem}; + int[] indices = {album.getAlbumItems().indexOf(albumItem)}; + deleteAlbumItems(albumItems, indices); + this.finish(); + } else { + deleteAlbumItemSnackbar(albumItem, false); + } } } @@ -300,10 +322,50 @@ protected void onPostResume() { } } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.album, menu); + this.menu = menu; + + Drawable icon = menu.findItem(R.id.share).getIcon().mutate(); + icon.setTint(ContextCompat.getColor(this, R.color.grey_900_translucent)); + menu.findItem(R.id.share).setIcon(icon); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.share: + //share multiple items + final AlbumItem[] selected_items = + ((RecyclerViewAdapter) recyclerView.getAdapter()).cancelSelectorMode(); + + ArrayList uris = new ArrayList<>(); + for (int i = 0; i < selected_items.length; i++) { + uris.add(selected_items[i].getUri(this)); + } + + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND_MULTIPLE) + .setType(MediaType.getMimeType(this, selected_items[0].getPath())) + .putExtra(Intent.EXTRA_STREAM, uris); + + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(Intent.createChooser(intent, getString(R.string.share_photo))); + } + break; + } + return super.onOptionsItemSelected(item); + } + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { switch (requestCode) { - case MediaLoader.PERMISSION_REQUEST_CODE: { + case MediaProvider.PERMISSION_REQUEST_CODE: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //permission granted @@ -317,7 +379,7 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String permissi snackbar.setAction(R.string.retry, new View.OnClickListener() { @Override public void onClick(View view) { - MediaLoader.checkPermission(AlbumActivity.this); + MediaProvider.checkPermission(AlbumActivity.this); } }); Util.showSnackbar(snackbar); @@ -327,7 +389,7 @@ public void onClick(View view) { } public void deleteAlbumItemSnackbar(final AlbumItem albumItem, boolean VIEW_ONLY) { - if (!MediaLoader.checkPermission(this)) { + if (!MediaProvider.checkPermission(this)) { return; } @@ -370,11 +432,13 @@ public void onDismissed(Snackbar snackbar, int event) { } public void deleteAlbumItemsSnackbar() { - if (!MediaLoader.checkPermission(this)) { + if (!MediaProvider.checkPermission(this)) { return; } - final AlbumItem[] selected_items = ((RecyclerViewAdapter) recyclerView.getAdapter()).cancelSelectorMode(); + final AlbumItem[] selected_items + = ((RecyclerViewAdapter) recyclerView.getAdapter()).cancelSelectorMode(); + final int[] indices = new int[selected_items.length]; for (int i = 0; i < selected_items.length; i++) { AlbumItem albumItem = selected_items[i]; @@ -411,16 +475,20 @@ public void onDismissed(Snackbar snackbar, int event) { Util.showSnackbar(snackbar); } - public void deleteAlbumItems(AlbumItem[] selected_items, int[] indices) { + public void deleteAlbumItems(final AlbumItem[] selected_items, final int[] indices) { int successfully_deleted = 0; for (int i = 0; i < selected_items.length; i++) { boolean success; - if (!album.isHidden()) { - int result = getContentResolver().delete(MediaStore.Files.getContentUri("external"), - MediaStore.Files.FileColumns.DATA + "=?", new String[]{selected_items[i].getPath()}); + File file = new File(selected_items[i].getPath()); + if (Environment.isExternalStorageRemovable(file)) { + success = RemovableStorageUtil.delete(this, file); + } else if (!album.isHidden()) { + int result = getContentResolver() + .delete(MediaStore.Files.getContentUri("external"), + MediaStore.Files.FileColumns.DATA + "=?", + new String[]{selected_items[i].getPath()}); success = result > 0; } else { - File file = new File(selected_items[i].getPath()); success = file.delete(); } @@ -434,30 +502,37 @@ public void deleteAlbumItems(AlbumItem[] selected_items, int[] indices) { Uri.parse(selected_items[i].getPath()))); } } - if (refreshMainActivityAfterItemWasDeleted) { setResult(RESULT_OK, new Intent(MainActivity.REFRESH_MEDIA)); onBackPressed(); } - Toast.makeText(AlbumActivity.this, getString(R.string.successfully_deleted) + successfully_deleted + " / " + selected_items.length, Toast.LENGTH_SHORT).show(); + Toast.makeText(AlbumActivity.this, getString(R.string.successfully_deleted) + + successfully_deleted + " / " + + selected_items.length, Toast.LENGTH_SHORT).show(); } - public void setPhotosResult() { - final AlbumItem[] selected_items - = ((RecyclerViewAdapter) recyclerView.getAdapter()).cancelSelectorMode(); - - String[] mimeTypes = new String[selected_items.length]; - for (int i = 0; i < selected_items.length; i++) { - mimeTypes[i] = MediaType.getMimeType(this, selected_items[i].getPath()); + //needed to send multiple uris in intents + private ClipData createClipData(AlbumItem[] items) { + String[] mimeTypes = new String[items.length]; + for (int i = 0; i < items.length; i++) { + mimeTypes[i] = MediaType.getMimeType(this, items[i].getPath()); } ClipData clipData = new ClipData("Images", mimeTypes, - new ClipData.Item(selected_items[0].getUri(this))); - for (int i = 1; i < selected_items.length; i++) { - clipData.addItem(new ClipData.Item(selected_items[i].getUri(this))); + new ClipData.Item(items[0].getUri(this))); + for (int i = 1; i < items.length; i++) { + clipData.addItem(new ClipData.Item(items[i].getUri(this))); } + return clipData; + } + + public void setPhotosResult() { + final AlbumItem[] selected_items + = ((RecyclerViewAdapter) recyclerView.getAdapter()).cancelSelectorMode(); + + ClipData clipData = createClipData(selected_items); Intent intent = new Intent("us.koller.RESULT_ACTION"); intent.setClipData(clipData); @@ -469,7 +544,8 @@ public void setPhotosResult() { @Override public void onSelectorModeEnter() { final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - toolbar.setTitleTextColor(ContextCompat.getColor(AlbumActivity.this, android.R.color.transparent)); + toolbar.setTitleTextColor(ContextCompat + .getColor(AlbumActivity.this, android.R.color.transparent)); toolbar.setActivated(true); toolbar.animate().translationY(0.0f).start(); @@ -490,8 +566,10 @@ public void run() { } }, 300); } else { - toolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.colorAccent)); - toolbar.setTitleTextColor(ContextCompat.getColor(AlbumActivity.this, R.color.grey_900_translucent)); + toolbar.setBackgroundColor(ContextCompat + .getColor(this, R.color.colorAccent)); + toolbar.setTitleTextColor(ContextCompat + .getColor(AlbumActivity.this, R.color.grey_900_translucent)); } if (!pick_photos) { @@ -506,7 +584,8 @@ public void onSelectorModeExit() { } final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - toolbar.setTitleTextColor(ContextCompat.getColor(AlbumActivity.this, android.R.color.transparent)); + toolbar.setTitleTextColor(ContextCompat + .getColor(AlbumActivity.this, android.R.color.transparent)); toolbar.setActivated(false); ColorFade.fadeBackgroundColor(toolbar, ContextCompat.getColor(this, R.color.colorAccent), @@ -526,6 +605,7 @@ public void run() { }, 300); animateFab(false, false); + menu.findItem(R.id.share).setVisible(false); } @Override @@ -535,11 +615,20 @@ public void onItemSelected(int selectedItemCount) { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitle(title); - if (pick_photos) { - if (selectedItemCount > 0) { + + if (selectedItemCount > 0) { + if (pick_photos) { animateFab(true, false); } else { + //show share menu item + menu.findItem(R.id.share).setVisible(true); + } + } else { + if (pick_photos) { animateFab(false, false); + } else { + //hide share menu item + menu.findItem(R.id.share).setVisible(false); } } } @@ -549,12 +638,8 @@ public static void videoOnClick(Activity context, AlbumItem albumItem) { return; } - File file = new File(albumItem.getPath()); - Uri uri = FileProvider.getUriForFile(context, - context.getApplicationContext().getPackageName() + ".provider", file); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(uri, "video/*"); + intent.setDataAndType(albumItem.getUri(context), "video/*"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { context.startActivity(intent); @@ -638,17 +723,11 @@ public void onBackPressed() { @Override public void onSaveInstanceState(Bundle outState) { - outState.putParcelable(ALBUM, album); + //outState.putParcelable(ALBUM, album); outState.putParcelable(RECYCLER_VIEW_SCROLL_STATE, recyclerView.getLayoutManager().onSaveInstanceState()); } - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - - } - private void setupTaskDescription() { Bitmap overviewIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); setTaskDescription(new ActivityManager.TaskDescription(getString(R.string.app_name), @@ -664,7 +743,8 @@ public boolean canSwipeBack(int dir) { @Override public void onSwipeProcess(float percent) { - getWindow().getDecorView().setBackgroundColor(SwipeBackCoordinatorLayout.getBackgroundColor(percent)); + getWindow().getDecorView() + .setBackgroundColor(SwipeBackCoordinatorLayout.getBackgroundColor(percent)); } @Override diff --git a/app/src/main/java/us/koller/cameraroll/ui/FileExplorerActivity.java b/app/src/main/java/us/koller/cameraroll/ui/FileExplorerActivity.java index a2fde61d..0ee2e0cc 100644 --- a/app/src/main/java/us/koller/cameraroll/ui/FileExplorerActivity.java +++ b/app/src/main/java/us/koller/cameraroll/ui/FileExplorerActivity.java @@ -1,5 +1,7 @@ package us.koller.cameraroll.ui; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -20,6 +22,7 @@ import android.transition.Fade; import android.transition.Slide; import android.transition.TransitionSet; +import android.util.Log; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; @@ -28,6 +31,9 @@ import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -37,11 +43,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import us.koller.cameraroll.R; import us.koller.cameraroll.adapter.fileExplorer.RecyclerViewAdapter; import us.koller.cameraroll.data.File_POJO; -import us.koller.cameraroll.data.FilesLoader.FilesLoader; +import us.koller.cameraroll.data.Provider.FilesProvider; import us.koller.cameraroll.ui.widget.ParallaxImageView; import us.koller.cameraroll.ui.widget.SwipeBackCoordinatorLayout; import us.koller.cameraroll.util.ColorFade; @@ -50,13 +57,18 @@ public class FileExplorerActivity extends AppCompatActivity implements SwipeBackCoordinatorLayout.OnSwipeListener, RecyclerViewAdapter.Callback { - public static final String FILES = "FILES"; - public static final String CURRENT_FILE = "CURRENT_FILE"; + public interface OnDirectoryChangeCallback { + public void changeDir(String path); + } + + public static final String CURRENT_DIR = "CURRENT_DIR"; public static final String SELECTED_ITEMS = "SELECTED_ITEMS"; - private File_POJO files; + private File_POJO[] roots; - private FilesLoader loader; + private File_POJO currentDir; + + private FilesProvider filesProvider; private RecyclerView recyclerView; private RecyclerViewAdapter recyclerViewAdapter; @@ -70,17 +82,12 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_file_explorer); - //load files - if (savedInstanceState != null - && savedInstanceState.containsKey(FILES)) { - files = savedInstanceState.getParcelable(FILES); - } else { - files = new File_POJO("", false); - } + currentDir = new File_POJO("", false); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.black_translucent2)); - toolbar.setNavigationIcon(AnimatedVectorDrawableCompat.create(this, R.drawable.back_to_cancel_animateable)); + toolbar.setNavigationIcon(AnimatedVectorDrawableCompat + .create(this, R.drawable.back_to_cancel_animateable)); setSupportActionBar(toolbar); //set Toolbar overflow icon color @@ -96,9 +103,10 @@ protected void onCreate(Bundle savedInstanceState) { if (actionBar != null) { actionBar.setTitle(getString(R.string.file_explorer)); actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowTitleEnabled(false); } - final ViewGroup rootView = (ViewGroup) findViewById(R.id.root_view); + final ViewGroup rootView = (ViewGroup) findViewById(R.id.swipeBackView); if (rootView instanceof SwipeBackCoordinatorLayout) { ((SwipeBackCoordinatorLayout) rootView).setOnSwipeListener(this); } @@ -106,12 +114,15 @@ protected void onCreate(Bundle savedInstanceState) { recyclerView = (RecyclerView) findViewById(R.id.recyclerView); recyclerView.setTag(ParallaxImageView.RECYCLER_VIEW_TAG); recyclerView.setLayoutManager(new LinearLayoutManager(this)); - if (savedInstanceState != null && savedInstanceState.containsKey(CURRENT_FILE)) { - recyclerViewAdapter = new RecyclerViewAdapter(this) - .setFiles((File_POJO) savedInstanceState.getParcelable(CURRENT_FILE)); - } else { - recyclerViewAdapter = new RecyclerViewAdapter(this) - .setFiles(files); + recyclerViewAdapter = new RecyclerViewAdapter( + new OnDirectoryChangeCallback() { + @Override + public void changeDir(String path) { + loadDirectory(path); + } + }, this); + if (savedInstanceState != null && savedInstanceState.containsKey(CURRENT_DIR)) { + recyclerViewAdapter.setFiles(currentDir); } recyclerViewAdapter.notifyDataSetChanged(); recyclerView.setAdapter(recyclerViewAdapter); @@ -165,7 +176,11 @@ public boolean onPreDraw() { | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); //load files - if (savedInstanceState == null) { + if (savedInstanceState != null + && savedInstanceState.containsKey(CURRENT_DIR)) { + currentDir = savedInstanceState.getParcelable(CURRENT_DIR); + onDataChanged(); + } else { loadFiles(); } } @@ -175,21 +190,40 @@ public void loadFiles() { getString(R.string.loading), Snackbar.LENGTH_INDEFINITE); Util.showSnackbar(snackbar); - loader = new FilesLoader(); - loader.loadFiles(this, new FilesLoader.LoaderCallback() { + filesProvider = new FilesProvider(); + + roots = filesProvider.getRoots(); + setupSpinner(); + + loadDirectory(roots[0].getPath()); + } + + public void loadDirectory(final String path) { + Log.d("FileExplorerActivity", "loadDirectory(): " + path); + final Snackbar snackbar = Snackbar.make(findViewById(R.id.root_view), + getString(R.string.loading), Snackbar.LENGTH_INDEFINITE); + Util.showSnackbar(snackbar); + + filesProvider = new FilesProvider(); + + final FilesProvider.Callback callback = new FilesProvider.Callback() { @Override - public void onMediaLoaded(final File_POJO files) { + public void onDirLoaded(final File_POJO dir) { runOnUiThread(new Runnable() { @Override public void run() { - loader.onDestroy(); - loader = null; - FileExplorerActivity.this.files = files; - if (recyclerViewAdapter != null) { - recyclerViewAdapter.setFiles(files); - recyclerViewAdapter.notifyDataSetChanged(); - onDataChanged(); + filesProvider.onDestroy(); + filesProvider = null; + + if (dir != null) { + FileExplorerActivity.this.currentDir = dir; + if (recyclerViewAdapter != null) { + recyclerViewAdapter.setFiles(currentDir); + recyclerViewAdapter.notifyDataSetChanged(); + onDataChanged(); + } } + snackbar.dismiss(); } }); @@ -203,11 +237,11 @@ public void run() { snackbar.dismiss(); final Snackbar snackbar = Snackbar.make(findViewById(R.id.root_view), - getString(R.string.loading), Snackbar.LENGTH_INDEFINITE); + R.string.loading_failed, Snackbar.LENGTH_INDEFINITE); snackbar.setAction(getString(R.string.retry), new View.OnClickListener() { @Override public void onClick(View view) { - loadFiles(); + loadDirectory(path); } }); Util.showSnackbar(snackbar); @@ -224,14 +258,48 @@ public void run() { } }); } - }); + }; + + filesProvider.loadDir(this, path, callback); + } + + public void setupSpinner() { + Spinner spinner = (Spinner) findViewById(R.id.toolbar_spinner); + ArrayList spinnerList = new ArrayList<>(); + for (int i = 0; i < roots.length; i++) { + spinnerList.add(roots[i].getPath()); + } + ArrayAdapter dataAdapter + = new ArrayAdapter<>(this, R.layout.simple_spinner_item, spinnerList); + dataAdapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(dataAdapter); + + spinner.setOnItemSelectedListener( + new AdapterView.OnItemSelectedListener() { + + boolean firstCall = true; + + @Override + public void onItemSelected(AdapterView adapterView, + View view, int pos, long id) { + if (firstCall) { + firstCall = false; + return; + } + loadDirectory(roots[pos].getPath()); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(FILES, files); - outState.putParcelable(CURRENT_FILE, recyclerViewAdapter.getFiles()); + outState.putParcelable(CURRENT_DIR, currentDir); } @Override @@ -265,7 +333,7 @@ public boolean onOptionsItemSelected(MenuItem item) { new FileAction.Callback() { @Override public void done() { - loadFiles(); + loadDirectory(currentDir.getPath()); } }); } @@ -290,22 +358,35 @@ public void done() { public void onBackPressed() { if (recyclerViewAdapter.isModeActive()) { recyclerViewAdapter.cancelMode(); - } else if (recyclerViewAdapter != null && files != null - && !recyclerViewAdapter.getFiles().getPath().equals(files.getPath())) { - recyclerViewAdapter.setFiles(files); - recyclerViewAdapter.notifyDataSetChanged(); - this.onDataChanged(); + } else if (recyclerViewAdapter != null + && !isCurrentFileARoot()) { + String path = currentDir.getPath(); + int index = path.lastIndexOf("/"); + String parentPath = path.substring(0, index); + + loadDirectory(parentPath); } else { super.onBackPressed(); } } + private boolean isCurrentFileARoot() { + if (currentDir != null) { + for (int i = 0; i < roots.length; i++) { + if (currentDir.getPath().equals(roots[i].getPath())) { + return true; + } + } + } + return false; + } + @Override protected void onDestroy() { super.onDestroy(); - if (loader != null) { - loader.onDestroy(); + if (filesProvider != null) { + filesProvider.onDestroy(); } } @@ -321,22 +402,23 @@ public void onSwipeProcess(float percent) { @Override public void onSwipeFinish(int dir) { - if (recyclerViewAdapter != null && files != null - && !recyclerViewAdapter.getFiles().getPath().equals(files.getPath())) { - recyclerViewAdapter.setFiles(files); - recyclerViewAdapter.notifyDataSetChanged(); - this.onDataChanged(); - } getWindow().setReturnTransition(new TransitionSet() .setOrdering(TransitionSet.ORDERING_TOGETHER) .addTransition(new Slide(dir > 0 ? Gravity.TOP : Gravity.BOTTOM)) .addTransition(new Fade()) .setInterpolator(new AccelerateDecelerateInterpolator())); - onBackPressed(); + this.finish(); } @Override public void onSelectorModeEnter() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(true); + } + Spinner spinner = (Spinner) findViewById(R.id.toolbar_spinner); + spinner.setVisibility(View.GONE); + final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitleTextColor(ContextCompat.getColor(this, android.R.color.transparent)); toolbar.setActivated(true); @@ -374,7 +456,7 @@ public void onSelectorModeExit(File_POJO[] selected_items) { new FileAction.Callback() { @Override public void done() { - loadFiles(); + loadDirectory(currentDir.getPath()); } }); resetToolbar(); @@ -430,18 +512,33 @@ public void onPickTargetModeExit() { @Override public void onDataChanged() { - TextView emptyState = (TextView) findViewById(R.id.empty_state); - emptyState.setVisibility( - recyclerViewAdapter.getFiles().getChildren().size() == 0 ? - View.VISIBLE : View.GONE); + final TextView emptyState = (TextView) findViewById(R.id.empty_state); + emptyState.animate() + .alpha(currentDir.getChildren().size() == 0 ? 1.0f : 0.0f) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + emptyState.setVisibility( + currentDir.getChildren().size() == 0 ? + View.VISIBLE : View.GONE); + } + }) + .setDuration(100) + .start(); - ActionBar actionBar = getSupportActionBar(); + /*ActionBar actionBar = getSupportActionBar(); if (actionBar != null && recyclerViewAdapter.getMode() != RecyclerViewAdapter.PICK_TARGET_MODE) { - actionBar.setTitle(recyclerViewAdapter.getFiles().getPath()); - } + actionBar.setTitle(currentDir.getPath()); + }*/ } public void resetToolbar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(false); + } + final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitleTextColor(ContextCompat.getColor(this, android.R.color.transparent)); toolbar.setActivated(false); @@ -464,6 +561,10 @@ public void run() { for (int i = 0; i < menu.size(); i++) { menu.getItem(i).setVisible(false); } + + Spinner spinner = (Spinner) findViewById(R.id.toolbar_spinner); + spinner.setVisibility(View.VISIBLE); + spinner.requestLayout(); } }, 300); } @@ -576,7 +677,7 @@ private static boolean moveFile(String path, String destination) { private static boolean copyFile(String path, String destination) { //create output directory if it doesn't exist - File dir = new File(destination); + File dir = new File(destination, new File(path).getName()); if (!dir.exists()) { dir.mkdirs(); } diff --git a/app/src/main/java/us/koller/cameraroll/ui/ItemActivity.java b/app/src/main/java/us/koller/cameraroll/ui/ItemActivity.java index 3fd09d82..5cedd527 100644 --- a/app/src/main/java/us/koller/cameraroll/ui/ItemActivity.java +++ b/app/src/main/java/us/koller/cameraroll/ui/ItemActivity.java @@ -20,6 +20,7 @@ import android.support.v4.app.ShareCompat; import android.support.v4.app.SharedElementCallback; import android.support.v4.content.ContextCompat; +import android.support.v4.print.PrintHelper; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; @@ -56,7 +57,7 @@ import us.koller.cameraroll.adapter.item.ViewPagerAdapter; import us.koller.cameraroll.data.Album; import us.koller.cameraroll.data.AlbumItem; -import us.koller.cameraroll.data.MediaLoader.MediaLoader; +import us.koller.cameraroll.data.Provider.MediaProvider; import us.koller.cameraroll.data.Photo; import us.koller.cameraroll.data.Video; import us.koller.cameraroll.util.MediaType; @@ -67,6 +68,7 @@ public class ItemActivity extends AppCompatActivity { public static final String ALBUM_ITEM = "ALBUM_ITEM"; public static final String ALBUM = "ALBUM"; + public static final String ALBUM_PATH = "ALBUM_PATH"; public static final String ITEM_POSITION = "ITEM_POSITION"; public static final String VIEW_ONLY = "VIEW_ONLY"; public static final String FINISH_AFTER = "FINISH_AFTER"; @@ -161,7 +163,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_item); - MediaLoader.checkPermission(this); + MediaProvider.checkPermission(this); view_only = getIntent().getBooleanExtra(VIEW_ONLY, false); @@ -171,8 +173,20 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { getWindow().getSharedElementEnterTransition().addListener(transitionListener); } + if (!view_only) { + String path = ""; + if (savedInstanceState != null && savedInstanceState.containsKey(ALBUM_PATH)) { + path = savedInstanceState.getString(ALBUM_PATH); + } else { + path = getIntent().getStringExtra(ALBUM_PATH); + } + album = MediaProvider.loadAlbum(path); + } else { + album = getIntent().getExtras().getParcelable(ALBUM); + } + if (savedInstanceState != null) { - album = savedInstanceState.getParcelable(ALBUM); + //album = savedInstanceState.getParcelable(ALBUM); albumItem = savedInstanceState.getParcelable(ALBUM_ITEM); if (albumItem != null && albumItem instanceof Photo) { Photo photo = (Photo) albumItem; @@ -184,10 +198,12 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { showInfoDialog(); } } else { - album = getIntent().getExtras().getParcelable(AlbumActivity.ALBUM); + //album = getIntent().getExtras().getParcelable(AlbumActivity.ALBUM); int position = getIntent().getIntExtra(ITEM_POSITION, 0); - albumItem = album.getAlbumItems().get(position); - albumItem.isSharedElement = true; + if (album != null) { + albumItem = album.getAlbumItems().get(position); + albumItem.isSharedElement = true; + } } if (album == null || albumItem == null) { @@ -196,7 +212,6 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); - toolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.black_translucent2)); final ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { @@ -229,27 +244,27 @@ public void onPageSelected(int position) { rootView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @Override public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { - toolbar.setPadding(toolbar.getPaddingStart() /*+ insets.getSystemWindowInsetLeft()*/, + toolbar.setPadding(toolbar.getPaddingStart() + insets.getSystemWindowInsetLeft(), toolbar.getPaddingTop() + insets.getSystemWindowInsetTop(), - toolbar.getPaddingEnd() /*+ insets.getSystemWindowInsetRight()*/, + toolbar.getPaddingEnd() + insets.getSystemWindowInsetRight(), toolbar.getPaddingBottom()); - ViewGroup.MarginLayoutParams toolbarParams + /*ViewGroup.MarginLayoutParams toolbarParams = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams(); toolbarParams.leftMargin += insets.getSystemWindowInsetLeft(); toolbarParams.rightMargin += insets.getSystemWindowInsetRight(); - toolbar.setLayoutParams(toolbarParams); + toolbar.setLayoutParams(toolbarParams);*/ - bottomBar.setPadding(bottomBar.getPaddingStart() /*+ insets.getSystemWindowInsetLeft()*/, + bottomBar.setPadding(bottomBar.getPaddingStart() + insets.getSystemWindowInsetLeft(), bottomBar.getPaddingTop(), - bottomBar.getPaddingEnd() /*+ insets.getSystemWindowInsetRight()*/, + bottomBar.getPaddingEnd() + insets.getSystemWindowInsetRight(), bottomBar.getPaddingBottom() + insets.getSystemWindowInsetBottom()); - ViewGroup.MarginLayoutParams bottomBarParams + /*ViewGroup.MarginLayoutParams bottomBarParams = (ViewGroup.MarginLayoutParams) ((View) bottomBar.getParent()).getLayoutParams(); bottomBarParams.leftMargin += insets.getSystemWindowInsetLeft(); bottomBarParams.rightMargin += insets.getSystemWindowInsetRight(); - ((View) bottomBar.getParent()).setLayoutParams(bottomBarParams); + ((View) bottomBar.getParent()).setLayoutParams(bottomBarParams);*/ // clear this listener so insets aren't re-applied rootView.setOnApplyWindowInsetsListener(null); @@ -294,6 +309,9 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.share: sharePhoto(); break; + case R.id.print: + printPhoto(); + break; case R.id.edit: editPhoto(); break; @@ -348,11 +366,24 @@ public void sharePhoto() { } } - public void editPhoto() { - if (albumItem instanceof Video) { + public void printPhoto() { + if (!(albumItem instanceof Photo)) { + Toast.makeText(this, "Editing of " + albumItem.TYPE + " not supported", Toast.LENGTH_SHORT).show(); return; } + PrintHelper photoPrinter = new PrintHelper(this); + photoPrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT); + Bitmap bitmap = BitmapFactory.decodeFile(albumItem.getPath()); + photoPrinter.printBitmap(albumItem.getPath(), bitmap); + } + + public void editPhoto() { + /*if (albumItem instanceof Video) { + Toast.makeText(this, "Editing of Videos not supported", Toast.LENGTH_SHORT).show(); + return; + }*/ + Uri uri = albumItem.getUri(this); Intent intent = new Intent(Intent.ACTION_EDIT); @@ -362,7 +393,7 @@ public void editPhoto() { try { startActivity(intent); } catch (ActivityNotFoundException e) { - Toast.makeText(this, "No App found to edit your Photo", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, "No App found to edit your " + albumItem.TYPE, Toast.LENGTH_SHORT).show(); } } @@ -380,7 +411,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } public void deletePhoto() { - if (!MediaLoader.checkPermission(this)) { + if (!MediaProvider.checkPermission(this)) { return; } @@ -392,7 +423,8 @@ public void deletePhoto() { Intent intent = new Intent(this, AlbumActivity.class); intent.setAction(AlbumActivity.DELETE_ALBUMITEM); - intent.putExtra(AlbumActivity.ALBUM, album); + //intent.putExtra(AlbumActivity.ALBUM, album); + intent.putExtra(AlbumActivity.ALBUM_PATH, album.getPath()); intent.putExtra(ALBUM_ITEM, albumItem); intent.putExtra(HIDDEN_ALBUMITEM, album.isHidden()); intent.putExtra(VIEW_ONLY, view_only); @@ -505,7 +537,7 @@ public void bottomBarOnClick(View v) { new Handler().postDelayed(new Runnable() { @Override public void run() { - if (view_only && getIntent().getFlags() != Intent.FLAG_GRANT_READ_URI_PERMISSION) { + /*if (view_only && getIntent().getFlags() != Intent.FLAG_GRANT_READ_URI_PERMISSION) { new AlertDialog.Builder(ItemActivity.this, R.style.Theme_CameraRoll_Dialog) .setTitle(R.string.missing_permission_title) .setMessage(R.string.missing_delete_permission) @@ -513,7 +545,9 @@ public void run() { .create().show(); } else { showDeleteDialog(); - } + }*/ + + showDeleteDialog(); } }, 400); break; @@ -581,7 +615,7 @@ private void setupTaskDescription() { @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { switch (requestCode) { - case MediaLoader.PERMISSION_REQUEST_CODE: { + case MediaProvider.PERMISSION_REQUEST_CODE: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //permission granted @@ -593,7 +627,7 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String permissi snackbar.setAction(R.string.retry, new View.OnClickListener() { @Override public void onClick(View view) { - MediaLoader.checkPermission(ItemActivity.this); + MediaProvider.checkPermission(ItemActivity.this); } }); Util.showSnackbar(snackbar); @@ -612,7 +646,7 @@ public void onSaveInstanceState(Bundle outState) { outState.putSerializable(IMAGE_VIEW_SAVED_STATE, imageView.getState()); } } - outState.putParcelable(ALBUM, album); + //outState.putParcelable(ALBUM, album); outState.putParcelable(ALBUM_ITEM, albumItem); outState.putBoolean(WAS_SYSTEM_UI_HIDDEN, !systemUiVisible); outState.putBoolean(INFO_DIALOG_SHOWN, infoDialog != null); diff --git a/app/src/main/java/us/koller/cameraroll/ui/MainActivity.java b/app/src/main/java/us/koller/cameraroll/ui/MainActivity.java index af3fdf38..3da4a2bd 100644 --- a/app/src/main/java/us/koller/cameraroll/ui/MainActivity.java +++ b/app/src/main/java/us/koller/cameraroll/ui/MainActivity.java @@ -29,7 +29,7 @@ import us.koller.cameraroll.R; import us.koller.cameraroll.adapter.main.RecyclerViewAdapter; import us.koller.cameraroll.data.Album; -import us.koller.cameraroll.data.MediaLoader.MediaLoader; +import us.koller.cameraroll.data.Provider.MediaProvider; import us.koller.cameraroll.ui.widget.ParallaxImageView; import us.koller.cameraroll.util.Util; @@ -50,11 +50,11 @@ public class MainActivity extends AppCompatActivity { private Snackbar snackbar; - private MediaLoader mediaLoader; + private MediaProvider mediaProvider; private boolean hiddenFolders = false; - private boolean pick_photos; + private boolean pick_photos; private boolean allowMultiple; @Override @@ -68,12 +68,21 @@ protected void onCreate(Bundle savedInstanceState) { hiddenFolders = getSharedPreferences(SHARED_PREF_NAME, MODE_PRIVATE) .getBoolean(HIDDEN_FOLDERS, false); - //loading media - if (savedInstanceState != null && savedInstanceState.containsKey(ALBUMS)) { + //load media + albums = MediaProvider.getAlbums(); + if (albums == null) { + albums = new ArrayList<>(); + } + + if (savedInstanceState == null) { + refreshPhotos(); + } + + /*if (savedInstanceState != null && savedInstanceState.containsKey(ALBUMS)) { albums = savedInstanceState.getParcelableArrayList(ALBUMS); } else { albums = new ArrayList<>(); - } + }*/ final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); @@ -178,11 +187,6 @@ public boolean onPreDraw() { }); setupTaskDescription(); - - //load media - if (savedInstanceState == null) { - refreshPhotos(); - } } @Override @@ -217,12 +221,17 @@ private void setSystemUiFlags() { } public void refreshPhotos() { + if (mediaProvider != null) { + mediaProvider.onDestroy(); + mediaProvider = null; + } + snackbar = Snackbar.make(findViewById(R.id.root_view), R.string.loading, Snackbar.LENGTH_INDEFINITE); Util.showSnackbar(snackbar); - final MediaLoader.LoaderCallback callback - = new MediaLoader.LoaderCallback() { + final MediaProvider.Callback callback + = new MediaProvider.Callback() { @Override public void onMediaLoaded(final ArrayList albums) { if (albums != null) { @@ -235,8 +244,8 @@ public void run() { snackbar.dismiss(); - mediaLoader.onDestroy(); - mediaLoader = null; + mediaProvider.onDestroy(); + mediaProvider = null; } }); } @@ -251,8 +260,8 @@ public void timeout() { snackbar.setAction(getString(R.string.retry), new View.OnClickListener() { @Override public void onClick(View view) { - if (mediaLoader != null) { - mediaLoader.onDestroy(); + if (mediaProvider != null) { + mediaProvider.onDestroy(); } refreshPhotos(); snackbar.dismiss(); @@ -260,10 +269,10 @@ public void onClick(View view) { }); Util.showSnackbar(snackbar); - if (mediaLoader != null) { - mediaLoader.onDestroy(); + if (mediaProvider != null) { + mediaProvider.onDestroy(); } - mediaLoader = null; + mediaProvider = null; } @Override @@ -272,8 +281,8 @@ public void needPermission() { } }; - mediaLoader = new MediaLoader(); - mediaLoader.loadAlbums(MainActivity.this, hiddenFolders, callback); + mediaProvider = new MediaProvider(); + mediaProvider.loadAlbums(MainActivity.this, hiddenFolders, callback); } @Override @@ -322,7 +331,7 @@ private void setupTaskDescription() { @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { switch (requestCode) { - case MediaLoader.PERMISSION_REQUEST_CODE: { + case MediaProvider.PERMISSION_REQUEST_CODE: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //permission granted @@ -361,14 +370,15 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelableArrayList(ALBUMS, albums); + //not able to save albums in Bundle, --> TransactionTooLargeException + //outState.putParcelableArrayList(ALBUMS, albums); } @Override protected void onDestroy() { super.onDestroy(); - if (mediaLoader != null) { - mediaLoader.onDestroy(); + if (mediaProvider != null) { + mediaProvider.onDestroy(); } } } diff --git a/app/src/main/java/us/koller/cameraroll/util/ItemViewUtil.java b/app/src/main/java/us/koller/cameraroll/util/ItemViewUtil.java index a2988972..dbb6d5ef 100644 --- a/app/src/main/java/us/koller/cameraroll/util/ItemViewUtil.java +++ b/app/src/main/java/us/koller/cameraroll/util/ItemViewUtil.java @@ -55,8 +55,8 @@ public static View bindSubsamplingImageView(SubsamplingScaleImageView imageView, new SubsamplingScaleImageView.DefaultOnImageEventListener() { @Override public void onImageLoaded() { - placeholderView.setVisibility(View.INVISIBLE); super.onImageLoaded(); + placeholderView.setVisibility(View.INVISIBLE); } }); } diff --git a/app/src/main/java/us/koller/cameraroll/util/MediaType.java b/app/src/main/java/us/koller/cameraroll/util/MediaType.java index fe388630..c45fb6fe 100644 --- a/app/src/main/java/us/koller/cameraroll/util/MediaType.java +++ b/app/src/main/java/us/koller/cameraroll/util/MediaType.java @@ -7,9 +7,9 @@ public class MediaType { public static boolean isMedia(Context context, String path) { - return isImage(context, path) || - isVideo(context, path) || - isGif(context, path); + return checkImageExtension(path) || + checkGifExtension(path) || + checkVideoExtension(path); } public static String getMimeType(Context context, String path) { @@ -18,6 +18,10 @@ public static String getMimeType(Context context, String path) { if (mimeType == null) { mimeType = context.getContentResolver().getType(Uri.parse(path)); } + if (mimeType == null) { + mimeType = checkImageExtension(path) || checkGifExtension(path) ? "image/*" : + checkVideoExtension(path) ? "video/*" : "error"; + } return mimeType; } diff --git a/app/src/main/java/us/koller/cameraroll/util/RemovableStorageUtil.java b/app/src/main/java/us/koller/cameraroll/util/RemovableStorageUtil.java new file mode 100644 index 00000000..3da95fc0 --- /dev/null +++ b/app/src/main/java/us/koller/cameraroll/util/RemovableStorageUtil.java @@ -0,0 +1,69 @@ +package us.koller.cameraroll.util; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.BaseColumns; +import android.provider.MediaStore; +import android.util.Log; + +import java.io.File; + +//workarounds to handle removable storage + +//heavily inspired by: +//https://github.com/arpitkh96/AmazeFileManager/blob/master/src/main/java/com/amaze/filemanager/filesystem/MediaStoreHack.java +public class RemovableStorageUtil { + + //workaround to get content-Uri for items on removable storage + public static Uri getContentUriFromFilePath(Context context, String path) { + ContentResolver resolver = context.getContentResolver(); + + Cursor cursor = resolver.query(MediaStore.Files.getContentUri("external"), + new String[]{BaseColumns._ID}, MediaStore.MediaColumns.DATA + " = ?", + new String[]{path}, MediaStore.MediaColumns.DATE_ADDED + " desc"); + + if (cursor == null) { + return Uri.parse(path); + } + + cursor.moveToFirst(); + + if (cursor.isAfterLast()) { + cursor.close(); + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.DATA, path); + return resolver.insert(MediaStore.Files.getContentUri("external"), values); + } else { + int imageId = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID)); + Uri uri = MediaStore.Files.getContentUri("external").buildUpon().appendPath( + Integer.toString(imageId)).build(); + cursor.close(); + return uri; + } + } + + //not working; need to fix + public static boolean delete(final Context context, final File file) { + final String where = MediaStore.MediaColumns.DATA + "=?"; + final String[] selectionArgs = new String[]{ + file.getAbsolutePath() + }; + final ContentResolver contentResolver = context.getContentResolver(); + final Uri filesUri = MediaStore.Files.getContentUri("external"); + + // Delete the entry from the media database. This will actually delete media files. + contentResolver.delete(filesUri, where, selectionArgs); + // If the file is not a media file, create a new entry. + if (file.exists()) { + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); + contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + // Delete the created entry, such that content provider will delete the file. + contentResolver.delete(filesUri, where, selectionArgs); + } + return !file.exists(); + } +} diff --git a/app/src/main/java/us/koller/cameraroll/util/SizedColorDrawable.java b/app/src/main/java/us/koller/cameraroll/util/SizedColorDrawable.java deleted file mode 100644 index ed91148f..00000000 --- a/app/src/main/java/us/koller/cameraroll/util/SizedColorDrawable.java +++ /dev/null @@ -1,30 +0,0 @@ -package us.koller.cameraroll.util; - -import android.graphics.drawable.ColorDrawable; - -public class SizedColorDrawable extends ColorDrawable { - - private int width = 0, height = 0; - - public SizedColorDrawable(int color, int width, int height) { - super(color); - this.width = width; - this.height = height; - } - - public SizedColorDrawable(int color, int[] dimens) { - super(color); - this.width = dimens[0]; - this.height = dimens[1]; - } - - @Override - public int getIntrinsicWidth() { - return width; - } - - @Override - public int getIntrinsicHeight() { - return height; - } -} diff --git a/app/src/main/java/us/koller/cameraroll/util/SortUtil.java b/app/src/main/java/us/koller/cameraroll/util/SortUtil.java index 781399a8..8b0072af 100644 --- a/app/src/main/java/us/koller/cameraroll/util/SortUtil.java +++ b/app/src/main/java/us/koller/cameraroll/util/SortUtil.java @@ -53,18 +53,20 @@ private static ArrayList sort(Activity context, ArrayList sortByDate(final Activity context, ArrayList sortables) { + public static ArrayList sortByDate(final Activity context, ArrayList sortables) { // Sorting Collections.sort(sortables, new Comparator() { @Override public int compare(Sortable s1, Sortable s2) { - return Long.valueOf(s2.getDate(context)).compareTo(s1.getDate(context)); + Long l1 = s1.getDate(context); + Long l2 = s2.getDate(context); + return l2.compareTo(l1); } }); return sortables; } - private static ArrayList sortByName(ArrayList sortables) { + public static ArrayList sortByName(ArrayList sortables) { // Sorting Collections.sort(sortables, new Comparator() { @Override diff --git a/app/src/main/java/us/koller/cameraroll/util/Util.java b/app/src/main/java/us/koller/cameraroll/util/Util.java index 94d5e67e..4bb87be3 100644 --- a/app/src/main/java/us/koller/cameraroll/util/Util.java +++ b/app/src/main/java/us/koller/cameraroll/util/Util.java @@ -1,12 +1,18 @@ package us.koller.cameraroll.util; import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.media.MediaMetadataRetriever; +import android.net.Uri; import android.os.Build; +import android.provider.BaseColumns; +import android.provider.MediaStore; import android.support.design.widget.Snackbar; import android.util.DisplayMetrics; import android.util.Log; diff --git a/app/src/main/res/drawable-hdpi/ic_content_paste_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_content_paste_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a704a193e4d000a3277d5e3f285a0cfe9de5b4fd GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8LpDo+>3kP61PSI=`EGLUG0=%a9N zYgclFRtf7t+a~EJD(kMuZb|?DMnUPIcjtNqktIJl6x9#z+3)>8`}|fd8CBkC>b%|| z83&c6t$tZuTX|uI_5HgNM;*1JWDfg@oY`UU#?@QZkyUKv-RHa9PZ@nx?7cJ7l6&2W zOnD=Q8UJ*j*CziHTV>dIbz;DgNeg&VHJl|+U6?kd literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_content_paste_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_content_paste_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..40d05206e05520b049eb0c9669007a9ec7c8ec47 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+i6i*k&5R22v2?rSdTmJL^`~T(t zpUXO@ GgeCxCUOprM literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_content_paste_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_content_paste_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8015d55880ee921a84a2fe7b77d51fc16c589064 GIT binary patch literal 244 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0D-JULvAr*{ouiJ7Rau9HMXd=Wv zL7pZ1=Hf`sM_XHbi~2iW*s7!iXMAl*P&_zSCy~3mGFaE=)78l>$7TtO$Ju0BPMXx> z`#>uCT}^qau&~EY!?mfR{1zWp{}nnAaPjeLAGf}v*BzQCGB~$~nB|FjluFnKC{FK> zRxR2p|6<~1kttVBSlWNc{50?E@4ZZ1E&WVf8VedA#D6xPBPV_~wn^N4>Ym`iU-n5d n`fb&>IY%u6{1-oD!M<2tZ%1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_content_paste_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_content_paste_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3b6283fd2129c6b6c55f81c132ecd3c6a9a5f7c3 GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXoKNz$oqM;uuoF`1Y2!*Wmz>_K!Vz zX^P3#LPu>mtb~s2opNVe^rc*jAc0>DxzBg8$cs2#KKWdy<9u=FJgtBGjk&+K+-LH; zX6k40xX&~@&+_SvHMdKm??3Lk9=mC?<^@LoJ=d9k&t`c0>4f{;&2Ra4J=lD1ucLd- z)ra4km_(DOMMPa(zChuU#QpOUla@DB-APz~|DEd_-4~ILik-}^yCWxEnv=jB`hcma zk=I0R?Tx%XkL)aFUC-5uK|w}~6xZHjtGapn{WqTfKW{5EvIu!B;QL(d$i%5K!2v6= z_QR@ejcFC88ynjyLT@@)a@*`uyTQ--jBn1`DZ$-y7=yFw7R}-mZ9KzQG_#%wXk5Cc X%gTe~DWM4fsqT(M literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_content_paste_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_content_paste_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..42e5cfbc0d4612c175b86fe1c7754a19cde638e8 GIT binary patch literal 460 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z&P2{#WAFU@$GH9XYUQ7owYqav=XINnPq?GE)uf$N-tH0_Bf8jpPqO>Y_&rZ&ePF!vvDyCX z+`<

^%CZJNP9Hj(yl(#^b~q^RYMi*D2Q-4dybpi#r;;>xyzLpD$}b5d1k|b+3Q1 zxC8(GsQ&^99d8*q=T59#AIZ#B<1Kk<+1U=B`$g3bJ3p15 z&-s3skwc+@feB26)I(^52o{#QH6x3F0|S)M;8=M_Zh~M?cJ+Zd6K36DPU2aZ^5Pb= zl8{rT*Au1{o0VGb1#f11!^zq9e`ccB+4q)tXL_d037)<=&FT7Q+rl5}5WBDcWot=R Uwn!Gy0mU1Gr>mdKI;Vst02IBp`~Uy| literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/dark_to_transparent_gradient.xml b/app/src/main/res/drawable/dark_to_transparent_gradient.xml new file mode 100644 index 00000000..51739e53 --- /dev/null +++ b/app/src/main/res/drawable/dark_to_transparent_gradient.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_folder_white_24dp.png b/app/src/main/res/drawable/ic_folder_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a1afbe9daf5a5e3945f33915f8817be26b539148 GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z{un2;uunK>+P+xzK0zI93I~B z(C26sy3FL&q}$ba<$&VE(+|=Gi?KQ4VXb{{b%I@HcvNQkRwH8&_$~OogvaO4 zw%oRd*KGoKY|H)oF+L(JmZ@K7yH~M)rTJtXSd}W-{epFln3^$y#Qw2YO4`4lFD^P$~Ceo|M5hK8FSd#^MHkMkW@H6#Nv%Xop z`uOoFkL`Ea5IfQowI9-hvuwR$tP^fy(>8sSO2|8{wRtGpvc@#B$n~B~Rwm&yIIB)&jy;7gW zTKL?7mzK@v|L<(N|7b3=;GHmT*|XKxR_ zisKEuaTo73N#D@a7GnOhx@e+huI&M-u%#?A#q8EMMfY92V!WoXj#HDtTEZ^x>C~Zf8IWQby>8Ib0dT5wN0yQ*dG`x>m;y#Wc*hW`8x0OY*zD)2SPUjd28<` z%OxHB?A^isVY&9DKSD{2WgAXtCo%nb*|kec?7;kW6+k(LjH+Fa;+@JJK3|^tKOV%)fi;J>`G%jw)8gjVE${JR2aaY!G0~hnM zhG|@UElYTe3EXtc3bxQs9yfX7vVdw{;WqBh$^xF`2P*M9I}|qJ3{e@6PKArjoMZ`q zzv59!fmanSI=PO&6?&+plSP6ovR7f^QNATeik(_R0|VqKmg6Jp6&~t1O&))jcuMQg zz!ARWI(NwM6X$u29SRTC9OM@M7CFo&S;iB5kG}$E*e=U>kWUFR#U6!^Dg|IO$GL;Q zdG;$z?BZ=E`Hcb|x0vJv&02@&xrVwOhY7LL@4M9>gD@+_--REpnJm3KuU?nF43nuCUR}5|x-@k1XJG+|9FJHqb%=H!GZ? zR#tEVHz}HB3FEjJmo=nuF(+%t;UXt%$l)REk1CR+@t5HZYGfZVddcE%mgiK277p?u!wk{S zAzEYsZHy7*0=xAuo@I(+(gP_7Qy#3KAK38C}vBbxt+%o{LYzW*hKPd09?sdj*-7MlBEo& zWEs2huD(K!P{X>8db*at`_eW}ws7Fo4@Z0>svVM*lI^ z9PoJ{z*2NtWeTXl$p;X(v=Q{8(I1qV0U9AM;68Do=h11d31B2g3IH~6i)-W(vS`$1 z0(cM#0zMW;e~LyKE(#9#!2^)tV%ONM=+qDtFcUlg@SJO`j00%YLq#ya+dhCp;zCcN z)8Zh2aUAynz!KN!cut^Edl0~C@CAG$&Uz1>ruhNN*dG7@SGdM!p;NOTU>*blY{7lz zPbef_g$uy;fPj9+xb`eZr=?zi$z%coV1;YXDEiQ7ix*%c1O~`=>P=|0y)a+|hYSF4 zo%jI3!G5iU0gJ#0uuc37cIrP+$iKkB0Ygj_t4Q{{wlJW>5CH4MI$pQ-3WdCrn*a`q zrN??~c7hpRfKQA8uuv@erm@wac!Tc(tO)`5LR_*HJN?2;zxlu<(#8OXOI=6YNYrKd zj0M3a^^L{=cu8DzAJKJ~%}foxv{=b5W55wcxsF*$G=0wlj5K}0F`70*06ZiXeFutO ziF=Ey!@uahjSeHgkKzh%6t#r4im}CiZZn5>0d?kW?xr1nu>b%7 M07*qoM6N<$g1J@|uK)l5 diff --git a/app/src/main/res/drawable/ic_videocam_white_24dp.png b/app/src/main/res/drawable/ic_videocam_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ed20c0706292403018b019329a4608db85d99e06 GIT binary patch literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgUV6GXhEy=Vz2%$L>L9?7s4sjZ z`dg=nZ3NGX*QczyQwj`vc|$kX%>MU4_u27-XQ1Fsx=&8bpK^6=W#{+5{x0v--*iiI zK6_5eHWRkq-woYo`i}j$!>)2&V!wgH0wW@j83QAeoL2*oWZ@_>deZ&>ak+XyvA!1j^LK3P nm_Pheb$}Qopa5qqD6eI%v1Ev>S}gk$=xGK|S3j3^P64m#IZ1m3ww3N%B)GLVlWx-PMDb2k5~eQVjTGs^7W}i2bVM(}UO{Ej#XOyj zFbpp_(e(731K;!Q%4~ixF9{81D$BAg%VLWOcg%TiS?8YHoLj~W$n@!d?sJUJKR{PA zp|>oc&o>AP(47i`1RO(@02nU=*n(&QH3K(b5-|Xd+8zmamHh3tfdDiXjeF%9x{vzFrwS11y8@{ zcXRIo0>pSbEg17lN&tTHA_PE>SM`fm^7#`Un}z`B@#?;dF@L1pFg&1OQD4N{->z9g zSilHZpT*nXPA|bTK#y1VhY#ZIZ#NAC(Bsu(el6bqcEb<=Jzm`p^6uO3UV?8x!BaY+ zM5mYF9WdfwH}wy2k9W0(o&b8h9%`200ieh0A-x0-fC8UQ?LT)7U;qOcV6`(q@_ztv z8#SPG1IAGU4%~nNb+mw*O*bH85-s46Y#Bh0(@Y7WVt_8ZxNBW|dI p11d@m*tB0M4PXER7{CDP{sJ%=@W1%=NC*G`002ovPDHLkV1npg1C;;( literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/transparent_to_dark_gradient.xml b/app/src/main/res/drawable/transparent_to_dark_gradient.xml new file mode 100644 index 00000000..fd143f45 --- /dev/null +++ b/app/src/main/res/drawable/transparent_to_dark_gradient.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_file_explorer.xml b/app/src/main/res/layout/activity_file_explorer.xml index ab063576..b1284aa9 100644 --- a/app/src/main/res/layout/activity_file_explorer.xml +++ b/app/src/main/res/layout/activity_file_explorer.xml @@ -1,11 +1,38 @@ - + + + + + + + + + + android:textSize="24sp" + android:fontFamily="sans-serif-monospace"/> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_item.xml b/app/src/main/res/layout/activity_item.xml index 976f3d2e..7018a5f7 100644 --- a/app/src/main/res/layout/activity_item.xml +++ b/app/src/main/res/layout/activity_item.xml @@ -16,7 +16,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/toolbar" android:layout_width="match_parent" - android:layout_height="wrap_content"/> + android:layout_height="wrap_content" + android:background="@drawable/dark_to_transparent_gradient"/> \ No newline at end of file diff --git a/app/src/main/res/layout/file_cover.xml b/app/src/main/res/layout/file_cover.xml index b9487e3f..2f1bcba5 100644 --- a/app/src/main/res/layout/file_cover.xml +++ b/app/src/main/res/layout/file_cover.xml @@ -22,5 +22,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="16dp" + android:fontFamily="sans-serif-monospace" android:textSize="20sp"/> \ No newline at end of file diff --git a/app/src/main/res/layout/photo_view_button_bar.xml b/app/src/main/res/layout/photo_view_button_bar.xml index dcbdb0d2..94ee361c 100644 --- a/app/src/main/res/layout/photo_view_button_bar.xml +++ b/app/src/main/res/layout/photo_view_button_bar.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" - app:cardBackgroundColor="@color/black_translucent2" + app:cardBackgroundColor="@android:color/transparent" app:cardCornerRadius="0dp" app:cardElevation="0dp"> @@ -14,7 +14,8 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" - android:baselineAligned="false"> + android:baselineAligned="false" + android:background="@drawable/transparent_to_dark_gradient"> + \ No newline at end of file diff --git a/app/src/main/res/layout/simple_spinner_item.xml b/app/src/main/res/layout/simple_spinner_item.xml new file mode 100644 index 00000000..48a54ed2 --- /dev/null +++ b/app/src/main/res/layout/simple_spinner_item.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/album.xml b/app/src/main/res/menu/album.xml new file mode 100644 index 00000000..c58de77f --- /dev/null +++ b/app/src/main/res/menu/album.xml @@ -0,0 +1,10 @@ + +

+ + + \ No newline at end of file diff --git a/app/src/main/res/menu/item.xml b/app/src/main/res/menu/item.xml index d86037d2..00876fd9 100644 --- a/app/src/main/res/menu/item.xml +++ b/app/src/main/res/menu/item.xml @@ -4,7 +4,7 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14824c82..fe58763e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -67,8 +67,8 @@ Move Copy Paste - successfully moved - successfully copied + "successfully moved " + "successfully copied " Pick Photo Pick Photos @@ -98,4 +98,5 @@ Video Indicator Hidden Folder Indicator + Print diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml index 4495c28c..4278f59f 100644 --- a/app/src/main/res/xml/provider_paths.xml +++ b/app/src/main/res/xml/provider_paths.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file