package com.gianlu.aria2app.activities.moreabout.files; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.provider.Browser; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.gianlu.aria2app.PK; import com.gianlu.aria2app.R; import com.gianlu.aria2app.Utils; import com.gianlu.aria2app.activities.DirectDownloadActivity; import com.gianlu.aria2app.activities.moreabout.BigUpdateProvider; import com.gianlu.aria2app.activities.moreabout.OnBackPressed; import com.gianlu.aria2app.api.AbstractClient; import com.gianlu.aria2app.api.AriaRequests; import com.gianlu.aria2app.api.aria2.Aria2Helper; import com.gianlu.aria2app.api.aria2.AriaDirectory; import com.gianlu.aria2app.api.aria2.AriaFile; import com.gianlu.aria2app.api.aria2.AriaFiles; import com.gianlu.aria2app.api.aria2.Download; import com.gianlu.aria2app.api.aria2.DownloadWithUpdate; import com.gianlu.aria2app.api.aria2.OptionsMap; import com.gianlu.aria2app.api.updater.PayloadProvider; import com.gianlu.aria2app.api.updater.UpdaterFragment; import com.gianlu.aria2app.api.updater.Wants; import com.gianlu.aria2app.downloader.FetchHelper; import com.gianlu.aria2app.profiles.MultiProfile; import com.gianlu.aria2app.tutorial.Discovery; import com.gianlu.aria2app.tutorial.FilesTutorial; import com.gianlu.aria2app.tutorial.FoldersTutorial; import com.gianlu.commonutils.analytics.AnalyticsApplication; import com.gianlu.commonutils.dialogs.DialogUtils; import com.gianlu.commonutils.misc.BreadcrumbsView; import com.gianlu.commonutils.misc.RecyclerMessageView; import com.gianlu.commonutils.preferences.Prefs; import com.gianlu.commonutils.tutorial.BaseTutorial; import com.gianlu.commonutils.tutorial.TutorialManager; import com.gianlu.commonutils.ui.Toaster; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import okhttp3.HttpUrl; public class FilesFragment extends UpdaterFragment<DownloadWithUpdate.BigUpdate> implements TutorialManager.Listener, FilesAdapter.Listener, OnBackPressed, FileSheet.Listener, DirectorySheet.Listener, BreadcrumbsView.Listener { private static final String TAG = FilesFragment.class.getSimpleName(); private FilesAdapter adapter; private FileSheet fileSheet; private DirectorySheet dirSheet; private BreadcrumbsView breadcrumbs; private RecyclerMessageView rmv; private ActionMode actionMode = null; private DownloadWithUpdate download; private TutorialManager tutorialManager; private FetchHelper helper; @NonNull public static FilesFragment getInstance(Context context, String gid) { FilesFragment fragment = new FilesFragment(); Bundle args = new Bundle(); args.putString("title", context.getString(R.string.files)); args.putString("gid", gid); fragment.setArguments(args); return fragment; } @Override public boolean canGoBack(int code) { if (code == CODE_CLOSE_SHEET) { if (actionMode != null) actionMode.finish(); if (fileSheet != null) { fileSheet.dismiss(); fileSheet = null; dismissDialog(); } return true; } if (actionMode != null) { // Unluckily ActionMode intercepts the event (useless condition) actionMode.finish(); return false; } else if (hasVisibleDialog()) { dismissDialog(); fileSheet = null; dirSheet = null; return false; } else if (adapter != null && adapter.canGoUp()) { adapter.navigateUp(); return false; } else { return true; } } @NonNull @Override protected PayloadProvider<DownloadWithUpdate.BigUpdate> requireProvider(@NonNull Context context, @NonNull Bundle args) throws Aria2Helper.InitializingException { return new BigUpdateProvider(context, args.getString("gid")); } @Override public void onAttach(@NonNull Context context) { super.onAttach(context); try { helper = FetchHelper.get(context); } catch (FetchHelper.DirectDownloadNotEnabledException ignored) { } catch (FetchHelper.InitializationException ex) { Log.e(TAG, "Failed initializing Fetch.", ex); showToast(Toaster.build().message(R.string.failedLoading_reason, ex.getMessage())); } } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedInstanceState) { LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.fragment_files, parent, false); breadcrumbs = layout.findViewById(R.id.filesFragment_breadcrumbs); breadcrumbs.setListener(this); rmv = layout.findViewById(R.id.filesFragment_rmv); rmv.linearLayoutManager(LinearLayoutManager.VERTICAL, false); rmv.dividerDecoration(RecyclerView.VERTICAL); tutorialManager = new TutorialManager(this, Discovery.FILES, Discovery.FOLDERS); rmv.startLoading(); return layout; } @Override public void onFileSelected(@NonNull AriaFile file) { fileSheet = FileSheet.get(); fileSheet.show(getActivity(), download, file, this); } @Override public boolean onFileLongClick(@NonNull AriaFile file) { if (getActivity() == null || download.update().files.size() == 1) return false; adapter.enteredActionMode(file); actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mode.getMenuInflater().inflate(R.menu.files_action_mode, menu); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.filesActionMode_select: changeSelectionForBatch(adapter.getSelectedFiles(), true); return true; case R.id.filesActionMode_deselect: changeSelectionForBatch(adapter.getSelectedFiles(), false); return true; case R.id.filesActionMode_selectAll: adapter.selectAllInDirectory(); return true; default: return false; } } @Override public void onDestroyActionMode(ActionMode mode) { actionMode = null; adapter.exitedActionMode(); } }); return true; } private void changeSelectionForBatch(Collection<AriaFile> files, boolean select) { download.changeSelection(AriaFile.allIndexes(files), select, new AbstractClient.OnResult<Download.ChangeSelectionResult>() { @Override public void onResult(@NonNull Download.ChangeSelectionResult result) { Toaster toaster = Toaster.build(); toaster.extra(result); switch (result) { case EMPTY: toaster.message(R.string.cannotDeselectAllFiles); break; case SELECTED: toaster.message(R.string.fileSelected); break; case DESELECTED: toaster.message(R.string.fileDeselected); break; default: toaster.message(R.string.failedAction); break; } DialogUtils.showToast(getContext(), toaster); exitActionMode(); } @Override public void onException(@NonNull @NotNull Exception ex) { Log.e(TAG, "Failed changing selection", ex); showToast(Toaster.build().message(R.string.failedFileChangeSelection)); } }); } @Override public void exitActionMode() { if (actionMode != null) actionMode.finish(); } @Override public boolean onDirectoryLongClick(@NonNull AriaDirectory dir) { dirSheet = DirectorySheet.get(); dirSheet.show(getActivity(), download, dir, this); return true; } @Override public void onDirectoryChanged(@NonNull AriaDirectory dir) { breadcrumbs.clear(); List<BreadcrumbsView.Item> items = new ArrayList<>(); AriaDirectory current = dir; do { items.add(new BreadcrumbsView.Item(current.name, 0, current)); current = current.parent; } while (current != null); Collections.reverse(items); breadcrumbs.addItems(items.toArray(new BreadcrumbsView.Item[0])); tutorialManager.tryShowingTutorials(getActivity()); } private void startDownloadInternal(@NonNull MultiProfile profile, @Nullable AriaFile file, @Nullable AriaDirectory dir) { if (helper == null) return; boolean single = file != null; FetchHelper.StartListener listener = new FetchHelper.StartListener() { @Override public void onSuccess() { Snackbar.make(rmv, R.string.downloadAdded, Snackbar.LENGTH_LONG) .setAction(R.string.show, v -> startActivity(new Intent(getContext(), DirectDownloadActivity.class))) .show(); } @Override public void onFailed(@NonNull Throwable ex) { Log.e(TAG, "Failed starting download.", ex); DialogUtils.showToast(getContext(), Toaster.build().message(single ? R.string.failedAddingDownload : R.string.failedAddingDownloads)); } }; if (file != null) helper.start(requireContext(), profile, file, listener); else if (dir != null) helper.start(requireContext(), profile, dir, listener); } @Override public void onDownloadFile(@NonNull MultiProfile profile, @NonNull AriaFile file, boolean share) { if (fileSheet != null) { fileSheet.dismiss(); fileSheet = null; dismissDialog(); } showProgress(R.string.gathering_information); getHelper().request(AriaRequests.getGlobalOptions(), new AbstractClient.OnResult<OptionsMap>() { @Override public void onResult(@NonNull OptionsMap result) { dismissDialog(); String mime = file.getMimeType(); if (mime != null && Utils.isStreamable(mime) && getContext() != null) { Intent intent = Utils.getStreamIntent(profile.getProfile(getContext()), result, file); if (intent != null && Utils.canHandleIntent(requireContext(), intent)) { AlertDialog.Builder builder = new MaterialAlertDialogBuilder(requireContext()); builder.setTitle(R.string.couldStreamVideo) .setMessage(R.string.couldStreamVideo_message) .setNeutralButton(android.R.string.cancel, null) .setPositiveButton(R.string.stream, (dialog, which) -> { startActivity(intent); AnalyticsApplication.sendAnalytics(Utils.ACTION_PLAY_VIDEO); }) .setNegativeButton(R.string.download, (dialog, which) -> shouldDownload(profile, result, file, share)); showDialog(builder); return; } } shouldDownload(profile, result, file, share); } @Override public void onException(@NonNull Exception ex) { dismissDialog(); Log.e(TAG, "Failed getting global options.", ex); showToast(Toaster.build().message(R.string.failedDownloadingFile)); } }); } private void shouldDownload(@NonNull MultiProfile profile, @NonNull OptionsMap global, @NonNull AriaFile file, boolean share) { AnalyticsApplication.sendAnalytics(Utils.ACTION_DOWNLOAD_FILE); if (Prefs.getBoolean(PK.DD_USE_EXTERNAL) || share) { MultiProfile.DirectDownload dd = profile.getProfile(getContext()).directDownload; HttpUrl base; if (dd == null || (base = dd.getUrl()) == null) return; Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(file.getDownloadUrl(global, base).toString())); if (dd.auth) { Bundle headers = new Bundle(); headers.putString("Authorization", dd.getAuthorizationHeader()); intent.putExtra(Browser.EXTRA_HEADERS, headers); } startActivity(intent); } else { startDownloadInternal(profile, file, null); } } @Override public void onDownloadDirectory(@NonNull MultiProfile profile, @NonNull AriaDirectory dir) { if (dirSheet != null) { dirSheet.dismiss(); dirSheet = null; dismissDialog(); } AnalyticsApplication.sendAnalytics(Utils.ACTION_DOWNLOAD_DIRECTORY); if (Prefs.getBoolean(PK.DD_USE_EXTERNAL)) { AlertDialog.Builder builder = new MaterialAlertDialogBuilder(requireContext()); builder.setTitle(R.string.cannotDownloadDirWithExternal) .setMessage(R.string.cannotDownloadDirWithExternal_message) .setPositiveButton(android.R.string.yes, (dialog, which) -> startDownloadInternal(profile, null, dir)) .setNegativeButton(android.R.string.no, null); showDialog(builder); } else { startDownloadInternal(profile, null, dir); } } @Override public void onUpdateUi(@NonNull DownloadWithUpdate.BigUpdate payload) { AriaFiles files = payload.files; if (files.isEmpty() || files.get(0).path.isEmpty()) { rmv.showInfo(R.string.noFiles); breadcrumbs.setVisibility(View.GONE); } else { rmv.showList(); breadcrumbs.setVisibility(View.VISIBLE); if (adapter != null) adapter.update(payload.download(), files); if (fileSheet != null) fileSheet.update(files); if (dirSheet != null) dirSheet.update(payload.download(), files); tutorialManager.tryShowingTutorials(getActivity()); } } @Override public void onLoadUi(@NonNull DownloadWithUpdate.BigUpdate payload) { this.download = payload.download(); if (getContext() == null) return; adapter = new FilesAdapter(getContext(), this); rmv.loadListData(adapter); rmv.startLoading(); rmv.enableSwipeRefresh(() -> { canGoBack(CODE_CLOSE_SHEET); refresh(() -> { if (!isAdded() || isDetached()) return; adapter = new FilesAdapter(getContext(), this); rmv.loadListData(adapter); rmv.startLoading(); }); }, R.color.colorAccent, R.color.colorMetalink, R.color.colorTorrent); } @Override public boolean onCouldntLoad(@NonNull Exception ex) { rmv.showError(R.string.failedLoading); Log.e(TAG, "Failed load info.", ex); return false; } @NonNull @Override public Wants<DownloadWithUpdate.BigUpdate> wants(@NonNull Bundle args) { return Wants.bigUpdate(args.getString("gid")); } @Override public void onSegmentSelected(@NonNull BreadcrumbsView.Item item) { if (adapter != null && item.data != null) adapter.changeDir((AriaDirectory) item.data); } @Override public boolean canShow(@NonNull BaseTutorial tutorial) { if (tutorial instanceof FilesTutorial) return ((FilesTutorial) tutorial).canShow(this, adapter); else if (tutorial instanceof FoldersTutorial) return ((FoldersTutorial) tutorial).canShow(this, adapter); return false; } @Override public boolean buildSequence(@NonNull BaseTutorial tutorial) { if (tutorial instanceof FilesTutorial) return ((FilesTutorial) tutorial).buildSequence(rmv.list(), adapter != null ? adapter.getCurrentDir() : null); else if (tutorial instanceof FoldersTutorial) return ((FoldersTutorial) tutorial).buildSequence(rmv.list()); return true; } }