Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2020-09-22 12:31:42 +02:00 committed by Álvaro Brey Vilas
parent 3a39b041de
commit 96ea22525e
No known key found for this signature in database
GPG key ID: 2585783189A62105
11 changed files with 192 additions and 90 deletions

View file

@ -25,10 +25,13 @@ import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import com.nextcloud.client.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.SearchResult
import com.owncloud.android.lib.common.SearchResultEntry
import com.owncloud.android.ui.unifiedsearch.UnifiedSearchViewModel
import org.junit.Rule
import org.junit.Test
import java.io.File
class UnifiedSearchFragmentIT : AbstractIT() {
@get:Rule
@ -47,20 +50,20 @@ class UnifiedSearchFragmentIT : AbstractIT() {
sut.onSearchResultChanged(
mutableListOf(
SearchResult(
"files",
"Files",
false,
listOf(ex
SearchResultEntry ("thumbnailUrl",
"Test",
"in /Files/",
"resourceUrl",
"icon",
false
listOf(
SearchResultEntry("thumbnailUrl",
"Test",
"in /Files/",
"resourceUrl",
"icon",
false
)
)
)
)
)
)
}
longSleep()
@ -74,6 +77,13 @@ class UnifiedSearchFragmentIT : AbstractIT() {
val localRepository = UnifiedSearchLocalRepository()
testViewModel.setRepository(localRepository)
val ocFile = OCFile("/folder/test1.txt").apply {
storagePath = "/sdcard/1.txt"
storageManager.saveFile(this)
}
File(ocFile.storagePath).createNewFile()
activity.addFragment(sut)
shortSleep()

View file

@ -43,9 +43,16 @@ class UnifiedSearchLocalRepository : IUnifiedSearchRepository {
("thumbnailUrl",
"Test",
"in Files",
"resourceUrl",
"http://localhost/nc/index.php/apps/files/?dir=/Files&scrollto=Test",
"icon",
false))))
false),
SearchResultEntry
("thumbnailUrl",
"Test1",
"in Folder",
"http://localhost/nc/index.php/apps/files/?dir=/folder&scrollto=test1.txt",
"icon",
false))))
vm.onSearchResult(result)
Log_OC.d(this, "loadMore")
}

View file

@ -231,4 +231,9 @@ class AppModule {
LocalBroadcastManager localBroadcastManager(Context context) {
return LocalBroadcastManager.getInstance(context);
}
@Provides
FileDataStorageManager storageManager(CurrentAccountProvider currentAccountProvider, Context context) {
return new FileDataStorageManager(currentAccountProvider.getCurrentAccount(), context.getContentResolver());
}
}

View file

