mirror of https://github.com/nextcloud/android
555 lines
21 KiB
Java
555 lines
21 KiB
Java
/*
|
|
* Nextcloud Android client application
|
|
*
|
|
* @author Tobias Kaminsky
|
|
* Copyright (C) 2018 Tobias Kaminsky
|
|
* Copyright (C) 2018 Nextcloud
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
package com.owncloud.android.ui.adapter;
|
|
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Bitmap;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
|
|
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
|
|
import com.nextcloud.client.preferences.AppPreferences;
|
|
import com.owncloud.android.R;
|
|
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
|
import com.owncloud.android.ui.interfaces.LocalFileListFragmentInterface;
|
|
import com.owncloud.android.utils.DisplayUtils;
|
|
import com.owncloud.android.utils.FileSortOrder;
|
|
import com.owncloud.android.utils.MimeTypeUtil;
|
|
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
|
|
|
import java.io.File;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Set;
|
|
import java.util.concurrent.Executors;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.VisibleForTesting;
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
/**
|
|
* This Adapter populates a {@link RecyclerView} with all files and directories contained in a local directory
|
|
*/
|
|
public class LocalFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements FilterableListAdapter {
|
|
|
|
private static final String TAG = LocalFileListAdapter.class.getSimpleName();
|
|
|
|
private static final int showFilenameColumnThreshold = 4;
|
|
private AppPreferences preferences;
|
|
private Context mContext;
|
|
private List<File> mFiles = new ArrayList<>();
|
|
private List<File> mFilesAll = new ArrayList<>();
|
|
private boolean mLocalFolderPicker;
|
|
private boolean gridView = false;
|
|
private LocalFileListFragmentInterface localFileListFragmentInterface;
|
|
private Set<File> checkedFiles;
|
|
private ViewThemeUtils viewThemeUtils;
|
|
private boolean isWithinEncryptedFolder;
|
|
|
|
private static final int VIEWTYPE_ITEM = 0;
|
|
private static final int VIEWTYPE_FOOTER = 1;
|
|
private static final int VIEWTYPE_IMAGE = 2;
|
|
|
|
public LocalFileListAdapter(boolean localFolderPickerMode,
|
|
File directory,
|
|
LocalFileListFragmentInterface localFileListFragmentInterface,
|
|
AppPreferences preferences,
|
|
Context context,
|
|
final ViewThemeUtils viewThemeUtils,
|
|
boolean isWithinEncryptedFolder) {
|
|
this.preferences = preferences;
|
|
mContext = context;
|
|
mLocalFolderPicker = localFolderPickerMode;
|
|
this.localFileListFragmentInterface = localFileListFragmentInterface;
|
|
checkedFiles = new HashSet<>();
|
|
this.viewThemeUtils = viewThemeUtils;
|
|
this.isWithinEncryptedFolder = isWithinEncryptedFolder;
|
|
|
|
swapDirectory(directory);
|
|
}
|
|
|
|
@Override
|
|
public int getItemCount() {
|
|
return mFiles.size() + 1;
|
|
}
|
|
|
|
public int getFilesCount() {
|
|
return mFiles.size();
|
|
}
|
|
|
|
public boolean isCheckedFile(File file) {
|
|
return checkedFiles.contains(file);
|
|
}
|
|
|
|
public void removeCheckedFile(File file) {
|
|
checkedFiles.remove(file);
|
|
}
|
|
|
|
public void addCheckedFile(File file) {
|
|
checkedFiles.add(file);
|
|
}
|
|
|
|
public void addAllFilesToCheckedFiles() {
|
|
if (isWithinEncryptedFolder) {
|
|
for (File file : mFilesAll) {
|
|
if (file.isFile()) {
|
|
checkedFiles.add(file);
|
|
}
|
|
}
|
|
} else {
|
|
checkedFiles.addAll(mFiles);
|
|
}
|
|
}
|
|
|
|
public void removeAllFilesFromCheckedFiles() {
|
|
checkedFiles.clear();
|
|
}
|
|
|
|
public int getItemPosition(File file) {
|
|
return mFiles.indexOf(file);
|
|
}
|
|
|
|
public String[] getCheckedFilesPath() {
|
|
List<String> result = listFilesRecursive(checkedFiles);
|
|
|
|
Log_OC.d(TAG, "Returning " + result.size() + " selected files");
|
|
|
|
return result.toArray(new String[0]);
|
|
}
|
|
|
|
public List<String> listFilesRecursive(Collection<File> files) {
|
|
List<String> result = new ArrayList<>();
|
|
|
|
for (File file : files) {
|
|
if (file.isDirectory()) {
|
|
result.addAll(listFilesRecursive(getFiles(file)));
|
|
} else {
|
|
result.add(file.getAbsolutePath());
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public long getItemId(int position) {
|
|
return mFiles.size() <= position ? position : -1;
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
|
if (holder instanceof LocalFileListFooterViewHolder) {
|
|
((LocalFileListFooterViewHolder) holder).footerText.setText(getFooterText());
|
|
} else {
|
|
File file = null;
|
|
if (mFiles.size() > position && mFiles.get(position) != null) {
|
|
file = mFiles.get(position);
|
|
}
|
|
|
|
if (file != null) {
|
|
File finalFile = file;
|
|
|
|
LocalFileListGridImageViewHolder gridViewHolder = (LocalFileListGridImageViewHolder) holder;
|
|
|
|
if (mLocalFolderPicker) {
|
|
gridViewHolder.itemLayout.setBackgroundColor(mContext.getResources().getColor(R.color.bg_default));
|
|
gridViewHolder.checkbox.setVisibility(View.GONE);
|
|
} else {
|
|
gridViewHolder.checkbox.setVisibility(View.VISIBLE);
|
|
if (isCheckedFile(file)) {
|
|
gridViewHolder.itemLayout.setBackgroundColor(ContextCompat.getColor(mContext, R.color.selected_item_background));
|
|
|
|
gridViewHolder.checkbox.setImageDrawable(
|
|
viewThemeUtils.platform.tintDrawable(mContext, R.drawable.ic_checkbox_marked, ColorRole.PRIMARY));
|
|
} else {
|
|
gridViewHolder.itemLayout.setBackgroundColor(mContext.getResources().getColor(R.color.bg_default));
|
|
gridViewHolder.checkbox.setImageResource(R.drawable.ic_checkbox_blank_outline);
|
|
}
|
|
gridViewHolder.checkbox.setOnClickListener(v -> localFileListFragmentInterface
|
|
.onItemCheckboxClicked(finalFile));
|
|
}
|
|
|
|
gridViewHolder.thumbnail.setTag(file.hashCode());
|
|
setThumbnail(file, gridViewHolder.thumbnail, mContext, viewThemeUtils);
|
|
|
|
gridViewHolder.itemLayout.setOnClickListener(v -> localFileListFragmentInterface
|
|
.onItemClicked(finalFile));
|
|
|
|
if (holder instanceof LocalFileListItemViewHolder) {
|
|
LocalFileListItemViewHolder itemViewHolder = (LocalFileListItemViewHolder) holder;
|
|
|
|
if (file.isDirectory()) {
|
|
itemViewHolder.fileSize.setVisibility(View.GONE);
|
|
itemViewHolder.fileSeparator.setVisibility(View.GONE);
|
|
if (isWithinEncryptedFolder) {
|
|
itemViewHolder.checkbox.setVisibility(View.GONE);
|
|
}
|
|
} else {
|
|
itemViewHolder.fileSize.setVisibility(View.VISIBLE);
|
|
itemViewHolder.fileSeparator.setVisibility(View.VISIBLE);
|
|
itemViewHolder.fileSize.setText(DisplayUtils.bytesToHumanReadable(file.length()));
|
|
}
|
|
itemViewHolder.lastModification.setText(DisplayUtils.getRelativeTimestamp(mContext,
|
|
file.lastModified()));
|
|
}
|
|
|
|
if (gridViewHolder instanceof LocalFileListGridItemViewHolder) {
|
|
LocalFileListGridItemViewHolder itemVH = (LocalFileListGridItemViewHolder) gridViewHolder;
|
|
itemVH.fileName.setText(file.getName());
|
|
|
|
if (gridView && (MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file) ||
|
|
localFileListFragmentInterface.getColumnsCount() > showFilenameColumnThreshold)) {
|
|
itemVH.fileName.setVisibility(View.GONE);
|
|
} else {
|
|
itemVH.fileName.setVisibility(View.VISIBLE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void setThumbnail(File file,
|
|
ImageView thumbnailView,
|
|
Context context,
|
|
ViewThemeUtils viewThemeUtils) {
|
|
if (file.isDirectory()) {
|
|
thumbnailView.setImageDrawable(MimeTypeUtil.getDefaultFolderIcon(context, viewThemeUtils));
|
|
} else {
|
|
thumbnailView.setImageResource(R.drawable.file);
|
|
|
|
/* Cancellation needs do be checked and done before changing the drawable in fileIcon, or
|
|
* {@link ThumbnailsCacheManager#cancelPotentialThumbnailWork} will NEVER cancel any task.
|
|
*/
|
|
boolean allowedToCreateNewThumbnail = ThumbnailsCacheManager.cancelPotentialThumbnailWork(file,
|
|
thumbnailView);
|
|
|
|
|
|
// get Thumbnail if file is image
|
|
if (MimeTypeUtil.isImage(file)) {
|
|
// Thumbnail in Cache?
|
|
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
|
|
ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.hashCode()
|
|
);
|
|
if (thumbnail != null) {
|
|
thumbnailView.setImageBitmap(thumbnail);
|
|
} else {
|
|
|
|
// generate new Thumbnail
|
|
if (allowedToCreateNewThumbnail) {
|
|
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
|
|
new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView);
|
|
if (MimeTypeUtil.isVideo(file)) {
|
|
thumbnail = ThumbnailsCacheManager.mDefaultVideo;
|
|
} else {
|
|
thumbnail = ThumbnailsCacheManager.mDefaultImg;
|
|
}
|
|
final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable =
|
|
new ThumbnailsCacheManager.AsyncThumbnailDrawable(
|
|
context.getResources(),
|
|
thumbnail,
|
|
task
|
|
);
|
|
thumbnailView.setImageDrawable(asyncDrawable);
|
|
task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, null));
|
|
Log_OC.v(TAG, "Executing task to generate a new thumbnail");
|
|
|
|
} // else, already being generated, don't restart it
|
|
}
|
|
} else {
|
|
thumbnailView.setImageDrawable(MimeTypeUtil.getFileTypeIcon(null,
|
|
file.getName(),
|
|
context,
|
|
viewThemeUtils));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getItemViewType(int position) {
|
|
if (position == mFiles.size()) {
|
|
return VIEWTYPE_FOOTER;
|
|
} else {
|
|
if (MimeTypeUtil.isImageOrVideo(getItem(position))) {
|
|
return VIEWTYPE_IMAGE;
|
|
} else {
|
|
return VIEWTYPE_ITEM;
|
|
}
|
|
}
|
|
}
|
|
|
|
private File getItem(int position) {
|
|
return mFiles.get(position);
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
switch (viewType) {
|
|
default:
|
|
case VIEWTYPE_ITEM:
|
|
if (gridView) {
|
|
View itemView = LayoutInflater.from(mContext).inflate(R.layout.grid_item, parent, false);
|
|
return new LocalFileListGridItemViewHolder(itemView);
|
|
} else {
|
|
View itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
|
|
return new LocalFileListItemViewHolder(itemView);
|
|
}
|
|
|
|
case VIEWTYPE_IMAGE:
|
|
if (gridView) {
|
|
View itemView = LayoutInflater.from(mContext).inflate(R.layout.grid_image, parent, false);
|
|
return new LocalFileListGridImageViewHolder(itemView);
|
|
} else {
|
|
View itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
|
|
return new LocalFileListItemViewHolder(itemView);
|
|
}
|
|
|
|
case VIEWTYPE_FOOTER:
|
|
View itemView = LayoutInflater.from(mContext).inflate(R.layout.list_footer, parent, false);
|
|
return new LocalFileListFooterViewHolder(itemView);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change the adapted directory for a new one
|
|
*
|
|
* @param directory New file to adapt. Can be NULL, meaning "no content to adapt".
|
|
*/
|
|
public void swapDirectory(final File directory) {
|
|
localFileListFragmentInterface.setLoading(true);
|
|
final Handler uiHandler = new Handler(Looper.getMainLooper());
|
|
Executors.newSingleThreadExecutor().execute(() -> {
|
|
List<File> fileList;
|
|
if (directory == null) {
|
|
fileList = new ArrayList<>();
|
|
} else {
|
|
if (mLocalFolderPicker) {
|
|
fileList = getFolders(directory);
|
|
} else {
|
|
fileList = getFiles(directory);
|
|
}
|
|
}
|
|
|
|
if (!fileList.isEmpty()) {
|
|
FileSortOrder sortOrder = preferences.getSortOrderByType(FileSortOrder.Type.localFileListView);
|
|
fileList = sortOrder.sortLocalFiles(fileList);
|
|
|
|
// Fetch preferences for showing hidden files
|
|
boolean showHiddenFiles = preferences.isShowHiddenFilesEnabled();
|
|
if (!showHiddenFiles) {
|
|
fileList = filterHiddenFiles(fileList);
|
|
}
|
|
}
|
|
final List<File> newFiles = fileList;
|
|
|
|
uiHandler.post(() -> {
|
|
mFiles = newFiles;
|
|
mFilesAll = new ArrayList<>();
|
|
mFilesAll.addAll(mFiles);
|
|
|
|
notifyDataSetChanged();
|
|
localFileListFragmentInterface.setLoading(false);
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
public void setSortOrder(FileSortOrder sortOrder) {
|
|
localFileListFragmentInterface.setLoading(true);
|
|
final Handler uiHandler = new Handler(Looper.getMainLooper());
|
|
Executors.newSingleThreadExecutor().execute(() -> {
|
|
preferences.setSortOrder(FileSortOrder.Type.localFileListView, sortOrder);
|
|
mFiles = sortOrder.sortLocalFiles(mFiles);
|
|
|
|
uiHandler.post(() -> {
|
|
notifyDataSetChanged();
|
|
localFileListFragmentInterface.setLoading(false);
|
|
});
|
|
});
|
|
|
|
|
|
}
|
|
|
|
private List<File> getFolders(final File directory) {
|
|
File[] folders = directory.listFiles(File::isDirectory);
|
|
|
|
if (folders != null && folders.length > 0) {
|
|
return new ArrayList<>(Arrays.asList(folders));
|
|
} else {
|
|
return new ArrayList<>();
|
|
}
|
|
}
|
|
|
|
private List<File> getFiles(File directory) {
|
|
File[] files = directory.listFiles();
|
|
|
|
if (files != null && files.length > 0) {
|
|
return new ArrayList<>(Arrays.asList(files));
|
|
} else {
|
|
return new ArrayList<>();
|
|
}
|
|
}
|
|
|
|
public void filter(String text) {
|
|
if (text.isEmpty()) {
|
|
mFiles = mFilesAll;
|
|
} else {
|
|
List<File> result = new ArrayList<>();
|
|
String filterText = text.toLowerCase(Locale.getDefault());
|
|
for (File file : mFilesAll) {
|
|
if (file.getName().toLowerCase(Locale.getDefault()).contains(filterText)) {
|
|
result.add(file);
|
|
}
|
|
}
|
|
mFiles = result;
|
|
}
|
|
notifyDataSetChanged();
|
|
}
|
|
|
|
/**
|
|
* Filter for hidden files
|
|
*
|
|
* @param files ArrayList of files to filter
|
|
* @return Non-hidden files
|
|
*/
|
|
private List<File> filterHiddenFiles(List<File> files) {
|
|
List<File> ret = new ArrayList<>();
|
|
|
|
for (File file : files) {
|
|
if (!file.isHidden()) {
|
|
ret.add(file);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
private String getFooterText() {
|
|
int filesCount = 0;
|
|
int foldersCount = 0;
|
|
|
|
for (File file : mFiles) {
|
|
if (file.isDirectory()) {
|
|
foldersCount++;
|
|
} else {
|
|
if (!file.isHidden()) {
|
|
filesCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return generateFooterText(filesCount, foldersCount);
|
|
}
|
|
|
|
private String generateFooterText(int filesCount, int foldersCount) {
|
|
String output;
|
|
Resources resources = mContext.getResources();
|
|
|
|
if (filesCount + foldersCount <= 0) {
|
|
output = "";
|
|
} else if (foldersCount <= 0) {
|
|
output = resources.getQuantityString(R.plurals.file_list__footer__file, filesCount, filesCount);
|
|
} else if (filesCount <= 0) {
|
|
output = resources.getQuantityString(R.plurals.file_list__footer__folder, foldersCount, foldersCount);
|
|
} else {
|
|
output = resources.getQuantityString(R.plurals.file_list__footer__file, filesCount, filesCount) + ", " +
|
|
resources.getQuantityString(R.plurals.file_list__footer__folder, foldersCount, foldersCount);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
public void setGridView(boolean gridView) {
|
|
this.gridView = gridView;
|
|
}
|
|
|
|
public int checkedFilesCount() {
|
|
return checkedFiles.size();
|
|
}
|
|
|
|
private static class LocalFileListItemViewHolder extends LocalFileListGridItemViewHolder {
|
|
private final TextView fileSize;
|
|
private final TextView lastModification;
|
|
private final TextView fileSeparator;
|
|
|
|
private LocalFileListItemViewHolder(View itemView) {
|
|
super(itemView);
|
|
|
|
fileSize = itemView.findViewById(R.id.file_size);
|
|
fileSeparator = itemView.findViewById(R.id.file_separator);
|
|
lastModification = itemView.findViewById(R.id.last_mod);
|
|
|
|
itemView.findViewById(R.id.sharedAvatars).setVisibility(View.GONE);
|
|
itemView.findViewById(R.id.overflow_menu).setVisibility(View.GONE);
|
|
itemView.findViewById(R.id.tagsGroup).setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
static class LocalFileListGridImageViewHolder extends RecyclerView.ViewHolder {
|
|
protected final ImageView thumbnail;
|
|
protected final ImageView checkbox;
|
|
protected final LinearLayout itemLayout;
|
|
|
|
private LocalFileListGridImageViewHolder(View itemView) {
|
|
super(itemView);
|
|
|
|
thumbnail = itemView.findViewById(R.id.thumbnail);
|
|
checkbox = itemView.findViewById(R.id.custom_checkbox);
|
|
itemLayout = itemView.findViewById(R.id.ListItemLayout);
|
|
|
|
itemView.findViewById(R.id.sharedIcon).setVisibility(View.GONE);
|
|
itemView.findViewById(R.id.favorite_action).setVisibility(View.GONE);
|
|
itemView.findViewById(R.id.localFileIndicator).setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
static class LocalFileListGridItemViewHolder extends LocalFileListGridImageViewHolder {
|
|
private final TextView fileName;
|
|
|
|
private LocalFileListGridItemViewHolder(View itemView) {
|
|
super(itemView);
|
|
|
|
fileName = itemView.findViewById(R.id.Filename);
|
|
}
|
|
}
|
|
|
|
private static class LocalFileListFooterViewHolder extends RecyclerView.ViewHolder {
|
|
private final TextView footerText;
|
|
|
|
private LocalFileListFooterViewHolder(View itemView) {
|
|
super(itemView);
|
|
|
|
footerText = itemView.findViewById(R.id.footerText);
|
|
}
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public void setFiles(List<File> newFiles) {
|
|
mFiles = newFiles;
|
|
mFilesAll = new ArrayList<>();
|
|
mFilesAll.addAll(mFiles);
|
|
|
|
notifyDataSetChanged();
|
|
localFileListFragmentInterface.setLoading(false);
|
|
}
|
|
}
|