diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/BlacklistFolderChooserDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/BlacklistFolderChooserDialog.java index 4883e6c4..32a3129b 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/dialogs/BlacklistFolderChooserDialog.java +++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/BlacklistFolderChooserDialog.java @@ -11,7 +11,6 @@ import android.support.v4.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.view.View; -import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.kabouzeid.gramophone.R; @@ -105,9 +104,6 @@ public class BlacklistFolderChooserDialog extends DialogFragment implements Mate .onNegative((materialDialog, dialogAction) -> dismiss()) .positiveText(R.string.add_action) .negativeText(android.R.string.cancel); - if (File.pathSeparator.equals(initialPath)) { - canGoUp = false; - } return builder.build(); } @@ -118,7 +114,7 @@ public class BlacklistFolderChooserDialog extends DialogFragment implements Mate if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { parentFolder = parentFolder.getParentFile(); } - canGoUp = parentFolder.getParent() != null; + checkIfCanGoUp(); } else { parentFolder = parentContents[canGoUp ? i - 1 : i]; canGoUp = true; @@ -130,11 +126,7 @@ public class BlacklistFolderChooserDialog extends DialogFragment implements Mate } private void checkIfCanGoUp() { - try { - canGoUp = parentFolder.getPath().split(File.pathSeparator).length > 1; - } catch (IndexOutOfBoundsException e) { - canGoUp = false; - } + canGoUp = parentFolder.getParent() != null; } private void reload() { diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/ScanMediaFolderChooserDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/ScanMediaFolderChooserDialog.java new file mode 100644 index 00000000..66d1d90b --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/ScanMediaFolderChooserDialog.java @@ -0,0 +1,169 @@ +package com.kabouzeid.gramophone.dialogs; + +import android.Manifest; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.pm.PackageManager; +import android.media.MediaScannerConnection; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.DialogFragment; +import android.view.View; +import android.widget.Toast; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.misc.UpdateToastMediaScannerCompletionListener; +import com.kabouzeid.gramophone.ui.fragments.mainactivity.folders.FoldersFragment; +import com.kabouzeid.gramophone.util.PreferenceUtil; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * @author Aidan Follestad (afollestad), modified by Karim Abou Zeid + */ +public class ScanMediaFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback { + + String initialPath = PreferenceUtil.getInstance(getContext()).getStartDirectory().getAbsolutePath(); + private File parentFolder; + private File[] parentContents; + private boolean canGoUp = false; + + public static ScanMediaFolderChooserDialog create() { + return new ScanMediaFolderChooserDialog(); + } + + private static void scanPaths(@NonNull WeakReference activityWeakReference, @NonNull Context applicationContext, @Nullable String[] toBeScanned) { + Activity activity = activityWeakReference.get(); + if (toBeScanned == null || toBeScanned.length < 1) { + Toast.makeText(applicationContext, R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); + } else { + MediaScannerConnection.scanFile(applicationContext, toBeScanned, null, activity != null ? new UpdateToastMediaScannerCompletionListener(activity, toBeScanned) : null); + } + } + + private String[] getContentsArray() { + if (parentContents == null) { + if (canGoUp) { + return new String[]{".."}; + } + return new String[]{}; + } + String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)]; + if (canGoUp) { + results[0] = ".."; + } + for (int i = 0; i < parentContents.length; i++) { + results[canGoUp ? i + 1 : i] = parentContents[i].getName(); + } + return results; + } + + private File[] listFiles() { + File[] contents = parentFolder.listFiles(); + List results = new ArrayList<>(); + if (contents != null) { + for (File fi : contents) { + if (fi.isDirectory()) { + results.add(fi); + } + } + Collections.sort(results, new FolderSorter()); + return results.toArray(new File[results.size()]); + } + return null; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && ActivityCompat.checkSelfPermission( + getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + return new MaterialDialog.Builder(getActivity()) + .title(R.string.md_error_label) + .content(R.string.md_storage_perm_error) + .positiveText(android.R.string.ok) + .build(); + } + if (savedInstanceState == null) { + savedInstanceState = new Bundle(); + } + if (!savedInstanceState.containsKey("current_path")) { + savedInstanceState.putString("current_path", initialPath); + } + parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator)); + checkIfCanGoUp(); + parentContents = listFiles(); + MaterialDialog.Builder builder = + new MaterialDialog.Builder(getActivity()) + .title(parentFolder.getAbsolutePath()) + .items((CharSequence[]) getContentsArray()) + .itemsCallback(this) + .autoDismiss(false) + .onPositive((dialog, which) -> { + final Context applicationContext = getActivity().getApplicationContext(); + final WeakReference activityWeakReference = new WeakReference<>(getActivity()); + dismiss(); + new FoldersFragment.ListPathsAsyncTask(getActivity(), paths -> scanPaths(activityWeakReference, applicationContext, paths)).execute(new FoldersFragment.ListPathsAsyncTask.LoadingInfo(parentFolder, FoldersFragment.AUDIO_FILE_FILTER)); + }) + .onNegative((materialDialog, dialogAction) -> dismiss()) + .positiveText(R.string.action_scan_directory) + .negativeText(android.R.string.cancel); + return builder.build(); + } + + @Override + public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) { + if (canGoUp && i == 0) { + parentFolder = parentFolder.getParentFile(); + if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { + parentFolder = parentFolder.getParentFile(); + } + checkIfCanGoUp(); + } else { + parentFolder = parentContents[canGoUp ? i - 1 : i]; + canGoUp = true; + if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { + parentFolder = Environment.getExternalStorageDirectory(); + } + } + reload(); + } + + private void checkIfCanGoUp() { + canGoUp = parentFolder.getParent() != null; + } + + private void reload() { + parentContents = listFiles(); + MaterialDialog dialog = (MaterialDialog) getDialog(); + dialog.setTitle(parentFolder.getAbsolutePath()); + dialog.setItems((CharSequence[]) getContentsArray()); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString("current_path", parentFolder.getAbsolutePath()); + } + + private static class FolderSorter implements Comparator { + + @Override + public int compare(File lhs, File rhs) { + return lhs.getName().compareTo(rhs.getName()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/misc/UpdateToastMediaScannerCompletionListener.java b/app/src/main/java/com/kabouzeid/gramophone/misc/UpdateToastMediaScannerCompletionListener.java index 48c06eea..62204d6d 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/misc/UpdateToastMediaScannerCompletionListener.java +++ b/app/src/main/java/com/kabouzeid/gramophone/misc/UpdateToastMediaScannerCompletionListener.java @@ -14,15 +14,15 @@ import java.lang.ref.WeakReference; * @author Karim Abou Zeid (kabouzeid) */ public class UpdateToastMediaScannerCompletionListener implements MediaScannerConnection.OnScanCompletedListener { - int scanned = 0; - int failed = 0; + private int scanned = 0; + private int failed = 0; private final String[] toBeScanned; private final String scannedFiles; private final String couldNotScanFiles; - private final WeakReference toastWeakReference; + private Toast toast; private final WeakReference activityWeakReference; @SuppressLint("ShowToast") @@ -30,7 +30,7 @@ public class UpdateToastMediaScannerCompletionListener implements MediaScannerCo this.toBeScanned = toBeScanned; scannedFiles = activity.getString(R.string.scanned_files); couldNotScanFiles = activity.getString(R.string.could_not_scan_files); - toastWeakReference = new WeakReference<>(Toast.makeText(activity, "", Toast.LENGTH_SHORT)); + toast = Toast.makeText(activity.getApplicationContext(), "", Toast.LENGTH_SHORT); activityWeakReference = new WeakReference<>(activity); } @@ -39,17 +39,14 @@ public class UpdateToastMediaScannerCompletionListener implements MediaScannerCo Activity activity = activityWeakReference.get(); if (activity != null) { activity.runOnUiThread(() -> { - Toast toast = toastWeakReference.get(); - if (toast != null) { - if (uri == null) { - failed++; - } else { - scanned++; - } - String text = " " + String.format(scannedFiles, scanned, toBeScanned.length) + (failed > 0 ? " " + String.format(couldNotScanFiles, failed) : ""); - toast.setText(text); - toast.show(); + if (uri == null) { + failed++; + } else { + scanned++; } + String text = " " + String.format(scannedFiles, scanned, toBeScanned.length) + (failed > 0 ? " " + String.format(couldNotScanFiles, failed) : ""); + toast.setText(text); + toast.show(); }); } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java index 6bdc8ec4..0dfe27df 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java @@ -29,6 +29,7 @@ import com.kabouzeid.appthemehelper.util.NavigationViewUtil; import com.kabouzeid.gramophone.App; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.dialogs.ChangelogDialog; +import com.kabouzeid.gramophone.dialogs.ScanMediaFolderChooserDialog; import com.kabouzeid.gramophone.glide.SongGlideRequest; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.SearchQueryHelper; @@ -180,6 +181,12 @@ public class MainActivity extends AbsSlidingMusicPanelActivity { case R.id.buy_pro: new Handler().postDelayed(() -> startActivityForResult(new Intent(MainActivity.this, PurchaseActivity.class), PURCHASE_REQUEST), 200); break; + case R.id.action_scan: + new Handler().postDelayed(() -> { + ScanMediaFolderChooserDialog dialog = ScanMediaFolderChooserDialog.create(); + dialog.show(getSupportFragmentManager(), "SCAN_MEDIA_FOLDER_CHOOSER"); + }, 200); + break; case R.id.nav_settings: new Handler().postDelayed(() -> startActivity(new Intent(MainActivity.this, SettingsActivity.class)), 200); break; diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/folders/FoldersFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/folders/FoldersFragment.java index 34304880..29553b94 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/folders/FoldersFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/folders/FoldersFragment.java @@ -259,21 +259,10 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi ToolbarContentTintHelper.handleOnPrepareOptionsMenu(getActivity(), toolbar); } - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.action_go_to_start_directory: - setCrumb(new BreadCrumbLayout.Crumb(tryGetCanonicalFile(PreferenceUtil.getInstance(getActivity()).getStartDirectory())), true); - return true; - case R.id.action_scan: - BreadCrumbLayout.Crumb crumb = getActiveCrumb(); - if (crumb != null) { - new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), getFileFilter())); - } - return true; - } - return super.onOptionsItemSelected(item); - } + public static final FileFilter AUDIO_FILE_FILTER = file -> !file.isHidden() && (file.isDirectory() || + FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) || + FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton()) || + FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); @Override public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) { @@ -296,13 +285,29 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi return startFolder; } + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_go_to_start_directory: + setCrumb(new BreadCrumbLayout.Crumb(tryGetCanonicalFile(PreferenceUtil.getInstance(getActivity()).getStartDirectory())), true); + return true; + case R.id.action_scan: + BreadCrumbLayout.Crumb crumb = getActiveCrumb(); + if (crumb != null) { + new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), AUDIO_FILE_FILTER)); + } + return true; + } + return super.onOptionsItemSelected(item); + } + @Override public void onFileSelected(File file) { file = tryGetCanonicalFile(file); // important as we compare the path value later if (file.isDirectory()) { setCrumb(new BreadCrumbLayout.Crumb(file), true); } else { - FileFilter fileFilter = pathname -> !pathname.isDirectory() && getFileFilter().accept(pathname); + FileFilter fileFilter = pathname -> !pathname.isDirectory() && AUDIO_FILE_FILTER.accept(pathname); new ListSongsAsyncTask(getActivity(), file, (songs, extra) -> { File file1 = (File) extra; int startIndex = -1; @@ -317,7 +322,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi } else { final File finalFile = file1; Snackbar.make(coordinatorLayout, Html.fromHtml(String.format(getString(R.string.not_listed_in_media_store), file1.getName())), Snackbar.LENGTH_LONG) - .setAction(R.string.action_scan, v -> new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(finalFile, getFileFilter()))) + .setAction(R.string.action_scan, v -> new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(finalFile, AUDIO_FILE_FILTER))) .setActionTextColor(ThemeStore.accentColor(getActivity())) .show(); } @@ -328,58 +333,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi @Override public void onMultipleItemAction(MenuItem item, ArrayList files) { final int itemId = item.getItemId(); - new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId)).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator())); - } - - @Override - public void onFileMenuClicked(final File file, View view) { - PopupMenu popupMenu = new PopupMenu(getActivity(), view); - if (file.isDirectory()) { - popupMenu.inflate(R.menu.menu_item_directory); - popupMenu.setOnMenuItemClickListener(item -> { - final int itemId = item.getItemId(); - switch (itemId) { - case R.id.action_play_next: - case R.id.action_add_to_current_playing: - case R.id.action_add_to_playlist: - case R.id.action_delete_from_device: - new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId)).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), getFileFilter(), getFileComparator())); - return true; - case R.id.action_set_as_start_directory: - PreferenceUtil.getInstance(getActivity()).setStartDirectory(file); - Toast.makeText(getActivity(), String.format(getString(R.string.new_start_directory), file.getPath()), Toast.LENGTH_SHORT).show(); - return true; - case R.id.action_scan: - new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(file, getFileFilter())); - return true; - } - return false; - }); - } else { - popupMenu.inflate(R.menu.menu_item_file); - popupMenu.setOnMenuItemClickListener(item -> { - final int itemId = item.getItemId(); - switch (itemId) { - case R.id.action_play_next: - case R.id.action_add_to_current_playing: - case R.id.action_add_to_playlist: - case R.id.action_go_to_album: - case R.id.action_go_to_artist: - case R.id.action_share: - case R.id.action_tag_editor: - case R.id.action_details: - case R.id.action_set_as_ringtone: - case R.id.action_delete_from_device: - new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongMenuHelper.handleMenuClick(getActivity(), songs.get(0), itemId)).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), getFileFilter(), getFileComparator())); - return true; - case R.id.action_scan: - new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(file, getFileFilter())); - return true; - } - return false; - }); - } - popupMenu.show(); + new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId)).execute(new ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, getFileComparator())); } private ArrayList toList(File file) { @@ -403,12 +357,55 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi return fileComparator; } - FileFilter audioFileFilter = file -> !file.isHidden() && (file.isDirectory() || - FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) || - FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); - - private FileFilter getFileFilter() { - return audioFileFilter; + @Override + public void onFileMenuClicked(final File file, View view) { + PopupMenu popupMenu = new PopupMenu(getActivity(), view); + if (file.isDirectory()) { + popupMenu.inflate(R.menu.menu_item_directory); + popupMenu.setOnMenuItemClickListener(item -> { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_delete_from_device: + new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId)).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), AUDIO_FILE_FILTER, getFileComparator())); + return true; + case R.id.action_set_as_start_directory: + PreferenceUtil.getInstance(getActivity()).setStartDirectory(file); + Toast.makeText(getActivity(), String.format(getString(R.string.new_start_directory), file.getPath()), Toast.LENGTH_SHORT).show(); + return true; + case R.id.action_scan: + new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); + return true; + } + return false; + }); + } else { + popupMenu.inflate(R.menu.menu_item_file); + popupMenu.setOnMenuItemClickListener(item -> { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_go_to_album: + case R.id.action_go_to_artist: + case R.id.action_share: + case R.id.action_tag_editor: + case R.id.action_details: + case R.id.action_set_as_ringtone: + case R.id.action_delete_from_device: + new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongMenuHelper.handleMenuClick(getActivity(), songs.get(0), itemId)).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), AUDIO_FILE_FILTER, getFileComparator())); + return true; + case R.id.action_scan: + new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); + return true; + } + return false; + }); + } + popupMenu.show(); } @Override @@ -482,7 +479,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi } } if (directory != null) { - List files = FileUtil.listFiles(directory, foldersFragment.getFileFilter()); + List files = FileUtil.listFiles(directory, AUDIO_FILE_FILTER); Collections.sort(files, foldersFragment.getFileComparator()); return files; } else { @@ -574,7 +571,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi } } - private static class ListPathsAsyncTask extends ListingFilesDialogAsyncTask { + public static class ListPathsAsyncTask extends ListingFilesDialogAsyncTask { private WeakReference onPathsListedCallbackWeakReference; public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { diff --git a/app/src/main/res/menu/menu_drawer.xml b/app/src/main/res/menu/menu_drawer.xml index 6d992efc..7466ad79 100644 --- a/app/src/main/res/menu/menu_drawer.xml +++ b/app/src/main/res/menu/menu_drawer.xml @@ -14,6 +14,14 @@ android:title="@string/folders" /> + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c951c4c8..a11ddb3d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -296,4 +296,5 @@ Configure visibility and order of library categories. You have to select at least one category. Scan directory + Scan media