Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f9e8000
add: extension text view element
alperozturk96 Aug 13, 2025
55487a4
add: unit and screenshot tests
alperozturk96 Aug 13, 2025
60b190b
fix: more button layout constraint
alperozturk96 Aug 13, 2025
e3a0d9e
fix: path of screenshot test
alperozturk96 Aug 13, 2025
acc4246
add: dimens for grid layout
alperozturk96 Aug 14, 2025
2ce7fa6
fix: layout order
alperozturk96 Aug 14, 2025
00cfdaa
fix: layout for small screen devices
alperozturk96 Aug 15, 2025
9a7a8e2
fix: configure only for grid view
alperozturk96 Aug 15, 2025
73692d0
add: file name landscape margin value
alperozturk96 Aug 15, 2025
0eb2b07
add: orientation utility function
alperozturk96 Aug 15, 2025
632b54e
add: simpler configuration functions
alperozturk96 Aug 15, 2025
3872e0e
add: filename container to the grid view holder
alperozturk96 Aug 15, 2025
739f21a
fix: overly concrete parameter usage
alperozturk96 Aug 15, 2025
54d468b
fix: padding between thumbnail and file name
alperozturk96 Aug 16, 2025
e256b29
fix: folder name max width
alperozturk96 Aug 16, 2025
600ca3c
fix: git conflicts
alperozturk96 Aug 18, 2025
345367f
add: more button margin
alperozturk96 Aug 16, 2025
1e2b1ea
bi,di2
alperozturk96 Aug 16, 2025
4f38a4f
add: grid layout recalculation
alperozturk96 Aug 18, 2025
27194f2
add: title and extension in file actions bottom sheet
alperozturk96 Aug 19, 2025
f3cfd57
fix: dodgy code in oc file list adapter
alperozturk96 Aug 19, 2025
a7c0f2e
add: new tests
alperozturk96 Aug 19, 2025
d863a8b
add: new tests
alperozturk96 Aug 19, 2025
6a57917
fix: use only one more button
alperozturk96 Aug 19, 2025
c713db1
fix: more button alignment
alperozturk96 Aug 19, 2025
82e8de2
fix: layout padding
alperozturk96 Aug 19, 2025
2aab5de
fix: file features layout alignment
alperozturk96 Aug 21, 2025
2a7f54f
fix: layout alignment
alperozturk96 Aug 21, 2025
1dfb02b
add: missing bidi character
alperozturk96 Aug 21, 2025
e31d94a
fix: title max width
alperozturk96 Aug 21, 2025
b8fe4e3
fix: title max width
alperozturk96 Aug 21, 2025
2f9da59
add: max file name
alperozturk96 Aug 21, 2025
b4451ec
fix: title layout alignment
alperozturk96 Aug 21, 2025
618330c
fix: codacy analysis title max width
alperozturk96 Aug 21, 2025
013caa1
remove: file list grid controller
alperozturk96 Aug 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,13 @@ class OCFileListFragmentStaticServerIT : AbstractIT() {
sut.storageManager.saveFile(this)
}

OCFile("/Foo%e2%80%aedm.exe").apply {
remoteId = "000000011"
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
modificationTimestamp = 1000
sut.storageManager.saveFile(this)
}