@ -155,7 +155,8 @@ public class FileDataStorageManager {
return ocFile;
}
public OCFile getFileByLocalPath(String path) {
public @Nullable
OCFile getFileByLocalPath(String path) {
Cursor cursor = getFileCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path);
OCFile ocFile = null;

View file

@ -22,15 +22,31 @@
package com.owncloud.android.ui.adapter
import android.content.Context
import android.view.View
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
import com.owncloud.android.databinding.UnifiedSearchItemBinding
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.lib.common.SearchResultEntry
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
class UnifiedSearchItemViewHolder(val binding: UnifiedSearchItemBinding, val context: Context) :
class UnifiedSearchItemViewHolder(val binding: UnifiedSearchItemBinding,
val context: Context,
val storageManager: FileDataStorageManager,
val listInterface: UnifiedSearchListInterface) :
SectionedViewHolder(binding.root) {
fun bind(entry: SearchResultEntry) {
binding.title.text = entry.title
binding.subline.text = entry.subline
val ocFile = storageManager.getFileByDecryptedRemotePath(entry.remotePath())
if (ocFile?.isDown == true) {
binding.localFileIndicator.visibility = View.VISIBLE
} else {
binding.localFileIndicator.visibility = View.GONE
}
binding.unifiedSearchItemLayout.setOnClickListener { listInterface.onSearchResultClicked(entry) }
}
}

View file

@ -77,6 +77,7 @@ import com.owncloud.android.operations.RemoteOperationFailedException;
import com.owncloud.android.ui.TextDrawable;
import com.owncloud.android.ui.fragment.ExtendedListFragment;
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface;
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface;
import com.owncloud.android.utils.BitmapUtils;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.FileSortOrder;
@ -122,7 +123,7 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
private List<SearchResult> list = new ArrayList<>();
private FileDataStorageManager mStorageManager;
private FileDataStorageManager storageManager;
private User user;
private OCFileListFragmentInterface ocFileListFragmentInterface;
@ -140,8 +141,11 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
private boolean showShareAvatar = false;
private OCFile highlightedItem;
private Context context;
private UnifiedSearchListInterface listInterface;
public UnifiedSearchListAdapter(Context context) {
public UnifiedSearchListAdapter(FileDataStorageManager storageManager,
UnifiedSearchListInterface listInterface,
Context context) {
// this.ocFileListFragmentInterface = ocFileListFragmentInterface;
// this.activity = activity;
// this.preferences = preferences;
@ -150,6 +154,8 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
// this.gridView = gridView;
checkedFiles = new HashSet<>();
this.context = context;
this.storageManager = storageManager;
this.listInterface = listInterface;
if (this.user != null) {
AccountManager platformAccountManager = AccountManager.get(this.activity);
@ -177,7 +183,7 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
parent,
false);
return new UnifiedSearchItemViewHolder(binding, context);
return new UnifiedSearchItemViewHolder(binding, context, storageManager, listInterface);
}
}
@ -296,7 +302,7 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
if (mFiles.get(i).getRemoteId().equals(fileId)) {
OCFile file = mFiles.get(i);
file.setEncrypted(encrypted);
mStorageManager.saveFile(file);
storageManager.saveFile(file);
break;
}
@ -628,13 +634,13 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
) {
this.onlyOnDevice = onlyOnDevice;
if (updatedStorageManager != null && !updatedStorageManager.equals(mStorageManager)) {
mStorageManager = updatedStorageManager;
showShareAvatar = mStorageManager.getCapability(account.getAccountName()).getVersion().isShareesOnDavSupported();
if (updatedStorageManager != null && !updatedStorageManager.equals(storageManager)) {
storageManager = updatedStorageManager;
showShareAvatar = storageManager.getCapability(account.getAccountName()).getVersion().isShareesOnDavSupported();
this.user = account;
}
if (mStorageManager != null) {
mFiles = mStorageManager.getFolderContent(directory, onlyOnDevice);
if (storageManager != null) {
mFiles = storageManager.getFolderContent(directory, onlyOnDevice);
if (!preferences.isShowHiddenFilesEnabled()) {
mFiles = filterHiddenFiles(mFiles);
@ -662,13 +668,13 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
FileDataStorageManager storageManager,
@Nullable OCFile folder,
boolean clear) {
if (storageManager != null && mStorageManager == null) {
mStorageManager = storageManager;
showShareAvatar = mStorageManager.getCapability(user.getAccountName()).getVersion().isShareesOnDavSupported();
if (storageManager != null && this.storageManager == null) {
this.storageManager = storageManager;
showShareAvatar = this.storageManager.getCapability(user.getAccountName()).getVersion().isShareesOnDavSupported();
}
if (mStorageManager == null) {
mStorageManager = new FileDataStorageManager(user.toPlatformAccount(), activity.getContentResolver());
if (this.storageManager == null) {
this.storageManager = new FileDataStorageManager(user.toPlatformAccount(), activity.getContentResolver());
}
if (clear) {
@ -689,11 +695,11 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
break;
}
mStorageManager.deleteVirtuals(type);
this.storageManager.deleteVirtuals(type);
}
// early exit
if (objects.size() > 0 && mStorageManager != null) {
if (objects.size() > 0 && this.storageManager != null) {
if (searchType == ExtendedListFragment.SearchType.SHARED_FILTER) {
parseShares(objects);
} else {
@ -734,7 +740,7 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
if (result.isSuccess()) {
OCFile file = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
FileStorageUtils.searchForLocalFileInDefaultPath(file, user.toPlatformAccount());
file = mStorageManager.saveFileWithParent(file, activity);
file = storageManager.saveFileWithParent(file, activity);
ShareType newShareType = ocShare.getShareType();
if (newShareType == ShareType.PUBLIC_LINK) {
@ -748,7 +754,7 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
file.setSharedWithSharee(true);
}
mStorageManager.saveFile(file);
storageManager.saveFile(file);
if (!mFiles.contains(file)) {
mFiles.add(file);
@ -759,7 +765,7 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
}
}
mStorageManager.saveShares(shares);
storageManager.saveShares(shares);
}
private void parseVirtuals(List<Object> objects, ExtendedListFragment.SearchType searchType) {
@ -797,10 +803,10 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
try {
if (ExtendedListFragment.SearchType.PHOTO_SEARCH == searchType) {
mStorageManager.saveFile(ocFile);
storageManager.saveFile(ocFile);
} else {
ocFile = mStorageManager.saveFileWithParent(ocFile, activity);
ocFile = storageManager.saveFileWithParent(ocFile, activity);
// also sync folder content
if (ocFile.isFolder()) {
@ -809,7 +815,7 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
currentSyncTime,
true,
false,
mStorageManager,
storageManager,
user.toPlatformAccount(),
activity);
refreshFolderOperation.execute(user.toPlatformAccount(), activity);
@ -831,7 +837,7 @@ public class UnifiedSearchListAdapter extends SectionedRecyclerViewAdapter<Secti
}
preferences.setPhotoSearchTimestamp(System.currentTimeMillis());
mStorageManager.saveVirtuals(contentValues);
storageManager.saveVirtuals(contentValues);
}
public void showVirtuals(VirtualFolderType type, boolean onlyImages, FileDataStorageManager storageManager) {

View file

@ -22,10 +22,8 @@
package com.owncloud.android.ui.asynctasks;
import android.os.AsyncTask;
import android.text.TextUtils;
import com.nextcloud.client.account.User;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
@ -36,8 +34,6 @@ import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.FileStorageUtils;
import static com.owncloud.android.lib.resources.files.SearchRemoteOperation.SearchType.FILE_ID_SEARCH;
@ -124,18 +120,6 @@ public class FetchRemoteFileTask extends AsyncTask<Void, Void, String> {
protected void onPostExecute(String message) {
super.onPostExecute(message);
fileDisplayActivity.dismissLoadingDialog();
OCFileListFragment listOfFiles = fileDisplayActivity.getListOfFilesFragment();
if (listOfFiles != null) {
if (TextUtils.isEmpty(message)) {
OCFile temp = fileDisplayActivity.getFile();
fileDisplayActivity.setFile(fileDisplayActivity.getCurrentDir());
listOfFiles.listDirectory(fileDisplayActivity.getCurrentDir(), temp, MainApp.isOnlyOnDevice(), false);
fileDisplayActivity.updateActionBarTitleAndHomeButton(null);
} else {
DisplayUtils.showSnackMessage(listOfFiles.getView(), message);
}
}
fileDisplayActivity.showFile(message);
}
}

View file

@ -28,11 +28,19 @@ import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import com.nextcloud.client.account.CurrentAccountProvider
import com.nextcloud.client.core.AsyncRunner
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.di.ViewModelFactory
import com.nextcloud.client.network.ClientFactory
import com.owncloud.android.databinding.ListFragmentBinding
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.lib.common.SearchResult
import com.owncloud.android.lib.common.SearchResultEntry
import com.owncloud.android.ui.activities.GetRemoteFileTask
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.adapter.UnifiedSearchListAdapter
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
import com.owncloud.android.ui.unifiedsearch.UnifiedSearchViewModel
import javax.inject.Inject
@ -40,7 +48,7 @@ import javax.inject.Inject
* Starts query to all capable unified search providers and displays them Opens result in our app, redirect to other
* apps, if installed, or opens browser
*/
class UnifiedSearchFragment : Fragment(), Injectable {
class UnifiedSearchFragment : Fragment(), Injectable, UnifiedSearchListInterface {
private lateinit var adapter: UnifiedSearchListAdapter
private var _binding: ListFragmentBinding? = null
private val binding get() = _binding!!
@ -49,6 +57,18 @@ class UnifiedSearchFragment : Fragment(), Injectable {
@Inject
lateinit var vmFactory: ViewModelFactory
@Inject
lateinit var storageManager: FileDataStorageManager
@Inject
lateinit var runner: AsyncRunner
@Inject
lateinit var currentAccountProvider: CurrentAccountProvider
@Inject
lateinit var clientFactory: ClientFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vm = ViewModelProvider(this, vmFactory).get(UnifiedSearchViewModel::class.java)
@ -76,7 +96,7 @@ class UnifiedSearchFragment : Fragment(), Injectable {
super.onViewCreated(view, savedInstanceState)
val gridLayoutManager = GridLayoutManager(requireContext(), 1)
adapter = UnifiedSearchListAdapter(requireContext())
adapter = UnifiedSearchListAdapter(storageManager, this, requireContext())
adapter.setLayoutManager(gridLayoutManager)
binding.listRoot.layoutManager = gridLayoutManager
binding.listRoot.adapter = adapter
@ -92,6 +112,32 @@ class UnifiedSearchFragment : Fragment(), Injectable {
_binding = null
}
fun showFile(result: GetRemoteFileTask.Result) {
activity.let {
if (activity is FileDisplayActivity) {
val fda = activity as FileDisplayActivity
fda.file = result.file
fda.showFile("")
}
}
}
override fun onSearchResultClicked(searchResultEntry: SearchResultEntry) {
openFile(searchResultEntry.remotePath())
}
fun openFile(fileUrl: String) {
val user = currentAccountProvider.user
val task = GetRemoteFileTask(
requireContext(),
fileUrl,
clientFactory.create(currentAccountProvider.user),
FileDataStorageManager(user.toPlatformAccount(), requireContext().contentResolver),
user
)
runner.postQuickTask(task, onResult = this::showFile)
}
@VisibleForTesting
fun onSearchResultChanged(list: MutableList<SearchResult>) {
binding.emptyList.emptyListView.visibility = View.GONE

View file

@ -0,0 +1,30 @@
/*
*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2020 Tobias Kaminsky
* Copyright (C) 2020 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.interfaces
import com.owncloud.android.lib.common.SearchResultEntry
interface UnifiedSearchListInterface {
fun onSearchResultClicked(searchResultEntry: SearchResultEntry)
}

View file

@ -19,6 +19,7 @@
*/
package com.owncloud.android.ui.unifiedsearch
import android.content.Context
import android.content.res.Resources
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.MutableLiveData
@ -27,8 +28,10 @@ import com.nextcloud.client.account.CurrentAccountProvider
import com.nextcloud.client.core.AsyncRunner
import com.nextcloud.client.network.ClientFactory
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.lib.common.SearchResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.activities.GetRemoteFileTask
import javax.inject.Inject
@Suppress("LongParameterList")
@ -38,6 +41,7 @@ class UnifiedSearchViewModel() : ViewModel() {
lateinit var runner: AsyncRunner
lateinit var clientFactory: ClientFactory
lateinit var resources: Resources
lateinit var context: Context
private lateinit var repository: IUnifiedSearchRepository
private var loadingStarted: Boolean = false
@ -53,11 +57,13 @@ class UnifiedSearchViewModel() : ViewModel() {
currentAccountProvider: CurrentAccountProvider,
runner: AsyncRunner,
clientFactory: ClientFactory,
resources: Resources) : this() {
resources: Resources,
context: Context) : this() {
this.currentAccountProvider = currentAccountProvider
this.runner = runner
this.clientFactory = clientFactory
this.resources = resources
this.context = context
repository = UnifiedSearchRemoteRepository(
clientFactory,
@ -90,20 +96,20 @@ class UnifiedSearchViewModel() : ViewModel() {
}
// fun openFile(fileUrl: String) {
// if (isLoading.value == false) {
// (isLoading as MutableLiveData).value = true
// val user = currentUser.user
// val task = GetRemoteFileTask(
// context,
// fileUrl,
// clientFactory.create(currentUser.user),
// FileDataStorageManager(user.toPlatformAccount(), contentResolver),
// user
// )
// runner.postQuickTask(task, onResult = this::onFileRequestResult)
// }
// }
fun openFile(fileUrl: String) {
if (isLoading.value == false) {
isLoading.value = true
val user = currentAccountProvider.user
val task = GetRemoteFileTask(
context,
fileUrl,
clientFactory.create(currentAccountProvider.user),
FileDataStorageManager(user.toPlatformAccount(), context.contentResolver),
user
)
runner.postQuickTask(task, onResult = this::onFileRequestResult)
}
}
open fun clearError() {
error.value = ""
@ -144,12 +150,13 @@ class UnifiedSearchViewModel() : ViewModel() {
// }
// }
// private fun onFileRequestResult(result: GetRemoteFileTask.Result) {
// (isLoading as MutableLiveData).value = false
// if (result.success) {
// (file as MutableLiveData).value = result.file
// } else {
// (file as MutableLiveData).value = null
// }
// }
private fun onFileRequestResult(result: GetRemoteFileTask.Result) {
isLoading.value = false
if (result.success) {
// unifiedSearchFragment.showFile(result.file)
//(file as MutableLiveData).value = result.file
} else {
error.value = "Error showing search result"
}
}
}

View file

@ -22,7 +22,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/upload_list_item_layout"
android:id="@+id/unified_search_item_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/standard_list_item_size"
android:baselineAligned="false"
@ -66,22 +66,12 @@
android:contentDescription="@string/downloader_download_succeeded_ticker"
android:scaleType="fitCenter"
android:src="@drawable/ic_synced"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/thumbnail_layout"
app:layout_constraintEnd_toEndOf="@+id/thumbnail_layout"
app:layout_constraintStart_toEndOf="@+id/thumbnail_layout"
app:layout_constraintTop_toBottomOf="@+id/thumbnail_layout" />
<ImageView
android:id="@+id/favorite_action"
android:layout_width="@dimen/list_item_favorite_action_layout_width"
android:layout_height="@dimen/list_item_favorite_action_layout_height"
android:contentDescription="@string/favorite"
android:src="@drawable/favorite"
app:layout_constraintBottom_toTopOf="@+id/thumbnail_layout"
app:layout_constraintEnd_toEndOf="@+id/thumbnail_layout"
app:layout_constraintStart_toEndOf="@+id/thumbnail_layout"
app:layout_constraintTop_toTopOf="@+id/thumbnail_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout