From 16da9d482a8201d9de46ba9bb687253fdeb2b322 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sun, 10 Dec 2023 13:07:04 +0530 Subject: [PATCH 01/55] update developers --- .../ui/activities/AboutActivity.java | 9 +++- app/src/main/res/layout/activity_about.xml | 50 +++++++++++++++++++ app/src/main/res/values/translators.xml | 1 + 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java index d66be6aac7..75600b4852 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java @@ -67,13 +67,14 @@ public class AboutActivity extends ThemedActivity implements View.OnClickListene private AppBarLayout mAppBarLayout; private CollapsingToolbarLayout mCollapsingToolbarLayout; private AppCompatTextView mTitleTextView; - private View mAuthorsDivider, mDeveloper1Divider; + private View mAuthorsDivider, mDeveloper1Divider, mDeveloper2Divider; private Billing billing; private static final String URL_AUTHOR1_GITHUB = "https://github.com/arpitkh96"; private static final String URL_AUTHOR2_GITHUB = "https://github.com/VishalNehra"; private static final String URL_DEVELOPER1_GITHUB = "https://github.com/EmmanuelMess"; private static final String URL_DEVELOPER2_GITHUB = "https://github.com/TranceLove"; + private static final String URL_DEVELOPER3_GITHUB = "https://github.com/VishnuSanal"; private static final String URL_REPO_CHANGELOG = "https://github.com/TeamAmaze/AmazeFileManager/commits/master"; private static final String URL_REPO = "https://github.com/TeamAmaze/AmazeFileManager"; @@ -108,6 +109,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { mTitleTextView = findViewById(R.id.text_view_title); mAuthorsDivider = findViewById(R.id.view_divider_authors); mDeveloper1Divider = findViewById(R.id.view_divider_developers_1); + mDeveloper2Divider = findViewById(R.id.view_divider_developers_2); mAppBarLayout.setLayoutParams(calculateHeaderViewParams()); @@ -200,6 +202,7 @@ private void switchIcons() { // dark theme mAuthorsDivider.setBackgroundColor(Utils.getColor(this, R.color.divider_dark_card)); mDeveloper1Divider.setBackgroundColor(Utils.getColor(this, R.color.divider_dark_card)); + mDeveloper2Divider.setBackgroundColor(Utils.getColor(this, R.color.divider_dark_card)); } } @@ -280,6 +283,10 @@ public void onClick(View v) { openURL(URL_DEVELOPER2_GITHUB, this); break; + case R.id.text_view_developer_3_github: + openURL(URL_DEVELOPER3_GITHUB, this); + break; + case R.id.relative_layout_translate: openURL(URL_REPO_TRANSLATE, this); break; diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 495b763454..eb593f9e3e 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -441,6 +441,56 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/translators.xml b/app/src/main/res/values/translators.xml index cf70af665d..630f64cdf0 100644 --- a/app/src/main/res/values/translators.xml +++ b/app/src/main/res/values/translators.xml @@ -47,6 +47,7 @@ Vishal Nehra Emmanuel Messulam Raymond Lai + Vishnu Sanal T Team MoKee CookIcons.co From b3efbfdac089d81e9a69d56853795d7f773f3429 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sun, 10 Dec 2023 13:11:12 +0530 Subject: [PATCH 02/55] update cc email list --- app/src/main/java/com/amaze/filemanager/utils/Utils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/utils/Utils.java b/app/src/main/java/com/amaze/filemanager/utils/Utils.java index 0c5d642b35..bc50b16de3 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/Utils.java +++ b/app/src/main/java/com/amaze/filemanager/utils/Utils.java @@ -89,6 +89,7 @@ public class Utils { private static final String DATE_TIME_FORMAT = "%s | %s"; private static final String EMAIL_EMMANUEL = "emmanuelbendavid@gmail.com"; private static final String EMAIL_RAYMOND = "airwave209gt@gmail.com"; + private static final String EMAIL_VISHNU = "t.v.s10123@gmail.com"; private static final String EMAIL_VISHAL = "vishalmeham2@gmail.com"; private static final String URL_TELEGRAM = "https://t.me/AmazeFileManager"; private static final String URL_INSTGRAM = "https://www.instagram.com/teamamaze.xyz/"; @@ -426,7 +427,7 @@ public static void openInstagramURL(Context context) { public static Intent buildEmailIntent(Context context, String text, String supportMail) { Intent emailIntent = new Intent(Intent.ACTION_SEND); String[] aEmailList = {supportMail}; - String[] aEmailCCList = {EMAIL_VISHAL, EMAIL_EMMANUEL, EMAIL_RAYMOND}; + String[] aEmailCCList = {EMAIL_VISHAL, EMAIL_EMMANUEL, EMAIL_RAYMOND, EMAIL_VISHNU}; emailIntent.putExtra(Intent.EXTRA_EMAIL, aEmailList); emailIntent.putExtra(Intent.EXTRA_CC, aEmailCCList); emailIntent.putExtra( From 7af47b5475629bb36a3f920234035db622ced741 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sun, 10 Dec 2023 13:12:47 +0530 Subject: [PATCH 03/55] update README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6b18d24f5c..22427ec04e 100644 --- a/README.md +++ b/README.md @@ -85,9 +85,10 @@ We strongly recommend using apk signed by us (either Play Store version or from ### License: Copyright (C) 2014-2018 Arpit Khurana - Copyright (C) 2014-2021 Vishal Nehra - Copyright (C) 2017-2021 Emmanuel Messulam - Copyright (C) 2018-2021 Raymond Lai + Copyright (C) 2014-2023 Vishal Nehra + Copyright (C) 2017-2023 Emmanuel Messulam + Copyright (C) 2018-2023 Raymond Lai + Copyright (C) 2019-2023 Vishnu Sanal T This file is part of Amaze File Manager. Amaze File Manager is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From c0208ae4c51acc077f42894545c5fed3dfd6b806 Mon Sep 17 00:00:00 2001 From: Vishal Nehra Date: Thu, 11 Jan 2024 17:34:19 +0530 Subject: [PATCH 04/55] Build fix --- .../com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java b/app/src/main/java/com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java index 3b0597c2a2..f20e9d1f3e 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java @@ -441,7 +441,7 @@ public static void restoreFilesDialog( new MaterialDialog.Builder(context) .title(context.getString(R.string.restore_files)) .customView(dialogView, true) - .theme(appTheme.getMaterialDialogTheme(context)) + .theme(appTheme.getMaterialDialogTheme()) .negativeText(context.getString(R.string.cancel).toUpperCase()) .positiveText(context.getString(R.string.done).toUpperCase()) .positiveColor(accentColor) From a951d19f6beff47fa6d0ab731372cdddd339c855 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 11 Jan 2024 20:04:21 +0100 Subject: [PATCH 05/55] remove label for return --- .../com/amaze/filemanager/filesystem/files/FileListSorter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt index 9bd93791a2..6ebbcfb114 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt @@ -84,7 +84,7 @@ class FileListSorter( 0.0 } - return@compareBy 1.2 * matchPercentageScore + + 1.2 * matchPercentageScore + 0.7 * startScore + 0.7 * wordScore + 0.6 * timeScore From 98b776a8d6dae3a3ecc3f708fef35e6e59fda390 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 11 Jan 2024 20:14:12 +0100 Subject: [PATCH 06/55] formatting --- .../main/java/com/amaze/filemanager/filesystem/HybridFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java index 874ca0df50..a8bcfb18bd 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java @@ -46,8 +46,8 @@ import java.util.EnumSet; import java.util.List; import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; From 3943e24f46255c7a7c3e3dfcb9a39a857f1dfe79 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sun, 14 Jan 2024 20:58:18 +0100 Subject: [PATCH 07/55] convert SearchAsyncTask into Kotlin --- .../searchfilesystem/SearchAsyncTask.java | 218 ------------------ .../searchfilesystem/SearchAsyncTask.kt | 205 ++++++++++++++++ 2 files changed, 205 insertions(+), 218 deletions(-) delete mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.java create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.kt diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.java deleted file mode 100644 index ebfacc5aa9..0000000000 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem; - -import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES; - -import java.util.regex.Pattern; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.amaze.filemanager.asynchronous.asynctasks.StatefulAsyncTask; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; -import com.amaze.filemanager.filesystem.HybridFile; -import com.amaze.filemanager.filesystem.HybridFileParcelable; -import com.amaze.filemanager.ui.fragments.SearchWorkerFragment; - -import android.content.Context; -import android.os.AsyncTask; - -import androidx.preference.PreferenceManager; - -public class SearchAsyncTask extends AsyncTask - implements StatefulAsyncTask { - - private final Logger LOG = LoggerFactory.getLogger(SearchAsyncTask.class); - - /** This necessarily leaks the context */ - private final Context applicationContext; - - private SearchWorkerFragment.HelperCallbacks callbacks; - private final String input; - private final boolean rootMode; - private final boolean isRegexEnabled; - private final boolean isMatchesEnabled; - private final HybridFile file; - - public SearchAsyncTask( - Context context, - String input, - OpenMode openMode, - boolean root, - boolean regex, - boolean matches, - String path) { - this.applicationContext = context.getApplicationContext(); - this.input = input; - rootMode = root; - isRegexEnabled = regex; - isMatchesEnabled = matches; - - this.file = new HybridFile(openMode, path); - } - - @Override - protected void onPreExecute() { - /* - * Note that we need to check if the callbacks are null in each - * method in case they are invoked after the Activity's and - * Fragment's onDestroy() method have been called. - */ - if (callbacks != null) { - callbacks.onPreExecute(input); - } - } - - // callbacks not checked for null because of possibility of - // race conditions b/w worker thread main thread - @Override - protected Void doInBackground(Void... params) { - if (file.isSmb()) return null; - - // level 1 - // if regex or not - if (!isRegexEnabled) { - search(file, input); - } else { - // compile the regular expression in the input - Pattern pattern = Pattern.compile(bashRegexToJava(input)); - // level 2 - if (!isMatchesEnabled) searchRegExFind(file, pattern); - else searchRegExMatch(file, pattern); - } - return null; - } - - @Override - public void onPostExecute(Void c) { - if (callbacks != null) { - callbacks.onPostExecute(input); - } - } - - @Override - protected void onCancelled() { - if (callbacks != null) callbacks.onCancelled(); - } - - @Override - public void onProgressUpdate(HybridFileParcelable... val) { - if (!isCancelled() && callbacks != null) { - callbacks.onProgressUpdate(val[0], input); - } - } - - @Override - public void setCallback(SearchWorkerFragment.HelperCallbacks helperCallbacks) { - this.callbacks = helperCallbacks; - } - - /** - * Recursively search for occurrences of a given text in file names and publish the result - * - * @param directory the current path - */ - private void search(HybridFile directory, final SearchFilter filter) { - if (directory.isDirectory( - applicationContext)) { // do you have permission to read this directory? - directory.forEachChildrenFile( - applicationContext, - rootMode, - file -> { - boolean showHiddenFiles = - PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false); - - if ((!isCancelled() && (showHiddenFiles || !file.isHidden()))) { - if (filter.searchFilter(file.getName(applicationContext))) { - publishProgress(file); - } - if (file.isDirectory() && !isCancelled()) { - search(file, filter); - } - } - }); - } else { - LOG.warn("Cannot search " + directory.getPath() + ": Permission Denied"); - } - } - - /** - * Recursively search for occurrences of a given text in file names and publish the result - * - * @param file the current path - * @param query the searched text - */ - private void search(HybridFile file, final String query) { - search(file, fileName -> fileName.toLowerCase().contains(query.toLowerCase())); - } - - /** - * Recursively find a java regex pattern {@link Pattern} in the file names and publish the result - * - * @param file the current file - * @param pattern the compiled java regex - */ - private void searchRegExFind(HybridFile file, final Pattern pattern) { - search(file, fileName -> pattern.matcher(fileName).find()); - } - - /** - * Recursively match a java regex pattern {@link Pattern} with the file names and publish the - * result - * - * @param file the current file - * @param pattern the compiled java regex - */ - private void searchRegExMatch(HybridFile file, final Pattern pattern) { - search(file, fileName -> pattern.matcher(fileName).matches()); - } - - /** - * method converts bash style regular expression to java. See {@link Pattern} - * - * @return converted string - */ - private String bashRegexToJava(String originalString) { - StringBuilder stringBuilder = new StringBuilder(); - - for (int i = 0; i < originalString.length(); i++) { - switch (originalString.charAt(i) + "") { - case "*": - stringBuilder.append("\\w*"); - break; - case "?": - stringBuilder.append("\\w"); - break; - default: - stringBuilder.append(originalString.charAt(i)); - break; - } - } - - return stringBuilder.toString(); - } - - public interface SearchFilter { - boolean searchFilter(String fileName); - } -} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.kt new file mode 100644 index 0000000000..0c7f563082 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.kt @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.content.Context +import android.os.AsyncTask +import androidx.preference.PreferenceManager +import com.amaze.filemanager.asynchronous.asynctasks.StatefulAsyncTask +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.HybridFile +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.ui.fragments.SearchWorkerFragment.HelperCallbacks +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES +import com.amaze.filemanager.utils.OnFileFound +import org.slf4j.LoggerFactory +import java.util.Locale +import java.util.regex.Pattern + +class SearchAsyncTask( + context: Context, + private val input: String, + openMode: OpenMode?, + private val rootMode: Boolean, + private val isRegexEnabled: Boolean, + private val isMatchesEnabled: Boolean, + path: String? +) : AsyncTask(), StatefulAsyncTask { + private val LOG = LoggerFactory.getLogger(SearchAsyncTask::class.java) + + /** This necessarily leaks the context */ + private val applicationContext: Context + private var callbacks: HelperCallbacks? = null + private val file: HybridFile + + init { + applicationContext = context.applicationContext + file = HybridFile(openMode, path) + } + + override fun onPreExecute() { + /* + * Note that we need to check if the callbacks are null in each + * method in case they are invoked after the Activity's and + * Fragment's onDestroy() method have been called. + */ + if (callbacks != null) { + callbacks!!.onPreExecute(input) + } + } + + // callbacks not checked for null because of possibility of + // race conditions b/w worker thread main thread + protected override fun doInBackground(vararg params: Void?): Void? { + if (file.isSmb) return null + + // level 1 + // if regex or not + if (!isRegexEnabled) { + search(file, input) + } else { + // compile the regular expression in the input + val pattern = Pattern.compile( + bashRegexToJava( + input + ) + ) + // level 2 + if (!isMatchesEnabled) searchRegExFind(file, pattern) else searchRegExMatch( + file, + pattern + ) + } + return null + } + + public override fun onPostExecute(c: Void?) { + if (callbacks != null) { + callbacks!!.onPostExecute(input) + } + } + + override fun onCancelled() { + if (callbacks != null) callbacks!!.onCancelled() + } + + override fun onProgressUpdate(vararg values: HybridFileParcelable?) { + if (!isCancelled && callbacks != null) { + values[0]?.let { callbacks!!.onProgressUpdate(it, input) } + } + } + + override fun setCallback(helperCallbacks: HelperCallbacks?) { + callbacks = helperCallbacks + } + + /** + * Recursively search for occurrences of a given text in file names and publish the result + * + * @param directory the current path + */ + private fun search(directory: HybridFile, filter: SearchFilter) { + if (directory.isDirectory( + applicationContext + ) + ) { // do you have permission to read this directory? + directory.forEachChildrenFile( + applicationContext, + rootMode, + object : OnFileFound { + override fun onFileFound(file: HybridFileParcelable) { + val showHiddenFiles = + PreferenceManager.getDefaultSharedPreferences(applicationContext) + .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) + if (!isCancelled && (showHiddenFiles || !file.isHidden)) { + if (filter.searchFilter(file.getName(applicationContext))) { + publishProgress(file) + } + if (file.isDirectory && !isCancelled) { + search(file, filter) + } + } + } + } + ) + } else { + LOG.warn("Cannot search " + directory.path + ": Permission Denied") + } + } + + /** + * Recursively search for occurrences of a given text in file names and publish the result + * + * @param file the current path + * @param query the searched text + */ + private fun search(file: HybridFile, query: String) { + search(file) { fileName: String -> + fileName.lowercase(Locale.getDefault()).contains( + query.lowercase( + Locale.getDefault() + ) + ) + } + } + + /** + * Recursively find a java regex pattern [Pattern] in the file names and publish the result + * + * @param file the current file + * @param pattern the compiled java regex + */ + private fun searchRegExFind(file: HybridFile, pattern: Pattern) { + search(file) { fileName: String? -> pattern.matcher(fileName).find() } + } + + /** + * Recursively match a java regex pattern [Pattern] with the file names and publish the + * result + * + * @param file the current file + * @param pattern the compiled java regex + */ + private fun searchRegExMatch(file: HybridFile, pattern: Pattern) { + search(file) { fileName: String? -> pattern.matcher(fileName).matches() } + } + + /** + * method converts bash style regular expression to java. See [Pattern] + * + * @return converted string + */ + private fun bashRegexToJava(originalString: String): String { + val stringBuilder = StringBuilder() + for (i in originalString.indices) { + when (originalString[i].toString() + "") { + "*" -> stringBuilder.append("\\w*") + "?" -> stringBuilder.append("\\w") + else -> stringBuilder.append(originalString[i]) + } + } + return stringBuilder.toString() + } + + fun interface SearchFilter { + fun searchFilter(fileName: String): Boolean + } +} From 7ad2e16f37897c4531ebf382050830c2403d6b3d Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 10 Jan 2024 21:10:45 +0100 Subject: [PATCH 08/55] rename SearchAsyncTask to DeepSearch and change it to work with coroutines --- .../{SearchAsyncTask.kt => DeepSearch.kt} | 128 +++++++----------- .../ui/fragments/SearchWorkerFragment.java | 6 +- .../amaze/filemanager/utils/OnFileFound.kt | 2 +- 3 files changed, 55 insertions(+), 81 deletions(-) rename app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/{SearchAsyncTask.kt => DeepSearch.kt} (54%) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt similarity index 54% rename from app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.kt rename to app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt index 0c7f563082..8c4af3dbe3 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchAsyncTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , * Emmanuel Messulam, Raymond Lai and Contributors. * * This file is part of Amaze File Manager. @@ -21,65 +21,59 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem import android.content.Context -import android.os.AsyncTask -import androidx.preference.PreferenceManager -import com.amaze.filemanager.asynchronous.asynctasks.StatefulAsyncTask +import androidx.lifecycle.MutableLiveData import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile import com.amaze.filemanager.filesystem.HybridFileParcelable -import com.amaze.filemanager.ui.fragments.SearchWorkerFragment.HelperCallbacks -import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES -import com.amaze.filemanager.utils.OnFileFound +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.isActive +import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory import java.util.Locale import java.util.regex.Pattern -class SearchAsyncTask( +class DeepSearch( context: Context, - private val input: String, - openMode: OpenMode?, + private val query: String, + private val path: String, + private val openMode: OpenMode, private val rootMode: Boolean, private val isRegexEnabled: Boolean, private val isMatchesEnabled: Boolean, - path: String? -) : AsyncTask(), StatefulAsyncTask { - private val LOG = LoggerFactory.getLogger(SearchAsyncTask::class.java) + private val showHiddenFiles: Boolean, + private val ioDispatcher: CoroutineDispatcher +) { + private val LOG = LoggerFactory.getLogger(DeepSearch::class.java) - /** This necessarily leaks the context */ + private val hybridFileParcelables: ArrayList = ArrayList() + val mutableLiveData: MutableLiveData> = MutableLiveData( + hybridFileParcelables + ) + + /** This necessarily leaks the context */ private val applicationContext: Context - private var callbacks: HelperCallbacks? = null - private val file: HybridFile init { applicationContext = context.applicationContext - file = HybridFile(openMode, path) } - override fun onPreExecute() { - /* - * Note that we need to check if the callbacks are null in each - * method in case they are invoked after the Activity's and - * Fragment's onDestroy() method have been called. + /** + * Search for files, whose names match [query], starting from [path] and add them to + * [mutableLiveData]. */ - if (callbacks != null) { - callbacks!!.onPreExecute(input) - } - } - - // callbacks not checked for null because of possibility of - // race conditions b/w worker thread main thread - protected override fun doInBackground(vararg params: Void?): Void? { - if (file.isSmb) return null + suspend fun search() { + val file = HybridFile(openMode, path) + if (file.isSmb) return // level 1 // if regex or not if (!isRegexEnabled) { - search(file, input) + search(file, query) } else { // compile the regular expression in the input val pattern = Pattern.compile( bashRegexToJava( - input + query ) ) // level 2 @@ -88,70 +82,50 @@ class SearchAsyncTask( pattern ) } - return null - } - - public override fun onPostExecute(c: Void?) { - if (callbacks != null) { - callbacks!!.onPostExecute(input) - } - } - - override fun onCancelled() { - if (callbacks != null) callbacks!!.onCancelled() - } - - override fun onProgressUpdate(vararg values: HybridFileParcelable?) { - if (!isCancelled && callbacks != null) { - values[0]?.let { callbacks!!.onProgressUpdate(it, input) } - } - } - - override fun setCallback(helperCallbacks: HelperCallbacks?) { - callbacks = helperCallbacks } /** - * Recursively search for occurrences of a given text in file names and publish the result + * Search for occurrences of a given text in file names and publish the result * * @param directory the current path */ - private fun search(directory: HybridFile, filter: SearchFilter) { - if (directory.isDirectory( - applicationContext - ) - ) { // do you have permission to read this directory? - directory.forEachChildrenFile( - applicationContext, - rootMode, - object : OnFileFound { - override fun onFileFound(file: HybridFileParcelable) { - val showHiddenFiles = - PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) - if (!isCancelled && (showHiddenFiles || !file.isHidden)) { + private suspend fun search(directory: HybridFile, filter: SearchFilter) { + if (directory.isDirectory(applicationContext)) { + // you have permission to read this directory + withContext(ioDispatcher) { + val worklist = ArrayDeque() + worklist.add(directory) + while (isActive && worklist.isNotEmpty()) { + val nextFile = worklist.removeFirst() + nextFile.forEachChildrenFile(applicationContext, rootMode) { file -> + if (!file.isHidden || showHiddenFiles) { if (filter.searchFilter(file.getName(applicationContext))) { publishProgress(file) } - if (file.isDirectory && !isCancelled) { - search(file, filter) + if (file.isDirectory(applicationContext)) { + worklist.add(file) } } } } - ) + } } else { LOG.warn("Cannot search " + directory.path + ": Permission Denied") } } + private fun publishProgress(file: HybridFileParcelable) { + hybridFileParcelables.add(file) + mutableLiveData.postValue(hybridFileParcelables) + } + /** * Recursively search for occurrences of a given text in file names and publish the result * * @param file the current path * @param query the searched text */ - private fun search(file: HybridFile, query: String) { + private suspend fun search(file: HybridFile, query: String) { search(file) { fileName: String -> fileName.lowercase(Locale.getDefault()).contains( query.lowercase( @@ -167,8 +141,8 @@ class SearchAsyncTask( * @param file the current file * @param pattern the compiled java regex */ - private fun searchRegExFind(file: HybridFile, pattern: Pattern) { - search(file) { fileName: String? -> pattern.matcher(fileName).find() } + private suspend fun searchRegExFind(file: HybridFile, pattern: Pattern) { + search(file) { fileName: String -> pattern.matcher(fileName).find() } } /** @@ -178,8 +152,8 @@ class SearchAsyncTask( * @param file the current file * @param pattern the compiled java regex */ - private fun searchRegExMatch(file: HybridFile, pattern: Pattern) { - search(file) { fileName: String? -> pattern.matcher(fileName).matches() } + private suspend fun searchRegExMatch(file: HybridFile, pattern: Pattern) { + search(file) { fileName: String -> pattern.matcher(fileName).matches() } } /** diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/SearchWorkerFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/SearchWorkerFragment.java index f3d3b2207b..45ee5cea93 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/SearchWorkerFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/SearchWorkerFragment.java @@ -20,7 +20,7 @@ package com.amaze.filemanager.ui.fragments; -import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchAsyncTask; +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.DeepSearch; import com.amaze.filemanager.fileoperations.filesystem.OpenMode; import com.amaze.filemanager.filesystem.HybridFileParcelable; @@ -46,7 +46,7 @@ public class SearchWorkerFragment extends Fragment { public static final String KEY_REGEX = "regex"; public static final String KEY_REGEX_MATCHES = "matches"; - public SearchAsyncTask searchAsyncTask; + public DeepSearch searchAsyncTask; private HelperCallbacks callbacks; @@ -82,7 +82,7 @@ public void onCreate(Bundle savedInstanceState) { boolean isMatchesEnabled = getArguments().getBoolean(KEY_REGEX_MATCHES); searchAsyncTask = - new SearchAsyncTask( + new DeepSearch( requireContext(), input, openMode, rootMode, isRegexEnabled, isMatchesEnabled, path); searchAsyncTask.setCallback(callbacks); searchAsyncTask.execute(); diff --git a/app/src/main/java/com/amaze/filemanager/utils/OnFileFound.kt b/app/src/main/java/com/amaze/filemanager/utils/OnFileFound.kt index f593791338..4c4b68fabf 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/OnFileFound.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/OnFileFound.kt @@ -27,7 +27,7 @@ import com.amaze.filemanager.filesystem.HybridFileParcelable * * @author Emmanuel on 21/9/2017, at 15:23. */ -interface OnFileFound { +fun interface OnFileFound { @Suppress("UndocumentedPublicFunction") fun onFileFound(file: HybridFileParcelable) } From 53c85dadb66dfb31861b10df36a0bd4b5712ad83 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 10 Jan 2024 21:14:36 +0100 Subject: [PATCH 09/55] add deepSearch method to MainActivityViewModel --- .../ui/activities/MainActivityViewModel.kt | 40 +++++++++++++++++++ .../PreferencesConstants.kt | 2 + 2 files changed, 42 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 9d66f0141e..f3c2d067d4 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -31,12 +31,15 @@ import androidx.preference.PreferenceManager import com.amaze.filemanager.R import com.amaze.filemanager.adapters.data.LayoutElementParcelable import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.DeepSearch import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.files.MediaConnectionUtils.scanFile import com.amaze.filemanager.filesystem.root.ListFilesCommand.listFiles +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_REGEX +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_REGEX_MATCHES import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES import com.amaze.trashbin.MoveFilesCallback import com.amaze.trashbin.TrashBinFile @@ -185,6 +188,43 @@ class MainActivityViewModel(val applicationContext: Application) : return mutableLiveData } + /** + * Perform deep search: recursively search for + */ + fun deepSearch( + mainActivity: MainActivity, + query: String + ): MutableLiveData> { + val sharedPref = PreferenceManager.getDefaultSharedPreferences(mainActivity) + val showHiddenFiles = sharedPref.getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) + val isRegexEnabled = sharedPref.getBoolean(PREFERENCE_REGEX, false) + val isMatchesEnabled = sharedPref.getBoolean(PREFERENCE_REGEX_MATCHES, false) + + val path = mainActivity.currentMainFragment?.currentPath ?: "" + val openMode = + mainActivity.currentMainFragment?.mainFragmentViewModel?.openMode ?: OpenMode.FILE + + val context = this.applicationContext + + val deepSearch = DeepSearch( + context, + query, + path, + openMode, + mainActivity.isRootExplorer, + isRegexEnabled, + isMatchesEnabled, + showHiddenFiles, + Dispatchers.IO + ) + + viewModelScope.launch(Dispatchers.IO) { + deepSearch.search() + } + + return deepSearch.mutableLiveData + } + /** * TODO: Documentation */ diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/PreferencesConstants.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/PreferencesConstants.kt index 961fee9c54..5a02a269b2 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/PreferencesConstants.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/PreferencesConstants.kt @@ -82,6 +82,8 @@ object PreferencesConstants { const val PREFERENCE_TRASH_BIN_RETENTION_DAYS = "retention_days" const val PREFERENCE_TRASH_BIN_RETENTION_BYTES = "retention_bytes" const val PREFERENCE_TRASH_BIN_CLEANUP_INTERVAL = "cleanup_interval" + const val PREFERENCE_REGEX = "regex" + const val PREFERENCE_REGEX_MATCHES = "matches" // security_prefs.xml const val PREFERENCE_CRYPT_FINGERPRINT = "crypt_fingerprint" From b4053858a4b60b437563ceb880aed47beaeaf721 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 10 Jan 2024 21:15:21 +0100 Subject: [PATCH 10/55] integrate deepSearch results into SearchView --- .../amaze/filemanager/ui/views/appbar/SearchView.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index ee427be16f..631c741b6e 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -223,8 +223,13 @@ public void afterTextChanged(Editable s) {} } else if (searchMode == 2) { - searchListener.onSearch(s); - appbar.getSearchView().hideSearchView(); + mainActivity + .getCurrentMainFragment() + .getMainActivityViewModel() + .deepSearch(mainActivity, s) + .observe( + mainActivity.getCurrentMainFragment().getViewLifecycleOwner(), + hybridFileParcelables -> updateResultList(hybridFileParcelables, s)); deepSearchTV.setVisibility(View.GONE); } From f678496de770d9cab6492b1359c866b906b4b464 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 10 Jan 2024 21:16:57 +0100 Subject: [PATCH 11/55] remove unused SearchListener interface --- .../amaze/filemanager/ui/activities/MainActivity.java | 10 +--------- .../com/amaze/filemanager/ui/views/appbar/AppBar.java | 5 ++--- .../amaze/filemanager/ui/views/appbar/SearchView.java | 6 +----- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 24ccb65341..793adae85b 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -1711,15 +1711,7 @@ void initialisePreferences() { void initialiseViews() { - appbar = - new AppBar( - this, - getPrefs(), - queue -> { - if (!queue.isEmpty()) { - mainActivityHelper.search(getPrefs(), queue); - } - }); + appbar = new AppBar(this, getPrefs()); appBarLayout = getAppbar().getAppbarLayout(); setSupportActionBar(getAppbar().getToolbar()); diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/AppBar.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/AppBar.java index b090d0df6a..12c17004d2 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/AppBar.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/AppBar.java @@ -50,10 +50,9 @@ public class AppBar { private AppBarLayout appbarLayout; - public AppBar( - MainActivity a, SharedPreferences sharedPref, SearchView.SearchListener searchListener) { + public AppBar(MainActivity a, SharedPreferences sharedPref) { toolbar = a.findViewById(R.id.action_bar); - searchView = new SearchView(this, a, searchListener); + searchView = new SearchView(this, a); bottomBar = new BottomBar(this, a); appbarLayout = a.findViewById(R.id.lin); diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 631c741b6e..df8a9e5fdc 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -127,7 +127,7 @@ public class SearchView { @SuppressWarnings("ConstantConditions") @SuppressLint("NotifyDataSetChanged") - public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener searchListener) { + public SearchView(final AppBar appbar, MainActivity mainActivity) { this.mainActivity = mainActivity; this.appbar = appbar; @@ -627,8 +627,4 @@ private SpannableString getSpannableText(String s1, String s2) { private String getSearchTerm() { return searchViewEditText.getText().toString().trim(); } - - public interface SearchListener { - void onSearch(String queue); - } } From 048f53f4dc8d40269794b159e38a479d83cab9eb Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 10 Jan 2024 22:48:40 +0100 Subject: [PATCH 12/55] remove things related to old deep search --- .../SortSearchResultCallable.kt | 35 --- .../searchfilesystem/SortSearchResultTask.kt | 78 ----- .../ui/activities/MainActivity.java | 48 --- .../fragments/CompressedExplorerFragment.kt | 2 +- .../ui/fragments/MainFragment.java | 292 ++++-------------- .../ui/fragments/SearchWorkerFragment.java | 104 ------- .../filemanager/ui/fragments/TabFragment.java | 2 - .../fragments/data/MainFragmentViewModel.kt | 4 - .../ui/views/appbar/BottomBar.java | 9 +- .../filemanager/utils/MainActivityHelper.java | 90 ------ 10 files changed, 60 insertions(+), 604 deletions(-) delete mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultCallable.kt delete mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultTask.kt delete mode 100644 app/src/main/java/com/amaze/filemanager/ui/fragments/SearchWorkerFragment.java diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultCallable.kt deleted file mode 100644 index f87788ff59..0000000000 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultCallable.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem - -import com.amaze.filemanager.adapters.data.LayoutElementParcelable -import com.amaze.filemanager.filesystem.files.FileListSorter -import java.util.* -import java.util.concurrent.Callable - -class SortSearchResultCallable( - val elements: MutableList, - val sorter: FileListSorter -) : Callable { - override fun call() { - Collections.sort(elements, sorter) - } -} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultTask.kt deleted file mode 100644 index 18d159217d..0000000000 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SortSearchResultTask.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem - -import com.amaze.filemanager.R -import com.amaze.filemanager.adapters.data.LayoutElementParcelable -import com.amaze.filemanager.asynchronous.asynctasks.Task -import com.amaze.filemanager.filesystem.files.FileListSorter -import com.amaze.filemanager.ui.fragments.MainFragment -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class SortSearchResultTask( - val elements: MutableList, - val sorter: FileListSorter, - val mainFragment: MainFragment, - val query: String -) : Task { - - private val log: Logger = LoggerFactory.getLogger(SortSearchResultTask::class.java) - - private val task = SortSearchResultCallable(elements, sorter) - - override fun getTask(): SortSearchResultCallable = task - - override fun onError(error: Throwable) { - log.error("Could not sort search results because of exception", error) - } - - override fun onFinish(value: Unit) { - val mainFragmentViewModel = mainFragment.mainFragmentViewModel - - if (mainFragmentViewModel == null) { - log.error( - "Could not show sorted search results because main fragment view model is null" - ) - return - } - - val mainActivity = mainFragment.mainActivity - - if (mainActivity == null) { - log.error("Could not show sorted search results because main activity is null") - return - } - - mainFragment.reloadListElements( - true, - true, - !mainFragmentViewModel.isList - ) // TODO: 7/7/2017 this is really inneffient, use RecycleAdapter's - - // createHeaders() - mainActivity.appbar.bottomBar.setPathText("") - mainActivity - .appbar - .bottomBar.fullPathText = mainActivity.getString(R.string.search_results, query) - mainFragmentViewModel.results = false - } -} diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 793adae85b..33a212b751 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -123,7 +123,6 @@ import com.amaze.filemanager.ui.fragments.FtpServerFragment; import com.amaze.filemanager.ui.fragments.MainFragment; import com.amaze.filemanager.ui.fragments.ProcessViewerFragment; -import com.amaze.filemanager.ui.fragments.SearchWorkerFragment; import com.amaze.filemanager.ui.fragments.TabFragment; import com.amaze.filemanager.ui.fragments.data.MainFragmentViewModel; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; @@ -213,7 +212,6 @@ public class MainActivity extends PermissionsActivity implements SmbConnectionListener, BookmarkCallback, - SearchWorkerFragment.HelperCallbacks, CloudConnectionCallbacks, LoaderManager.LoaderCallbacks, FolderChooserDialog.FolderCallback, @@ -1083,7 +1081,6 @@ public boolean onPrepareOptionsMenu(Menu menu) { .updatePath( mainFragment.getCurrentPath(), mainFragment.getMainFragmentViewModel().getResults(), - MainActivityHelper.SEARCH_TEXT, mainFragment.getMainFragmentViewModel().getOpenMode(), mainFragment.getMainFragmentViewModel().getFolderCount(), mainFragment.getMainFragmentViewModel().getFileCount(), @@ -2235,51 +2232,6 @@ public void modify(String oldpath, String oldname, String newPath, String newnam .subscribe(() -> drawer.refreshDrawer()); } - @Override - public void onPreExecute(String query) { - executeWithMainFragment( - mainFragment -> { - mainFragment.mSwipeRefreshLayout.setRefreshing(true); - mainFragment.onSearchPreExecute(query); - return null; - }); - } - - @Override - public void onPostExecute(String query) { - final MainFragment mainFragment = getCurrentMainFragment(); - if (mainFragment == null) { - // TODO cancel search - return; - } - - mainFragment.onSearchCompleted(query); - mainFragment.mSwipeRefreshLayout.setRefreshing(false); - } - - @Override - public void onProgressUpdate(@NonNull HybridFileParcelable hybridFileParcelable, String query) { - final MainFragment mainFragment = getCurrentMainFragment(); - if (mainFragment == null) { - // TODO cancel search - return; - } - - mainFragment.addSearchResult(hybridFileParcelable, query); - } - - @Override - public void onCancelled() { - final MainFragment mainFragment = getCurrentMainFragment(); - if (mainFragment == null) { - return; - } - - mainFragment.reloadListElements( - false, false, !mainFragment.getMainFragmentViewModel().isList()); - mainFragment.mSwipeRefreshLayout.setRefreshing(false); - } - @Override public void addConnection(OpenMode service) { try { diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/CompressedExplorerFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/CompressedExplorerFragment.kt index 29412de076..9fdac35c0a 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/CompressedExplorerFragment.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/CompressedExplorerFragment.kt @@ -663,7 +663,7 @@ class CompressedExplorerFragment : Fragment(), BottomBarButtonPath { requireMainActivity() .getAppbar() .bottomBar - .updatePath(path, false, null, OpenMode.FILE, folder, file, this) + .updatePath(path, false, OpenMode.FILE, folder, file, this) } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index ce9f91edb6..9e06b815ea 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -50,8 +50,6 @@ import com.amaze.filemanager.application.AppConfig; import com.amaze.filemanager.asynchronous.asynctasks.DeleteTask; import com.amaze.filemanager.asynchronous.asynctasks.LoadFilesListTask; -import com.amaze.filemanager.asynchronous.asynctasks.TaskKt; -import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SortSearchResultTask; import com.amaze.filemanager.asynchronous.handlers.FileHandler; import com.amaze.filemanager.database.SortHandler; import com.amaze.filemanager.database.models.explorer.Tab; @@ -63,7 +61,6 @@ import com.amaze.filemanager.filesystem.SafRootHolder; import com.amaze.filemanager.filesystem.files.CryptUtil; import com.amaze.filemanager.filesystem.files.EncryptDecryptUtils; -import com.amaze.filemanager.filesystem.files.FileListSorter; import com.amaze.filemanager.filesystem.files.FileUtils; import com.amaze.filemanager.filesystem.files.MediaConnectionUtils; import com.amaze.filemanager.ui.ExtensionsKt; @@ -84,7 +81,6 @@ import com.amaze.filemanager.utils.BottomBarButtonPath; import com.amaze.filemanager.utils.DataUtils; import com.amaze.filemanager.utils.GenericExtKt; -import com.amaze.filemanager.utils.MainActivityHelper; import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.Utils; import com.google.android.material.appbar.AppBarLayout; @@ -444,25 +440,6 @@ public void onListItemClicked( int position, LayoutElementParcelable layoutElementParcelable, AppCompatImageView imageView) { - if (mainFragmentViewModel.getResults()) { - // check to initialize search results - // if search task is been running, cancel it - FragmentManager fragmentManager = requireActivity().getSupportFragmentManager(); - SearchWorkerFragment fragment = - (SearchWorkerFragment) fragmentManager.findFragmentByTag(MainActivity.TAG_ASYNC_HELPER); - if (fragment != null) { - if (fragment.searchAsyncTask.getStatus() == AsyncTask.Status.RUNNING) { - fragment.searchAsyncTask.cancel(true); - } - requireActivity().getSupportFragmentManager().beginTransaction().remove(fragment).commit(); - } - - mainFragmentViewModel.setRetainSearchTask(true); - mainFragmentViewModel.setResults(false); - } else { - mainFragmentViewModel.setRetainSearchTask(false); - MainActivityHelper.SEARCH_TEXT = null; - } if (requireMainActivity().getListItemSelected()) { if (isBackButton) { @@ -1108,125 +1085,83 @@ public void goBack() { HybridFile currentFile = new HybridFile(mainFragmentViewModel.getOpenMode(), mainFragmentViewModel.getCurrentPath()); if (!mainFragmentViewModel.getResults()) { - if (!mainFragmentViewModel.getRetainSearchTask()) { - // normal case - if (requireMainActivity().getListItemSelected()) { - adapter.toggleChecked(false); - } else { - if (OpenMode.SMB.equals(mainFragmentViewModel.getOpenMode())) { - if (mainFragmentViewModel.getSmbPath() != null - && !mainFragmentViewModel - .getSmbPath() - .equals(mainFragmentViewModel.getCurrentPath())) { - StringBuilder path = new StringBuilder(currentFile.getSmbFile().getParent()); - if (mainFragmentViewModel.getCurrentPath() != null - && mainFragmentViewModel.getCurrentPath().indexOf('?') > 0) - path.append( - mainFragmentViewModel - .getCurrentPath() - .substring(mainFragmentViewModel.getCurrentPath().indexOf('?'))); - loadlist( - path.toString().replace("%3D", "="), - true, - mainFragmentViewModel.getOpenMode(), - false); - } else loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); - } else if (OpenMode.SFTP.equals(mainFragmentViewModel.getOpenMode())) { - if (currentFile.getParent(requireContext()) == null) { - loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); - } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode())) { - loadlist(currentFile.getParent(getContext()), true, currentFile.getMode(), false); - } else { + if (requireMainActivity().getListItemSelected()) { + adapter.toggleChecked(false); + } else { + if (OpenMode.SMB.equals(mainFragmentViewModel.getOpenMode())) { + if (mainFragmentViewModel.getSmbPath() != null + && !mainFragmentViewModel + .getSmbPath() + .equals(mainFragmentViewModel.getCurrentPath())) { + StringBuilder path = new StringBuilder(currentFile.getSmbFile().getParent()); + if (mainFragmentViewModel.getCurrentPath() != null + && mainFragmentViewModel.getCurrentPath().indexOf('?') > 0) + path.append( + mainFragmentViewModel + .getCurrentPath() + .substring(mainFragmentViewModel.getCurrentPath().indexOf('?'))); + loadlist( + path.toString().replace("%3D", "="), + true, + mainFragmentViewModel.getOpenMode(), + false); + } else loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); + } else if (OpenMode.SFTP.equals(mainFragmentViewModel.getOpenMode())) { + if (currentFile.getParent(requireContext()) == null) { + loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); + } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode())) { + loadlist(currentFile.getParent(getContext()), true, currentFile.getMode(), false); + } else { - String parent = currentFile.getParent(getContext()); + String parent = currentFile.getParent(getContext()); - if (parent == null) - parent = - mainFragmentViewModel.getHome(); // fall back by traversing back to home folder + if (parent == null) + parent = + mainFragmentViewModel.getHome(); // fall back by traversing back to home folder + loadlist(parent, true, mainFragmentViewModel.getOpenMode(), false); + } + } else if (OpenMode.FTP.equals(mainFragmentViewModel.getOpenMode())) { + if (mainFragmentViewModel.getCurrentPath() != null) { + String parent = currentFile.getParent(getContext()); + // Hack. + if (parent != null && parent.contains("://")) { loadlist(parent, true, mainFragmentViewModel.getOpenMode(), false); - } - } else if (OpenMode.FTP.equals(mainFragmentViewModel.getOpenMode())) { - if (mainFragmentViewModel.getCurrentPath() != null) { - String parent = currentFile.getParent(getContext()); - // Hack. - if (parent != null && parent.contains("://")) { - loadlist(parent, true, mainFragmentViewModel.getOpenMode(), false); - } else { - loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); - } } else { loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); } - } else if (("/").equals(mainFragmentViewModel.getCurrentPath()) - || (mainFragmentViewModel.getHome() != null - && mainFragmentViewModel.getHome().equals(mainFragmentViewModel.getCurrentPath())) - || mainFragmentViewModel.getIsOnCloudRoot()) { - getMainActivity().exit(); - } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode()) - && !currentFile.getPath().startsWith("content://")) { - if (CollectionsKt.contains( - ANDROID_DEVICE_DATA_DIRS, currentFile.getParent(getContext()))) { - loadlist(currentFile.getParent(getContext()), false, OpenMode.ANDROID_DATA, false); - } else { - loadlist( - currentFile.getParent(getContext()), - true, - mainFragmentViewModel.getOpenMode(), - false); - } - } else if (FileUtils.canGoBack(getContext(), currentFile)) { + } else { + loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); + } + } else if (("/").equals(mainFragmentViewModel.getCurrentPath()) + || (mainFragmentViewModel.getHome() != null + && mainFragmentViewModel.getHome().equals(mainFragmentViewModel.getCurrentPath())) + || mainFragmentViewModel.getIsOnCloudRoot()) { + getMainActivity().exit(); + } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode()) + && !currentFile.getPath().startsWith("content://")) { + if (CollectionsKt.contains( + ANDROID_DEVICE_DATA_DIRS, currentFile.getParent(getContext()))) { + loadlist(currentFile.getParent(getContext()), false, OpenMode.ANDROID_DATA, false); + } else { loadlist( currentFile.getParent(getContext()), true, mainFragmentViewModel.getOpenMode(), false); - } else { - requireMainActivity().exit(); } - } - } else { - // case when we had pressed on an item from search results and wanna go back - // leads to resuming the search task - - if (MainActivityHelper.SEARCH_TEXT != null) { - - // starting the search query again :O - FragmentManager fm = requireMainActivity().getSupportFragmentManager(); - - // getting parent path to resume search from there - String parentPath = - new HybridFile( - mainFragmentViewModel.getOpenMode(), mainFragmentViewModel.getCurrentPath()) - .getParent(getActivity()); - // don't fuckin' remove this line, we need to change - // the path back to parent on back press - mainFragmentViewModel.setCurrentPath(parentPath); - - MainActivityHelper.addSearchFragment( - fm, - new SearchWorkerFragment(), - parentPath, - MainActivityHelper.SEARCH_TEXT, + } else if (FileUtils.canGoBack(getContext(), currentFile)) { + loadlist( + currentFile.getParent(getContext()), + true, mainFragmentViewModel.getOpenMode(), - requireMainActivity().isRootExplorer(), - sharedPref.getBoolean(SearchWorkerFragment.KEY_REGEX, false), - sharedPref.getBoolean(SearchWorkerFragment.KEY_REGEX_MATCHES, false)); + false); } else { - loadlist(mainFragmentViewModel.getCurrentPath(), true, OpenMode.UNKNOWN, false); + requireMainActivity().exit(); } - mainFragmentViewModel.setRetainSearchTask(false); } } else { // to go back after search list have been popped - FragmentManager fm = requireMainActivity().getSupportFragmentManager(); - SearchWorkerFragment fragment = - (SearchWorkerFragment) fm.findFragmentByTag(MainActivity.TAG_ASYNC_HELPER); - if (fragment != null) { - if (fragment.searchAsyncTask.getStatus() == AsyncTask.Status.RUNNING) { - fragment.searchAsyncTask.cancel(true); - } - } if (mainFragmentViewModel.getCurrentPath() != null) { loadlist( new File(mainFragmentViewModel.getCurrentPath()).getPath(), @@ -1405,58 +1340,6 @@ public ArrayList addToSmb( return smbFileList; } - // method to add search result entry to the LIST_ELEMENT arrayList - @Nullable - private LayoutElementParcelable addTo(@NonNull HybridFileParcelable hybridFileParcelable) { - if (DataUtils.getInstance().isFileHidden(hybridFileParcelable.getPath())) { - return null; - } - - if (hybridFileParcelable.isDirectory()) { - LayoutElementParcelable layoutElement = - new LayoutElementParcelable( - requireContext(), - hybridFileParcelable.getPath(), - hybridFileParcelable.getPermission(), - hybridFileParcelable.getLink(), - "", - 0, - true, - hybridFileParcelable.getDate() + "", - true, - getBoolean(PREFERENCE_SHOW_THUMB), - hybridFileParcelable.getMode()); - - mainFragmentViewModel.getListElements().add(layoutElement); - mainFragmentViewModel.setFolderCount(mainFragmentViewModel.getFolderCount() + 1); - return layoutElement; - } else { - long longSize = 0; - String size = ""; - if (hybridFileParcelable.getSize() != -1) { - longSize = hybridFileParcelable.getSize(); - size = Formatter.formatFileSize(getContext(), longSize); - } - - LayoutElementParcelable layoutElement = - new LayoutElementParcelable( - requireContext(), - hybridFileParcelable.getPath(), - hybridFileParcelable.getPermission(), - hybridFileParcelable.getLink(), - size, - longSize, - false, - hybridFileParcelable.getDate() + "", - false, - getBoolean(PREFERENCE_SHOW_THUMB), - hybridFileParcelable.getMode()); - mainFragmentViewModel.getListElements().add(layoutElement); - mainFragmentViewModel.setFileCount(mainFragmentViewModel.getFileCount() + 1); - return layoutElement; - } - } - @Override public void onDestroy() { super.onDestroy(); @@ -1519,65 +1402,6 @@ public void addShortcut(LayoutElementParcelable path) { ShortcutManagerCompat.requestPinShortcut(ctx, info, null); } - // This method is used to implement the modification for the pre Searching - public void onSearchPreExecute(String query) { - requireMainActivity().getAppbar().getBottomBar().setPathText(""); - requireMainActivity() - .getAppbar() - .getBottomBar() - .setFullPathText(getString(R.string.searching, query)); - } - - // adds search results based on result boolean. If false, the adapter is initialised with initial - // values, if true, new values are added to the adapter. - public void addSearchResult(@NonNull HybridFileParcelable hybridFileParcelable, String query) { - if (listView == null) { - return; - } - - // initially clearing the array for new result set - if (!mainFragmentViewModel.getResults()) { - mainFragmentViewModel.getListElements().clear(); - mainFragmentViewModel.setFileCount(0); - mainFragmentViewModel.setFolderCount(0); - } - - // adding new value to LIST_ELEMENTS - @Nullable LayoutElementParcelable layoutElementAdded = addTo(hybridFileParcelable); - if (!requireMainActivity() - .getAppbar() - .getBottomBar() - .getFullPathText() - .contains(getString(R.string.searching))) { - requireMainActivity() - .getAppbar() - .getBottomBar() - .setFullPathText(getString(R.string.searching, query)); - } - if (!mainFragmentViewModel.getResults()) { - reloadListElements(false, true, !mainFragmentViewModel.isList()); - requireMainActivity().getAppbar().getBottomBar().setPathText(""); - } else if (layoutElementAdded != null) { - adapter.addItem(layoutElementAdded); - } - stopAnimation(); - } - - public void onSearchCompleted(final String query) { - final List elements = mainFragmentViewModel.getListElements(); - if (!mainFragmentViewModel.getResults()) { - // no results were found - mainFragmentViewModel.getListElements().clear(); - } - TaskKt.fromTask( - new SortSearchResultTask( - elements, - new FileListSorter( - mainFragmentViewModel.getDsort(), mainFragmentViewModel.getSortType()), - this, - query)); - } - @Override public void onDetach() { super.onDetach(); diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/SearchWorkerFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/SearchWorkerFragment.java deleted file mode 100644 index 45ee5cea93..0000000000 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/SearchWorkerFragment.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.ui.fragments; - -import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.DeepSearch; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; -import com.amaze.filemanager.filesystem.HybridFileParcelable; - -import android.content.Context; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -/** - * Worker fragment designed to not be destroyed when the activity holding it is recreated (aka the - * state changes like screen rotation) thus maintaining alive an AsyncTask (SearchTask in this case) - * - *

Created by vishal on 26/2/16 edited by EmmanuelMess. - */ -public class SearchWorkerFragment extends Fragment { - - public static final String KEY_PATH = "path"; - public static final String KEY_INPUT = "input"; - public static final String KEY_OPEN_MODE = "open_mode"; - public static final String KEY_ROOT_MODE = "root_mode"; - public static final String KEY_REGEX = "regex"; - public static final String KEY_REGEX_MATCHES = "matches"; - - public DeepSearch searchAsyncTask; - - private HelperCallbacks callbacks; - - // interface for activity to communicate with asynctask - public interface HelperCallbacks { - void onPreExecute(String query); - - void onPostExecute(String query); - - void onProgressUpdate(@NonNull HybridFileParcelable val, String query); - - void onCancelled(); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - - // hold instance of activity as there is a change in device configuration - callbacks = (HelperCallbacks) context; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setRetainInstance(true); - String path = getArguments().getString(KEY_PATH); - String input = getArguments().getString(KEY_INPUT); - OpenMode openMode = OpenMode.getOpenMode(getArguments().getInt(KEY_OPEN_MODE)); - boolean rootMode = getArguments().getBoolean(KEY_ROOT_MODE); - boolean isRegexEnabled = getArguments().getBoolean(KEY_REGEX); - boolean isMatchesEnabled = getArguments().getBoolean(KEY_REGEX_MATCHES); - - searchAsyncTask = - new DeepSearch( - requireContext(), input, openMode, rootMode, isRegexEnabled, isMatchesEnabled, path); - searchAsyncTask.setCallback(callbacks); - searchAsyncTask.execute(); - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - searchAsyncTask.setCallback(callbacks); - } - - @Override - public void onDetach() { - super.onDetach(); - - // to avoid activity instance leak while changing activity configurations - callbacks = null; - } -} diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java index 1e7ffe6be9..b68ed80df2 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java @@ -46,7 +46,6 @@ import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.views.Indicator; import com.amaze.filemanager.utils.DataUtils; -import com.amaze.filemanager.utils.MainActivityHelper; import com.amaze.filemanager.utils.Utils; import android.animation.ArgbEvaluator; @@ -436,7 +435,6 @@ public void updateBottomBar(MainFragment mainFragment) { .updatePath( mainFragment.getCurrentPath(), mainFragment.getMainFragmentViewModel().getResults(), - MainActivityHelper.SEARCH_TEXT, mainFragment.getMainFragmentViewModel().getOpenMode(), mainFragment.getMainFragmentViewModel().getFolderCount(), mainFragment.getMainFragmentViewModel().getFileCount(), diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt index 71d80bafff..fc7a57e60e 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt @@ -88,10 +88,6 @@ class MainFragmentViewModel : ViewModel() { // defines the current visible tab, default either 0 or 1 // private int mCurrentTab; - /*boolean identifying if the search task should be re-run on back press after pressing on - any of the search result*/ - var retainSearchTask = false - /** boolean to identify if the view is a list or grid */ var isList = true var addHeader = false diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java index 0a72ee7ade..575a13c88c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java @@ -364,7 +364,6 @@ public void setVisibility(int visibility) { public void updatePath( @NonNull final String news, boolean results, - String query, OpenMode openmode, int folderCount, int fileCount, @@ -397,13 +396,7 @@ public void updatePath( newPath = news; } - if (!results) { - pathText.setText(mainActivity.getString(R.string.folderfilecount, folderCount, fileCount)); - } else { - fullPathText.setText(mainActivity.getString(R.string.search_results, query)); - pathText.setText(""); - return; - } + pathText.setText(mainActivity.getString(R.string.folderfilecount, folderCount, fileCount)); final String oldPath = fullPathText.getText().toString(); diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java index 5a5703da2a..110002bc82 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java @@ -62,8 +62,6 @@ import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation; import com.amaze.filemanager.ui.fragments.MainFragment; -import com.amaze.filemanager.ui.fragments.SearchWorkerFragment; -import com.amaze.filemanager.ui.fragments.TabFragment; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.views.WarnableTextInputValidator; import com.amaze.filemanager.utils.smb.SmbUtil; @@ -75,10 +73,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.os.AsyncTask; import android.os.Build; -import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Toast; @@ -89,8 +84,6 @@ import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatTextView; import androidx.documentfile.provider.DocumentFile; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; public class MainActivityHelper { @@ -102,12 +95,6 @@ public class MainActivityHelper { private int accentColor; private SpeedDialView.OnActionSelectedListener fabActionListener; - /* - * A static string which saves the last searched query. Used to retain search task after - * user presses back button from pressing on any list item of search results - */ - public static String SEARCH_TEXT; - public MainActivityHelper(MainActivity mainActivity) { this.mainActivity = mainActivity; accentColor = mainActivity.getAccent(); @@ -751,81 +738,4 @@ public String parseCloudPath(OpenMode serviceType, String path) { return path; } } - - /** - * Creates a fragment which will handle the search AsyncTask {@link SearchWorkerFragment} - * - * @param query the text query entered the by user - */ - public void search(SharedPreferences sharedPrefs, String query) { - TabFragment tabFragment = mainActivity.getTabFragment(); - if (tabFragment == null) { - Log.w(getClass().getSimpleName(), "Failed to search: tab fragment not available"); - return; - } - final MainFragment ma = (MainFragment) tabFragment.getCurrentTabFragment(); - if (ma == null || ma.getMainFragmentViewModel() == null) { - Log.w(getClass().getSimpleName(), "Failed to search: main fragment not available"); - return; - } - final String fpath = ma.getCurrentPath(); - - /*SearchTask task = new SearchTask(ma.searchHelper, ma, query); - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, fpath);*/ - // ma.searchTask = task; - SEARCH_TEXT = query; - FragmentManager fm = mainActivity.getSupportFragmentManager(); - SearchWorkerFragment fragment = - (SearchWorkerFragment) fm.findFragmentByTag(MainActivity.TAG_ASYNC_HELPER); - - if (fragment != null) { - if (fragment.searchAsyncTask.getStatus() == AsyncTask.Status.RUNNING) { - fragment.searchAsyncTask.cancel(true); - } - fm.beginTransaction().remove(fragment).commit(); - } - - addSearchFragment( - fm, - new SearchWorkerFragment(), - fpath, - query, - ma.getMainFragmentViewModel().getOpenMode(), - mainActivity.isRootExplorer(), - sharedPrefs.getBoolean(SearchWorkerFragment.KEY_REGEX, false), - sharedPrefs.getBoolean(SearchWorkerFragment.KEY_REGEX_MATCHES, false)); - } - - /** - * Adds a search fragment that can persist it's state on config change - * - * @param fragmentManager fragmentManager - * @param fragment current fragment - * @param path current path - * @param input query typed by user - * @param openMode defines the file type - * @param rootMode is root enabled - * @param regex is regular expression search enabled - * @param matches is matches enabled for patter matching - */ - public static void addSearchFragment( - @NonNull FragmentManager fragmentManager, - @NonNull Fragment fragment, - @NonNull String path, - @NonNull String input, - @NonNull OpenMode openMode, - boolean rootMode, - boolean regex, - boolean matches) { - Bundle args = new Bundle(); - args.putString(SearchWorkerFragment.KEY_INPUT, input); - args.putString(SearchWorkerFragment.KEY_PATH, path); - args.putInt(SearchWorkerFragment.KEY_OPEN_MODE, openMode.ordinal()); - args.putBoolean(SearchWorkerFragment.KEY_ROOT_MODE, rootMode); - args.putBoolean(SearchWorkerFragment.KEY_REGEX, regex); - args.putBoolean(SearchWorkerFragment.KEY_REGEX_MATCHES, matches); - - fragment.setArguments(args); - fragmentManager.beginTransaction().add(fragment, MainActivity.TAG_ASYNC_HELPER).commit(); - } } From 5eb1ccfb30244ec5fca191b95bc3bc37cc0f0b11 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 10 Jan 2024 23:35:07 +0100 Subject: [PATCH 13/55] delete results property of MainFragmentViewModel --- .../asynchronous/handlers/FileHandler.kt | 1 - .../ui/activities/MainActivity.java | 1 - .../fragments/CompressedExplorerFragment.kt | 2 +- .../ui/fragments/MainFragment.java | 194 ++++++++---------- .../filemanager/ui/fragments/TabFragment.java | 1 - .../fragments/data/MainFragmentViewModel.kt | 2 - .../ui/views/appbar/BottomBar.java | 4 +- .../utils/MainActivityActionMode.kt | 54 ++--- 8 files changed, 97 insertions(+), 162 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/handlers/FileHandler.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/handlers/FileHandler.kt index 513bc0e5d6..64069eb375 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/handlers/FileHandler.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/handlers/FileHandler.kt @@ -89,7 +89,6 @@ class FileHandler( // no item left in list, recreate views main.reloadListElements( true, - mainFragmentViewModel.results, !mainFragmentViewModel.isList ) } else { diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 33a212b751..825126b0a4 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -1080,7 +1080,6 @@ public boolean onPrepareOptionsMenu(Menu menu) { .getBottomBar() .updatePath( mainFragment.getCurrentPath(), - mainFragment.getMainFragmentViewModel().getResults(), mainFragment.getMainFragmentViewModel().getOpenMode(), mainFragment.getMainFragmentViewModel().getFolderCount(), mainFragment.getMainFragmentViewModel().getFileCount(), diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/CompressedExplorerFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/CompressedExplorerFragment.kt index 9fdac35c0a..975e1f52de 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/CompressedExplorerFragment.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/CompressedExplorerFragment.kt @@ -663,7 +663,7 @@ class CompressedExplorerFragment : Fragment(), BottomBarButtonPath { requireMainActivity() .getAppbar() .bottomBar - .updatePath(path, false, OpenMode.FILE, folder, file, this) + .updatePath(path, OpenMode.FILE, folder, file, this) } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index 9e06b815ea..a6275417f2 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -371,21 +371,19 @@ public void switchView() { DataUtils.getInstance() .getListOrGridForPath(mainFragmentViewModel.getCurrentPath(), DataUtils.LIST) == DataUtils.GRID; - reloadListElements(false, mainFragmentViewModel.getResults(), isPathLayoutGrid); + reloadListElements(false, isPathLayoutGrid); } private void loadViews() { if (mainFragmentViewModel.getCurrentPath() != null) { - if (mainFragmentViewModel.getListElements().size() == 0 - && !mainFragmentViewModel.getResults()) { + if (mainFragmentViewModel.getListElements().size() == 0) { loadlist( mainFragmentViewModel.getCurrentPath(), true, mainFragmentViewModel.getOpenMode(), false); } else { - reloadListElements( - true, mainFragmentViewModel.getResults(), !mainFragmentViewModel.isList()); + reloadListElements(true, !mainFragmentViewModel.isList()); } } else { loadlist(mainFragmentViewModel.getHome(), true, mainFragmentViewModel.getOpenMode(), false); @@ -670,8 +668,7 @@ else if (actualPath.startsWith("/") boolean isPathLayoutGrid = DataUtils.getInstance().getListOrGridForPath(providedPath, DataUtils.LIST) == DataUtils.GRID; - setListElements( - data.second, back, providedPath, data.first, false, isPathLayoutGrid); + setListElements(data.second, back, providedPath, data.first, isPathLayoutGrid); } else { LOG.warn("Load list operation cancelled"); } @@ -792,13 +789,12 @@ public void setListElements( boolean back, String path, final OpenMode openMode, - boolean results, boolean grid) { if (bitmap != null) { mainFragmentViewModel.setListElements(bitmap); mainFragmentViewModel.setCurrentPath(path); mainFragmentViewModel.setOpenMode(openMode); - reloadListElements(back, results, grid); + reloadListElements(back, grid); } else { // list loading cancelled // TODO: Add support for cancelling list loading @@ -806,9 +802,8 @@ public void setListElements( } } - public void reloadListElements(boolean back, boolean results, boolean grid) { + public void reloadListElements(boolean back, boolean grid) { if (isAdded()) { - mainFragmentViewModel.setResults(results); boolean isOtg = (OTGUtil.PREFIX_OTG + "/").equals(mainFragmentViewModel.getCurrentPath()); if (getBoolean(PREFERENCE_SHOW_GOBACK_BUTTON) @@ -823,12 +818,11 @@ public void reloadListElements(boolean back, boolean results, boolean grid) { .getListElements() .get(0) .size - .equals(getString(R.string.goback))) - && !results) { + .equals(getString(R.string.goback)))) { mainFragmentViewModel.getListElements().add(0, getBackElement()); } - if (mainFragmentViewModel.getListElements().size() == 0 && !results) { + if (mainFragmentViewModel.getListElements().size() == 0) { nofilesview.setVisibility(View.VISIBLE); listView.setVisibility(View.GONE); mSwipeRefreshLayout.setEnabled(false); @@ -1084,92 +1078,73 @@ public void goBack() { HybridFile currentFile = new HybridFile(mainFragmentViewModel.getOpenMode(), mainFragmentViewModel.getCurrentPath()); - if (!mainFragmentViewModel.getResults()) { - if (requireMainActivity().getListItemSelected()) { - adapter.toggleChecked(false); - } else { - if (OpenMode.SMB.equals(mainFragmentViewModel.getOpenMode())) { - if (mainFragmentViewModel.getSmbPath() != null - && !mainFragmentViewModel - .getSmbPath() - .equals(mainFragmentViewModel.getCurrentPath())) { - StringBuilder path = new StringBuilder(currentFile.getSmbFile().getParent()); - if (mainFragmentViewModel.getCurrentPath() != null - && mainFragmentViewModel.getCurrentPath().indexOf('?') > 0) - path.append( - mainFragmentViewModel - .getCurrentPath() - .substring(mainFragmentViewModel.getCurrentPath().indexOf('?'))); - loadlist( - path.toString().replace("%3D", "="), - true, - mainFragmentViewModel.getOpenMode(), - false); - } else loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); - } else if (OpenMode.SFTP.equals(mainFragmentViewModel.getOpenMode())) { - if (currentFile.getParent(requireContext()) == null) { - loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); - } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode())) { - loadlist(currentFile.getParent(getContext()), true, currentFile.getMode(), false); - } else { + if (requireMainActivity().getListItemSelected()) { + adapter.toggleChecked(false); + } else { + if (OpenMode.SMB.equals(mainFragmentViewModel.getOpenMode())) { + if (mainFragmentViewModel.getSmbPath() != null + && !mainFragmentViewModel.getSmbPath().equals(mainFragmentViewModel.getCurrentPath())) { + StringBuilder path = new StringBuilder(currentFile.getSmbFile().getParent()); + if (mainFragmentViewModel.getCurrentPath() != null + && mainFragmentViewModel.getCurrentPath().indexOf('?') > 0) + path.append( + mainFragmentViewModel + .getCurrentPath() + .substring(mainFragmentViewModel.getCurrentPath().indexOf('?'))); + loadlist( + path.toString().replace("%3D", "="), + true, + mainFragmentViewModel.getOpenMode(), + false); + } else loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); + } else if (OpenMode.SFTP.equals(mainFragmentViewModel.getOpenMode())) { + if (currentFile.getParent(requireContext()) == null) { + loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); + } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode())) { + loadlist(currentFile.getParent(getContext()), true, currentFile.getMode(), false); + } else { - String parent = currentFile.getParent(getContext()); + String parent = currentFile.getParent(getContext()); - if (parent == null) - parent = - mainFragmentViewModel.getHome(); // fall back by traversing back to home folder + if (parent == null) + parent = mainFragmentViewModel.getHome(); // fall back by traversing back to home folder + loadlist(parent, true, mainFragmentViewModel.getOpenMode(), false); + } + } else if (OpenMode.FTP.equals(mainFragmentViewModel.getOpenMode())) { + if (mainFragmentViewModel.getCurrentPath() != null) { + String parent = currentFile.getParent(getContext()); + // Hack. + if (parent != null && parent.contains("://")) { loadlist(parent, true, mainFragmentViewModel.getOpenMode(), false); - } - } else if (OpenMode.FTP.equals(mainFragmentViewModel.getOpenMode())) { - if (mainFragmentViewModel.getCurrentPath() != null) { - String parent = currentFile.getParent(getContext()); - // Hack. - if (parent != null && parent.contains("://")) { - loadlist(parent, true, mainFragmentViewModel.getOpenMode(), false); - } else { - loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); - } } else { loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); } - } else if (("/").equals(mainFragmentViewModel.getCurrentPath()) - || (mainFragmentViewModel.getHome() != null - && mainFragmentViewModel.getHome().equals(mainFragmentViewModel.getCurrentPath())) - || mainFragmentViewModel.getIsOnCloudRoot()) { - getMainActivity().exit(); - } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode()) - && !currentFile.getPath().startsWith("content://")) { - if (CollectionsKt.contains( - ANDROID_DEVICE_DATA_DIRS, currentFile.getParent(getContext()))) { - loadlist(currentFile.getParent(getContext()), false, OpenMode.ANDROID_DATA, false); - } else { - loadlist( - currentFile.getParent(getContext()), - true, - mainFragmentViewModel.getOpenMode(), - false); - } - } else if (FileUtils.canGoBack(getContext(), currentFile)) { + } else { + loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); + } + } else if (("/").equals(mainFragmentViewModel.getCurrentPath()) + || (mainFragmentViewModel.getHome() != null + && mainFragmentViewModel.getHome().equals(mainFragmentViewModel.getCurrentPath())) + || mainFragmentViewModel.getIsOnCloudRoot()) { + getMainActivity().exit(); + } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode()) + && !currentFile.getPath().startsWith("content://")) { + if (CollectionsKt.contains(ANDROID_DEVICE_DATA_DIRS, currentFile.getParent(getContext()))) { + loadlist(currentFile.getParent(getContext()), false, OpenMode.ANDROID_DATA, false); + } else { loadlist( currentFile.getParent(getContext()), true, mainFragmentViewModel.getOpenMode(), false); - } else { - requireMainActivity().exit(); } - } - } else { - // to go back after search list have been popped - if (mainFragmentViewModel.getCurrentPath() != null) { + } else if (FileUtils.canGoBack(getContext(), currentFile)) { loadlist( - new File(mainFragmentViewModel.getCurrentPath()).getPath(), - true, - OpenMode.UNKNOWN, - false); + currentFile.getParent(getContext()), true, mainFragmentViewModel.getOpenMode(), false); + } else { + requireMainActivity().exit(); } - mainFragmentViewModel.setResults(false); } } @@ -1206,36 +1181,27 @@ public void goBackItemClick() { } HybridFile currentFile = new HybridFile(mainFragmentViewModel.getOpenMode(), mainFragmentViewModel.getCurrentPath()); - if (!mainFragmentViewModel.getResults()) { - if (requireMainActivity().getListItemSelected()) { - adapter.toggleChecked(false); - } else { - if (mainFragmentViewModel.getOpenMode() == OpenMode.SMB) { - if (mainFragmentViewModel.getCurrentPath() != null - && !mainFragmentViewModel - .getCurrentPath() - .equals(mainFragmentViewModel.getSmbPath())) { - StringBuilder path = new StringBuilder(currentFile.getSmbFile().getParent()); - if (mainFragmentViewModel.getCurrentPath().indexOf('?') > 0) - path.append( - mainFragmentViewModel - .getCurrentPath() - .substring(mainFragmentViewModel.getCurrentPath().indexOf('?'))); - loadlist(path.toString(), true, OpenMode.SMB, false); - } else loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); - } else if (("/").equals(mainFragmentViewModel.getCurrentPath()) - || mainFragmentViewModel.getIsOnCloudRoot()) { - requireMainActivity().exit(); - } else if (FileUtils.canGoBack(getContext(), currentFile)) { - loadlist( - currentFile.getParent(getContext()), - true, - mainFragmentViewModel.getOpenMode(), - false); - } else requireMainActivity().exit(); - } + if (requireMainActivity().getListItemSelected()) { + adapter.toggleChecked(false); } else { - loadlist(currentFile.getPath(), true, mainFragmentViewModel.getOpenMode(), false); + if (mainFragmentViewModel.getOpenMode() == OpenMode.SMB) { + if (mainFragmentViewModel.getCurrentPath() != null + && !mainFragmentViewModel.getCurrentPath().equals(mainFragmentViewModel.getSmbPath())) { + StringBuilder path = new StringBuilder(currentFile.getSmbFile().getParent()); + if (mainFragmentViewModel.getCurrentPath().indexOf('?') > 0) + path.append( + mainFragmentViewModel + .getCurrentPath() + .substring(mainFragmentViewModel.getCurrentPath().indexOf('?'))); + loadlist(path.toString(), true, OpenMode.SMB, false); + } else loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); + } else if (("/").equals(mainFragmentViewModel.getCurrentPath()) + || mainFragmentViewModel.getIsOnCloudRoot()) { + requireMainActivity().exit(); + } else if (FileUtils.canGoBack(getContext(), currentFile)) { + loadlist( + currentFile.getParent(getContext()), true, mainFragmentViewModel.getOpenMode(), false); + } else requireMainActivity().exit(); } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java index b68ed80df2..8bcd4081de 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java @@ -434,7 +434,6 @@ public void updateBottomBar(MainFragment mainFragment) { .getBottomBar() .updatePath( mainFragment.getCurrentPath(), - mainFragment.getMainFragmentViewModel().getResults(), mainFragment.getMainFragmentViewModel().getOpenMode(), mainFragment.getMainFragmentViewModel().getFolderCount(), mainFragment.getMainFragmentViewModel().getFileCount(), diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt index fc7a57e60e..b99139943d 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt @@ -66,8 +66,6 @@ class MainFragmentViewModel : ViewModel() { var home: String? = null - var results: Boolean = false - lateinit var openMode: OpenMode // defines the current visible tab, default either 0 or 1 diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java index 575a13c88c..fe127201db 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java @@ -199,8 +199,7 @@ public void onLongPress(MotionEvent e) { final MainFragment mainFragment = mainActivity.getCurrentMainFragment(); Objects.requireNonNull(mainFragment); if (mainActivity.getBoolean(PREFERENCE_CHANGEPATHS) - && ((mainFragment.getMainFragmentViewModel() != null - && !mainFragment.getMainFragmentViewModel().getResults()) + && (mainFragment.getMainFragmentViewModel() != null || buttons.getVisibility() == View.VISIBLE)) { GeneralDialogCreation.showChangePathsDialog( mainActivity, mainActivity.getPrefs()); @@ -363,7 +362,6 @@ public void setVisibility(int visibility) { public void updatePath( @NonNull final String news, - boolean results, OpenMode openmode, int folderCount, int fileCount, diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt b/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt index 4cadc75af0..2c50641a14 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt @@ -151,48 +151,26 @@ class MainActivityActionMode(private val mainActivityReference: WeakReference - if (!mainFragmentViewModel.results) { - adapter.toggleChecked(false, mainFragmentViewModel.currentPath) - } else adapter.toggleChecked(false) + adapter.toggleChecked(false, mainFragmentViewModel.currentPath) mainActivity .updateViews( ColorDrawable( From f26bec15f1485a90a3bf43c25f5c6330bf289cee Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 11 Jan 2024 00:14:51 +0100 Subject: [PATCH 14/55] address most codacy issue --- .../asynctasks/searchfilesystem/DeepSearch.kt | 32 ++++++++----------- .../ui/activities/MainActivityViewModel.kt | 3 +- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt index 8c4af3dbe3..597fa2235d 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -25,12 +25,11 @@ import androidx.lifecycle.MutableLiveData import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile import com.amaze.filemanager.filesystem.HybridFileParcelable -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.isActive -import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory import java.util.Locale import java.util.regex.Pattern +import kotlin.coroutines.coroutineContext class DeepSearch( context: Context, @@ -40,8 +39,7 @@ class DeepSearch( private val rootMode: Boolean, private val isRegexEnabled: Boolean, private val isMatchesEnabled: Boolean, - private val showHiddenFiles: Boolean, - private val ioDispatcher: CoroutineDispatcher + private val showHiddenFiles: Boolean ) { private val LOG = LoggerFactory.getLogger(DeepSearch::class.java) @@ -50,7 +48,6 @@ class DeepSearch( hybridFileParcelables ) - /** This necessarily leaks the context */ private val applicationContext: Context init { @@ -92,19 +89,17 @@ class DeepSearch( private suspend fun search(directory: HybridFile, filter: SearchFilter) { if (directory.isDirectory(applicationContext)) { // you have permission to read this directory - withContext(ioDispatcher) { - val worklist = ArrayDeque() - worklist.add(directory) - while (isActive && worklist.isNotEmpty()) { - val nextFile = worklist.removeFirst() - nextFile.forEachChildrenFile(applicationContext, rootMode) { file -> - if (!file.isHidden || showHiddenFiles) { - if (filter.searchFilter(file.getName(applicationContext))) { - publishProgress(file) - } - if (file.isDirectory(applicationContext)) { - worklist.add(file) - } + val worklist = ArrayDeque() + worklist.add(directory) + while (coroutineContext.isActive && worklist.isNotEmpty()) { + val nextFile = worklist.removeFirst() + nextFile.forEachChildrenFile(applicationContext, rootMode) { file -> + if (!file.isHidden || showHiddenFiles) { + if (filter.searchFilter(file.getName(applicationContext))) { + publishProgress(file) + } + if (file.isDirectory(applicationContext)) { + worklist.add(file) } } } @@ -174,6 +169,7 @@ class DeepSearch( } fun interface SearchFilter { + /** Returns if the file with [fileName] as name should fulfills some predicate */ fun searchFilter(fileName: String): Boolean } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index f3c2d067d4..4aa825d18b 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -214,8 +214,7 @@ class MainActivityViewModel(val applicationContext: Application) : mainActivity.isRootExplorer, isRegexEnabled, isMatchesEnabled, - showHiddenFiles, - Dispatchers.IO + showHiddenFiles ) viewModelScope.launch(Dispatchers.IO) { From 699263dd4172e05ea9efd16789e9d1f6c3f26360 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 12 Jan 2024 21:21:08 +0100 Subject: [PATCH 15/55] documentation and formatting --- .../asynctasks/searchfilesystem/DeepSearch.kt | 11 ++++++----- .../ui/activities/MainActivityViewModel.kt | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt index 597fa2235d..5bfe99ab6c 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -74,10 +74,11 @@ class DeepSearch( ) ) // level 2 - if (!isMatchesEnabled) searchRegExFind(file, pattern) else searchRegExMatch( - file, - pattern - ) + if (!isMatchesEnabled) { + searchRegExFind(file, pattern) + } else { + searchRegExMatch(file, pattern) + } } } @@ -169,7 +170,7 @@ class DeepSearch( } fun interface SearchFilter { - /** Returns if the file with [fileName] as name should fulfills some predicate */ + /** Returns if the file with the given [fileName] fulfills some predicate */ fun searchFilter(fileName: String): Boolean } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 4aa825d18b..edf2505617 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -189,7 +189,7 @@ class MainActivityViewModel(val applicationContext: Application) : } /** - * Perform deep search: recursively search for + * Perform deep search: search recursively for files matching [query] in the current path */ fun deepSearch( mainActivity: MainActivity, From 98eba10aa6a82f0f3ac435e3ab3a37e1bcef33c5 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 12 Jan 2024 21:54:40 +0100 Subject: [PATCH 16/55] make search with "regex" consistent It is now case-insensitive like the search without "regex" --- .../asynchronous/asynctasks/searchfilesystem/DeepSearch.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt index 5bfe99ab6c..8df64e428e 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -69,9 +69,8 @@ class DeepSearch( } else { // compile the regular expression in the input val pattern = Pattern.compile( - bashRegexToJava( - query - ) + bashRegexToJava(query), + Pattern.CASE_INSENSITIVE ) // level 2 if (!isMatchesEnabled) { From b7de22411fab3575ae358399729f94f72fc7fe0b Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 12 Jan 2024 22:37:28 +0100 Subject: [PATCH 17/55] combine search parameters into enum set --- .../asynctasks/searchfilesystem/DeepSearch.kt | 16 +++---- .../searchfilesystem/SearchParameter.kt | 31 ++++++++++++ .../searchfilesystem/SearchParameters.kt | 47 +++++++++++++++++++ .../ui/activities/MainActivityViewModel.kt | 13 +++-- 4 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt index 8df64e428e..38bd336071 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -36,10 +36,7 @@ class DeepSearch( private val query: String, private val path: String, private val openMode: OpenMode, - private val rootMode: Boolean, - private val isRegexEnabled: Boolean, - private val isMatchesEnabled: Boolean, - private val showHiddenFiles: Boolean + private val searchParameters: SearchParameters ) { private val LOG = LoggerFactory.getLogger(DeepSearch::class.java) @@ -64,7 +61,7 @@ class DeepSearch( // level 1 // if regex or not - if (!isRegexEnabled) { + if (SearchParameter.REGEX !in searchParameters) { search(file, query) } else { // compile the regular expression in the input @@ -73,7 +70,7 @@ class DeepSearch( Pattern.CASE_INSENSITIVE ) // level 2 - if (!isMatchesEnabled) { + if (SearchParameter.REGEX_MATCHES !in searchParameters) { searchRegExFind(file, pattern) } else { searchRegExMatch(file, pattern) @@ -93,8 +90,11 @@ class DeepSearch( worklist.add(directory) while (coroutineContext.isActive && worklist.isNotEmpty()) { val nextFile = worklist.removeFirst() - nextFile.forEachChildrenFile(applicationContext, rootMode) { file -> - if (!file.isHidden || showHiddenFiles) { + nextFile.forEachChildrenFile( + applicationContext, + SearchParameter.ROOT in searchParameters + ) { file -> + if (!file.isHidden || SearchParameter.SHOW_HIDDEN_FILES in searchParameters) { if (filter.searchFilter(file.getName(applicationContext))) { publishProgress(file) } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt new file mode 100644 index 0000000000..7fe0ced45e --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +enum class SearchParameter { + ROOT, + REGEX, + REGEX_MATCHES, + SHOW_HIDDEN_FILES; + + infix fun and(other: SearchParameter): SearchParameters = SearchParameters.of(this, other) + operator fun plus(other: SearchParameter): SearchParameters = this and other +} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt new file mode 100644 index 0000000000..df15bcb2ed --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import java.util.EnumSet + +typealias SearchParameters = EnumSet +infix fun SearchParameters.allOf(other: SearchParameters) = this.containsAll(other) +infix fun SearchParameters.and(other: SearchParameter): SearchParameters = SearchParameters.of( + other, + *this.toTypedArray() +) +operator fun SearchParameters.plus(other: SearchParameter): SearchParameters = this and other + +fun searchParametersFromBoolean( + showHiddenFiles: Boolean, + isRegexEnabled: Boolean, + isRegexMatchesEnabled: Boolean, + isRoot: Boolean +): SearchParameters { + val searchParameterList = mutableListOf() + + if (showHiddenFiles) searchParameterList.add(SearchParameter.SHOW_HIDDEN_FILES) + if (isRegexEnabled) searchParameterList.add(SearchParameter.REGEX) + if (isRegexMatchesEnabled) searchParameterList.add(SearchParameter.REGEX_MATCHES) + if (isRoot) searchParameterList.add(SearchParameter.ROOT) + + return SearchParameters.copyOf(searchParameterList) +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index edf2505617..51b8357f13 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -32,6 +32,7 @@ import com.amaze.filemanager.R import com.amaze.filemanager.adapters.data.LayoutElementParcelable import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.DeepSearch +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.searchParametersFromBoolean import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile import com.amaze.filemanager.filesystem.HybridFileParcelable @@ -199,6 +200,13 @@ class MainActivityViewModel(val applicationContext: Application) : val showHiddenFiles = sharedPref.getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) val isRegexEnabled = sharedPref.getBoolean(PREFERENCE_REGEX, false) val isMatchesEnabled = sharedPref.getBoolean(PREFERENCE_REGEX_MATCHES, false) + val isRoot = mainActivity.isRootExplorer + val searchParameters = searchParametersFromBoolean( + showHiddenFiles, + isRegexEnabled, + isMatchesEnabled, + isRoot + ) val path = mainActivity.currentMainFragment?.currentPath ?: "" val openMode = @@ -211,10 +219,7 @@ class MainActivityViewModel(val applicationContext: Application) : query, path, openMode, - mainActivity.isRootExplorer, - isRegexEnabled, - isMatchesEnabled, - showHiddenFiles + searchParameters ) viewModelScope.launch(Dispatchers.IO) { From dec15544056aac6239c8981ba93239eb8368d619 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 12 Jan 2024 23:24:13 +0100 Subject: [PATCH 18/55] fix bug and clean up SearchParameters --- .../searchfilesystem/SearchParameters.kt | 15 +++++++++------ .../ui/activities/MainActivityViewModel.kt | 12 ++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt index df15bcb2ed..0fb78b2635 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt @@ -23,7 +23,6 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem import java.util.EnumSet typealias SearchParameters = EnumSet -infix fun SearchParameters.allOf(other: SearchParameters) = this.containsAll(other) infix fun SearchParameters.and(other: SearchParameter): SearchParameters = SearchParameters.of( other, *this.toTypedArray() @@ -31,10 +30,10 @@ infix fun SearchParameters.and(other: SearchParameter): SearchParameters = Searc operator fun SearchParameters.plus(other: SearchParameter): SearchParameters = this and other fun searchParametersFromBoolean( - showHiddenFiles: Boolean, - isRegexEnabled: Boolean, - isRegexMatchesEnabled: Boolean, - isRoot: Boolean + showHiddenFiles: Boolean = false, + isRegexEnabled: Boolean = false, + isRegexMatchesEnabled: Boolean = false, + isRoot: Boolean = false ): SearchParameters { val searchParameterList = mutableListOf() @@ -43,5 +42,9 @@ fun searchParametersFromBoolean( if (isRegexMatchesEnabled) searchParameterList.add(SearchParameter.REGEX_MATCHES) if (isRoot) searchParameterList.add(SearchParameter.ROOT) - return SearchParameters.copyOf(searchParameterList) + return if (searchParameterList.isEmpty()) { + SearchParameters.noneOf(SearchParameter::class.java) + } else { + SearchParameters.copyOf(searchParameterList) + } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 51b8357f13..663f3cb6cf 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -197,15 +197,11 @@ class MainActivityViewModel(val applicationContext: Application) : query: String ): MutableLiveData> { val sharedPref = PreferenceManager.getDefaultSharedPreferences(mainActivity) - val showHiddenFiles = sharedPref.getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) - val isRegexEnabled = sharedPref.getBoolean(PREFERENCE_REGEX, false) - val isMatchesEnabled = sharedPref.getBoolean(PREFERENCE_REGEX_MATCHES, false) - val isRoot = mainActivity.isRootExplorer val searchParameters = searchParametersFromBoolean( - showHiddenFiles, - isRegexEnabled, - isMatchesEnabled, - isRoot + showHiddenFiles = sharedPref.getBoolean(PREFERENCE_SHOW_HIDDENFILES, false), + isRegexEnabled = sharedPref.getBoolean(PREFERENCE_REGEX, false), + isRegexMatchesEnabled = sharedPref.getBoolean(PREFERENCE_REGEX_MATCHES, false), + isRoot = mainActivity.isRootExplorer ) val path = mainActivity.currentMainFragment?.currentPath ?: "" From 4cb5e52505d0d01f0b91d3bf93e96e48a9bf49c4 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 12 Jan 2024 23:33:34 +0100 Subject: [PATCH 19/55] add tests for SearchParameter(s) --- .../searchfilesystem/SearchParameterTest.kt | 41 ++++++++ .../searchfilesystem/SearchParametersTest.kt | 95 +++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameterTest.kt create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParametersTest.kt diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameterTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameterTest.kt new file mode 100644 index 0000000000..e22d665725 --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameterTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import org.junit.Assert +import org.junit.Test +import java.util.EnumSet + +class SearchParameterTest { + @Test + fun testAnd() { + val expected = EnumSet.of(SearchParameter.ROOT, SearchParameter.REGEX_MATCHES) + val actual = SearchParameter.ROOT and SearchParameter.REGEX_MATCHES + Assert.assertEquals(expected, actual) + } + + @Test + fun testPlus() { + val expected = EnumSet.of(SearchParameter.ROOT, SearchParameter.REGEX_MATCHES) + val actual = SearchParameter.ROOT + SearchParameter.REGEX_MATCHES + Assert.assertEquals(expected, actual) + } +} diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParametersTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParametersTest.kt new file mode 100644 index 0000000000..a8927e30dc --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParametersTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import org.junit.Assert +import org.junit.Test +import java.util.EnumSet + +class SearchParametersTest { + + @Test + fun testAdd() { + val expected = EnumSet.of(SearchParameter.SHOW_HIDDEN_FILES, SearchParameter.ROOT) + val actual = SearchParameter.SHOW_HIDDEN_FILES and SearchParameter.ROOT + Assert.assertEquals(expected, actual) + } + + @Test + fun testPlus() { + val expected = EnumSet.of(SearchParameter.REGEX, SearchParameter.ROOT) + val actual = EnumSet.of(SearchParameter.REGEX) + SearchParameter.ROOT + Assert.assertEquals(expected, actual) + } + + @Test + fun testSearchParametersFromBooleanWithNone() { + val expected = EnumSet.noneOf(SearchParameter::class.java) + val actual = searchParametersFromBoolean() + Assert.assertEquals(expected, actual) + } + + @Test + fun testSearchParametersFromBooleanWithOne() { + val expected = EnumSet.of(SearchParameter.ROOT) + val actual = searchParametersFromBoolean(isRoot = true) + Assert.assertEquals(expected, actual) + } + + @Test + fun testSearchParametersFromBooleanWithTwo() { + val expected = EnumSet.of(SearchParameter.ROOT, SearchParameter.SHOW_HIDDEN_FILES) + val actual = searchParametersFromBoolean(isRoot = true, showHiddenFiles = true) + Assert.assertEquals(expected, actual) + } + + @Test + fun testSearchParametersFromBooleanWithThree() { + val expected = EnumSet.of( + SearchParameter.ROOT, + SearchParameter.REGEX, + SearchParameter.REGEX_MATCHES + ) + val actual = searchParametersFromBoolean( + isRoot = true, + isRegexEnabled = true, + isRegexMatchesEnabled = true + ) + Assert.assertEquals(expected, actual) + } + + @Test + fun testSearchParametersFromBooleanWithFour() { + val expected = EnumSet.of( + SearchParameter.ROOT, + SearchParameter.REGEX, + SearchParameter.REGEX_MATCHES, + SearchParameter.SHOW_HIDDEN_FILES + ) + val actual = searchParametersFromBoolean( + isRoot = true, + isRegexEnabled = true, + isRegexMatchesEnabled = true, + showHiddenFiles = true + ) + Assert.assertEquals(expected, actual) + } +} From 0d40d19827d4c5e925ef3fe3b564027cb05ca587 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 12 Jan 2024 23:52:51 +0100 Subject: [PATCH 20/55] add documentation --- .../asynctasks/searchfilesystem/SearchParameter.kt | 7 +++++++ .../asynctasks/searchfilesystem/SearchParameters.kt | 11 +++++++++++ .../searchfilesystem/SearchParameterTest.kt | 3 +++ .../searchfilesystem/SearchParametersTest.kt | 9 ++++++++- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt index 7fe0ced45e..8c22765f91 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameter.kt @@ -26,6 +26,13 @@ enum class SearchParameter { REGEX_MATCHES, SHOW_HIDDEN_FILES; + /** + * Returns [SearchParameters] containing [this] and [other] + */ infix fun and(other: SearchParameter): SearchParameters = SearchParameters.of(this, other) + + /** + * Returns [SearchParameters] containing [this] and [other] + */ operator fun plus(other: SearchParameter): SearchParameters = this and other } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt index 0fb78b2635..62104b9e69 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameters.kt @@ -23,12 +23,23 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem import java.util.EnumSet typealias SearchParameters = EnumSet + +/** + * Returns [SearchParameters] extended by [other] + */ infix fun SearchParameters.and(other: SearchParameter): SearchParameters = SearchParameters.of( other, *this.toTypedArray() ) + +/** + * Returns [SearchParameters] extended by [other] + */ operator fun SearchParameters.plus(other: SearchParameter): SearchParameters = this and other +/** + * Returns [SearchParameters] that reflect the given Booleans + */ fun searchParametersFromBoolean( showHiddenFiles: Boolean = false, isRegexEnabled: Boolean = false, diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameterTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameterTest.kt index e22d665725..4ae5630359 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameterTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParameterTest.kt @@ -25,6 +25,8 @@ import org.junit.Test import java.util.EnumSet class SearchParameterTest { + + /** Tests [SearchParameter.and] */ @Test fun testAnd() { val expected = EnumSet.of(SearchParameter.ROOT, SearchParameter.REGEX_MATCHES) @@ -32,6 +34,7 @@ class SearchParameterTest { Assert.assertEquals(expected, actual) } + /** Tests [SearchParameter.plus] */ @Test fun testPlus() { val expected = EnumSet.of(SearchParameter.ROOT, SearchParameter.REGEX_MATCHES) diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParametersTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParametersTest.kt index a8927e30dc..006669d43b 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParametersTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchParametersTest.kt @@ -26,13 +26,15 @@ import java.util.EnumSet class SearchParametersTest { + /** Tests [SearchParameters.and] */ @Test - fun testAdd() { + fun testAnd() { val expected = EnumSet.of(SearchParameter.SHOW_HIDDEN_FILES, SearchParameter.ROOT) val actual = SearchParameter.SHOW_HIDDEN_FILES and SearchParameter.ROOT Assert.assertEquals(expected, actual) } + /** Tests [SearchParameters.plus] */ @Test fun testPlus() { val expected = EnumSet.of(SearchParameter.REGEX, SearchParameter.ROOT) @@ -40,6 +42,7 @@ class SearchParametersTest { Assert.assertEquals(expected, actual) } + /** Tests [searchParametersFromBoolean] with no flag turned on */ @Test fun testSearchParametersFromBooleanWithNone() { val expected = EnumSet.noneOf(SearchParameter::class.java) @@ -47,6 +50,7 @@ class SearchParametersTest { Assert.assertEquals(expected, actual) } + /** Tests [searchParametersFromBoolean] with one flag turned on */ @Test fun testSearchParametersFromBooleanWithOne() { val expected = EnumSet.of(SearchParameter.ROOT) @@ -54,6 +58,7 @@ class SearchParametersTest { Assert.assertEquals(expected, actual) } + /** Tests [searchParametersFromBoolean] with two flags turned on */ @Test fun testSearchParametersFromBooleanWithTwo() { val expected = EnumSet.of(SearchParameter.ROOT, SearchParameter.SHOW_HIDDEN_FILES) @@ -61,6 +66,7 @@ class SearchParametersTest { Assert.assertEquals(expected, actual) } + /** Tests [searchParametersFromBoolean] with three flags turned on */ @Test fun testSearchParametersFromBooleanWithThree() { val expected = EnumSet.of( @@ -76,6 +82,7 @@ class SearchParametersTest { Assert.assertEquals(expected, actual) } + /** Tests [searchParametersFromBoolean] with four flags turned on */ @Test fun testSearchParametersFromBooleanWithFour() { val expected = EnumSet.of( From c1e3593ff12dbdfe395abcc85db5cef24be2b976 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 15 Jan 2024 17:52:00 +0100 Subject: [PATCH 21/55] add data class to store more information about the search result --- .../searchfilesystem/SearchResult.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt new file mode 100644 index 0000000000..2b0dc863eb --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import com.amaze.filemanager.filesystem.HybridFileParcelable + +data class SearchResult(val file: HybridFileParcelable, val match: Match) + +typealias Match = IntProgression From caa32186f68375bcdfcd6ef965a2abf029f5b548 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sun, 14 Jan 2024 23:07:43 +0100 Subject: [PATCH 22/55] add sealed class FileSearch This will be the base class for all search modes --- .../asynctasks/searchfilesystem/FileSearch.kt | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt new file mode 100644 index 0000000000..385c9b3686 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.amaze.filemanager.filesystem.HybridFileParcelable +import java.util.Locale +import java.util.regex.Pattern + +sealed class FileSearch { + private val mutableFoundFilesLiveData: MutableLiveData> = + MutableLiveData() + val foundFilesLiveData: LiveData> = mutableFoundFilesLiveData + + /** + * Search for files, whose names match [query], starting from [path] and add them to + * [foundFilesLiveData] + */ + suspend fun search(query: String, path: String, searchParameters: SearchParameters) { + if (SearchParameter.REGEX !in searchParameters) { + // regex not turned on so we use simpleFilter + search(path, simpleFilter(query), searchParameters) + } else { + if (SearchParameter.REGEX_MATCHES !in searchParameters) { + // only regex turned on so we use regexFilter + search(path, regexFilter(query), searchParameters) + } else { + // regex turned on and names must match pattern so use regexMatchFilter + search(path, regexMatchFilter(query), searchParameters) + } + } + } + + /** + * Search for files, whose names fulfill [filter], starting from [path] and add them to + * [foundFilesLiveData]. + */ + protected abstract suspend fun search( + path: String, + filter: SearchFilter, + searchParameters: SearchParameters + ) + + /** + * Add [file] to list of found files and post it to [foundFilesLiveData] + */ + protected fun publishProgress( + file: HybridFileParcelable, + match: Match + ) { + val files = mutableFoundFilesLiveData.value.orEmpty().toMutableList() + files.add(SearchResult(file, match)) + mutableFoundFilesLiveData.postValue(files) + } + + private fun simpleFilter(query: String): SearchFilter = + SearchFilter { fileName -> + // check case-insensitively if query is contained in fileName + val start = fileName.lowercase(Locale.getDefault()).indexOf( + query.lowercase( + Locale.getDefault() + ) + ) + if (start >= 0) { + start..(start + query.length) + } else { + null + } + } + + private fun regexFilter(query: String): SearchFilter { + val pattern = regexPattern(query) + return SearchFilter { fileName -> + // check case-insensitively if the pattern compiled from query can be found in fileName + val matcher = pattern.matcher(fileName) + if (matcher.find()) { + matcher.start()..matcher.end() + } else { + null + } + } + } + + private fun regexMatchFilter(query: String): SearchFilter { + val pattern = regexPattern(query) + return SearchFilter { fileName -> + // check case-insensitively if the pattern compiled from query matches fileName + if (pattern.matcher(fileName).matches()) { + fileName.indices + } else { + null + } + } + } + + private fun regexPattern(query: String): Pattern = + // compiles the given query into a Pattern + Pattern.compile( + bashRegexToJava(query), + Pattern.CASE_INSENSITIVE + ) + + /** + * method converts bash style regular expression to java. See [Pattern] + * + * @return converted string + */ + private fun bashRegexToJava(originalString: String): String { + val stringBuilder = StringBuilder() + for (i in originalString.indices) { + when (originalString[i].toString() + "") { + "*" -> stringBuilder.append("\\w*") + "?" -> stringBuilder.append("\\w") + else -> stringBuilder.append(originalString[i]) + } + } + return stringBuilder.toString() + } + + fun interface SearchFilter { + /** If the file with the given [fileName] fulfills some predicate, return the part that fulfills the predicate */ + fun searchFilter(fileName: String): Match? + } +} From 6aaad39073909afa747779ed41a81dda87d68e7b Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 15 Jan 2024 18:09:00 +0100 Subject: [PATCH 23/55] make `DeepSearch` a subclass of `FileSearch` --- .../asynctasks/searchfilesystem/DeepSearch.kt | 116 ++---------------- .../ui/activities/MainActivityViewModel.kt | 29 ++--- 2 files changed, 25 insertions(+), 120 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt index 38bd336071..a8ae637966 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -21,69 +21,37 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem import android.content.Context -import androidx.lifecycle.MutableLiveData import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile -import com.amaze.filemanager.filesystem.HybridFileParcelable import kotlinx.coroutines.isActive import org.slf4j.LoggerFactory -import java.util.Locale -import java.util.regex.Pattern import kotlin.coroutines.coroutineContext class DeepSearch( context: Context, - private val query: String, - private val path: String, - private val openMode: OpenMode, - private val searchParameters: SearchParameters -) { + private val openMode: OpenMode +) : FileSearch() { private val LOG = LoggerFactory.getLogger(DeepSearch::class.java) - private val hybridFileParcelables: ArrayList = ArrayList() - val mutableLiveData: MutableLiveData> = MutableLiveData( - hybridFileParcelables - ) - private val applicationContext: Context init { applicationContext = context.applicationContext } - /** - * Search for files, whose names match [query], starting from [path] and add them to - * [mutableLiveData]. - */ - suspend fun search() { - val file = HybridFile(openMode, path) - if (file.isSmb) return - - // level 1 - // if regex or not - if (SearchParameter.REGEX !in searchParameters) { - search(file, query) - } else { - // compile the regular expression in the input - val pattern = Pattern.compile( - bashRegexToJava(query), - Pattern.CASE_INSENSITIVE - ) - // level 2 - if (SearchParameter.REGEX_MATCHES !in searchParameters) { - searchRegExFind(file, pattern) - } else { - searchRegExMatch(file, pattern) - } - } - } - /** * Search for occurrences of a given text in file names and publish the result * * @param directory the current path */ - private suspend fun search(directory: HybridFile, filter: SearchFilter) { + override suspend fun search( + path: String, + filter: SearchFilter, + searchParameters: SearchParameters + ) { + val directory = HybridFile(openMode, path) + if (directory.isSmb) return + if (directory.isDirectory(applicationContext)) { // you have permission to read this directory val worklist = ArrayDeque() @@ -108,68 +76,4 @@ class DeepSearch( LOG.warn("Cannot search " + directory.path + ": Permission Denied") } } - - private fun publishProgress(file: HybridFileParcelable) { - hybridFileParcelables.add(file) - mutableLiveData.postValue(hybridFileParcelables) - } - - /** - * Recursively search for occurrences of a given text in file names and publish the result - * - * @param file the current path - * @param query the searched text - */ - private suspend fun search(file: HybridFile, query: String) { - search(file) { fileName: String -> - fileName.lowercase(Locale.getDefault()).contains( - query.lowercase( - Locale.getDefault() - ) - ) - } - } - - /** - * Recursively find a java regex pattern [Pattern] in the file names and publish the result - * - * @param file the current file - * @param pattern the compiled java regex - */ - private suspend fun searchRegExFind(file: HybridFile, pattern: Pattern) { - search(file) { fileName: String -> pattern.matcher(fileName).find() } - } - - /** - * Recursively match a java regex pattern [Pattern] with the file names and publish the - * result - * - * @param file the current file - * @param pattern the compiled java regex - */ - private suspend fun searchRegExMatch(file: HybridFile, pattern: Pattern) { - search(file) { fileName: String -> pattern.matcher(fileName).matches() } - } - - /** - * method converts bash style regular expression to java. See [Pattern] - * - * @return converted string - */ - private fun bashRegexToJava(originalString: String): String { - val stringBuilder = StringBuilder() - for (i in originalString.indices) { - when (originalString[i].toString() + "") { - "*" -> stringBuilder.append("\\w*") - "?" -> stringBuilder.append("\\w") - else -> stringBuilder.append(originalString[i]) - } - } - return stringBuilder.toString() - } - - fun interface SearchFilter { - /** Returns if the file with the given [fileName] fulfills some predicate */ - fun searchFilter(fileName: String): Boolean - } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 663f3cb6cf..a57c1062a9 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -195,14 +195,8 @@ class MainActivityViewModel(val applicationContext: Application) : fun deepSearch( mainActivity: MainActivity, query: String - ): MutableLiveData> { - val sharedPref = PreferenceManager.getDefaultSharedPreferences(mainActivity) - val searchParameters = searchParametersFromBoolean( - showHiddenFiles = sharedPref.getBoolean(PREFERENCE_SHOW_HIDDENFILES, false), - isRegexEnabled = sharedPref.getBoolean(PREFERENCE_REGEX, false), - isRegexMatchesEnabled = sharedPref.getBoolean(PREFERENCE_REGEX_MATCHES, false), - isRoot = mainActivity.isRootExplorer - ) + ): LiveData> { + val searchParameters = createSearchParameters(mainActivity) val path = mainActivity.currentMainFragment?.currentPath ?: "" val openMode = @@ -212,17 +206,24 @@ class MainActivityViewModel(val applicationContext: Application) : val deepSearch = DeepSearch( context, - query, - path, - openMode, - searchParameters + openMode ) viewModelScope.launch(Dispatchers.IO) { - deepSearch.search() + deepSearch.search(query, path, searchParameters) } - return deepSearch.mutableLiveData + return deepSearch.foundFilesLiveData + } + + private fun createSearchParameters(mainActivity: MainActivity): SearchParameters { + val sharedPref = PreferenceManager.getDefaultSharedPreferences(mainActivity) + return searchParametersFromBoolean( + showHiddenFiles = sharedPref.getBoolean(PREFERENCE_SHOW_HIDDENFILES, false), + isRegexEnabled = sharedPref.getBoolean(PREFERENCE_REGEX, false), + isRegexMatchesEnabled = sharedPref.getBoolean(PREFERENCE_REGEX_MATCHES, false), + isRoot = mainActivity.isRootExplorer + ) } /** From 2b82693961b3e3a613b71a850f217afae7110fc9 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 15 Jan 2024 18:12:12 +0100 Subject: [PATCH 24/55] refactor basic search into subclass of `FileSearch` --- .../searchfilesystem/BasicSearch.kt | 51 +++++++++++++++++++ .../ui/activities/MainActivityViewModel.kt | 38 ++++---------- 2 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt new file mode 100644 index 0000000000..28cd4382ab --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.content.Context +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.root.ListFilesCommand.listFiles + +class BasicSearch(context: Context) : FileSearch() { + private val applicationContext = context.applicationContext + + override suspend fun search( + path: String, + filter: SearchFilter, + searchParameters: SearchParameters + ) { + listFiles( + path, + SearchParameter.ROOT in searchParameters, + SearchParameter.SHOW_HIDDEN_FILES in searchParameters, + { } + ) { hybridFileParcelable: HybridFileParcelable -> + if (filter.searchFilter(hybridFileParcelable.getName(applicationContext)) && + ( + SearchParameter.SHOW_HIDDEN_FILES in searchParameters || + !hybridFileParcelable.isHidden + ) + ) { + publishProgress(hybridFileParcelable) + } + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index a57c1062a9..eaff1ca912 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -25,20 +25,22 @@ import android.content.Intent import android.provider.MediaStore import androidx.collection.LruCache import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import com.amaze.filemanager.R import com.amaze.filemanager.adapters.data.LayoutElementParcelable import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.BasicSearch import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.DeepSearch +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.IndexedSearch +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchParameters import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.searchParametersFromBoolean import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile import com.amaze.filemanager.filesystem.HybridFileParcelable -import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.files.MediaConnectionUtils.scanFile -import com.amaze.filemanager.filesystem.root.ListFilesCommand.listFiles import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_REGEX import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_REGEX_MATCHES import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES @@ -48,7 +50,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.slf4j.LoggerFactory import java.io.File -import java.util.Locale class MainActivityViewModel(val applicationContext: Application) : AndroidViewModel(applicationContext) { @@ -101,37 +102,18 @@ class MainActivityViewModel(val applicationContext: Application) : * Perform basic search: searches on the current directory */ fun basicSearch(mainActivity: MainActivity, query: String): - MutableLiveData> { - val hybridFileParcelables = ArrayList() + LiveData> { + val searchParameters = createSearchParameters(mainActivity) - val mutableLiveData: - MutableLiveData> = - MutableLiveData(hybridFileParcelables) + val path = mainActivity.currentMainFragment?.currentPath ?: "" - val showHiddenFiles = PreferenceManager - .getDefaultSharedPreferences(mainActivity) - .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) + val basicSearch = BasicSearch(this.applicationContext) viewModelScope.launch(Dispatchers.IO) { - listFiles( - mainActivity.currentMainFragment!!.currentPath!!, - mainActivity.isRootExplorer, - showHiddenFiles, - { _: OpenMode? -> null } - ) { hybridFileParcelable: HybridFileParcelable -> - if (hybridFileParcelable.getName(mainActivity) - .lowercase(Locale.getDefault()) - .contains(query.lowercase(Locale.getDefault())) && - (showHiddenFiles || !hybridFileParcelable.isHidden) - ) { - hybridFileParcelables.add(hybridFileParcelable) - - mutableLiveData.postValue(hybridFileParcelables) - } - } + basicSearch.search(query, path, searchParameters) } - return mutableLiveData + return basicSearch.foundFilesLiveData } /** From 3dca8eed00a850a5004394094715e598da464a18 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 15 Jan 2024 18:13:30 +0100 Subject: [PATCH 25/55] refactor indexed search into subclass of `FileSearch` --- .../searchfilesystem/IndexedSearch.kt | 60 +++++++++++++++++++ .../ui/activities/MainActivityViewModel.kt | 53 ++++------------ 2 files changed, 72 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt new file mode 100644 index 0000000000..8f2b647313 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.database.Cursor +import android.provider.MediaStore +import com.amaze.filemanager.filesystem.RootHelper +import java.io.File + +class IndexedSearch(private val cursor: Cursor) : FileSearch() { + override suspend fun search( + path: String, + filter: SearchFilter, + searchParameters: SearchParameters + ) { + if (cursor.count > 0 && cursor.moveToFirst()) { + do { + val nextPath = + cursor.getString( + cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA) + ) + + if (nextPath != null && + nextPath.contains(path) && + filter.searchFilter(path) + ) { + val hybridFileParcelable = + RootHelper.generateBaseFile( + File(nextPath), + SearchParameter.SHOW_HIDDEN_FILES in searchParameters + ) + + if (hybridFileParcelable != null) { + publishProgress(hybridFileParcelable) + } + } + } while (cursor.moveToNext()) + } + + cursor.close() + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index eaff1ca912..3566f7d582 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -122,53 +122,24 @@ class MainActivityViewModel(val applicationContext: Application) : fun indexedSearch( mainActivity: MainActivity, query: String - ): MutableLiveData> { - val list = ArrayList() - - val mutableLiveData: MutableLiveData> = MutableLiveData( - list - ) - - val showHiddenFiles = - PreferenceManager.getDefaultSharedPreferences(mainActivity) - .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) - - viewModelScope.launch(Dispatchers.IO) { - val projection = arrayOf(MediaStore.Files.FileColumns.DATA) - - val cursor = mainActivity - .contentResolver - .query(MediaStore.Files.getContentUri("external"), projection, null, null, null) - ?: return@launch + ): LiveData> { + val projection = arrayOf(MediaStore.Files.FileColumns.DATA) + val cursor = mainActivity + .contentResolver + .query(MediaStore.Files.getContentUri("external"), projection, null, null, null) + ?: return MutableLiveData() - if (cursor.count > 0 && cursor.moveToFirst()) { - do { - val path = - cursor.getString( - cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA) - ) + val searchParameters = createSearchParameters(mainActivity) - if (path != null && - path.contains(mainActivity.currentMainFragment?.currentPath!!) && - File(path).name.lowercase(Locale.getDefault()).contains( - query.lowercase(Locale.getDefault()) - ) - ) { - val hybridFileParcelable = - RootHelper.generateBaseFile(File(path), showHiddenFiles) + val path = mainActivity.currentMainFragment?.currentPath ?: "" - if (hybridFileParcelable != null) { - list.add(hybridFileParcelable) - mutableLiveData.postValue(list) - } - } - } while (cursor.moveToNext()) - } + val indexedSearch = IndexedSearch(cursor) - cursor.close() + viewModelScope.launch(Dispatchers.IO) { + indexedSearch.search(query, path, searchParameters) } - return mutableLiveData + return indexedSearch.foundFilesLiveData } /** From d45fa8652829c106f8f6cb99d4ce44cb8e9c9239 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 15 Jan 2024 18:26:26 +0100 Subject: [PATCH 26/55] rename `Match` to `MatchRange` --- .../asynchronous/asynctasks/searchfilesystem/FileSearch.kt | 6 +++--- .../asynctasks/searchfilesystem/SearchResult.kt | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt index 385c9b3686..7ddb01584e 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt @@ -65,10 +65,10 @@ sealed class FileSearch { */ protected fun publishProgress( file: HybridFileParcelable, - match: Match + matchRange: MatchRange ) { val files = mutableFoundFilesLiveData.value.orEmpty().toMutableList() - files.add(SearchResult(file, match)) + files.add(SearchResult(file, matchRange)) mutableFoundFilesLiveData.postValue(files) } @@ -138,6 +138,6 @@ sealed class FileSearch { fun interface SearchFilter { /** If the file with the given [fileName] fulfills some predicate, return the part that fulfills the predicate */ - fun searchFilter(fileName: String): Match? + fun searchFilter(fileName: String): MatchRange? } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt index 2b0dc863eb..ec6d4e70f1 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt @@ -22,6 +22,8 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem import com.amaze.filemanager.filesystem.HybridFileParcelable -data class SearchResult(val file: HybridFileParcelable, val match: Match) +data class SearchResult(val file: HybridFileParcelable, val matchRange: MatchRange) -typealias Match = IntProgression +typealias MatchRange = IntProgression + +fun MatchRange.size(): Int = this.last - this.first From cff7394b87e040f789c6898d1ae3ae242bd12035 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 15 Jan 2024 18:26:51 +0100 Subject: [PATCH 27/55] change type of file into `ComparabelParcelable` --- .../asynchronous/asynctasks/searchfilesystem/SearchResult.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt index ec6d4e70f1..877e54fb00 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt @@ -20,9 +20,9 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem -import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.files.sort.ComparableParcelable -data class SearchResult(val file: HybridFileParcelable, val matchRange: MatchRange) +data class SearchResult(val file: ComparableParcelable, val matchRange: MatchRange) typealias MatchRange = IntProgression From 42824d33951a2c4abc57964db5bad3916c7d1782 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 15 Jan 2024 18:35:52 +0100 Subject: [PATCH 28/55] Revert "change type of file into `ComparabelParcelable`" This reverts commit 04a824160d7ae91cad61a1e728f613ffee538b1d. --- .../asynchronous/asynctasks/searchfilesystem/SearchResult.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt index 877e54fb00..ec6d4e70f1 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt @@ -20,9 +20,9 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem -import com.amaze.filemanager.filesystem.files.sort.ComparableParcelable +import com.amaze.filemanager.filesystem.HybridFileParcelable -data class SearchResult(val file: ComparableParcelable, val matchRange: MatchRange) +data class SearchResult(val file: HybridFileParcelable, val matchRange: MatchRange) typealias MatchRange = IntProgression From d165156cdb717d8f1b8e05c4cae34c3959bed6af Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 18:06:15 +0100 Subject: [PATCH 29/55] add documentation --- .../asynchronous/asynctasks/searchfilesystem/SearchResult.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt index ec6d4e70f1..e9aa24d41d 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResult.kt @@ -26,4 +26,5 @@ data class SearchResult(val file: HybridFileParcelable, val matchRange: MatchRan typealias MatchRange = IntProgression +/** Returns the size of the [MatchRange] which means how many characters were matched */ fun MatchRange.size(): Int = this.last - this.first From dabecc8fe7194a0b918935c8af318d3ecfdb6e4d Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 18:09:06 +0100 Subject: [PATCH 30/55] change to store found files in separate list --- .../asynctasks/searchfilesystem/FileSearch.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt index 7ddb01584e..4393cbbd42 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt @@ -30,6 +30,7 @@ sealed class FileSearch { private val mutableFoundFilesLiveData: MutableLiveData> = MutableLiveData() val foundFilesLiveData: LiveData> = mutableFoundFilesLiveData + private val foundFilesList: MutableList = mutableListOf() /** * Search for files, whose names match [query], starting from [path] and add them to @@ -67,9 +68,8 @@ sealed class FileSearch { file: HybridFileParcelable, matchRange: MatchRange ) { - val files = mutableFoundFilesLiveData.value.orEmpty().toMutableList() - files.add(SearchResult(file, matchRange)) - mutableFoundFilesLiveData.postValue(files) + foundFilesList.add(SearchResult(file, matchRange)) + mutableFoundFilesLiveData.postValue(foundFilesList) } private fun simpleFilter(query: String): SearchFilter = @@ -137,7 +137,10 @@ sealed class FileSearch { } fun interface SearchFilter { - /** If the file with the given [fileName] fulfills some predicate, return the part that fulfills the predicate */ + /** + * If the file with the given [fileName] fulfills some predicate, returns the part that fulfills the predicate. + * Otherwise returns null. + */ fun searchFilter(fileName: String): MatchRange? } } From 33915099f2d4487b3a0f2666269757f8487dae2a Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 18:12:19 +0100 Subject: [PATCH 31/55] change to use `SearchResult` --- .../searchfilesystem/BasicSearch.kt | 13 +++++---- .../asynctasks/searchfilesystem/DeepSearch.kt | 5 ++-- .../searchfilesystem/IndexedSearch.kt | 28 ++++++++++--------- .../ui/activities/MainActivityViewModel.kt | 14 ++++++---- .../ui/views/appbar/SearchView.java | 1 + 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt index 28cd4382ab..185a95a24a 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt @@ -38,13 +38,14 @@ class BasicSearch(context: Context) : FileSearch() { SearchParameter.SHOW_HIDDEN_FILES in searchParameters, { } ) { hybridFileParcelable: HybridFileParcelable -> - if (filter.searchFilter(hybridFileParcelable.getName(applicationContext)) && - ( - SearchParameter.SHOW_HIDDEN_FILES in searchParameters || - !hybridFileParcelable.isHidden - ) + if (SearchParameter.SHOW_HIDDEN_FILES in searchParameters || + !hybridFileParcelable.isHidden ) { - publishProgress(hybridFileParcelable) + val resultRange = + filter.searchFilter(hybridFileParcelable.getName(applicationContext)) + if (resultRange != null) { + publishProgress(hybridFileParcelable, resultRange) + } } } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt index a8ae637966..a6ec911d04 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -63,8 +63,9 @@ class DeepSearch( SearchParameter.ROOT in searchParameters ) { file -> if (!file.isHidden || SearchParameter.SHOW_HIDDEN_FILES in searchParameters) { - if (filter.searchFilter(file.getName(applicationContext))) { - publishProgress(file) + val resultRange = filter.searchFilter(file.getName(applicationContext)) + if (resultRange != null) { + publishProgress(file, resultRange) } if (file.isDirectory(applicationContext)) { worklist.add(file) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt index 8f2b647313..02f82c7c74 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt @@ -37,19 +37,21 @@ class IndexedSearch(private val cursor: Cursor) : FileSearch() { cursor.getString( cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA) ) - - if (nextPath != null && - nextPath.contains(path) && - filter.searchFilter(path) - ) { - val hybridFileParcelable = - RootHelper.generateBaseFile( - File(nextPath), - SearchParameter.SHOW_HIDDEN_FILES in searchParameters - ) - - if (hybridFileParcelable != null) { - publishProgress(hybridFileParcelable) + val displayName = + cursor.getString( + cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME) + ) + if (nextPath != null && displayName != null && nextPath.contains(path)) { + val resultRange = filter.searchFilter(displayName) + if (resultRange != null) { + val hybridFileParcelable = + RootHelper.generateBaseFile( + File(nextPath), + SearchParameter.SHOW_HIDDEN_FILES in searchParameters + ) + if (hybridFileParcelable != null) { + publishProgress(hybridFileParcelable, resultRange) + } } } } while (cursor.moveToNext()) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 3566f7d582..46c3b0feaa 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -36,10 +36,10 @@ import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.BasicSearc import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.DeepSearch import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.IndexedSearch import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchParameters +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResult import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.searchParametersFromBoolean import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile -import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.filesystem.files.MediaConnectionUtils.scanFile import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_REGEX import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_REGEX_MATCHES @@ -102,7 +102,7 @@ class MainActivityViewModel(val applicationContext: Application) : * Perform basic search: searches on the current directory */ fun basicSearch(mainActivity: MainActivity, query: String): - LiveData> { + LiveData> { val searchParameters = createSearchParameters(mainActivity) val path = mainActivity.currentMainFragment?.currentPath ?: "" @@ -122,8 +122,12 @@ class MainActivityViewModel(val applicationContext: Application) : fun indexedSearch( mainActivity: MainActivity, query: String - ): LiveData> { - val projection = arrayOf(MediaStore.Files.FileColumns.DATA) + ): LiveData> { + val projection = arrayOf( + MediaStore.Files.FileColumns.DATA, + MediaStore.Files.FileColumns.DISPLAY_NAME + ) + MediaStore.VOLUME_EXTERNAL_PRIMARY val cursor = mainActivity .contentResolver .query(MediaStore.Files.getContentUri("external"), projection, null, null, null) @@ -148,7 +152,7 @@ class MainActivityViewModel(val applicationContext: Application) : fun deepSearch( mainActivity: MainActivity, query: String - ): LiveData> { + ): LiveData> { val searchParameters = createSearchParameters(mainActivity) val path = mainActivity.currentMainFragment?.currentPath ?: "" diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index df8a9e5fdc..2bfd805c4a 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -30,6 +30,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.SearchRecyclerViewAdapter; +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResult; import com.amaze.filemanager.filesystem.HybridFileParcelable; import com.amaze.filemanager.filesystem.files.FileListSorter; import com.amaze.filemanager.filesystem.files.sort.DirSortBy; From 74f10c11c0bab7fa1424504368e1fddd4ffd12a8 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 18:14:28 +0100 Subject: [PATCH 32/55] add new sorter for sorting `SearchResult` Sort by relevance is moved there and completely removed from `FileListSorter` --- .../SearchResultListSorter.kt | 91 +++++++++++++++++++ .../filesystem/files/FileListSorter.kt | 67 +------------- 2 files changed, 94 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorter.kt diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorter.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorter.kt new file mode 100644 index 0000000000..517faa68fd --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorter.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import com.amaze.filemanager.filesystem.files.FileListSorter +import com.amaze.filemanager.filesystem.files.sort.DirSortBy +import com.amaze.filemanager.filesystem.files.sort.SortBy +import com.amaze.filemanager.filesystem.files.sort.SortType +import java.util.Date +import java.util.concurrent.TimeUnit + +class SearchResultListSorter( + private val dirArg: DirSortBy, + private val sortType: SortType, + private val searchTerm: String +) : Comparator { + private val fileListSorter: FileListSorter by lazy { FileListSorter(dirArg, sortType) } + + private val relevanceComparator: Comparator by lazy { + Comparator { o1, o2 -> + val currentTime = Date().time + val comparator = compareBy { (item, matchRange) -> + // the match percentage of the search term in the name + val matchPercentageScore = + matchRange.size().toDouble() / item.getParcelableName().length.toDouble() + + // if the name starts with the search term + val startScore = (matchRange.first == 0).toInt() + + // if the search term is surrounded by separators + // e.g. "my-cat" more relevant than "mysterious" for search term "my" + val wordScore = item.getParcelableName().split('-', '_', '.', ' ').any { + it.contentEquals( + searchTerm, + ignoreCase = true + ) + }.toInt() + + val modificationDate = item.getDate() + // the time difference as minutes + val timeDiff = + TimeUnit.MILLISECONDS.toMinutes(currentTime - modificationDate) + // 30 days as minutes + val relevantModificationPeriod = TimeUnit.DAYS.toMinutes(30) + val timeScore = if (timeDiff < relevantModificationPeriod) { + // if the file was modified within the last 30 days, the recency is normalized + (relevantModificationPeriod - timeDiff) / + relevantModificationPeriod.toDouble() + } else { + // for all older modification time, the recency doesn't change the relevancy + 0.0 + } + + 1.2 * matchPercentageScore + + 0.7 * startScore + + 0.7 * wordScore + + 0.6 * timeScore + } + // Reverts the sorting to make most relevant first + comparator.compare(o1, o2) * -1 + } + } + + private fun Boolean.toInt() = if (this) 1 else 0 + + override fun compare(result1: SearchResult, result2: SearchResult): Int { + return when (sortType.sortBy) { + SortBy.RELEVANCE -> relevanceComparator.compare(result1, result2) + SortBy.SIZE, SortBy.TYPE, SortBy.LAST_MODIFIED, SortBy.NAME -> + fileListSorter.compare(result1.file, result2.file) + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt index 6ebbcfb114..1b57468a08 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileListSorter.kt @@ -26,80 +26,19 @@ import com.amaze.filemanager.filesystem.files.sort.DirSortBy import com.amaze.filemanager.filesystem.files.sort.SortBy import com.amaze.filemanager.filesystem.files.sort.SortType import java.lang.Long -import java.util.Date import java.util.Locale -import java.util.concurrent.TimeUnit /** * [Comparator] implementation to sort [LayoutElementParcelable]s. */ class FileListSorter( dirArg: DirSortBy, - sortType: SortType, - searchTerm: String? + sortType: SortType ) : Comparator { private var dirsOnTop = dirArg private val asc: Int = sortType.sortOrder.sortFactor private val sort: SortBy = sortType.sortBy - private val relevanceComparator: Comparator by lazy { - if (searchTerm == null) { - // no search term given, so every result is equally relevant - Comparator { _, _ -> - 0 - } - } else { - Comparator { o1, o2 -> - val currentTime = Date().time - val comparator = compareBy { item -> - // the match percentage of the search term in the name - val matchPercentageScore = - searchTerm.length.toDouble() / item.getParcelableName().length.toDouble() - - // if the name starts with the search term - val startScore = - item.getParcelableName().startsWith(searchTerm, ignoreCase = true).toInt() - - // if the search term is surrounded by separators - // e.g. "my-cat" more relevant than "mysterious" for search term "my" - val wordScore = item.getParcelableName().split('-', '_', '.', ' ').any { - it.contentEquals( - searchTerm, - ignoreCase = true - ) - }.toInt() - - val modificationDate = item.getDate() - // the time difference as minutes - val timeDiff = - TimeUnit.MILLISECONDS.toMinutes(currentTime - modificationDate) - // 30 days as minutes - val relevantModificationPeriod = TimeUnit.DAYS.toMinutes(30) - val timeScore = if (timeDiff < relevantModificationPeriod) { - // if the file was modified within the last 30 days, the recency is normalized - (relevantModificationPeriod - timeDiff) / - relevantModificationPeriod.toDouble() - } else { - // for all older modification time, the recency doesn't change the relevancy - 0.0 - } - - 1.2 * matchPercentageScore + - 0.7 * startScore + - 0.7 * wordScore + - 0.6 * timeScore - } - // Reverts the sorting to make most relevant first - comparator.compare(o1, o2) * -1 - } - } - } - - /** Constructor for convenience if there is no searchTerm */ - constructor(dirArg: DirSortBy, sortType: SortType) : this(dirArg, sortType, null) - - private fun Boolean.toInt() = if (this) 1 else 0 - private fun isDirectory(path: ComparableParcelable): Boolean { return path.isDirectory() } @@ -178,8 +117,8 @@ class FileListSorter( } } SortBy.RELEVANCE -> { - // sort by relevance to the search query - return asc * relevanceComparator.compare(file1, file2) + // This case should not be called because it is not defined + return 0 } } } From 192bb1fa468aa0a76e79644a412bc8aa52753a61 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 18:16:41 +0100 Subject: [PATCH 33/55] change to use `SearchResultListSorter` --- .../filemanager/ui/views/appbar/SearchView.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 2bfd805c4a..a344818a9c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -31,8 +31,8 @@ import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.SearchRecyclerViewAdapter; import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResult; +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResultListSorter; import com.amaze.filemanager.filesystem.HybridFileParcelable; -import com.amaze.filemanager.filesystem.files.FileListSorter; import com.amaze.filemanager.filesystem.files.sort.DirSortBy; import com.amaze.filemanager.filesystem.files.sort.SortBy; import com.amaze.filemanager.filesystem.files.sort.SortOrder; @@ -362,10 +362,15 @@ private void resetSearchMode() { * @param newResults The list of results that should be displayed * @param searchTerm The search term that resulted in the search results */ - private void updateResultList(List newResults, String searchTerm) { - ArrayList items = new ArrayList<>(newResults); - Collections.sort(items, new FileListSorter(DirSortBy.NONE_ON_TOP, sortType, searchTerm)); - searchRecyclerViewAdapter.submitList(items); + private void updateResultList(List newResults, String searchTerm) { + ArrayList items = new ArrayList<>(newResults); + Collections.sort( + items, new SearchResultListSorter(DirSortBy.NONE_ON_TOP, sortType, searchTerm)); + ArrayList files = new ArrayList<>(); + for (SearchResult searchResult : items) { + files.add(searchResult.getFile()); + } + searchRecyclerViewAdapter.submitList(files); searchRecyclerViewAdapter.notifyDataSetChanged(); } From 9200874f7c4d07ce98bcb07eaa0f425121222987 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 18:49:30 +0100 Subject: [PATCH 34/55] change sort by relevance test to test `SearchResultListSorter` --- .../SearchResultListSorterTest.kt | 794 ++++++++++++++++++ .../filesystem/files/FileListSorterTest.kt | 592 ------------- 2 files changed, 794 insertions(+), 592 deletions(-) create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorterTest.kt diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorterTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorterTest.kt new file mode 100644 index 0000000000..3ca332cd6f --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/SearchResultListSorterTest.kt @@ -0,0 +1,794 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.os.Build +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.amaze.filemanager.adapters.data.LayoutElementParcelable +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.files.sort.DirSortBy +import com.amaze.filemanager.filesystem.files.sort.SortBy +import com.amaze.filemanager.filesystem.files.sort.SortOrder +import com.amaze.filemanager.filesystem.files.sort.SortType +import com.amaze.filemanager.shadows.ShadowMultiDex +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import java.util.Date +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern + +@RunWith(AndroidJUnit4::class) +@Config( + shadows = [ShadowMultiDex::class], + sdk = [Build.VERSION_CODES.KITKAT, Build.VERSION_CODES.P, Build.VERSION_CODES.R] +) +@Suppress("StringLiteralDuplication", "ComplexMethod", "LongMethod", "LargeClass") +class SearchResultListSorterTest { + + private fun getSimpleMatchRange(searchTerm: String, fileName: String): MatchRange { + val startIndex = fileName.lowercase().indexOf(searchTerm.lowercase()) + return startIndex..(startIndex + searchTerm.length) + } + + private fun getPatternMatchRange(pattern: Pattern, fileName: String): MatchRange { + val matcher = pattern.matcher(fileName) + matcher.find() + return matcher.start()..matcher.end() + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term more than file2, result is positive + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" more than file2 title + * + * Expected: return negative integer + */ + @Test + fun testSortByRelevanceWithFile1MoreMatchThanFile2() { + val searchTerm = "abc" + val title1 = "abc.txt" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABCDE.txt" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abc", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + "ABCDE.txt", + "C:\\AmazeFileManager\\ABCDE", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + + Assert.assertEquals( + -1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term less than file2, result is positive + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" less than file2 title + * + * Expected: return positive integer + */ + @Test + fun testSortByRelevanceWithFile1LessMatchThanFile2() { + val searchTerm = "abc" + val title1 = "abcdefg.txt" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABC.txt" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abcdefg", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABC", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + 1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2 + * and file1 starts with search term, result is negative + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title and file1 starts with "abc" + * + * Expected: return negative integer + */ + @Test + fun testSortByRelevanceWithFile1StartsWithSearchTerm() { + val searchTerm = "abc" + val title1 = "abc.txt" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "XYZ_ABC" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abc", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\XYZ_ABC", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + -1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2 + * and file2 starts with search term, result is positive + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title and file2 starts with "abc" + * + * Expected: return positive integer + */ + @Test + fun testSortByRelevanceWithFile2StartWithSearchTerm() { + val searchTerm = "abc" + val title1 = "txt-abc" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABC.txt" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\txt-abc", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABC", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + 1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, + * both start with search term and file1 contains the search term as a word (surrounded by + * separators), result is negative + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc" + * and file1 contains "abc" as word (separated by "-") + * + * Expected: return negative integer + */ + @Test + fun testSortByRelevanceWithFile1HasSearchTermAsWord() { + val searchTerm = "abc" + val title1 = "abc-efg.txt" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABCD-FG.txt" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abc-efg", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABCD-FG", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + -1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, + * both start with search term and file2 contains the search term as a word (surrounded by + * separators), result is positive + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc" + * and file2 contains "abc" as word (separated by "_") + * + * Expected: return positive integer + */ + @Test + fun testSortByRelevanceWithFile2HasSearchTermAsWord() { + val searchTerm = "abc" + val title1 = "abcdefg" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABC_EFG" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abcdefg", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABC_EFG", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + 1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, + * both start with search term and file2 contains the search term as a word (surrounded by + * separators), result is positive + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc" + * and file2 contains "abc" as word (separated by " ") + * + * Expected: return positive integer + */ + @Test + fun testSortByRelevanceWithSpaceWordSeparator() { + val searchTerm = "abc" + val title1 = "abcdefg" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABC EFG" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + "abc" + ) + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abcdefg", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABC EFG", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + 1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, + * both start with search term and file2 contains the search term as a word (surrounded by + * separators), result is positive + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc" + * and file2 contains "abc" as word (separated by ".") + * + * Expected: return positive integer + */ + @Test + fun testSortByRelevanceWithDotWordSeparator() { + val searchTerm = "abc" + val title1 = "abcdefg" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABC.EFG" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abcdefg", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABC.EFG", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + 1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, + * both start with search term, both contain the search term as a word and file1 date is more recent, + * result is negative + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc", + * both contain "abc" as word and file1 date is more recent + * + * Expected: return negative integer + */ + @Test + fun testSortByRelevanceWithFile1MoreRecent() { + val searchTerm = "abc" + val title1 = "abc.efg" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABC_EFG" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val currentTime = Date().time + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abc.efg", + "user", + "symlink", + "100", + 123L, + true, + (currentTime - TimeUnit.MINUTES.toMillis(5)).toString(), + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABC_EFG", + "user", + "symlink", + "101", + 124L, + true, + (currentTime - TimeUnit.MINUTES.toMillis(10)).toString(), + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + -1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, + * both start with search term, both contain the search term as a word and file2 date is more recent, + * result is positive + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc", + * both contain "abc" as word and file2 date is more recent + * + * Expected: return positive integer + */ + @Test + fun testSortByRelevanceWithFile2MoreRecent() { + val searchTerm = "abc" + val title1 = "abc.efg" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABC_EFG" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val currentTime = Date().time + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abc.efg", + "user", + "symlink", + "100", + 123L, + true, + (currentTime - TimeUnit.MINUTES.toMillis(10)).toString(), + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABC_EFG", + "user", + "symlink", + "101", + 124L, + true, + (currentTime - TimeUnit.MINUTES.toMillis(5)).toString(), + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + 1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, + * both start with search term, both contain the search term as a word and date is same, + * result is zero + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc", + * both contain "abc" as word and the date of both is the same + * + * Expected: return zero + */ + @Test + fun testSortByRelevanceWithSameRelevance() { + val searchTerm = "abc" + val title1 = "abc.efg" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "ABC_EFG" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + searchTerm + ) + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abc.efg", + "user", + "symlink", + "100", + 123L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\ABC_EFG", + "user", + "symlink", + "101", + 124L, + true, + "1234", + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + 0, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } + + /** + * Purpose: when sort is [SortBy.RELEVANCE], if file2 matches the search term more than file1 + * and file2 date is more recent, but file1 starts with search term and contains the + * search term as a word, the result is negative. + * + * Input: SearchResultListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" + * compare(file1,file2) file2 title matches "abc" more than file1 title and is more recent both start with "abc", + * both contain "abc" as word and the date of both is the same + * + * Expected: return negative integer + */ + @Test + fun testSortByRelevanceWhole() { + val searchTerm = "abc" + val title1 = "abc.efghij" + val matchRange1 = getSimpleMatchRange(searchTerm, title1) + val title2 = "EFGABC" + val matchRange2 = getSimpleMatchRange(searchTerm, title2) + + val searchResultListSorter = SearchResultListSorter( + DirSortBy.NONE_ON_TOP, + SortType(SortBy.RELEVANCE, SortOrder.ASC), + "abc" + ) + val currentTime = Date().time + + // matches 3/10 + // starts with search term + // contains search as whole word + // modification time is less recent + val file1 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title1, + "C:\\AmazeFileManager\\abc.efghij", + "user", + "symlink", + "100", + 123L, + true, + (currentTime - TimeUnit.MINUTES.toMillis(10)).toString(), + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + // matches 3/6 + // doesn't start with search term + // doesn't contain as whole word + // modification time is more recent + val file2 = LayoutElementParcelable( + ApplicationProvider.getApplicationContext(), + title2, + "C:\\AmazeFileManager\\EFGABC", + "user", + "symlink", + "101", + 124L, + true, + (currentTime - TimeUnit.MINUTES.toMillis(5)).toString(), + false, + false, + OpenMode.UNKNOWN + ).generateBaseFile() + Assert.assertEquals( + -1, + searchResultListSorter.compare( + SearchResult(file1, matchRange1), + SearchResult(file2, matchRange2) + ).toLong() + ) + } +} diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.kt index 841231b640..5a4f657bd9 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.kt @@ -35,8 +35,6 @@ import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -import java.util.Date -import java.util.concurrent.TimeUnit /** * because of test based on mock-up, extension testing isn't tested so, assume all extension is @@ -1013,594 +1011,4 @@ class FileListSorterTest { ) Assert.assertEquals(fileListSorter.compare(file1, file2).toLong(), 0) } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term more than file2, result is positive - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" more than file2 title - * - * Expected: return negative integer - */ - @Test - fun testSortByRelevanceWithFile1MoreMatchThanFile2() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abc.txt", - "C:\\AmazeFileManager\\abc", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABCDE.txt", - "C:\\AmazeFileManager\\ABCDE", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(-1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term less than file2, result is positive - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" less than file2 title - * - * Expected: return positive integer - */ - @Test - fun testSortByRelevanceWithFile1LessMatchThanFile2() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abcdefg.txt", - "C:\\AmazeFileManager\\abcdefg", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABC.txt", - "C:\\AmazeFileManager\\ABC", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2 - * and file1 starts with search term, result is negative - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title and file1 starts with "abc" - * - * Expected: return negative integer - */ - @Test - fun testSortByRelevanceWithFile1StartsWithSearchTerm() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abc.txt", - "C:\\AmazeFileManager\\abc", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "XYZ_ABC", - "C:\\AmazeFileManager\\XYZ_ABC", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(-1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2 - * and file2 starts with search term, result is positive - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title and file2 starts with "abc" - * - * Expected: return positive integer - */ - @Test - fun testSortByRelevanceWithFile2StartWithSearchTerm() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "txt-abc", - "C:\\AmazeFileManager\\txt-abc", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABC.txt", - "C:\\AmazeFileManager\\ABC", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, - * both start with search term and file1 contains the search term as a word (surrounded by - * separators), result is negative - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc" - * and file1 contains "abc" as word (separated by "-") - * - * Expected: return negative integer - */ - @Test - fun testSortByRelevanceWithFile1HasSearchTermAsWord() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abc-efg.txt", - "C:\\AmazeFileManager\\abc-efg", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABCD-FG.txt", - "C:\\AmazeFileManager\\ABCD-FG", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(-1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, - * both start with search term and file2 contains the search term as a word (surrounded by - * separators), result is positive - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc" - * and file2 contains "abc" as word (separated by "_") - * - * Expected: return positive integer - */ - @Test - fun testSortByRelevanceWithFile2HasSearchTermAsWord() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abcdefg", - "C:\\AmazeFileManager\\abcdefg", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABC_EFG", - "C:\\AmazeFileManager\\ABC_EFG", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, - * both start with search term and file2 contains the search term as a word (surrounded by - * separators), result is positive - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc" - * and file2 contains "abc" as word (separated by " ") - * - * Expected: return positive integer - */ - @Test - fun testSortByRelevanceWithSpaceWordSeparator() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abcdefg", - "C:\\AmazeFileManager\\abcdefg", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABC EFG", - "C:\\AmazeFileManager\\ABC EFG", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, - * both start with search term and file2 contains the search term as a word (surrounded by - * separators), result is positive - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc" - * and file2 contains "abc" as word (separated by ".") - * - * Expected: return positive integer - */ - @Test - fun testSortByRelevanceWithDotWordSeparator() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abcdefg", - "C:\\AmazeFileManager\\abcdefg", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABC.EFG", - "C:\\AmazeFileManager\\ABC.EFG", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, - * both start with search term, both contain the search term as a word and file1 date is more recent, - * result is negative - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc", - * both contain "abc" as word and file1 date is more recent - * - * Expected: return negative integer - */ - @Test - fun testSortByRelevanceWithFile1MoreRecent() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val currentTime = Date().time - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abc.efg", - "C:\\AmazeFileManager\\abc.efg", - "user", - "symlink", - "100", - 123L, - true, - (currentTime - TimeUnit.MINUTES.toMillis(5)).toString(), - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABC_EFG", - "C:\\AmazeFileManager\\ABC_EFG", - "user", - "symlink", - "101", - 124L, - true, - (currentTime - TimeUnit.MINUTES.toMillis(10)).toString(), - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(-1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, - * both start with search term, both contain the search term as a word and file2 date is more recent, - * result is positive - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc", - * both contain "abc" as word and file2 date is more recent - * - * Expected: return positive integer - */ - @Test - fun testSortByRelevanceWithFile2MoreRecent() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val currentTime = Date().time - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abc.efg", - "C:\\AmazeFileManager\\abc.efg", - "user", - "symlink", - "100", - 123L, - true, - (currentTime - TimeUnit.MINUTES.toMillis(10)).toString(), - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABC_EFG", - "C:\\AmazeFileManager\\ABC_EFG", - "user", - "symlink", - "101", - 124L, - true, - (currentTime - TimeUnit.MINUTES.toMillis(5)).toString(), - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(1, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file1 matches the search term as much as file2, - * both start with search term, both contain the search term as a word and date is same, - * result is zero - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file1 title matches "abc" as much as file2 title, both start with "abc", - * both contain "abc" as word and the date of both is the same - * - * Expected: return zero - */ - @Test - fun testSortByRelevanceWithSameRelevance() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abc.efg", - "C:\\AmazeFileManager\\abc.efg", - "user", - "symlink", - "100", - 123L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "ABC_EFG", - "C:\\AmazeFileManager\\ABC_EFG", - "user", - "symlink", - "101", - 124L, - true, - "1234", - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(0, fileListSorter.compare(file1, file2).toLong()) - } - - /** - * Purpose: when sort is [SortBy.RELEVANCE], if file2 matches the search term more than file1 - * and file2 date is more recent, but file1 starts with search term and contains the - * search term as a word, the result is negative. - * - * Input: FileListSorter with [DirSortBy.NONE_ON_TOP], [SortBy.RELEVANCE], [SortOrder.ASC] and search term "abc" - * compare(file1,file2) file2 title matches "abc" more than file1 title and is more recent both start with "abc", - * both contain "abc" as word and the date of both is the same - * - * Expected: return negative integer - */ - @Test - fun testSortByRelevanceWhole() { - val fileListSorter = FileListSorter( - DirSortBy.NONE_ON_TOP, - SortType(SortBy.RELEVANCE, SortOrder.ASC), - "abc" - ) - val currentTime = Date().time - - // matches 3/10 - // starts with search term - // contains search as whole word - // modification time is less recent - val file1 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "abc.efghij", - "C:\\AmazeFileManager\\abc.efghij", - "user", - "symlink", - "100", - 123L, - true, - (currentTime - TimeUnit.MINUTES.toMillis(10)).toString(), - false, - false, - OpenMode.UNKNOWN - ) - // matches 3/6 - // doesn't start with search term - // doesn't contain as whole word - // modification time is more recent - val file2 = LayoutElementParcelable( - ApplicationProvider.getApplicationContext(), - "EFGABC", - "C:\\AmazeFileManager\\EFGABC", - "user", - "symlink", - "101", - 124L, - true, - (currentTime - TimeUnit.MINUTES.toMillis(5)).toString(), - false, - false, - OpenMode.UNKNOWN - ) - Assert.assertEquals(-1, fileListSorter.compare(file1, file2).toLong()) - } } From efd66fb19f3a6919c2770bd34cd47be38a25b89b Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 21:26:14 +0100 Subject: [PATCH 35/55] change `FileSearch` to abstract to add tests for filters --- app/build.gradle | 1 + .../asynctasks/searchfilesystem/FileSearch.kt | 2 +- .../searchfilesystem/FileSearchTest.kt | 165 ++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt diff --git a/app/build.gradle b/app/build.gradle index 46b9bedd3e..598fc3f7f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -151,6 +151,7 @@ dependencies { testImplementation "org.jsoup:jsoup:$jsoupVersion" testImplementation "androidx.room:room-migration:$roomVersion" testImplementation "io.mockk:mockk:$mockkVersion" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3" kaptTest "com.google.auto.service:auto-service:1.0-rc4" androidTestImplementation "junit:junit:$junitVersion"//tests the app logic diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt index 4393cbbd42..1bbbc31f42 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt @@ -26,7 +26,7 @@ import com.amaze.filemanager.filesystem.HybridFileParcelable import java.util.Locale import java.util.regex.Pattern -sealed class FileSearch { +abstract class FileSearch { private val mutableFoundFilesLiveData: MutableLiveData> = MutableLiveData() val foundFilesLiveData: LiveData> = mutableFoundFilesLiveData diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt new file mode 100644 index 0000000000..c6a18c4ddf --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import kotlinx.coroutines.test.runTest +import org.junit.Assert +import org.junit.Test +import java.util.EnumSet + +class FileSearchTest { + + private val fileSearchMatch = object : FileSearch() { + override suspend fun search( + path: String, + filter: SearchFilter, + searchParameters: SearchParameters + ) { + val matchRange = filter.searchFilter(path) + Assert.assertNotNull("Expected $path to match filter", matchRange) + Assert.assertTrue("Start of match range is negative", matchRange!!.first >= 0) + Assert.assertTrue( + "End of match range is larger than length of $path", + matchRange.last < path.length + ) + val expectedRange = 5..9 + Assert.assertEquals( + "Range was not as expected $expectedRange but $matchRange", + expectedRange, + matchRange + ) + } + } + + private val fileSearchNotMatch = object : FileSearch() { + override suspend fun search( + path: String, + filter: SearchFilter, + searchParameters: SearchParameters + ) { + val matchRange = filter.searchFilter(path) + Assert.assertNull("Expected $path to not match filter", matchRange) + } + } + + private val fileSearchRegexMatches = object : FileSearch() { + override suspend fun search( + path: String, + filter: SearchFilter, + searchParameters: SearchParameters + ) { + val matchRange = filter.searchFilter(path) + Assert.assertNotNull("Expected $path to match filter", matchRange) + Assert.assertTrue("Start of match range is negative", matchRange!!.first >= 0) + Assert.assertTrue( + "End of match range is larger than length of $path", + matchRange.last < path.length + ) + val expectedRange = path.indices + Assert.assertEquals( + "Range was not as expected $expectedRange but $matchRange", + expectedRange, + matchRange + ) + } + } + + @Test + fun simpleFilterMatchTest() = runTest { + fileSearchMatch.search( + "abcde", + "01234ABcDe012", + EnumSet.noneOf(SearchParameter::class.java) + ) + } + + @Test + fun simpleFilterNotMatchTest() = runTest { + // There is no "e" + fileSearchNotMatch.search( + "abcde", + "01234abcd9012", + EnumSet.noneOf(SearchParameter::class.java) + ) + } + + @Test + fun regexFilterStarMatchTest() = runTest { + fileSearchMatch.search("a*e", "01234ABcDe012", SearchParameters.of(SearchParameter.REGEX)) + } + + @Test + fun regexFilterStarNotMatchTest() = runTest { + // There is no "e" + fileSearchNotMatch.search( + "a*e", + "01234aBcD9012", + SearchParameters.of(SearchParameter.REGEX) + ) + } + + @Test + fun regexFilterQuestionMarkMatchTest() = runTest { + fileSearchMatch.search( + "a???e", + "01234ABcDe0123", + SearchParameters.of(SearchParameter.REGEX) + ) + } + + @Test + fun regexFilterQuestionMarkNotMatchTest() = runTest { + // There is one character missing between "a" and "e" + fileSearchNotMatch.search( + "a???e", + "01234ABce9012", + SearchParameters.of(SearchParameter.REGEX) + ) + } + + @Test + fun regexFilterNotMatchNonWordCharacterTest() = runTest { + fileSearchNotMatch.search( + "a?c*e", + "0A-corn search", + SearchParameters.of(SearchParameter.REGEX) + ) + } + + @Test + fun regexMatchFilterMatchTest() = runTest { + fileSearchRegexMatches.search( + "a*e", + "A1234ABcDe0123e", + SearchParameter.REGEX + SearchParameter.REGEX_MATCHES + ) + } + + @Test + fun regexMatchFilterNotMatchTest() = runTest { + // Pattern does not match whole name + fileSearchNotMatch.search( + "a*e", + "01234ABcDe0123", + SearchParameter.REGEX + SearchParameter.REGEX_MATCHES + ) + } +} From 6a7747a24861504bd77c092164f06157b8202cac Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 21:53:56 +0100 Subject: [PATCH 36/55] fix one off error in filters --- .../asynchronous/asynctasks/searchfilesystem/FileSearch.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt index 1bbbc31f42..275404322b 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt @@ -81,7 +81,7 @@ abstract class FileSearch { ) ) if (start >= 0) { - start..(start + query.length) + start until start + query.length } else { null } @@ -93,7 +93,7 @@ abstract class FileSearch { // check case-insensitively if the pattern compiled from query can be found in fileName val matcher = pattern.matcher(fileName) if (matcher.find()) { - matcher.start()..matcher.end() + matcher.start() until matcher.end() } else { null } From 43d26d80a47aa82bb2d879e0876d49c3e8cf58f1 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 22:03:37 +0100 Subject: [PATCH 37/55] fix sorting in `SearchView` --- .../filemanager/ui/activities/MainActivityViewModel.kt | 7 +++++++ .../com/amaze/filemanager/ui/views/appbar/SearchView.java | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 46c3b0feaa..35dc90fbaa 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -58,6 +58,10 @@ class MainActivityViewModel(val applicationContext: Application) : var listCache: LruCache> = LruCache(50) var trashBinFilesLiveData: MutableLiveData?>? = null + /** The [LiveData] of the last triggered search */ + var lastSearchLiveData: LiveData> = MutableLiveData(listOf()) + private set + companion object { /** * size of list to be cached for local files @@ -113,6 +117,7 @@ class MainActivityViewModel(val applicationContext: Application) : basicSearch.search(query, path, searchParameters) } + lastSearchLiveData = basicSearch.foundFilesLiveData return basicSearch.foundFilesLiveData } @@ -143,6 +148,7 @@ class MainActivityViewModel(val applicationContext: Application) : indexedSearch.search(query, path, searchParameters) } + lastSearchLiveData = indexedSearch.foundFilesLiveData return indexedSearch.foundFilesLiveData } @@ -170,6 +176,7 @@ class MainActivityViewModel(val applicationContext: Application) : deepSearch.search(query, path, searchParameters) } + lastSearchLiveData = deepSearch.foundFilesLiveData return deepSearch.foundFilesLiveData } diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index a344818a9c..045daf9722 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -74,6 +74,7 @@ import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; import androidx.core.widget.NestedScrollView; +import androidx.lifecycle.LiveData; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.RecyclerView; @@ -467,7 +468,9 @@ private void onSortTypeSelected(MaterialDialog dialog, int index, SortOrder sort this.sortType = new SortType(SortBy.getSortBy(index), sortOrder); dialog.dismiss(); updateSearchResultsSortButtonDisplay(); - updateResultList(searchRecyclerViewAdapter.getCurrentList(), getSearchTerm()); + LiveData> lastSearchLiveData = + mainActivity.getCurrentMainFragment().getMainActivityViewModel().getLastSearchLiveData(); + updateResultList(lastSearchLiveData.getValue(), getSearchTerm()); } private void resetSearchResultsSortButton() { From 0bb23befe6fd1db0f1d868e029706cd920da24f0 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 22:04:29 +0100 Subject: [PATCH 38/55] remove observer if search is not relevant anymore --- .../filemanager/ui/views/appbar/SearchView.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 045daf9722..fa8939134c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -165,6 +165,9 @@ public SearchView(final AppBar appbar, MainActivity mainActivity) { clearImageView.setOnClickListener( v -> { + // observers of last search are removed to stop updating the results + removeObserversOfLastSearch(); + searchViewEditText.setText(""); clearRecyclerView(); }); @@ -204,6 +207,9 @@ public void afterTextChanged(Editable s) {} v -> { String s = getSearchTerm(); + // Remove observers of last search since its results should not be displayed anymore + removeObserversOfLastSearch(); + if (searchMode == 1) { saveRecentPreference(s); @@ -605,6 +611,8 @@ private void clearRecyclerView() { searchRecyclerViewAdapter.submitList(new ArrayList<>()); searchRecyclerViewAdapter.notifyDataSetChanged(); + deepSearchTV.setVisibility(View.GONE); + searchResultsHintTV.setVisibility(View.GONE); searchResultsSortHintTV.setVisibility(View.GONE); searchResultsSortButton.setVisibility(View.GONE); @@ -636,4 +644,12 @@ private SpannableString getSpannableText(String s1, String s2) { private String getSearchTerm() { return searchViewEditText.getText().toString().trim(); } + + private void removeObserversOfLastSearch() { + mainActivity + .getCurrentMainFragment() + .getMainActivityViewModel() + .getLastSearchLiveData() + .removeObservers(mainActivity.getCurrentMainFragment().getViewLifecycleOwner()); + } } From 0aea3bc870f7ff347f3099047196a917c267dbe3 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 22:16:13 +0100 Subject: [PATCH 39/55] make `IndexedSearch` cancellable --- .../asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt index 02f82c7c74..c38da643d2 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt @@ -23,7 +23,9 @@ package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem import android.database.Cursor import android.provider.MediaStore import com.amaze.filemanager.filesystem.RootHelper +import kotlinx.coroutines.isActive import java.io.File +import kotlin.coroutines.coroutineContext class IndexedSearch(private val cursor: Cursor) : FileSearch() { override suspend fun search( @@ -54,7 +56,7 @@ class IndexedSearch(private val cursor: Cursor) : FileSearch() { } } } - } while (cursor.moveToNext()) + } while (cursor.moveToNext() && coroutineContext.isActive) } cursor.close() From e3764e6fba6ac9192b6926c51cc2948bc9570b1f Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 17 Jan 2024 22:24:36 +0100 Subject: [PATCH 40/55] cancel search jobs if they are outdated --- .../ui/activities/MainActivityViewModel.kt | 11 +++++--- .../ui/views/appbar/SearchView.java | 25 +++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 35dc90fbaa..816c7e0700 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -47,6 +47,7 @@ import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstan import com.amaze.trashbin.MoveFilesCallback import com.amaze.trashbin.TrashBinFile import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.slf4j.LoggerFactory import java.io.File @@ -62,6 +63,10 @@ class MainActivityViewModel(val applicationContext: Application) : var lastSearchLiveData: LiveData> = MutableLiveData(listOf()) private set + /** The [Job] of the last triggered search */ + var lastSearchJob: Job? = null + private set + companion object { /** * size of list to be cached for local files @@ -113,7 +118,7 @@ class MainActivityViewModel(val applicationContext: Application) : val basicSearch = BasicSearch(this.applicationContext) - viewModelScope.launch(Dispatchers.IO) { + lastSearchJob = viewModelScope.launch(Dispatchers.IO) { basicSearch.search(query, path, searchParameters) } @@ -144,7 +149,7 @@ class MainActivityViewModel(val applicationContext: Application) : val indexedSearch = IndexedSearch(cursor) - viewModelScope.launch(Dispatchers.IO) { + lastSearchJob = viewModelScope.launch(Dispatchers.IO) { indexedSearch.search(query, path, searchParameters) } @@ -172,7 +177,7 @@ class MainActivityViewModel(val applicationContext: Application) : openMode ) - viewModelScope.launch(Dispatchers.IO) { + lastSearchJob = viewModelScope.launch(Dispatchers.IO) { deepSearch.search(query, path, searchParameters) } diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index fa8939134c..c23e072a1d 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CancellationException; import com.afollestad.materialdialogs.MaterialDialog; import com.amaze.filemanager.R; @@ -38,6 +39,7 @@ import com.amaze.filemanager.filesystem.files.sort.SortOrder; import com.amaze.filemanager.filesystem.files.sort.SortType; import com.amaze.filemanager.ui.activities.MainActivity; +import com.amaze.filemanager.ui.activities.MainActivityViewModel; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.theme.AppTheme; import com.amaze.filemanager.utils.Utils; @@ -78,6 +80,8 @@ import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.RecyclerView; +import kotlinx.coroutines.Job; + /** * SearchView, a simple view to search * @@ -166,7 +170,7 @@ public SearchView(final AppBar appbar, MainActivity mainActivity) { clearImageView.setOnClickListener( v -> { // observers of last search are removed to stop updating the results - removeObserversOfLastSearch(); + cancelLastSearch(); searchViewEditText.setText(""); clearRecyclerView(); @@ -207,8 +211,7 @@ public void afterTextChanged(Editable s) {} v -> { String s = getSearchTerm(); - // Remove observers of last search since its results should not be displayed anymore - removeObserversOfLastSearch(); + cancelLastSearch(); if (searchMode == 1) { @@ -645,11 +648,19 @@ private String getSearchTerm() { return searchViewEditText.getText().toString().trim(); } - private void removeObserversOfLastSearch() { - mainActivity - .getCurrentMainFragment() - .getMainActivityViewModel() + private void cancelLastSearch() { + MainActivityViewModel viewModel = + mainActivity.getCurrentMainFragment().getMainActivityViewModel(); + + // remove all observers + viewModel .getLastSearchLiveData() .removeObservers(mainActivity.getCurrentMainFragment().getViewLifecycleOwner()); + + // stop the job + Job lastJob = viewModel.getLastSearchJob(); + if (lastJob != null) { + lastJob.cancel(new CancellationException("Search outdated")); + } } } From ae0b14eb8b1953cad46fa2f455e9f29f7a4119c6 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 19 Jan 2024 15:45:46 +0100 Subject: [PATCH 41/55] change `FileSearch` interface The new interface shows more clearly that for different searches different objects should be used --- .../searchfilesystem/BasicSearch.kt | 13 ++-- .../asynctasks/searchfilesystem/DeepSearch.kt | 11 ++- .../asynctasks/searchfilesystem/FileSearch.kt | 20 ++--- .../searchfilesystem/IndexedSearch.kt | 13 ++-- .../ui/activities/MainActivityViewModel.kt | 14 ++-- .../searchfilesystem/FileSearchTest.kt | 78 ++++++++++--------- 6 files changed, 80 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt index 185a95a24a..09c568164e 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearch.kt @@ -24,14 +24,15 @@ import android.content.Context import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.filesystem.root.ListFilesCommand.listFiles -class BasicSearch(context: Context) : FileSearch() { +class BasicSearch( + query: String, + path: String, + searchParameters: SearchParameters, + context: Context +) : FileSearch(query, path, searchParameters) { private val applicationContext = context.applicationContext - override suspend fun search( - path: String, - filter: SearchFilter, - searchParameters: SearchParameters - ) { + override suspend fun search(filter: SearchFilter) { listFiles( path, SearchParameter.ROOT in searchParameters, diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt index a6ec911d04..43ba6a0259 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt @@ -28,9 +28,12 @@ import org.slf4j.LoggerFactory import kotlin.coroutines.coroutineContext class DeepSearch( + query: String, + path: String, + searchParameters: SearchParameters, context: Context, private val openMode: OpenMode -) : FileSearch() { +) : FileSearch(query, path, searchParameters) { private val LOG = LoggerFactory.getLogger(DeepSearch::class.java) private val applicationContext: Context @@ -44,11 +47,7 @@ class DeepSearch( * * @param directory the current path */ - override suspend fun search( - path: String, - filter: SearchFilter, - searchParameters: SearchParameters - ) { + override suspend fun search(filter: SearchFilter) { val directory = HybridFile(openMode, path) if (directory.isSmb) return diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt index 275404322b..c866ce81f9 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearch.kt @@ -26,7 +26,11 @@ import com.amaze.filemanager.filesystem.HybridFileParcelable import java.util.Locale import java.util.regex.Pattern -abstract class FileSearch { +abstract class FileSearch( + protected val query: String, + protected val path: String, + protected val searchParameters: SearchParameters +) { private val mutableFoundFilesLiveData: MutableLiveData> = MutableLiveData() val foundFilesLiveData: LiveData> = mutableFoundFilesLiveData @@ -36,17 +40,17 @@ abstract class FileSearch { * Search for files, whose names match [query], starting from [path] and add them to * [foundFilesLiveData] */ - suspend fun search(query: String, path: String, searchParameters: SearchParameters) { + suspend fun search() { if (SearchParameter.REGEX !in searchParameters) { // regex not turned on so we use simpleFilter - search(path, simpleFilter(query), searchParameters) + this.search(simpleFilter(query)) } else { if (SearchParameter.REGEX_MATCHES !in searchParameters) { // only regex turned on so we use regexFilter - search(path, regexFilter(query), searchParameters) + this.search(regexFilter(query)) } else { // regex turned on and names must match pattern so use regexMatchFilter - search(path, regexMatchFilter(query), searchParameters) + this.search(regexMatchFilter(query)) } } } @@ -55,11 +59,7 @@ abstract class FileSearch { * Search for files, whose names fulfill [filter], starting from [path] and add them to * [foundFilesLiveData]. */ - protected abstract suspend fun search( - path: String, - filter: SearchFilter, - searchParameters: SearchParameters - ) + protected abstract suspend fun search(filter: SearchFilter) /** * Add [file] to list of found files and post it to [foundFilesLiveData] diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt index c38da643d2..aac138128f 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearch.kt @@ -27,12 +27,13 @@ import kotlinx.coroutines.isActive import java.io.File import kotlin.coroutines.coroutineContext -class IndexedSearch(private val cursor: Cursor) : FileSearch() { - override suspend fun search( - path: String, - filter: SearchFilter, - searchParameters: SearchParameters - ) { +class IndexedSearch( + query: String, + path: String, + searchParameters: SearchParameters, + private val cursor: Cursor +) : FileSearch(query, path, searchParameters) { + override suspend fun search(filter: SearchFilter) { if (cursor.count > 0 && cursor.moveToFirst()) { do { val nextPath = diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 816c7e0700..82c290f4f2 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -116,10 +116,10 @@ class MainActivityViewModel(val applicationContext: Application) : val path = mainActivity.currentMainFragment?.currentPath ?: "" - val basicSearch = BasicSearch(this.applicationContext) + val basicSearch = BasicSearch(query, path, searchParameters, this.applicationContext) lastSearchJob = viewModelScope.launch(Dispatchers.IO) { - basicSearch.search(query, path, searchParameters) + basicSearch.search() } lastSearchLiveData = basicSearch.foundFilesLiveData @@ -137,7 +137,6 @@ class MainActivityViewModel(val applicationContext: Application) : MediaStore.Files.FileColumns.DATA, MediaStore.Files.FileColumns.DISPLAY_NAME ) - MediaStore.VOLUME_EXTERNAL_PRIMARY val cursor = mainActivity .contentResolver .query(MediaStore.Files.getContentUri("external"), projection, null, null, null) @@ -147,10 +146,10 @@ class MainActivityViewModel(val applicationContext: Application) : val path = mainActivity.currentMainFragment?.currentPath ?: "" - val indexedSearch = IndexedSearch(cursor) + val indexedSearch = IndexedSearch(query, path, searchParameters, cursor) lastSearchJob = viewModelScope.launch(Dispatchers.IO) { - indexedSearch.search(query, path, searchParameters) + indexedSearch.search() } lastSearchLiveData = indexedSearch.foundFilesLiveData @@ -173,12 +172,15 @@ class MainActivityViewModel(val applicationContext: Application) : val context = this.applicationContext val deepSearch = DeepSearch( + query, + path, + searchParameters, context, openMode ) lastSearchJob = viewModelScope.launch(Dispatchers.IO) { - deepSearch.search(query, path, searchParameters) + deepSearch.search() } lastSearchLiveData = deepSearch.foundFilesLiveData diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt index c6a18c4ddf..43a035c961 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt @@ -26,12 +26,13 @@ import org.junit.Test import java.util.EnumSet class FileSearchTest { - - private val fileSearchMatch = object : FileSearch() { + private fun getFileSearchMatch( + query: String, + path: String, + searchParameters: SearchParameters + ): FileSearch = object : FileSearch(query, path, searchParameters) { override suspend fun search( - path: String, - filter: SearchFilter, - searchParameters: SearchParameters + filter: SearchFilter ) { val matchRange = filter.searchFilter(path) Assert.assertNotNull("Expected $path to match filter", matchRange) @@ -49,22 +50,25 @@ class FileSearchTest { } } - private val fileSearchNotMatch = object : FileSearch() { - override suspend fun search( - path: String, - filter: SearchFilter, - searchParameters: SearchParameters - ) { - val matchRange = filter.searchFilter(path) - Assert.assertNull("Expected $path to not match filter", matchRange) + private fun getFileSearchNotMatch( + query: String, + path: String, + searchParameters: SearchParameters + ): FileSearch = + object : FileSearch(query, path, searchParameters) { + override suspend fun search(filter: SearchFilter) { + val matchRange = filter.searchFilter(path) + Assert.assertNull("Expected $path to not match filter", matchRange) + } } - } - private val fileSearchRegexMatches = object : FileSearch() { + private fun getFileSearchRegexMatches( + query: String, + path: String, + searchParameters: SearchParameters + ): FileSearch = object : FileSearch(query, path, searchParameters) { override suspend fun search( - path: String, - filter: SearchFilter, - searchParameters: SearchParameters + filter: SearchFilter ) { val matchRange = filter.searchFilter(path) Assert.assertNotNull("Expected $path to match filter", matchRange) @@ -84,82 +88,86 @@ class FileSearchTest { @Test fun simpleFilterMatchTest() = runTest { - fileSearchMatch.search( + getFileSearchMatch( "abcde", "01234ABcDe012", EnumSet.noneOf(SearchParameter::class.java) - ) + ).search() } @Test fun simpleFilterNotMatchTest() = runTest { // There is no "e" - fileSearchNotMatch.search( + getFileSearchNotMatch( "abcde", "01234abcd9012", EnumSet.noneOf(SearchParameter::class.java) - ) + ).search() } @Test fun regexFilterStarMatchTest() = runTest { - fileSearchMatch.search("a*e", "01234ABcDe012", SearchParameters.of(SearchParameter.REGEX)) + getFileSearchMatch( + "a*e", + "01234ABcDe012", + SearchParameters.of(SearchParameter.REGEX) + ).search() } @Test fun regexFilterStarNotMatchTest() = runTest { // There is no "e" - fileSearchNotMatch.search( + getFileSearchNotMatch( "a*e", "01234aBcD9012", SearchParameters.of(SearchParameter.REGEX) - ) + ).search() } @Test fun regexFilterQuestionMarkMatchTest() = runTest { - fileSearchMatch.search( + getFileSearchMatch( "a???e", "01234ABcDe0123", SearchParameters.of(SearchParameter.REGEX) - ) + ).search() } @Test fun regexFilterQuestionMarkNotMatchTest() = runTest { // There is one character missing between "a" and "e" - fileSearchNotMatch.search( + getFileSearchNotMatch( "a???e", "01234ABce9012", SearchParameters.of(SearchParameter.REGEX) - ) + ).search() } @Test fun regexFilterNotMatchNonWordCharacterTest() = runTest { - fileSearchNotMatch.search( + getFileSearchNotMatch( "a?c*e", "0A-corn search", SearchParameters.of(SearchParameter.REGEX) - ) + ).search() } @Test fun regexMatchFilterMatchTest() = runTest { - fileSearchRegexMatches.search( + getFileSearchRegexMatches( "a*e", "A1234ABcDe0123e", SearchParameter.REGEX + SearchParameter.REGEX_MATCHES - ) + ).search() } @Test fun regexMatchFilterNotMatchTest() = runTest { // Pattern does not match whole name - fileSearchNotMatch.search( + getFileSearchNotMatch( "a*e", "01234ABcDe0123", SearchParameter.REGEX + SearchParameter.REGEX_MATCHES - ) + ).search() } } From 56e0bf919a37475b83b8f0e0003bbae1be1993ee Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 19 Jan 2024 22:41:36 +0100 Subject: [PATCH 42/55] add test for all search modes --- app/build.gradle | 3 +- .../searchfilesystem/BasicSearchTest.kt | 170 +++++++++++++++++ .../searchfilesystem/DeepSearchTest.kt | 174 ++++++++++++++++++ .../searchfilesystem/IndexedSearchTest.kt | 156 ++++++++++++++++ build.gradle | 2 + 5 files changed, 504 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearchTest.kt diff --git a/app/build.gradle b/app/build.gradle index 598fc3f7f3..ec2383e529 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -151,7 +151,8 @@ dependencies { testImplementation "org.jsoup:jsoup:$jsoupVersion" testImplementation "androidx.room:room-migration:$roomVersion" testImplementation "io.mockk:mockk:$mockkVersion" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutineTestVersion" + testImplementation "androidx.arch.core:core-testing:$androidXArchCoreTestVersion" kaptTest "com.google.auto.service:auto-service:1.0-rc4" androidTestImplementation "junit:junit:$junitVersion"//tests the app logic diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt new file mode 100644 index 0000000000..6d9adb3cc7 --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.content.Context +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.amaze.filemanager.filesystem.HybridFile +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.root.ListFilesCommand +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.unmockkAll +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.util.EnumSet + +class BasicSearchTest { + @get:Rule + val rule = InstantTaskExecutorRule() + + @MockK(relaxed = true, relaxUnitFun = true) + lateinit var context: Context + + @MockK(relaxed = true, relaxUnitFun = true) + lateinit var foundFileMock: HybridFileParcelable + + @Before + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + every { context.applicationContext } returns context + + mockkConstructor(HybridFile::class) + every { anyConstructed().isDirectory(any()) } returns true + + mockkObject(ListFilesCommand) + every { ListFilesCommand.listFiles(any(), any(), any(), any(), any()) } answers { + val onFileFoundCallback = it.invocation.args.last() as (HybridFileParcelable) -> Unit + onFileFoundCallback(foundFileMock) + } + + every { foundFileMock.isDirectory(any()) } returns false + every { foundFileMock.isHidden } returns false + } + + @After + fun cleanup() { + unmockkAll() + } + + /** + * If the file name matches the query, the file should be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSimpleSearchMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName + + val deepSearch = BasicSearch( + "ab", + filePath, + EnumSet.noneOf(SearchParameter::class.java), + context + ) + + val expectedMatchRanges = listOf(0..1) + + deepSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertEquals(listOf(foundFileMock), actualResults.map { it.file }) + Assert.assertEquals(expectedMatchRanges, actualResults.map { it.matchRange }) + } + + runTest { + deepSearch.search() + } + } + + /** + * If the file name does not match the query, the file should not be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSimpleSearchNotMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName + + val deepSearch = BasicSearch( + "ba", + filePath, + EnumSet.noneOf(SearchParameter::class.java), + context + ) + + deepSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertTrue( + "List was not empty as expected but had ${actualResults.size} elements", + actualResults.isEmpty() + ) + } + + runTest { + deepSearch.search() + } + } + + /** + * If the match is in the path but not in the name, it should not be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSearchWithPathMatchButNameNotMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName + + val deepSearch = BasicSearch( + "test", + filePath, + EnumSet.noneOf(SearchParameter::class.java), + context + ) + + deepSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertTrue( + "List was not empty as expected but had ${actualResults.size} elements", + actualResults.isEmpty() + ) + } + + runTest { + deepSearch.search() + } + } +} diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt new file mode 100644 index 0000000000..dce6661c7b --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.content.Context +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.HybridFile +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.root.ListFilesCommand +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.unmockkAll +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.util.EnumSet + +class DeepSearchTest { + @get:Rule + val rule = InstantTaskExecutorRule() + + @MockK(relaxed = true, relaxUnitFun = true) + lateinit var context: Context + + @MockK(relaxed = true, relaxUnitFun = true) + lateinit var foundFileMock: HybridFileParcelable + + @Before + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + every { context.applicationContext } returns context + + mockkConstructor(HybridFile::class) + every { anyConstructed().isDirectory(any()) } returns true + + mockkObject(ListFilesCommand) + every { ListFilesCommand.listFiles(any(), any(), any(), any(), any()) } answers { + val onFileFoundCallback = it.invocation.args.last() as (HybridFileParcelable) -> Unit + onFileFoundCallback(foundFileMock) + } + + every { foundFileMock.isDirectory(any()) } returns false + every { foundFileMock.isHidden } returns false + } + + @After + fun cleanup() { + unmockkAll() + } + + /** + * If the file name matches the query, the file should be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSimpleSearchMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName + + val deepSearch = DeepSearch( + "ab", + filePath, + EnumSet.noneOf(SearchParameter::class.java), + context, + OpenMode.FILE + ) + + val expectedMatchRanges = listOf(0..1) + + deepSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertEquals(listOf(foundFileMock), actualResults.map { it.file }) + Assert.assertEquals(expectedMatchRanges, actualResults.map { it.matchRange }) + } + + runTest { + deepSearch.search() + } + } + + /** + * If the file name does not match the query, the file should not be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSimpleSearchNotMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName + + val deepSearch = DeepSearch( + "ba", + filePath, + EnumSet.noneOf(SearchParameter::class.java), + context, + OpenMode.FILE + ) + + deepSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertTrue( + "List was not empty as expected but had ${actualResults.size} elements", + actualResults.isEmpty() + ) + } + + runTest { + deepSearch.search() + } + } + + /** + * If the match is in the path but not in the name, it should not be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSearchWithPathMatchButNameNotMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName + + val deepSearch = DeepSearch( + "test", + filePath, + EnumSet.noneOf(SearchParameter::class.java), + context, + OpenMode.FILE + ) + + deepSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertTrue( + "List was not empty as expected but had ${actualResults.size} elements", + actualResults.isEmpty() + ) + } + + runTest { + deepSearch.search() + } + } +} diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearchTest.kt new file mode 100644 index 0000000000..7078a33a21 --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearchTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem + +import android.database.Cursor +import android.provider.MediaStore +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.RelaxedMockK +import io.mockk.unmockkAll +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.util.EnumSet + +class IndexedSearchTest { + @get:Rule + val rule = InstantTaskExecutorRule() + + val dataColumn = 0 + val displayNameColumn = 1 + + @RelaxedMockK + lateinit var mockCursor: Cursor + + @Before + fun setup() { + MockKAnnotations.init(this) + every { mockCursor.count } returns 1 + every { mockCursor.moveToFirst() } returns true + every { mockCursor.moveToNext() } returns false + every { + mockCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA) + } returns dataColumn + every { + mockCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME) + } returns displayNameColumn + } + + @After + fun cleanup() { + unmockkAll() + } + + /** + * If the file name matches the query, the file should be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSimpleSearchMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + every { mockCursor.getString(dataColumn) } returns filePath + every { mockCursor.getString(displayNameColumn) } returns fileName + + val expectedNames = listOf(fileName) + val expectedPaths = listOf(filePath) + val expectedRanges = listOf(0..1) + + val indexedSearch = IndexedSearch( + "ab", + "/", + EnumSet.noneOf(SearchParameter::class.java), + mockCursor + ) + indexedSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertEquals(expectedNames, actualResults!!.map { (file, _) -> file.name }) + Assert.assertEquals(expectedPaths, actualResults!!.map { (file, _) -> file.path }) + Assert.assertEquals(expectedRanges, actualResults.map { it.matchRange }) + } + runTest { + indexedSearch.search() + } + } + + /** + * If the file name does not match the query, the file should not be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSimpleSearchNotMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + every { mockCursor.getString(dataColumn) } returns filePath + every { mockCursor.getString(displayNameColumn) } returns fileName + + val indexedSearch = IndexedSearch( + "ba", + "/", + EnumSet.noneOf(SearchParameter::class.java), + mockCursor + ) + indexedSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertTrue( + "List was not empty as expected but had ${actualResults.size} elements", + actualResults.isEmpty() + ) + } + runTest { + indexedSearch.search() + } + } + + /** + * If the match is in the path but not in the name, it should not be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testSearchWithPathMatchButNameNotMatch() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + every { mockCursor.getString(dataColumn) } returns filePath + every { mockCursor.getString(displayNameColumn) } returns fileName + + val indexedSearch = IndexedSearch( + "te", + "/", + EnumSet.noneOf(SearchParameter::class.java), + mockCursor + ) + indexedSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertTrue( + "List was not empty as expected but had ${actualResults.size} elements", + actualResults.isEmpty() + ) + } + runTest { + indexedSearch.search() + } + } +} diff --git a/build.gradle b/build.gradle index 97650bcd52..d532ded02f 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,8 @@ buildscript { androidXTestVersion = "1.5.0" androidXTestRunnerVersion = "1.5.2" androidXTestExtVersion = "1.1.5" + androidXArchCoreTestVersion = "2.2.0" + kotlinxCoroutineTestVersion = "1.7.3" uiAutomatorVersion = "2.2.0" junitVersion = "4.13.2" slf4jVersion = "1.7.25" From 1b7f84b4ab157d24788fae86a1462fda2b794e16 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sat, 20 Jan 2024 00:21:51 +0100 Subject: [PATCH 43/55] add test for behavior with hidden files --- .../searchfilesystem/BasicSearchTest.kt | 56 +++++++++++++++---- .../searchfilesystem/DeepSearchTest.kt | 41 ++++++++++++-- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt index 6d9adb3cc7..d20dbfbd6c 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt @@ -64,7 +64,7 @@ class BasicSearchTest { } every { foundFileMock.isDirectory(any()) } returns false - every { foundFileMock.isHidden } returns false + every { foundFileMock.isHidden } returns true } @After @@ -84,23 +84,23 @@ class BasicSearchTest { every { foundFileMock.path } returns filePath every { foundFileMock.getName(any()) } returns fileName - val deepSearch = BasicSearch( + val basicSearch = BasicSearch( "ab", filePath, - EnumSet.noneOf(SearchParameter::class.java), + EnumSet.of(SearchParameter.SHOW_HIDDEN_FILES), context ) val expectedMatchRanges = listOf(0..1) - deepSearch.foundFilesLiveData.observeForever { actualResults -> + basicSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertEquals(listOf(foundFileMock), actualResults.map { it.file }) Assert.assertEquals(expectedMatchRanges, actualResults.map { it.matchRange }) } runTest { - deepSearch.search() + basicSearch.search() } } @@ -116,14 +116,14 @@ class BasicSearchTest { every { foundFileMock.path } returns filePath every { foundFileMock.getName(any()) } returns fileName - val deepSearch = BasicSearch( + val basicSearch = BasicSearch( "ba", filePath, - EnumSet.noneOf(SearchParameter::class.java), + EnumSet.of(SearchParameter.SHOW_HIDDEN_FILES), context ) - deepSearch.foundFilesLiveData.observeForever { actualResults -> + basicSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( "List was not empty as expected but had ${actualResults.size} elements", @@ -132,7 +132,7 @@ class BasicSearchTest { } runTest { - deepSearch.search() + basicSearch.search() } } @@ -148,14 +148,46 @@ class BasicSearchTest { every { foundFileMock.path } returns filePath every { foundFileMock.getName(any()) } returns fileName - val deepSearch = BasicSearch( + val basicSearch = BasicSearch( "test", filePath, + EnumSet.of(SearchParameter.SHOW_HIDDEN_FILES), + context + ) + + basicSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertTrue( + "List was not empty as expected but had ${actualResults.size} elements", + actualResults.isEmpty() + ) + } + + runTest { + basicSearch.search() + } + } + + /** + * If a file is hidden and hidden files should not be shown, it should not be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testMatchHiddenFile() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName + + val basicSearch = BasicSearch( + "ab", + filePath, EnumSet.noneOf(SearchParameter::class.java), context ) - deepSearch.foundFilesLiveData.observeForever { actualResults -> + basicSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( "List was not empty as expected but had ${actualResults.size} elements", @@ -164,7 +196,7 @@ class BasicSearchTest { } runTest { - deepSearch.search() + basicSearch.search() } } } diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt index dce6661c7b..0b667c25df 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt @@ -65,7 +65,7 @@ class DeepSearchTest { } every { foundFileMock.isDirectory(any()) } returns false - every { foundFileMock.isHidden } returns false + every { foundFileMock.isHidden } returns true } @After @@ -88,7 +88,7 @@ class DeepSearchTest { val deepSearch = DeepSearch( "ab", filePath, - EnumSet.noneOf(SearchParameter::class.java), + EnumSet.of(SearchParameter.SHOW_HIDDEN_FILES), context, OpenMode.FILE ) @@ -121,7 +121,7 @@ class DeepSearchTest { val deepSearch = DeepSearch( "ba", filePath, - EnumSet.noneOf(SearchParameter::class.java), + EnumSet.of(SearchParameter.SHOW_HIDDEN_FILES), context, OpenMode.FILE ) @@ -154,7 +154,7 @@ class DeepSearchTest { val deepSearch = DeepSearch( "test", filePath, - EnumSet.noneOf(SearchParameter::class.java), + EnumSet.of(SearchParameter.SHOW_HIDDEN_FILES), context, OpenMode.FILE ) @@ -171,4 +171,37 @@ class DeepSearchTest { deepSearch.search() } } + + /** + * If a file is hidden and hidden files should not be shown, it should not be added to + * [FileSearch.foundFilesLiveData] + */ + @Test + fun testMatchHiddenFile() { + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName + + val basicSearch = DeepSearch( + "ab", + filePath, + EnumSet.noneOf(SearchParameter::class.java), + context, + OpenMode.FILE + ) + + basicSearch.foundFilesLiveData.observeForever { actualResults -> + Assert.assertNotNull(actualResults) + Assert.assertTrue( + "List was not empty as expected but had ${actualResults.size} elements", + actualResults.isEmpty() + ) + } + + runTest { + basicSearch.search() + } + } } From c78c5f2f4d53a9c6e338fabb6ce0c92b9c137bfc Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sun, 21 Jan 2024 22:00:10 +0100 Subject: [PATCH 44/55] add documentation to tests and refactor them --- .../searchfilesystem/BasicSearchTest.kt | 42 +++++++------------ .../searchfilesystem/DeepSearchTest.kt | 40 ++++++------------ .../searchfilesystem/FileSearchTest.kt | 12 ++++++ .../searchfilesystem/IndexedSearchTest.kt | 30 ++++++------- 4 files changed, 52 insertions(+), 72 deletions(-) diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt index d20dbfbd6c..5a5702e3fa 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/BasicSearchTest.kt @@ -49,6 +49,10 @@ class BasicSearchTest { @MockK(relaxed = true, relaxUnitFun = true) lateinit var foundFileMock: HybridFileParcelable + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + /** Set up all mocks */ @Before fun setup() { MockKAnnotations.init(this, relaxUnitFun = true) @@ -65,8 +69,11 @@ class BasicSearchTest { every { foundFileMock.isDirectory(any()) } returns false every { foundFileMock.isHidden } returns true + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName } + /** Clean up all mocks */ @After fun cleanup() { unmockkAll() @@ -78,12 +85,6 @@ class BasicSearchTest { */ @Test fun testSimpleSearchMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - - every { foundFileMock.path } returns filePath - every { foundFileMock.getName(any()) } returns fileName - val basicSearch = BasicSearch( "ab", filePath, @@ -110,12 +111,6 @@ class BasicSearchTest { */ @Test fun testSimpleSearchNotMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - - every { foundFileMock.path } returns filePath - every { foundFileMock.getName(any()) } returns fileName - val basicSearch = BasicSearch( "ba", filePath, @@ -126,7 +121,7 @@ class BasicSearchTest { basicSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( - "List was not empty as expected but had ${actualResults.size} elements", + listNotEmptyError(actualResults.size), actualResults.isEmpty() ) } @@ -142,12 +137,6 @@ class BasicSearchTest { */ @Test fun testSearchWithPathMatchButNameNotMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - - every { foundFileMock.path } returns filePath - every { foundFileMock.getName(any()) } returns fileName - val basicSearch = BasicSearch( "test", filePath, @@ -158,7 +147,7 @@ class BasicSearchTest { basicSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( - "List was not empty as expected but had ${actualResults.size} elements", + listNotEmptyError(actualResults.size), actualResults.isEmpty() ) } @@ -169,17 +158,11 @@ class BasicSearchTest { } /** - * If a file is hidden and hidden files should not be shown, it should not be added to + * If a file is hidden and hidden files should not be shown, it should not be added to * [FileSearch.foundFilesLiveData] */ @Test fun testMatchHiddenFile() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - - every { foundFileMock.path } returns filePath - every { foundFileMock.getName(any()) } returns fileName - val basicSearch = BasicSearch( "ab", filePath, @@ -190,7 +173,7 @@ class BasicSearchTest { basicSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( - "List was not empty as expected but had ${actualResults.size} elements", + listNotEmptyError(actualResults.size), actualResults.isEmpty() ) } @@ -199,4 +182,7 @@ class BasicSearchTest { basicSearch.search() } } + + private fun listNotEmptyError(size: Int) = + "List was not empty as expected but had $size elements" } diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt index 0b667c25df..e13c7dc553 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt @@ -50,6 +50,10 @@ class DeepSearchTest { @MockK(relaxed = true, relaxUnitFun = true) lateinit var foundFileMock: HybridFileParcelable + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + /** Set up all mocks */ @Before fun setup() { MockKAnnotations.init(this, relaxUnitFun = true) @@ -66,8 +70,11 @@ class DeepSearchTest { every { foundFileMock.isDirectory(any()) } returns false every { foundFileMock.isHidden } returns true + every { foundFileMock.path } returns filePath + every { foundFileMock.getName(any()) } returns fileName } + /** Clean up all mocks */ @After fun cleanup() { unmockkAll() @@ -79,12 +86,6 @@ class DeepSearchTest { */ @Test fun testSimpleSearchMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - - every { foundFileMock.path } returns filePath - every { foundFileMock.getName(any()) } returns fileName - val deepSearch = DeepSearch( "ab", filePath, @@ -112,12 +113,6 @@ class DeepSearchTest { */ @Test fun testSimpleSearchNotMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - - every { foundFileMock.path } returns filePath - every { foundFileMock.getName(any()) } returns fileName - val deepSearch = DeepSearch( "ba", filePath, @@ -129,7 +124,7 @@ class DeepSearchTest { deepSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( - "List was not empty as expected but had ${actualResults.size} elements", + listNotEmptyError(actualResults.size), actualResults.isEmpty() ) } @@ -145,12 +140,6 @@ class DeepSearchTest { */ @Test fun testSearchWithPathMatchButNameNotMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - - every { foundFileMock.path } returns filePath - every { foundFileMock.getName(any()) } returns fileName - val deepSearch = DeepSearch( "test", filePath, @@ -162,7 +151,7 @@ class DeepSearchTest { deepSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( - "List was not empty as expected but had ${actualResults.size} elements", + listNotEmptyError(actualResults.size), actualResults.isEmpty() ) } @@ -178,12 +167,6 @@ class DeepSearchTest { */ @Test fun testMatchHiddenFile() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - - every { foundFileMock.path } returns filePath - every { foundFileMock.getName(any()) } returns fileName - val basicSearch = DeepSearch( "ab", filePath, @@ -195,7 +178,7 @@ class DeepSearchTest { basicSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( - "List was not empty as expected but had ${actualResults.size} elements", + listNotEmptyError(actualResults.size), actualResults.isEmpty() ) } @@ -204,4 +187,7 @@ class DeepSearchTest { basicSearch.search() } } + + private fun listNotEmptyError(size: Int) = + "List was not empty as expected but had $size elements" } diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt index 43a035c961..57a8f61556 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/FileSearchTest.kt @@ -86,6 +86,7 @@ class FileSearchTest { } } + /** Test the simple filter with a path that matches the query */ @Test fun simpleFilterMatchTest() = runTest { getFileSearchMatch( @@ -95,6 +96,7 @@ class FileSearchTest { ).search() } + /** Test the simple filter with a path that does not match the query */ @Test fun simpleFilterNotMatchTest() = runTest { // There is no "e" @@ -105,6 +107,7 @@ class FileSearchTest { ).search() } + /** Test the regex filter with a path that matches the query. The query contains `*`. */ @Test fun regexFilterStarMatchTest() = runTest { getFileSearchMatch( @@ -114,6 +117,7 @@ class FileSearchTest { ).search() } + /** Test the regex filter with a path that does not match the query. The query contains `*`. */ @Test fun regexFilterStarNotMatchTest() = runTest { // There is no "e" @@ -124,6 +128,7 @@ class FileSearchTest { ).search() } + /** Test the regex filter with a path that matches the query. The query contains `?`. */ @Test fun regexFilterQuestionMarkMatchTest() = runTest { getFileSearchMatch( @@ -133,6 +138,7 @@ class FileSearchTest { ).search() } + /** Test the regex filter with a path that does not match the query. The query contains `?`. */ @Test fun regexFilterQuestionMarkNotMatchTest() = runTest { // There is one character missing between "a" and "e" @@ -143,6 +149,10 @@ class FileSearchTest { ).search() } + /** + * Test the regex filter with a path that does not match the query + * because `-` is not recognized by `?` or `*`. + */ @Test fun regexFilterNotMatchNonWordCharacterTest() = runTest { getFileSearchNotMatch( @@ -152,6 +162,7 @@ class FileSearchTest { ).search() } + /** Test the regex match filter with a path that completely matches the query */ @Test fun regexMatchFilterMatchTest() = runTest { getFileSearchRegexMatches( @@ -161,6 +172,7 @@ class FileSearchTest { ).search() } + /** Test the regex match filter with a path that does not completely match the query */ @Test fun regexMatchFilterNotMatchTest() = runTest { // Pattern does not match whole name diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearchTest.kt index 7078a33a21..6f78265f4c 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearchTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/IndexedSearchTest.kt @@ -45,6 +45,10 @@ class IndexedSearchTest { @RelaxedMockK lateinit var mockCursor: Cursor + val filePath = "/test/abc.txt" + val fileName = "abc.txt" + + /** Set up all mocks */ @Before fun setup() { MockKAnnotations.init(this) @@ -57,8 +61,12 @@ class IndexedSearchTest { every { mockCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME) } returns displayNameColumn + + every { mockCursor.getString(dataColumn) } returns filePath + every { mockCursor.getString(displayNameColumn) } returns fileName } + /** Clean up all mocks */ @After fun cleanup() { unmockkAll() @@ -70,11 +78,6 @@ class IndexedSearchTest { */ @Test fun testSimpleSearchMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - every { mockCursor.getString(dataColumn) } returns filePath - every { mockCursor.getString(displayNameColumn) } returns fileName - val expectedNames = listOf(fileName) val expectedPaths = listOf(filePath) val expectedRanges = listOf(0..1) @@ -102,11 +105,6 @@ class IndexedSearchTest { */ @Test fun testSimpleSearchNotMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - every { mockCursor.getString(dataColumn) } returns filePath - every { mockCursor.getString(displayNameColumn) } returns fileName - val indexedSearch = IndexedSearch( "ba", "/", @@ -116,7 +114,7 @@ class IndexedSearchTest { indexedSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( - "List was not empty as expected but had ${actualResults.size} elements", + listNotEmptyError(actualResults.size), actualResults.isEmpty() ) } @@ -131,11 +129,6 @@ class IndexedSearchTest { */ @Test fun testSearchWithPathMatchButNameNotMatch() { - val filePath = "/test/abc.txt" - val fileName = "abc.txt" - every { mockCursor.getString(dataColumn) } returns filePath - every { mockCursor.getString(displayNameColumn) } returns fileName - val indexedSearch = IndexedSearch( "te", "/", @@ -145,7 +138,7 @@ class IndexedSearchTest { indexedSearch.foundFilesLiveData.observeForever { actualResults -> Assert.assertNotNull(actualResults) Assert.assertTrue( - "List was not empty as expected but had ${actualResults.size} elements", + listNotEmptyError(actualResults.size), actualResults.isEmpty() ) } @@ -153,4 +146,7 @@ class IndexedSearchTest { indexedSearch.search() } } + + private fun listNotEmptyError(size: Int) = + "List was not empty as expected but had $size elements" } From 58a39d9dc3405c7abb3151a0d604ba291ce08e9e Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 26 Jan 2024 19:18:41 +0100 Subject: [PATCH 45/55] clean up DeepSearchTest --- .../asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt index e13c7dc553..cf26588e63 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearchTest.kt @@ -64,7 +64,7 @@ class DeepSearchTest { mockkObject(ListFilesCommand) every { ListFilesCommand.listFiles(any(), any(), any(), any(), any()) } answers { - val onFileFoundCallback = it.invocation.args.last() as (HybridFileParcelable) -> Unit + val onFileFoundCallback = lastArg<(HybridFileParcelable) -> Unit>() onFileFoundCallback(foundFileMock) } From 9d8aa44ef07bc586104c6b35acdfd42895ce65fe Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 22 Jan 2024 18:18:03 +0100 Subject: [PATCH 46/55] change `SearchRecyclerViewAdapter` to work with `SearchResult` --- .../adapters/SearchRecyclerViewAdapter.kt | 37 ++++++++++--------- .../ui/views/appbar/SearchView.java | 7 +--- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt index d9ea4c87ce..02ad78f390 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -31,27 +31,30 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.application.AppConfig -import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResult import com.amaze.filemanager.ui.activities.MainActivity import com.amaze.filemanager.ui.colors.ColorPreference import java.util.Random class SearchRecyclerViewAdapter : - ListAdapter( + ListAdapter( - object : DiffUtil.ItemCallback() { + object : DiffUtil.ItemCallback() { override fun areItemsTheSame( - oldItem: HybridFileParcelable, - newItem: HybridFileParcelable + oldItem: SearchResult, + newItem: SearchResult ): Boolean { - return oldItem.path == newItem.path && oldItem.name == newItem.name + return oldItem.file.path == newItem.file.path && + oldItem.file.name == newItem.file.name } override fun areContentsTheSame( - oldItem: HybridFileParcelable, - newItem: HybridFileParcelable + oldItem: SearchResult, + newItem: SearchResult ): Boolean { - return oldItem.path == newItem.path && oldItem.name == newItem.name + return oldItem.file.path == newItem.file.path && + oldItem.file.name == newItem.file.name && + oldItem.matchRange == newItem.matchRange } } ) { @@ -62,17 +65,17 @@ class SearchRecyclerViewAdapter : } override fun onBindViewHolder(holder: SearchRecyclerViewAdapter.ViewHolder, position: Int) { - val item = getItem(position) + val (file, matchResult) = getItem(position) - holder.fileNameTV.text = item.name - holder.filePathTV.text = item.path.substring(0, item.path.lastIndexOf("/")) + holder.fileNameTV.text = file.name + holder.filePathTV.text = file.path.substring(0, file.path.lastIndexOf("/")) holder.colorView.setBackgroundColor(getRandomColor(holder.colorView.context)) val colorPreference = (AppConfig.getInstance().mainActivityContext as MainActivity).currentColorPreference - if (item.isDirectory) { + if (file.isDirectory) { holder.colorView.setBackgroundColor(colorPreference.primaryFirstTab) } else { holder.colorView.setBackgroundColor(colorPreference.accent) @@ -93,16 +96,16 @@ class SearchRecyclerViewAdapter : view.setOnClickListener { - val item = getItem(adapterPosition) + val (file, _) = getItem(adapterPosition) - if (!item.isDirectory) { - item.openFile( + if (!file.isDirectory) { + file.openFile( AppConfig.getInstance().mainActivityContext as MainActivity?, false ) } else { (AppConfig.getInstance().mainActivityContext as MainActivity?) - ?.goToMain(item.path) + ?.goToMain(file.path) } (AppConfig.getInstance().mainActivityContext as MainActivity?) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index c23e072a1d..2975035013 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -33,7 +33,6 @@ import com.amaze.filemanager.adapters.SearchRecyclerViewAdapter; import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResult; import com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem.SearchResultListSorter; -import com.amaze.filemanager.filesystem.HybridFileParcelable; import com.amaze.filemanager.filesystem.files.sort.DirSortBy; import com.amaze.filemanager.filesystem.files.sort.SortBy; import com.amaze.filemanager.filesystem.files.sort.SortOrder; @@ -376,11 +375,7 @@ private void updateResultList(List newResults, String searchTerm) ArrayList items = new ArrayList<>(newResults); Collections.sort( items, new SearchResultListSorter(DirSortBy.NONE_ON_TOP, sortType, searchTerm)); - ArrayList files = new ArrayList<>(); - for (SearchResult searchResult : items) { - files.add(searchResult.getFile()); - } - searchRecyclerViewAdapter.submitList(files); + searchRecyclerViewAdapter.submitList(items); searchRecyclerViewAdapter.notifyDataSetChanged(); } From d8c3a16b06f55d78d8d713a2ec3ab82d789cb68d Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sun, 4 Feb 2024 22:17:34 +0100 Subject: [PATCH 47/55] add boolean to MenuMetadata The boolean specifies if the FAB should be shown when the entry is clicked --- .../filemanager/ui/views/drawer/Drawer.java | 32 +++++++++---------- .../ui/views/drawer/MenuMetadata.java | 5 ++- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 44b8b85794..eea5dbe14e 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -308,7 +308,7 @@ public void refreshDrawer() { STORAGES_GROUP, order++, "OTG", - new MenuMetadata(file), + new MenuMetadata(file, false), R.drawable.ic_usb_white_24dp, R.drawable.ic_show_chart_black_24dp, Formatter.formatFileSize(mainActivity, freeSpace), @@ -322,7 +322,7 @@ public void refreshDrawer() { STORAGES_GROUP, order++, name, - new MenuMetadata(file), + new MenuMetadata(file, false), icon, R.drawable.ic_show_chart_black_24dp, Formatter.formatFileSize(mainActivity, freeSpace), @@ -344,7 +344,7 @@ public void refreshDrawer() { SERVERS_GROUP, order++, file[0], - new MenuMetadata(file[1]), + new MenuMetadata(file[1], false), R.drawable.ic_settings_remote_white_24dp, R.drawable.ic_edit_24dp); } @@ -363,7 +363,7 @@ public void refreshDrawer() { CLOUDS_GROUP, order++, CloudHandler.CLOUD_NAME_DROPBOX, - new MenuMetadata(CloudHandler.CLOUD_PREFIX_DROPBOX + "/"), + new MenuMetadata(CloudHandler.CLOUD_PREFIX_DROPBOX + "/", false), R.drawable.ic_dropbox_white_24dp, deleteIcon); @@ -377,7 +377,7 @@ public void refreshDrawer() { CLOUDS_GROUP, order++, CloudHandler.CLOUD_NAME_BOX, - new MenuMetadata(CloudHandler.CLOUD_PREFIX_BOX + "/"), + new MenuMetadata(CloudHandler.CLOUD_PREFIX_BOX + "/", false), R.drawable.ic_box_white_24dp, deleteIcon); @@ -391,7 +391,7 @@ public void refreshDrawer() { CLOUDS_GROUP, order++, CloudHandler.CLOUD_NAME_ONE_DRIVE, - new MenuMetadata(CloudHandler.CLOUD_PREFIX_ONE_DRIVE + "/"), + new MenuMetadata(CloudHandler.CLOUD_PREFIX_ONE_DRIVE + "/", false), R.drawable.ic_onedrive_white_24dp, deleteIcon); @@ -405,7 +405,7 @@ public void refreshDrawer() { CLOUDS_GROUP, order++, CloudHandler.CLOUD_NAME_GOOGLE_DRIVE, - new MenuMetadata(CloudHandler.CLOUD_PREFIX_GOOGLE_DRIVE + "/"), + new MenuMetadata(CloudHandler.CLOUD_PREFIX_GOOGLE_DRIVE + "/", false), R.drawable.ic_google_drive_white_24dp, deleteIcon); @@ -430,7 +430,7 @@ public void refreshDrawer() { FOLDERS_GROUP, order++, file[0], - new MenuMetadata(file[1]), + new MenuMetadata(file[1], false), R.drawable.ic_folder_white_24dp, R.drawable.ic_edit_24dp); } @@ -451,7 +451,7 @@ public void refreshDrawer() { QUICKACCESSES_GROUP, order++, R.string.quick, - new MenuMetadata("5"), + new MenuMetadata("5", true), R.drawable.ic_star_white_24dp, null); } @@ -461,7 +461,7 @@ public void refreshDrawer() { QUICKACCESSES_GROUP, order++, R.string.recent, - new MenuMetadata("6"), + new MenuMetadata("6", true), R.drawable.ic_history_white_24dp, null); } @@ -471,7 +471,7 @@ public void refreshDrawer() { QUICKACCESSES_GROUP, order++, R.string.images, - new MenuMetadata("0"), + new MenuMetadata("0", true), R.drawable.ic_photo_library_white_24dp, null); } @@ -481,7 +481,7 @@ public void refreshDrawer() { QUICKACCESSES_GROUP, order++, R.string.videos, - new MenuMetadata("1"), + new MenuMetadata("1", true), R.drawable.ic_video_library_white_24dp, null); } @@ -491,7 +491,7 @@ public void refreshDrawer() { QUICKACCESSES_GROUP, order++, R.string.audio, - new MenuMetadata("2"), + new MenuMetadata("2", true), R.drawable.ic_library_music_white_24dp, null); } @@ -501,7 +501,7 @@ public void refreshDrawer() { QUICKACCESSES_GROUP, order++, R.string.documents, - new MenuMetadata("3"), + new MenuMetadata("3", true), R.drawable.ic_library_books_white_24dp, null); } @@ -511,7 +511,7 @@ public void refreshDrawer() { QUICKACCESSES_GROUP, order++, R.string.apks, - new MenuMetadata("4"), + new MenuMetadata("4", true), R.drawable.ic_apk_library_white_24dp, null); } @@ -596,7 +596,7 @@ public void refreshDrawer() { LASTGROUP, order++, R.string.trash_bin, - new MenuMetadata("7"), + new MenuMetadata("7", true), R.drawable.round_delete_outline_24, null); diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/MenuMetadata.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/MenuMetadata.java index af2b98b8da..bfcc7ee4e4 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/MenuMetadata.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/MenuMetadata.java @@ -26,17 +26,20 @@ public final class MenuMetadata { public final int type; public final String path; + public final boolean hideFabInMainFragment; public final OnClickListener onClickListener; - public MenuMetadata(String path) { + public MenuMetadata(String path, boolean hideFabInMainFragment) { this.type = ITEM_ENTRY; this.path = path; + this.hideFabInMainFragment = hideFabInMainFragment; this.onClickListener = null; } public MenuMetadata(OnClickListener onClickListener) { this.type = ITEM_INTENT; this.onClickListener = onClickListener; + this.hideFabInMainFragment = false; this.path = null; } From b795699a1ed79792808503a551231cedbd3a67a2 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sun, 4 Feb 2024 22:47:37 +0100 Subject: [PATCH 48/55] change pendingPath to store hideFab boolean as well --- .../filemanager/ui/views/drawer/Drawer.java | 12 +++++----- .../ui/views/drawer/PendingPath.kt | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/amaze/filemanager/ui/views/drawer/PendingPath.kt diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index eea5dbe14e..34d2839ebe 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -128,7 +128,7 @@ public class Drawer implements NavigationView.OnNavigationItemSelectedListener { 0; // number of storage available (internal/external/otg etc) private boolean isDrawerLocked = false; private FragmentTransaction pending_fragmentTransaction; - private String pendingPath; + private PendingPath pendingPath; private String firstPath = null, secondPath = null; private DrawerLayout mDrawerLayout; @@ -778,20 +778,20 @@ public void onDrawerClosed() { } if (pendingPath != null) { - HybridFile hFile = new HybridFile(OpenMode.UNKNOWN, pendingPath); + HybridFile hFile = new HybridFile(OpenMode.UNKNOWN, pendingPath.getPath()); hFile.generateMode(mainActivity); if (hFile.isSimpleFile()) { - FileUtils.openFile(new File(pendingPath), mainActivity, mainActivity.getPrefs()); + FileUtils.openFile(new File(pendingPath.getPath()), mainActivity, mainActivity.getPrefs()); resetPendingPath(); return; } MainFragment mainFragment = mainActivity.getCurrentMainFragment(); if (mainFragment != null) { - mainFragment.loadlist(pendingPath, false, OpenMode.UNKNOWN, false); + mainFragment.loadlist(pendingPath.getPath(), false, OpenMode.UNKNOWN, false); resetPendingPath(); } else { - mainActivity.goToMain(pendingPath); + mainActivity.goToMain(pendingPath.getPath(), pendingPath.getHideFabInMainFragment()); resetPendingPath(); return; } @@ -845,7 +845,7 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { }); dialog.show(); } else { - pendingPath = meta.path; + pendingPath = new PendingPath(meta.path, meta.hideFabInMainFragment); closeIfNotLocked(); if (isLocked()) { onDrawerClosed(); diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/PendingPath.kt b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/PendingPath.kt new file mode 100644 index 0000000000..ec07212886 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/PendingPath.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.ui.views.drawer + +data class PendingPath(val path: String, val hideFabInMainFragment: Boolean) From 38a49d391b15b69c5a234987e4ab453fe22304ae Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sun, 4 Feb 2024 22:50:57 +0100 Subject: [PATCH 49/55] store hideFab boolean in MainActivity It determines if the FAB is shown or not when there is a MainFragment --- .../ui/activities/MainActivity.java | 21 ++++++++++++++++--- .../ui/fragments/MainFragment.java | 2 ++ .../filemanager/ui/views/drawer/Drawer.java | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 825126b0a4..3fa776484b 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -321,6 +321,8 @@ public class MainActivity extends PermissionsActivity private String scrollToFileName = null; + private boolean hideFabInMainFragment = false; + public static final int REQUEST_CODE_CLOUD_LIST_KEYS = 5463; public static final int REQUEST_CODE_CLOUD_LIST_KEY = 5472; @@ -948,7 +950,7 @@ public void onBackPressed() { fragmentTransaction.remove(compressedExplorerFragment); fragmentTransaction.commit(); supportInvalidateOptionsMenu(); - floatingActionButton.show(); + showFab(); } } else { compressedExplorerFragment.mActionMode.finish(); @@ -999,6 +1001,10 @@ public void exit() { } public void goToMain(String path) { + goToMain(path, false); + } + + public void goToMain(String path, boolean hideFab) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // title.setText(R.string.app_name); TabFragment tabFragment = new TabFragment(); @@ -1019,7 +1025,8 @@ public void goToMain(String path) { transaction.addToBackStack("tabt" + 1); transaction.commitAllowingStateLoss(); appbar.setTitle(null); - floatingActionButton.show(); + this.hideFabInMainFragment = hideFab; + if (isCompressedOpen && pathInCompressedArchive != null) { openCompressed(pathInCompressedArchive); pathInCompressedArchive = null; @@ -1527,7 +1534,11 @@ public SpeedDialView getFAB() { } public void showFab() { - showFab(getFAB()); + if (hideFabInMainFragment) { + hideFab(); + } else { + showFab(getFAB()); + } } private void showFab(SpeedDialView fab) { @@ -2542,4 +2553,8 @@ private void executeWithMainFragment( } } } + + public void setHideFabInMainFragment(boolean hideFabInMainFragment) { + this.hideFabInMainFragment = hideFabInMainFragment; + } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index a6275417f2..aec91017ec 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -1073,6 +1073,7 @@ public void goBack() { if (mainFragmentViewModel.getOpenMode() == OpenMode.CUSTOM || mainFragmentViewModel.getOpenMode() == OpenMode.TRASH_BIN) { loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); + requireMainActivity().setHideFabInMainFragment(false); return; } @@ -1081,6 +1082,7 @@ public void goBack() { if (requireMainActivity().getListItemSelected()) { adapter.toggleChecked(false); } else { + requireMainActivity().setHideFabInMainFragment(false); if (OpenMode.SMB.equals(mainFragmentViewModel.getOpenMode())) { if (mainFragmentViewModel.getSmbPath() != null && !mainFragmentViewModel.getSmbPath().equals(mainFragmentViewModel.getCurrentPath())) { diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 34d2839ebe..7aacfae928 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -786,6 +786,7 @@ public void onDrawerClosed() { return; } + mainActivity.setHideFabInMainFragment(pendingPath.getHideFabInMainFragment()); MainFragment mainFragment = mainActivity.getCurrentMainFragment(); if (mainFragment != null) { mainFragment.loadlist(pendingPath.getPath(), false, OpenMode.UNKNOWN, false); From df411018e3296579f3017c8deba7d851cdcfc8eb Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sun, 4 Feb 2024 22:58:14 +0100 Subject: [PATCH 50/55] hide FAB when SearchView is shown --- .../java/com/amaze/filemanager/ui/views/appbar/SearchView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index c23e072a1d..afec6fd7e9 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -415,6 +415,7 @@ public void revealSearchView() { } mainActivity.showSmokeScreen(); + mainActivity.hideFab(); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.setDuration(600); @@ -546,6 +547,7 @@ public void hideSearchView() { // removing background fade view mainActivity.hideSmokeScreen(); + mainActivity.showFab(); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.setDuration(600); animator.start(); From af94154064d7ccd385fbc5673b00d1b8f16f9bb4 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 5 Feb 2024 17:50:25 +0100 Subject: [PATCH 51/55] move hideFab boolean to MainFragment Since each MainFragment can be in different paths, the information if the FAB should be shown or not should be stored there --- .../ui/activities/MainActivity.java | 16 ++++--------- .../ui/fragments/MainFragment.java | 19 +++++++++++++-- .../filemanager/ui/fragments/TabFragment.java | 24 +++++++++++-------- .../filemanager/ui/views/drawer/Drawer.java | 2 +- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 3fa776484b..fe184bcd81 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -321,8 +321,6 @@ public class MainActivity extends PermissionsActivity private String scrollToFileName = null; - private boolean hideFabInMainFragment = false; - public static final int REQUEST_CODE_CLOUD_LIST_KEYS = 5463; public static final int REQUEST_CODE_CLOUD_LIST_KEY = 5472; @@ -540,7 +538,7 @@ public void onPermissionGranted() { .subscribe( () -> { if (tabFragment != null) { - tabFragment.refactorDrawerStorages(false); + tabFragment.refactorDrawerStorages(false, false); Fragment main = tabFragment.getFragmentAtIndex(0); if (main != null) ((MainFragment) main).updateTabWithDb(tabHandler.findTab(1)); Fragment main1 = tabFragment.getFragmentAtIndex(1); @@ -1015,17 +1013,17 @@ public void goToMain(String path, boolean hideFab) { path = "6"; } } + Bundle b = new Bundle(); if (path != null && path.length() > 0) { - Bundle b = new Bundle(); b.putString("path", path); - tabFragment.setArguments(b); } + b.putBoolean(MainFragment.BUNDLE_HIDE_FAB, hideFab); + tabFragment.setArguments(b); transaction.replace(R.id.content_frame, tabFragment); // Commit the transaction transaction.addToBackStack("tabt" + 1); transaction.commitAllowingStateLoss(); appbar.setTitle(null); - this.hideFabInMainFragment = hideFab; if (isCompressedOpen && pathInCompressedArchive != null) { openCompressed(pathInCompressedArchive); @@ -1534,7 +1532,7 @@ public SpeedDialView getFAB() { } public void showFab() { - if (hideFabInMainFragment) { + if (getCurrentMainFragment() != null && getCurrentMainFragment().getHideFab()) { hideFab(); } else { showFab(getFAB()); @@ -2553,8 +2551,4 @@ private void executeWithMainFragment( } } } - - public void setHideFabInMainFragment(boolean hideFabInMainFragment) { - this.hideFabInMainFragment = hideFabInMainFragment; - } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index aec91017ec..95ae344623 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -143,6 +143,8 @@ public class MainFragment extends Fragment private static final Logger LOG = LoggerFactory.getLogger(MainFragment.class); private static final String KEY_FRAGMENT_MAIN = "main"; + public static final String BUNDLE_HIDE_FAB = "hideFab"; + public SwipeRefreshLayout mSwipeRefreshLayout; public RecyclerAdapter adapter; @@ -168,6 +170,8 @@ public class MainFragment extends Fragment private MainFragmentViewModel mainFragmentViewModel; private MainActivityViewModel mainActivityViewModel; + private boolean hideFab; + private final ActivityResultLauncher handleDocumentUriForRestrictedDirectories = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), @@ -207,6 +211,9 @@ public void onCreate(Bundle savedInstanceState) { requireMainActivity().getCurrentColorPreference().getPrimaryFirstTab()); mainFragmentViewModel.setPrimaryTwoColor( requireMainActivity().getCurrentColorPreference().getPrimarySecondTab()); + if (getArguments() != null) { + hideFab = getArguments().getBoolean(BUNDLE_HIDE_FAB, false); + } } @Override @@ -1073,7 +1080,7 @@ public void goBack() { if (mainFragmentViewModel.getOpenMode() == OpenMode.CUSTOM || mainFragmentViewModel.getOpenMode() == OpenMode.TRASH_BIN) { loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); - requireMainActivity().setHideFabInMainFragment(false); + setHideFab(false); return; } @@ -1082,7 +1089,7 @@ public void goBack() { if (requireMainActivity().getListItemSelected()) { adapter.toggleChecked(false); } else { - requireMainActivity().setHideFabInMainFragment(false); + setHideFab(false); if (OpenMode.SMB.equals(mainFragmentViewModel.getOpenMode())) { if (mainFragmentViewModel.getSmbPath() != null && !mainFragmentViewModel.getSmbPath().equals(mainFragmentViewModel.getCurrentPath())) { @@ -1529,4 +1536,12 @@ > requireContext().getResources().getDisplayMetrics().heightPixels) { LOG.warn("Failed to adjust scrollview for tv", e); } } + + public boolean getHideFab() { + return this.hideFab; + } + + public void setHideFab(boolean hideFab) { + this.hideFab = hideFab; + } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java index 8bcd4081de..a2cf115875 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java @@ -126,8 +126,10 @@ public View onCreateView( viewPager = rootView.findViewById(R.id.pager); + boolean hideFab = false; if (getArguments() != null) { path = getArguments().getString(KEY_PATH); + hideFab = getArguments().getBoolean(MainFragment.BUNDLE_HIDE_FAB); } requireMainActivity().supportInvalidateOptionsMenu(); @@ -138,7 +140,7 @@ public View onCreateView( int lastOpenTab = sharedPrefs.getInt(PREFERENCE_CURRENT_TAB, DEFAULT_CURRENT_TAB); MainActivity.currentTab = lastOpenTab; - refactorDrawerStorages(true); + refactorDrawerStorages(true, hideFab); viewPager.setAdapter(sectionsPagerAdapter); @@ -331,7 +333,7 @@ public Fragment createFragment(int position) { } private void addNewTab(int num, String path) { - addTab(new Tab(num, path, path), ""); + addTab(new Tab(num, path, path), "", false); } /** @@ -340,7 +342,7 @@ private void addNewTab(int num, String path) { * * @param addTab whether new tabs should be added to ui or just change values in database */ - public void refactorDrawerStorages(boolean addTab) { + public void refactorDrawerStorages(boolean addTab, boolean hideFabInCurrentTab) { TabHandler tabHandler = TabHandler.getInstance(); Tab tab1 = tabHandler.findTab(1); Tab tab2 = tabHandler.findTab(2); @@ -366,22 +368,23 @@ public void refactorDrawerStorages(boolean addTab) { } else { if (path != null && path.length() != 0) { if (MainActivity.currentTab == 0) { - addTab(tab1, path); - addTab(tab2, ""); + addTab(tab1, path, hideFabInCurrentTab); + addTab(tab2, "", false); } if (MainActivity.currentTab == 1) { - addTab(tab1, ""); - addTab(tab2, path); + addTab(tab1, "", false); + addTab(tab2, path, hideFabInCurrentTab); } } else { - addTab(tab1, ""); - addTab(tab2, ""); + addTab(tab1, "", false); + addTab(tab2, "", false); } } } - private void addTab(@NonNull Tab tab, String path) { + // TODO + private void addTab(@NonNull Tab tab, String path, boolean hideFabInTab) { MainFragment main = new MainFragment(); Bundle b = new Bundle(); @@ -394,6 +397,7 @@ private void addTab(@NonNull Tab tab, String path) { b.putString("home", tab.home); b.putInt("no", tab.tabNumber); + b.putBoolean(MainFragment.BUNDLE_HIDE_FAB, hideFabInTab); main.setArguments(b); fragments.add(main); sectionsPagerAdapter.notifyDataSetChanged(); diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 7aacfae928..0a58210845 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -786,10 +786,10 @@ public void onDrawerClosed() { return; } - mainActivity.setHideFabInMainFragment(pendingPath.getHideFabInMainFragment()); MainFragment mainFragment = mainActivity.getCurrentMainFragment(); if (mainFragment != null) { mainFragment.loadlist(pendingPath.getPath(), false, OpenMode.UNKNOWN, false); + mainFragment.setHideFab(pendingPath.getHideFabInMainFragment()); resetPendingPath(); } else { mainActivity.goToMain(pendingPath.getPath(), pendingPath.getHideFabInMainFragment()); From 053e834554902efe8baf6c1bc5052a857d2c55a8 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 22 Jan 2024 18:18:59 +0100 Subject: [PATCH 52/55] add highlight to the file name text view --- .../adapters/SearchRecyclerViewAdapter.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt index 02ad78f390..7859f68a06 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -21,6 +21,9 @@ package com.amaze.filemanager.adapters import android.content.Context +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -67,14 +70,22 @@ class SearchRecyclerViewAdapter : override fun onBindViewHolder(holder: SearchRecyclerViewAdapter.ViewHolder, position: Int) { val (file, matchResult) = getItem(position) - holder.fileNameTV.text = file.name + val colorPreference = + (AppConfig.getInstance().mainActivityContext as MainActivity).currentColorPreference + + val fileName = SpannableString(file.name) + fileName.setSpan( + ForegroundColorSpan(colorPreference.accent), + matchResult.first, + matchResult.last + 1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + holder.fileNameTV.text = fileName holder.filePathTV.text = file.path.substring(0, file.path.lastIndexOf("/")) holder.colorView.setBackgroundColor(getRandomColor(holder.colorView.context)) - val colorPreference = - (AppConfig.getInstance().mainActivityContext as MainActivity).currentColorPreference - if (file.isDirectory) { holder.colorView.setBackgroundColor(colorPreference.primaryFirstTab) } else { From 5b81154fa27c4c2c961747bc8134a7f2cc91fdf5 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 5 Feb 2024 21:15:37 +0100 Subject: [PATCH 53/55] call `showFab()` when the tab is changed Since each tab has different `MainFragments` and they might have different behaviors for the FAB --- .../java/com/amaze/filemanager/ui/fragments/TabFragment.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java index a2cf115875..10c1a02fe3 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java @@ -301,6 +301,9 @@ public void onPageSelected(int p1) { if (ma.getCurrentPath() != null) { requireMainActivity().getDrawer().selectCorrectDrawerItemForPath(ma.getCurrentPath()); updateBottomBar(ma); + // FAB might be hidden in the previous tab + // so we check if it should be shown for the new tab + requireMainActivity().showFab(); } } From c964a793cd08cb662b9d2a013520736c21dc937f Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 5 Feb 2024 21:32:23 +0100 Subject: [PATCH 54/55] clean up and add documentation --- .../amaze/filemanager/ui/activities/MainActivity.java | 7 +++++++ .../amaze/filemanager/ui/fragments/MainFragment.java | 5 ++++- .../amaze/filemanager/ui/fragments/TabFragment.java | 10 ++++++---- .../com/amaze/filemanager/ui/views/drawer/Drawer.java | 1 + 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index fe184bcd81..8fcda74bbb 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -1002,6 +1002,12 @@ public void goToMain(String path) { goToMain(path, false); } + /** + * Sets up the main view with the {@link MainFragment} + * + * @param path The path to which to go in the {@link MainFragment} + * @param hideFab Whether the FAB should be hidden in the new created {@link MainFragment} or not + */ public void goToMain(String path, boolean hideFab) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // title.setText(R.string.app_name); @@ -1017,6 +1023,7 @@ public void goToMain(String path, boolean hideFab) { if (path != null && path.length() > 0) { b.putString("path", path); } + // This boolean will be given to the newly created MainFragment b.putBoolean(MainFragment.BUNDLE_HIDE_FAB, hideFab); tabFragment.setArguments(b); transaction.replace(R.id.content_frame, tabFragment); diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index 95ae344623..24ad0250e5 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -143,6 +143,7 @@ public class MainFragment extends Fragment private static final Logger LOG = LoggerFactory.getLogger(MainFragment.class); private static final String KEY_FRAGMENT_MAIN = "main"; + /** Key for boolean in arguments whether to hide the FAB if this {@link MainFragment} is shown */ public static final String BUNDLE_HIDE_FAB = "hideFab"; public SwipeRefreshLayout mSwipeRefreshLayout; @@ -170,7 +171,7 @@ public class MainFragment extends Fragment private MainFragmentViewModel mainFragmentViewModel; private MainActivityViewModel mainActivityViewModel; - private boolean hideFab; + private boolean hideFab = false; private final ActivityResultLauncher handleDocumentUriForRestrictedDirectories = registerForActivityResult( @@ -1537,10 +1538,12 @@ > requireContext().getResources().getDisplayMetrics().heightPixels) { } } + /** Whether the FAB should be hidden when this MainFragment is shown */ public boolean getHideFab() { return this.hideFab; } + /** Set whether the FAB should be hidden when this MainFragment is shown */ public void setHideFab(boolean hideFab) { this.hideFab = hideFab; } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java index 10c1a02fe3..5c8a2950dc 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java @@ -344,8 +344,10 @@ private void addNewTab(int num, String path) { * change paths in database. Calls should implement updating each tab's list for new paths. * * @param addTab whether new tabs should be added to ui or just change values in database + * @param hideFabInCurrentMainFragment whether the FAB should be hidden in the current {@link + * MainFragment} */ - public void refactorDrawerStorages(boolean addTab, boolean hideFabInCurrentTab) { + public void refactorDrawerStorages(boolean addTab, boolean hideFabInCurrentMainFragment) { TabHandler tabHandler = TabHandler.getInstance(); Tab tab1 = tabHandler.findTab(1); Tab tab2 = tabHandler.findTab(2); @@ -371,13 +373,13 @@ public void refactorDrawerStorages(boolean addTab, boolean hideFabInCurrentTab) } else { if (path != null && path.length() != 0) { if (MainActivity.currentTab == 0) { - addTab(tab1, path, hideFabInCurrentTab); + addTab(tab1, path, hideFabInCurrentMainFragment); addTab(tab2, "", false); } if (MainActivity.currentTab == 1) { addTab(tab1, "", false); - addTab(tab2, path, hideFabInCurrentTab); + addTab(tab2, path, hideFabInCurrentMainFragment); } } else { addTab(tab1, "", false); @@ -386,7 +388,6 @@ public void refactorDrawerStorages(boolean addTab, boolean hideFabInCurrentTab) } } - // TODO private void addTab(@NonNull Tab tab, String path, boolean hideFabInTab) { MainFragment main = new MainFragment(); Bundle b = new Bundle(); @@ -400,6 +401,7 @@ private void addTab(@NonNull Tab tab, String path, boolean hideFabInTab) { b.putString("home", tab.home); b.putInt("no", tab.tabNumber); + // specifies if the constructed MainFragment hides the FAB when it is shown b.putBoolean(MainFragment.BUNDLE_HIDE_FAB, hideFabInTab); main.setArguments(b); fragments.add(main); diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 0a58210845..2f1f604bc8 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -789,6 +789,7 @@ public void onDrawerClosed() { MainFragment mainFragment = mainActivity.getCurrentMainFragment(); if (mainFragment != null) { mainFragment.loadlist(pendingPath.getPath(), false, OpenMode.UNKNOWN, false); + // Set if the FAB should be hidden when displaying the pendingPath mainFragment.setHideFab(pendingPath.getHideFabInMainFragment()); resetPendingPath(); } else { From f71bf6a377bd50b8b8612aa213298c44530f4647 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sat, 10 Feb 2024 19:31:30 +0100 Subject: [PATCH 55/55] fix typo --- .../java/com/amaze/filemanager/ui/activities/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 8fcda74bbb..ff894315c9 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -1003,7 +1003,7 @@ public void goToMain(String path) { } /** - * Sets up the main view with the {@link MainFragment} + * Sets up the main view with a {@link MainFragment} * * @param path The path to which to go in the {@link MainFragment} * @param hideFab Whether the FAB should be hidden in the new created {@link MainFragment} or not