sut.addFragment(fragment)
val root = sut.storageManager.getFileByEncryptedRemotePath("/")
fragment.listDirectory(root, false, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.account.CurrentAccountProvider
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.di.ViewModelFactory
import com.nextcloud.utils.extensions.setVisibleIf
import com.owncloud.android.R
import com.owncloud.android.databinding.FileActionsBottomSheetBinding
import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding
Expand All @@ -44,6 +45,7 @@ import com.owncloud.android.lib.resources.files.model.FileLockType
import com.owncloud.android.ui.activity.ComponentsGetter
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener
import com.owncloud.android.utils.FileStorageUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import javax.inject.Inject

Expand Down Expand Up @@ -204,11 +206,23 @@ class FileActionsBottomSheet :
private fun displayTitle(titleFile: OCFile?) {
val decryptedFileName = titleFile?.decryptedFileName
if (decryptedFileName != null) {
decryptedFileName.let {
binding.title.text = it
val isFolder = titleFile.isFolder
val isRTL = DisplayUtils.isRTL()
val (base, ext) = FileStorageUtils.getFilenameAndExtension(decryptedFileName, isFolder, isRTL)
val titleMaxWidth = DisplayUtils.convertDpToPixel(
requireContext().resources.configuration.screenWidthDp.times(FILENAME_MAX_WIDTH_PERCENTAGE).toFloat(),
context
)

binding.title.maxWidth = titleMaxWidth
binding.title.text = base
binding.extension.setVisibleIf(!isFolder)
if (!isFolder) {
binding.extension.text = ext
}
} else {
binding.title.isVisible = false
binding.extension.isVisible = false
}
}

Expand Down Expand Up @@ -300,6 +314,7 @@ class FileActionsBottomSheet :
companion object {
private const val REQUEST_KEY = "REQUEST_KEY_ACTION"
private const val RESULT_KEY_ACTION_ID = "RESULT_KEY_ACTION_ID"
private const val FILENAME_MAX_WIDTH_PERCENTAGE = 0.6

@JvmStatic
@JvmOverloads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class GroupfolderListAdapter(
listHolder.apply {
fileName.text = file.name
fileSize.text = file.parentFile?.path ?: "/"
extension.visibility = View.GONE
fileSizeSeparator.visibility = View.GONE
lastModification.visibility = View.GONE
checkbox.visibility = View.GONE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ import android.widget.TextView

internal interface ListGridItemViewHolder : ListViewHolder {
val fileName: TextView
val extension: TextView?
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import kotlin.Pair;
import me.zhanghai.android.fastscroll.PopupTextProvider;

/**
Expand All @@ -123,6 +124,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
private FileDataStorageManager mStorageManager;
private User user;
private final OCFileListFragmentInterface ocFileListFragmentInterface;
private final boolean isRTL;

private OCFile currentDirectory;
private static final String TAG = OCFileListAdapter.class.getSimpleName();
Expand Down Expand Up @@ -195,6 +197,7 @@ public OCFileListAdapter(

// initialise thumbnails cache on background thread
ThumbnailsCacheManager.initDiskCacheAsync();
isRTL = DisplayUtils.isRTL();
}

public boolean isMultiSelect() {
Expand Down Expand Up @@ -476,7 +479,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
}

if (holder instanceof ListGridItemViewHolder gridItemViewHolder) {
bindListGridItemViewHolder(gridItemViewHolder, file);
setFilenameAndExtension(gridItemViewHolder, file);
checkVisibilityOfFileFeaturesLayout(gridItemViewHolder);
}

Expand Down Expand Up @@ -556,8 +559,53 @@ private void updateLivePhotoIndicators(ListViewHolder holder, OCFile file) {
}
}

private void bindListGridItemViewHolder(ListGridItemViewHolder holder, OCFile file) {
holder.getFileName().setText(mStorageManager.getFilenameConsideringOfflineOperation(file));
private void setFilenameAndExtension(ListGridItemViewHolder holder, OCFile file) {
final String filename = mStorageManager.getFilenameConsideringOfflineOperation(file);
final var pair = FileStorageUtils.getFilenameAndExtension(filename, file.isFolder(), isRTL);
final boolean isFolder = file.isFolder();

if (holder instanceof OCFileListGridItemViewHolder gridItemViewHolder) {
handleGridMode(filename, gridItemViewHolder, pair, file);
} else {
handleListMode(holder, pair, isFolder);
}
}

private void handleGridMode(String filename, OCFileListGridItemViewHolder holder, Pair<String, String> filenamePair, OCFile file) {
boolean containsBidiControlCharacters = FileStorageUtils.containsBidiControlCharacters(filename);
ViewExtensionsKt.setVisibleIf(holder.getFileName(),!containsBidiControlCharacters);
ViewExtensionsKt.setVisibleIf(holder.getBinding().bidiFilenameContainer, containsBidiControlCharacters);
final var extension = holder.getExtension();

if (containsBidiControlCharacters) {
holder.getBidiFilename().setText(filenamePair.getFirst());
if (extension != null) {
extension.setText(filenamePair.getSecond());
}
holder.getBinding().more.setVisibility(View.GONE);
holder.getBinding().bidiMore.setOnClickListener(v -> ocFileListFragmentInterface.onOverflowIconClicked(file, v));
} else {
holder.getFileName().setText(filename);
if (extension != null) {
extension.setVisibility(View.GONE);
}
}
}

private void handleListMode(ListGridItemViewHolder holder,
Pair<String, String> filenamePair,
boolean isFolder) {
holder.getFileName().setText(filenamePair.getFirst());

final var extension = holder.getExtension();
if (extension != null) {
if (isFolder) {
extension.setVisibility(View.GONE);
} else {
extension.setVisibility(View.VISIBLE);
extension.setText(filenamePair.getSecond());
}
}
}

private void bindListItemViewHolder(ListItemViewHolder holder, OCFile file) {
Expand Down Expand Up @@ -1145,6 +1193,10 @@ public void cancelAllPendingTasks() {
ocFileListDelegate.cancelAllPendingTasks();
}

public boolean isGridView() {
return gridView;
}

public void setGridView(boolean bool) {
gridView = bool;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,26 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.elyeproj.loaderviewlibrary.LoaderImageView
import com.owncloud.android.databinding.GridItemBinding

internal class OCFileListGridItemViewHolder(var binding: GridItemBinding) :
class OCFileListGridItemViewHolder(var binding: GridItemBinding) :
RecyclerView.ViewHolder(
binding.root
),
ListGridItemViewHolder {
val bidiFilename: TextView
get() = binding.bidiFilename
override val fileName: TextView
get() = binding.Filename
override val extension: TextView?
get() = if (binding.bidiFilenameContainer.isVisible) {
binding.bidiExtension
} else {
null
}
override val thumbnail: ImageView
get() = binding.thumbnail

Expand Down Expand Up @@ -56,8 +65,11 @@ internal class OCFileListGridItemViewHolder(var binding: GridItemBinding) :
override val fileFeaturesLayout: LinearLayout
get() = binding.fileFeaturesLayout
override val more: ImageButton
get() = binding.more

get() = if (binding.bidiFilenameContainer.isVisible) {
binding.bidiMore
} else {
binding.more
}
init {
binding.favoriteAction.drawable.mutate()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) :
get() = binding.sharedAvatars
override val fileName: TextView
get() = binding.Filename
override val extension: TextView
get() = binding.extension
override val thumbnail: ImageView
get() = binding.thumbnailLayout.thumbnail
override val tagsGroup: ChipGroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.elyeproj.loaderviewlibrary.LoaderImageView
import com.owncloud.android.databinding.GridItemBinding
Expand All @@ -26,7 +27,11 @@ internal class OCFileListViewHolder(var binding: GridItemBinding) :
get() = binding.thumbnail

override val imageFileName: TextView
get() = binding.Filename
get() = if (binding.bidiFilenameContainer.isVisible) {
binding.bidiFilename
} else {
binding.Filename
}

override fun showVideoOverlay() {
// noop
Expand All @@ -47,7 +52,11 @@ internal class OCFileListViewHolder(var binding: GridItemBinding) :
override val unreadComments: ImageView
get() = binding.unreadComments
override val more: ImageButton
get() = binding.more
get() = if (binding.bidiFilenameContainer.isVisible) {
binding.bidiMore
} else {
binding.more
}
override val fileFeaturesLayout: LinearLayout
get() = binding.fileFeaturesLayout
override val gridLivePhotoIndicator: ImageView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
Expand Down Expand Up @@ -685,6 +686,14 @@ public static float convertPixelToDp(int px, Context context) {
return px * (DisplayMetrics.DENSITY_DEFAULT / (float) metrics.densityDpi);
}

public static boolean isRTL() {
return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
}

public static boolean isOrientationLandscape() {
return MainApp.getAppContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}

static public void showServerOutdatedSnackbar(Activity activity, int length) {
Snackbar.make(activity.findViewById(android.R.id.content),
R.string.outdated_server, length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,19 @@
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.lib.resources.shares.ShareeUser;
import com.owncloud.android.ui.helpers.FileOperationsHelper;

import org.apache.commons.io.FilenameUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
Expand All @@ -53,6 +57,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.core.app.ActivityCompat;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import kotlin.Pair;

/**
* Static methods to help in access to local file system.
Expand All @@ -69,6 +74,56 @@ private FileStorageUtils() {
// utility class -> private constructor
}

public static boolean containsBidiControlCharacters(String filename) {
if (filename == null) return false;

String decoded;
try {
decoded = URLDecoder.decode(filename, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
return false;
}

int[] bidiControlCharacters = {
0x202A, 0x202B, 0x202C, 0x202D, 0x202E,
0x200E, 0x200F, 0x2066, 0x2067, 0x2068,
0x2069, 0x061C
};

for (int i = 0; i < decoded.length(); i++) {
int codePoint = decoded.codePointAt(i);
for (int chars : bidiControlCharacters) {
if (codePoint == chars) {
return true;
}
}
}

for (char c : decoded.toCharArray()) {
if (c < 32) return true;
}

return false;
}

public static Pair<String,String> getFilenameAndExtension(String filename, boolean isFolder, boolean isRTL) {
if (isFolder) {
return new Pair<>(filename, "");
}

final String base = FilenameUtils.getBaseName(filename);
String extension = FilenameUtils.getExtension(filename);
if (!extension.isEmpty()) {
extension = StringConstants.DOT + extension;
}

if (isRTL) {
return new Pair<>(extension, base);
} else {
return new Pair<>(base, extension);
}
}

public static boolean isValidExtFilename(String name) {
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
Expand Down
Loading
Loading