From 9713c312a0462b116d64a12d0a9112f860d6fb59 Mon Sep 17 00:00:00 2001 From: yasminhosam Date: Sat, 15 Aug 2026 21:21:49 +0300 Subject: [PATCH 1/3] Add teleport to file feature from search results --- .../adapters/SearchRecyclerViewAdapter.kt | 16 ++++++++++++++++ .../filemanager/ui/activities/MainActivity.java | 15 ++++++++++++--- .../main/res/drawable/ic_location_on_24dp.png | Bin 0 -> 307 bytes app/src/main/res/layout/search_row_item.xml | 15 +++++++++++++-- 4 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/drawable/ic_location_on_24dp.png 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 5c6afc3259..938d045d65 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -21,12 +21,14 @@ package com.amaze.filemanager.adapters import android.content.Context +import android.graphics.PorterDuff 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 +import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DiffUtil @@ -91,11 +93,14 @@ class SearchRecyclerViewAdapter : holder.filePathTV.text = file.path.substring(0, file.path.lastIndexOf("/")) holder.colorView.setBackgroundColor(getRandomColor(holder.colorView.context)) + holder.teleportIV.setColorFilter(colorPreference.accent, PorterDuff.Mode.SRC_ATOP) if (file.isDirectory) { holder.colorView.setBackgroundColor(colorPreference.primaryFirstTab) + holder.teleportIV.visibility = View.GONE } else { holder.colorView.setBackgroundColor(colorPreference.accent) + holder.teleportIV.visibility = View.VISIBLE } } @@ -103,12 +108,14 @@ class SearchRecyclerViewAdapter : val fileNameTV: AppCompatTextView val filePathTV: AppCompatTextView val colorView: View + val teleportIV: AppCompatImageView init { fileNameTV = view.findViewById(R.id.searchItemFileNameTV) filePathTV = view.findViewById(R.id.searchItemFilePathTV) colorView = view.findViewById(R.id.searchItemSampleColorView) + teleportIV = view.findViewById(R.id.searchItemTeleportIV) view.setOnClickListener { @@ -127,6 +134,15 @@ class SearchRecyclerViewAdapter : (AppConfig.getInstance().mainActivityContext as MainActivity?) ?.appbar?.searchView?.hideSearchView() } + teleportIV.setOnClickListener { + val (file, _) = getItem(adapterPosition) + if (!file.isDirectory) { + (AppConfig.getInstance().mainActivityContext as MainActivity?) + ?.teleportToFile(file.path) + (AppConfig.getInstance().mainActivityContext as MainActivity?) + ?.appbar?.searchView?.hideSearchView() + } + } } } 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 9b3d90904c..54047e7926 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 @@ -1069,6 +1069,15 @@ public void goToMain(String path, boolean hideFab) { } } + public void teleportToFile(String filePath) { + int lastSlash = filePath.lastIndexOf("/"); + String parentPath = filePath.substring(0, lastSlash); + String fileName = filePath.substring(lastSlash + 1); + + scrollToFileName = fileName; + goToMain(parentPath); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater menuInflater = getMenuInflater(); @@ -2451,11 +2460,11 @@ private void initLeftRightAndTopDragListeners(boolean destroy, boolean shouldInv /** * Invoke {@link FtpServerFragment#changeFTPServerPath(String)} to change FTP server share path. * + * @param dialog + * @param folder selected folder * @see FtpServerFragment#changeFTPServerPath(String) * @see FolderChooserDialog * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback - * @param dialog - * @param folder selected folder */ @Override public void onFolderSelection(@NonNull FolderChooserDialog dialog, @NonNull File folder) { @@ -2547,8 +2556,8 @@ public void setListItemSelected(boolean value) { /** * Do nothing other than dismissing the folder selection dialog. * - * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback * @param dialog + * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback */ @Override public void onFolderChooserDismissed(@NonNull FolderChooserDialog dialog) { diff --git a/app/src/main/res/drawable/ic_location_on_24dp.png b/app/src/main/res/drawable/ic_location_on_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..11084e80658ee62e65046b34b8caae0a90f2c38c GIT binary patch literal 307 zcmV-30nGl1P)CPW z&uh)GZYTn#X_}pLw-$31fRYG8u|>rlw3x~@zX70YxZP@_{Bu8!cX0I+9Wn3-0O*tj z;^W>C7x{AmKu&Bm-W~7;00960W!y-h00006Nkl + + @@ -46,7 +57,7 @@ android:letterSpacing="0.05" android:textSize="14sp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@+id/searchItemTeleportIV" app:layout_constraintStart_toEndOf="@id/searchItemSampleColorView" app:layout_constraintTop_toBottomOf="@id/searchItemFileNameTV" app:layout_constraintVertical_bias="0" /> From f19ba5426471b86fedb8eeb28c0ca6cc52560d34 Mon Sep 17 00:00:00 2001 From: yasminhosam Date: Sun, 16 Aug 2026 01:18:48 +0300 Subject: [PATCH 2/3] Fix teleport path splitting to avoid android.util.Pair mocking issue in unit tests --- .../ui/activities/MainActivity.java | 33 +++++++++- .../ui/activities/MainActivityTeleportTest.kt | 65 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTeleportTest.kt 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 54047e7926..1891c7446f 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 @@ -1070,12 +1070,41 @@ public void goToMain(String path, boolean hideFab) { } public void teleportToFile(String filePath) { + ParentAndFileName split = splitParentAndFileName(filePath); + if (split == null) { + goToMain(filePath); + return; + } + scrollToFileName = split.fileName; + goToMain(split.parentPath); + } + + /** + * Splits a file path into its parent directory path and file name. Returns null if the path has + * no parent (no "/" found). + */ + static ParentAndFileName splitParentAndFileName(String filePath) { int lastSlash = filePath.lastIndexOf("/"); + if (lastSlash <= 0) { + return null; + } String parentPath = filePath.substring(0, lastSlash); String fileName = filePath.substring(lastSlash + 1); + return new ParentAndFileName(parentPath, fileName); + } + + /** + * Simple holder for a file's parent path and name. Not an android.util.Pair to keep it usable in + * plain JUnit tests (android.* classes are stubbed out in non-Robolectric unit tests). + */ + static class ParentAndFileName { + final String parentPath; + final String fileName; - scrollToFileName = fileName; - goToMain(parentPath); + ParentAndFileName(String parentPath, String fileName) { + this.parentPath = parentPath; + this.fileName = fileName; + } } @Override diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTeleportTest.kt b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTeleportTest.kt new file mode 100644 index 0000000000..f864a3bcd4 --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTeleportTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2014-2026 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.activities + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +/** + * Tests for [MainActivity.splitParentAndFileName], used by the search + * "teleport to file" feature to navigate to a file's parent folder. + */ +class MainActivityTeleportTest { + @Test + fun `splits a normal file path into parent and file name`() { + val result = MainActivity.splitParentAndFileName("/storage/emulated/0/Documents/report.pdf") + + assertEquals("/storage/emulated/0/Documents", result?.parentPath) + assertEquals("report.pdf", result?.fileName) + } + + @Test + fun `splits a path with a single directory level`() { + val result = MainActivity.splitParentAndFileName("/storage/file.txt") + + assertEquals("/storage", result?.parentPath) + assertEquals("file.txt", result?.fileName) + } + + @Test + fun `returns null when path has no parent directory`() { + val result = MainActivity.splitParentAndFileName("file.txt") + + assertNull(result) + } + + @Test + fun `handles file names containing spaces and special characters`() { + val result = + MainActivity.splitParentAndFileName( + "/storage/emulated/0/WhatsApp/Media/Yasmin Hosam (1).pdf", + ) + + assertEquals("/storage/emulated/0/WhatsApp/Media", result?.parentPath) + assertEquals("Yasmin Hosam (1).pdf", result?.fileName) + } +} From f9aca24686b6a720e4bd1971d0a9150b95d40c50 Mon Sep 17 00:00:00 2001 From: yasminhosam Date: Sun, 16 Aug 2026 01:40:34 +0300 Subject: [PATCH 3/3] Add missing KDoc to test functions --- .../filemanager/ui/activities/MainActivityTeleportTest.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTeleportTest.kt b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTeleportTest.kt index f864a3bcd4..4f3ae21a1b 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTeleportTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTeleportTest.kt @@ -29,6 +29,8 @@ import org.junit.Test * "teleport to file" feature to navigate to a file's parent folder. */ class MainActivityTeleportTest { + + /** Verifies a normal file path is split into its parent directory and file name. */ @Test fun `splits a normal file path into parent and file name`() { val result = MainActivity.splitParentAndFileName("/storage/emulated/0/Documents/report.pdf") @@ -37,6 +39,7 @@ class MainActivityTeleportTest { assertEquals("report.pdf", result?.fileName) } + /** Verifies a path with only one directory level splits correctly. */ @Test fun `splits a path with a single directory level`() { val result = MainActivity.splitParentAndFileName("/storage/file.txt") @@ -45,6 +48,7 @@ class MainActivityTeleportTest { assertEquals("file.txt", result?.fileName) } + /** Verifies null is returned when the path has no parent directory. */ @Test fun `returns null when path has no parent directory`() { val result = MainActivity.splitParentAndFileName("file.txt") @@ -52,6 +56,7 @@ class MainActivityTeleportTest { assertNull(result) } + /** Verifies file names with spaces and special characters split correctly. */ @Test fun `handles file names containing spaces and special characters`() { val result =