Add scan button to navigation drawer

This commit is contained in:
Karim Abou Zeid 2018-04-30 13:14:24 +02:00
commit 9b3243e3e2
7 changed files with 248 additions and 77 deletions

View file

@ -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() {

View file

@ -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<Activity> 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<File> 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<Activity> 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<File> {
@Override
public int compare(File lhs, File rhs) {
return lhs.getName().compareTo(rhs.getName());
}
}
}

View file

@ -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<Toast> toastWeakReference;
private Toast toast;
private final WeakReference<Activity> 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();
});
}
}

View file

@ -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;

View file

@ -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<File> 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<File> 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<File> files = FileUtil.listFiles(directory, foldersFragment.getFileFilter());
List<File> 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<ListPathsAsyncTask.LoadingInfo, String, String[]> {
public static class ListPathsAsyncTask extends ListingFilesDialogAsyncTask<ListPathsAsyncTask.LoadingInfo, String, String[]> {
private WeakReference<OnPathsListedCallback> onPathsListedCallbackWeakReference;
public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) {

View file

@ -14,6 +14,14 @@
android:title="@string/folders" />
</group>
<group android:checkableBehavior="none">
<item
android:id="@+id/action_scan"
android:icon="@drawable/ic_scanner_white_24dp"
android:title="@string/scan_media" />
</group>
<group
android:id="@+id/navigation_drawer_menu_category_other"
android:checkableBehavior="none">

View file

@ -296,4 +296,5 @@
<string name="pref_summary_library_categories">Configure visibility and order of library categories.</string>
<string name="you_have_to_select_at_least_one_category">You have to select at least one category.</string>
<string name="action_scan_directory">Scan directory</string>
<string name="scan_media">Scan media</string>
</resources>