diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eae0dc7..2b5b5272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Preserve modified date when unzip files ([#176]) +- File modification time is now preserved when compressing/decompressing files ([#176]) ## [1.1.0] - 2025-05-21 diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index 21d2112b..753abbe9 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -9,7 +9,7 @@ ComplexCondition:MimeTypesActivity.kt$MimeTypesActivity$mimetype != "image" && mimetype != "video" && mimetype != "audio" && mimetype != "text" && !extraAudioMimeTypes.contains(fullMimetype) && !extraDocumentMimeTypes.contains(fullMimetype) && !archiveMimeTypes.contains(fullMimetype) ComplexCondition:ReadTextActivity.kt$ReadTextActivity$requestCode == SELECT_SAVE_FILE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null CyclomaticComplexMethod:DecompressActivity.kt$DecompressActivity$private fun decompressTo(destination: String) - CyclomaticComplexMethod:ItemsAdapter.kt$ItemsAdapter$@SuppressLint("NewApi") private fun compressPaths(sourcePaths: List<String>, targetPath: String, password: String? = null): Boolean + CyclomaticComplexMethod:ItemsAdapter.kt$ItemsAdapter$@SuppressLint("NewApi") private fun compressPaths( sourcePaths: List<String>, targetPath: String, password: String? = null ): Boolean CyclomaticComplexMethod:ItemsAdapter.kt$ItemsAdapter$override fun actionItemPressed(id: Int) CyclomaticComplexMethod:MainActivity.kt$MainActivity$private fun setupOptionsMenu() CyclomaticComplexMethod:MimeTypesActivity.kt$MimeTypesActivity$private fun getProperFileDirItems(callback: (ArrayList<FileDirItem>) -> Unit) @@ -58,32 +58,7 @@ MaxLineLength:FavoritesActivity.kt$FavoritesActivity$FilePickerDialog MaxLineLength:FavoritesActivity.kt$FavoritesActivity$ManageFavoritesAdapter(this@FavoritesActivity, favorites, this@FavoritesActivity, manageFavoritesList) { } MaxLineLength:FavoritesActivity.kt$FavoritesActivity$updateMaterialActivityViews(manageFavoritesCoordinator, manageFavoritesList, useTransparentNavigation = true, useTopSearchMenu = false) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$"${baseConfig.OTGTreeUri}/document/${baseConfig.OTGPartition}%3A${itemToLoad.substring(baseConfig.OTGPath.length).replace("/", "%2F")}" - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$(drawable as LayerDrawable).findDrawableByLayerId(R.id.shortcut_folder_background).applyColorFilter(appIconColor) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$File(path).listFiles()?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') } - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$activity.getDocumentFile(path)?.listFiles()?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") } - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$if - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$itemName?.text = if (textToHighlight.isEmpty()) fileName else fileName.highlightTextPart(textToHighlight, properPrimaryColor) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$itemSection?.text = if (textToHighlight.isEmpty()) listItem.mName else listItem.mName.highlightTextPart(textToHighlight, properPrimaryColor) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$override fun getIsItemSelectable(position: Int) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$override fun onChange(position: Int) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$private - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$private fun getSelectedFileDirItems() - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$sourceFile.list()?.isEmpty() == true - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$val currPath = if (entry.isDirectory) path else "${path.getParentPath().trimEnd('/')}/${entry.fileName}" - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$val drawable = fileDrawables.getOrElse(fileName.substringAfterLast(".").lowercase(Locale.getDefault()), { fileDrawable }) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$val fileDirItem = FileDirItem(currPath, entry.fileName, entry.isDirectory, 0, entry.uncompressedSize) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$val fileIcon = fileDrawables.getOrElse(path.substringAfterLast(".").lowercase(Locale.getDefault()), { fileDrawable }) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter$val paths = getSelectedFileDirItems().asSequence().filter { !it.isDirectory }.map { it.path }.toMutableList() as ArrayList<String> MaxLineLength:ItemsAdapter.kt$ItemsAdapter$} - MaxLineLength:ItemsAdapter.kt$ItemsAdapter.Binding.ItemDirGrid$override - MaxLineLength:ItemsAdapter.kt$ItemsAdapter.Binding.ItemEmpty$override - MaxLineLength:ItemsAdapter.kt$ItemsAdapter.Binding.ItemFileDirList$override - MaxLineLength:ItemsAdapter.kt$ItemsAdapter.Binding.ItemFileDirList$return ItemFileDirListBindingAdapter(ItemFileDirListBinding.inflate(layoutInflater, viewGroup, attachToRoot)) - MaxLineLength:ItemsAdapter.kt$ItemsAdapter.Binding.ItemFileGrid$override - MaxLineLength:ItemsAdapter.kt$ItemsAdapter.Binding.ItemSection$override MaxLineLength:ItemsFragment.kt$ItemsFragment$ItemsAdapter MaxLineLength:ItemsFragment.kt$ItemsFragment$class MaxLineLength:ItemsFragment.kt$ItemsFragment$context @@ -107,7 +82,6 @@ MaxLineLength:MainActivity.kt$MainActivity$private fun getInactiveTabIndexes(activeIndex: Int) MaxLineLength:MainActivity.kt$MainActivity$resultIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION MaxLineLength:MainActivity.kt$MainActivity$updateMaterialActivityViews(binding.mainCoordinator, null, useTransparentNavigation = false, useTopSearchMenu = true) - MaxLineLength:MainActivity.kt$MainActivity$val isPickFileIntent = action == RingtoneManager.ACTION_RINGTONE_PICKER || action == Intent.ACTION_GET_CONTENT || action == Intent.ACTION_PICK MaxLineLength:MainActivity.kt$MainActivity$val licenses = LICENSE_GLIDE or LICENSE_PATTERN or LICENSE_REPRINT or LICENSE_GESTURE_VIEWS or LICENSE_AUTOFITTEXTVIEW or LICENSE_ZIP4J MaxLineLength:MimeTypesActivity.kt$MimeTypesActivity$!extraAudioMimeTypes.contains(fullMimetype) MaxLineLength:MimeTypesActivity.kt$MimeTypesActivity$if @@ -136,18 +110,17 @@ MaxLineLength:StorageFragment.kt$StorageFragment$val filtered = allDeviceListItems.filter { it.mName.contains(text, true) }.toMutableList() as ArrayList<ListItem> MaxLineLength:StorageFragment.kt$StorageFragment$val mimeType = cursor.getStringValue(MediaStore.Files.FileColumns.MIME_TYPE)?.lowercase(Locale.getDefault()) MaxLineLength:StorageFragment.kt$StorageFragment$val storageStatsManager = context.getSystemService(AppCompatActivity.STORAGE_STATS_SERVICE) as StorageStatsManager - MaxLineLength:ViewPagerAdapter.kt$ViewPagerAdapter$val isGetContentIntent = activity.intent.action == Intent.ACTION_GET_CONTENT || activity.intent.action == Intent.ACTION_PICK NestedBlockDepth:DecompressActivity.kt$DecompressActivity$private fun decompressTo(destination: String) - NestedBlockDepth:ItemsAdapter.kt$ItemsAdapter$@SuppressLint("NewApi") private fun compressPaths(sourcePaths: List<String>, targetPath: String, password: String? = null): Boolean - NestedBlockDepth:ItemsAdapter.kt$ItemsAdapter$private fun decompressPaths(paths: List<String>, conflictResolutions: LinkedHashMap<String, Int>, callback: (success: Boolean) -> Unit) + NestedBlockDepth:ItemsAdapter.kt$ItemsAdapter$@SuppressLint("NewApi") private fun compressPaths( sourcePaths: List<String>, targetPath: String, password: String? = null ): Boolean + NestedBlockDepth:ItemsAdapter.kt$ItemsAdapter$private fun decompressPaths( paths: List<String>, conflictResolutions: LinkedHashMap<String, Int>, callback: (success: Boolean) -> Unit ) NestedBlockDepth:ItemsAdapter.kt$ItemsAdapter$private fun setupView(binding: ItemViewBinding, listItem: ListItem) - NestedBlockDepth:ItemsAdapter.kt$ItemsAdapter$private fun tryDecompressingPaths(sourcePaths: List<String>, callback: (success: Boolean) -> Unit) + NestedBlockDepth:ItemsAdapter.kt$ItemsAdapter$private fun tryDecompressingPaths( sourcePaths: List<String>, callback: (success: Boolean) -> Unit ) NestedBlockDepth:ItemsFragment.kt$ItemsFragment$private fun getRegularItemsOf(path: String, callback: (originalPath: String, items: ArrayList<ListItem>) -> Unit) NestedBlockDepth:ItemsFragment.kt$ItemsFragment$private fun searchFiles(text: String, path: String): ArrayList<ListItem> NestedBlockDepth:RecentsFragment.kt$RecentsFragment$private fun getRecents(callback: (recents: ArrayList<ListItem>) -> Unit) NestedBlockDepth:StorageFragment.kt$StorageFragment$override fun setupFragment(activity: SimpleActivity) NestedBlockDepth:StorageFragment.kt$StorageFragment$private fun getAllFiles(volumeName: String): ArrayList<FileDirItem> - ReturnCount:ItemsAdapter.kt$ItemsAdapter$@SuppressLint("NewApi") private fun compressPaths(sourcePaths: List<String>, targetPath: String, password: String? = null): Boolean + ReturnCount:ItemsAdapter.kt$ItemsAdapter$@SuppressLint("NewApi") private fun compressPaths( sourcePaths: List<String>, targetPath: String, password: String? = null ): Boolean SwallowedException:ItemsAdapter.kt$ItemsAdapter$e: Exception SwallowedException:MimeTypesActivity.kt$MimeTypesActivity$e: Exception SwallowedException:StorageFragment.kt$StorageFragment$e: Exception diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index 2b2184b5..e49ad51f 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -1,5 +1,5 @@ - + + message="A newer version of Gradle than 8.13 is available: 8.14.3" + errorLine1="distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -107,7 +107,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -613,7 +613,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -624,7 +624,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -635,7 +635,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -646,7 +646,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -657,7 +657,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> diff --git a/app/src/main/kotlin/org/fossify/filemanager/adapters/ItemsAdapter.kt b/app/src/main/kotlin/org/fossify/filemanager/adapters/ItemsAdapter.kt index 9b052b8c..67712316 100644 --- a/app/src/main/kotlin/org/fossify/filemanager/adapters/ItemsAdapter.kt +++ b/app/src/main/kotlin/org/fossify/filemanager/adapters/ItemsAdapter.kt @@ -128,7 +128,8 @@ class ItemsAdapter( private val swipeRefreshLayout: SwipeRefreshLayout?, canHaveIndividualViewType: Boolean = true, itemClick: (Any) -> Unit, -) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate { +) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), + RecyclerViewFastScroller.OnPopupTextUpdate { private lateinit var fileDrawable: Drawable private lateinit var folderDrawable: Drawable @@ -143,7 +144,9 @@ class ItemsAdapter( private val config = activity.config private val viewType = if (canHaveIndividualViewType) { - config.getFolderViewType(listItems.firstOrNull { !it.isSectionTitle }?.mPath?.getParentPath() ?: "") + config.getFolderViewType( + path = listItems.firstOrNull { !it.isSectionTitle }?.mPath?.getParentPath().orEmpty() + ) } else { config.viewType } @@ -169,7 +172,8 @@ class ItemsAdapter( override fun prepareActionMode(menu: Menu) { menu.apply { - findItem(R.id.cab_decompress).isVisible = getSelectedFileDirItems().map { it.path }.any { it.isZipFile() } + findItem(R.id.cab_decompress).isVisible = + getSelectedFileDirItems().map { it.path }.any { it.isZipFile() } findItem(R.id.cab_confirm_selection).isVisible = isPickMultipleIntent findItem(R.id.cab_copy_path).isVisible = isOneItemSelected() findItem(R.id.cab_open_with).isVisible = isOneFileSelected() @@ -207,13 +211,21 @@ class ItemsAdapter( } } - override fun getSelectableItemCount() = listItems.filter { !it.isSectionTitle && !it.isGridTypeDivider }.size + override fun getSelectableItemCount(): Int { + return listItems.filter { !it.isSectionTitle && !it.isGridTypeDivider }.size + } - override fun getIsItemSelectable(position: Int) = !listItems[position].isSectionTitle && !listItems[position].isGridTypeDivider + override fun getIsItemSelectable(position: Int): Boolean { + return !listItems[position].isSectionTitle && !listItems[position].isGridTypeDivider + } - override fun getItemSelectionKey(position: Int) = listItems.getOrNull(position)?.path?.hashCode() + override fun getItemSelectionKey(position: Int): Int? { + return listItems.getOrNull(position)?.path?.hashCode() + } - override fun getItemKeyPosition(key: Int) = listItems.indexOfFirst { it.path.hashCode() == key } + override fun getItemKeyPosition(key: Int): Int { + return listItems.indexOfFirst { it.path.hashCode() == key } + } override fun onActionModeCreated() { swipeRefreshLayout?.isRefreshing = false @@ -234,25 +246,37 @@ class ItemsAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding = Binding.getByItemViewType(viewType, isListViewType).inflate(layoutInflater, parent, false) + val binding = Binding.getByItemViewType(viewType, isListViewType) + .inflate(layoutInflater, parent, false) return createViewHolder(binding.root) } override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) { val fileDirItem = listItems[position] - holder.bindView(fileDirItem, true, !fileDirItem.isSectionTitle) { itemView, layoutPosition -> + holder.bindView( + any = fileDirItem, + allowSingleClick = true, + allowLongClick = !fileDirItem.isSectionTitle + ) { itemView, layoutPosition -> val viewType = getItemViewType(position) - setupView(Binding.getByItemViewType(viewType, isListViewType).bind(itemView), fileDirItem) + setupView( + binding = Binding.getByItemViewType(viewType, isListViewType).bind(itemView), + listItem = fileDirItem + ) } bindViewHolder(holder) } override fun getItemCount() = listItems.size - private fun getItemWithKey(key: Int): FileDirItem? = listItems.firstOrNull { it.path.hashCode() == key } + private fun getItemWithKey(key: Int): FileDirItem? { + return listItems.firstOrNull { it.path.hashCode() == key } + } - private fun isOneFileSelected() = isOneItemSelected() && getItemWithKey(selectedKeys.first())?.isDirectory == false + private fun isOneFileSelected(): Boolean { + return isOneItemSelected() && getItemWithKey(selectedKeys.first())?.isDirectory == false + } private fun checkHideBtnVisibility(menu: Menu) { var hiddenCnt = 0 @@ -271,7 +295,10 @@ class ItemsAdapter( private fun confirmSelection() { if (selectedKeys.isNotEmpty()) { - val paths = getSelectedFileDirItems().asSequence().filter { !it.isDirectory }.map { it.path }.toMutableList() as ArrayList + val paths = getSelectedFileDirItems() + .asSequence() + .filter { !it.isDirectory }.map { it.path } + .toMutableList() as ArrayList if (paths.isEmpty()) { finishActMode() } else { @@ -350,7 +377,11 @@ class ItemsAdapter( getShortcutImage(path, drawable) { val intent = Intent(activity, SplashActivity::class.java) intent.action = Intent.ACTION_VIEW - intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY + intent.flags = + intent.flags or + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_ACTIVITY_NO_HISTORY intent.data = Uri.fromFile(File(path)) val shortcut = ShortcutInfo.Builder(activity, path) @@ -366,7 +397,8 @@ class ItemsAdapter( private fun getShortcutImage(path: String, drawable: Drawable, callback: () -> Unit) { val appIconColor = baseConfig.appIconColor - (drawable as LayerDrawable).findDrawableByLayerId(R.id.shortcut_folder_background).applyColorFilter(appIconColor) + (drawable as LayerDrawable).findDrawableByLayerId(R.id.shortcut_folder_background) + .applyColorFilter(appIconColor) if (activity.getIsPathDirectory(path)) { callback() } else { @@ -387,10 +419,15 @@ class ItemsAdapter( try { val bitmap = builder.get() - drawable.findDrawableByLayerId(R.id.shortcut_folder_background).applyColorFilter(0) + drawable.findDrawableByLayerId(R.id.shortcut_folder_background) + .applyColorFilter(0) drawable.setDrawableByLayerId(R.id.shortcut_folder_image, bitmap) } catch (e: Exception) { - val fileIcon = fileDrawables.getOrElse(path.substringAfterLast(".").lowercase(Locale.getDefault()), { fileDrawable }) + val fileIcon = fileDrawables + .getOrElse( + key = path.substringAfterLast(".").lowercase(Locale.getDefault()), + defaultValue = { fileDrawable } + ) drawable.setDrawableByLayerId(R.id.shortcut_folder_image, fileIcon) } @@ -415,15 +452,19 @@ class ItemsAdapter( } activity.isPathOnOTG(path) -> { - activity.getDocumentFile(path)?.listFiles()?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") }?.forEach { - addFileUris(it.uri.toString(), paths) - } + activity.getDocumentFile(path)?.listFiles() + ?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") } + ?.forEach { + addFileUris(it.uri.toString(), paths) + } } else -> { - File(path).listFiles()?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') }?.forEach { - addFileUris(it.absolutePath, paths) - } + File(path).listFiles() + ?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') } + ?.forEach { + addFileUris(it.absolutePath, paths) + } } } } else { @@ -482,11 +523,21 @@ class ItemsAdapter( if (activity.isPathOnRoot(it) || activity.isPathOnRoot(firstFile.path)) { copyMoveRootItems(files, it, isCopyOperation) } else { - activity.copyMoveFilesTo(files, source, it, isCopyOperation, false, config.shouldShowHidden()) { + activity.copyMoveFilesTo( + fileDirItems = files, + source = source, + destination = it, + isCopyOperation = isCopyOperation, + copyPhotoVideoOnly = false, + copyHidden = config.shouldShowHidden() + ) { if (!isCopyOperation) { files.forEach { sourceFileDir -> val sourcePath = sourceFileDir.path - if (activity.isRestrictedSAFOnlyRoot(sourcePath) && activity.getDoesFilePathExist(sourcePath)) { + if ( + activity.isRestrictedSAFOnlyRoot(sourcePath) + && activity.getDoesFilePathExist(sourcePath) + ) { activity.deleteFile(sourceFileDir, true) { listener?.refreshFragment() activity.runOnUiThread { @@ -495,8 +546,12 @@ class ItemsAdapter( } } else { val sourceFile = File(sourcePath) - if (activity.getDoesFilePathExist(source) && activity.getIsPathDirectory(source) && - sourceFile.list()?.isEmpty() == true && sourceFile.getProperSize(true) == 0L && sourceFile.getFileCount(true) == 0 + if ( + activity.getDoesFilePathExist(source) + && activity.getIsPathDirectory(source) + && sourceFile.list()?.isEmpty() == true + && sourceFile.getProperSize(true) == 0L + && sourceFile.getFileCount(true) == 0 ) { val sourceFolder = sourceFile.toFileDirItem(activity) activity.deleteFile(sourceFolder, true) { @@ -520,7 +575,11 @@ class ItemsAdapter( } } - private fun copyMoveRootItems(files: ArrayList, destinationPath: String, isCopyOperation: Boolean) { + private fun copyMoveRootItems( + files: ArrayList, + destinationPath: String, + isCopyOperation: Boolean + ) { activity.toast(R.string.copying) ensureBackgroundThread { val fileCnt = files.size @@ -586,7 +645,11 @@ class ItemsAdapter( return@handleSAFDialog } - val paths = getSelectedFileDirItems().asSequence().map { it.path }.filter { it.isZipFile() }.toList() + val paths = getSelectedFileDirItems() + .asSequence() + .map { it.path } + .filter { it.isZipFile() } + .toList() ensureBackgroundThread { tryDecompressingPaths(paths) { success -> activity.runOnUiThread { @@ -603,15 +666,28 @@ class ItemsAdapter( } } - private fun tryDecompressingPaths(sourcePaths: List, callback: (success: Boolean) -> Unit) { + private fun tryDecompressingPaths( + sourcePaths: List, + callback: (success: Boolean) -> Unit + ) { sourcePaths.forEach { path -> ZipInputStream(BufferedInputStream(activity.getFileInputStreamSync(path))).use { zipInputStream -> try { val fileDirItems = ArrayList() var entry = zipInputStream.nextEntry while (entry != null) { - val currPath = if (entry.isDirectory) path else "${path.getParentPath().trimEnd('/')}/${entry.fileName}" - val fileDirItem = FileDirItem(currPath, entry.fileName, entry.isDirectory, 0, entry.uncompressedSize) + val currPath = if (entry.isDirectory) { + path + } else { + "${path.getParentPath().trimEnd('/')}/${entry.fileName}" + } + val fileDirItem = FileDirItem( + path = currPath, + name = entry.fileName, + isDirectory = entry.isDirectory, + children = 0, + size = entry.uncompressedSize + ) fileDirItems.add(fileDirItem) entry = zipInputStream.nextEntry } @@ -636,9 +712,14 @@ class ItemsAdapter( } } - private fun decompressPaths(paths: List, conflictResolutions: LinkedHashMap, callback: (success: Boolean) -> Unit) { + private fun decompressPaths( + paths: List, + conflictResolutions: LinkedHashMap, + callback: (success: Boolean) -> Unit + ) { paths.forEach { path -> - val zipInputStream = ZipInputStream(BufferedInputStream(activity.getFileInputStreamSync(path))) + val zipInputStream = + ZipInputStream(BufferedInputStream(activity.getFileInputStreamSync(path))) zipInputStream.use { try { var entry = zipInputStream.nextEntry @@ -651,7 +732,11 @@ class ItemsAdapter( val resolution = getConflictResolution(conflictResolutions, newPath) val doesPathExist = activity.getDoesFilePathExist(newPath) if (doesPathExist && resolution == CONFLICT_OVERWRITE) { - val fileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), entry.isDirectory) + val fileDirItem = FileDirItem( + path = newPath, + name = newPath.getFilenameFromPath(), + isDirectory = entry.isDirectory + ) if (activity.getIsPathDirectory(path)) { activity.deleteFolderBg(fileDirItem, false) { if (it) { @@ -684,10 +769,15 @@ class ItemsAdapter( } } - private fun extractEntry(newPath: String, entry: LocalFileHeader, zipInputStream: ZipInputStream) { + private fun extractEntry( + newPath: String, + entry: LocalFileHeader, + zipInputStream: ZipInputStream + ) { if (entry.isDirectory) { if (!activity.createDirectorySync(newPath) && !activity.getDoesFilePathExist(newPath)) { - val error = String.format(activity.getString(R.string.could_not_create_file), newPath) + val error = + String.format(activity.getString(R.string.could_not_create_file), newPath) activity.showErrorToast(error) } } else { @@ -699,7 +789,10 @@ class ItemsAdapter( } } - private fun getConflictResolution(conflictResolutions: LinkedHashMap, path: String): Int { + private fun getConflictResolution( + conflictResolutions: LinkedHashMap, + path: String + ): Int { return if (conflictResolutions.size == 1 && conflictResolutions.containsKey("")) { conflictResolutions[""]!! } else if (conflictResolutions.containsKey(path)) { @@ -710,15 +803,21 @@ class ItemsAdapter( } @SuppressLint("NewApi") - private fun compressPaths(sourcePaths: List, targetPath: String, password: String? = null): Boolean { + private fun compressPaths( + sourcePaths: List, + targetPath: String, + password: String? = null + ): Boolean { val queue = LinkedList() val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false - val zout = password?.let { ZipOutputStream(fos, password.toCharArray()) } ?: ZipOutputStream(fos) + val zout = + password?.let { ZipOutputStream(fos, password.toCharArray()) } ?: ZipOutputStream(fos) var res: Closeable = fos - fun zipEntry(name: String) = ZipParameters().also { + fun zipEntry(name: String, lastModified: Long) = ZipParameters().also { it.fileNameInZip = name + it.lastModifiedFileTime = lastModified if (password != null) { it.isEncryptFiles = true it.encryptionMethod = EncryptionMethod.AES @@ -734,9 +833,11 @@ class ItemsAdapter( queue.push(mainFilePath) if (activity.getIsPathDirectory(mainFilePath)) { name = "${mainFilePath.getFilenameFromPath()}/" + val dirModified = File(mainFilePath).lastModified() zout.putNextEntry( ZipParameters().also { it.fileNameInZip = name + it.lastModifiedFileTime = dirModified } ) } @@ -751,9 +852,9 @@ class ItemsAdapter( if (activity.getIsPathDirectory(file.path)) { queue.push(file.path) name = "${name.trimEnd('/')}/" - zout.putNextEntry(zipEntry(name)) + zout.putNextEntry(zipEntry(name, file.modified)) } else { - zout.putNextEntry(zipEntry(name)) + zout.putNextEntry(zipEntry(name, file.modified)) activity.getFileInputStreamSync(file.path)!!.copyTo(zout) zout.closeEntry() } @@ -766,9 +867,9 @@ class ItemsAdapter( if (activity.getIsPathDirectory(file.absolutePath)) { queue.push(file.absolutePath) name = "${name.trimEnd('/')}/" - zout.putNextEntry(zipEntry(name)) + zout.putNextEntry(zipEntry(name, file.lastModified())) } else { - zout.putNextEntry(zipEntry(name)) + zout.putNextEntry(zipEntry(name, file.lastModified())) activity.getFileInputStreamSync(file.path)!!.copyTo(zout) zout.closeEntry() } @@ -776,8 +877,14 @@ class ItemsAdapter( } } else { - name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base) - zout.putNextEntry(zipEntry(name)) + name = + if (base == currentPath) { + currentPath.getFilenameFromPath() + } else { + mainFilePath.relativizeWith(base) + } + val fileModified = File(mainFilePath).lastModified() + zout.putNextEntry(zipEntry(name, fileModified)) activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout) zout.closeEntry() } @@ -851,7 +958,11 @@ class ItemsAdapter( private fun getFirstSelectedItemPath() = getSelectedFileDirItems().first().path - private fun getSelectedFileDirItems() = listItems.filter { selectedKeys.contains(it.path.hashCode()) } as ArrayList + private fun getSelectedFileDirItems(): ArrayList { + return listItems.filter { + selectedKeys.contains(it.path.hashCode()) + } as ArrayList + } fun updateItems(newItems: ArrayList, highlightText: String = "") { if (newItems.hashCode() != currentItemsHash) { @@ -897,7 +1008,8 @@ class ItemsAdapter( override fun onViewRecycled(holder: ViewHolder) { super.onViewRecycled(holder) if (!activity.isDestroyed && !activity.isFinishing) { - val icon = Binding.getByItemViewType(holder.itemViewType, isListViewType).bind(holder.itemView).itemIcon + val icon = Binding.getByItemViewType(holder.itemViewType, isListViewType) + .bind(holder.itemView).itemIcon if (icon != null) { Glide.with(activity).clear(icon) } @@ -909,16 +1021,35 @@ class ItemsAdapter( binding.apply { if (listItem.isSectionTitle) { itemIcon?.setImageDrawable(folderDrawable) - itemSection?.text = if (textToHighlight.isEmpty()) listItem.mName else listItem.mName.highlightTextPart(textToHighlight, properPrimaryColor) + itemSection?.text = + if (textToHighlight.isEmpty()) { + listItem.mName + } else { + listItem.mName.highlightTextPart( + textToHighlight = textToHighlight, + color = properPrimaryColor + ) + } itemSection?.setTextColor(textColor) itemSection?.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize) } else if (!listItem.isGridTypeDivider) { root.setupViewBackground(activity) itemFrame.isSelected = isSelected val fileName = listItem.name - itemName?.text = if (textToHighlight.isEmpty()) fileName else fileName.highlightTextPart(textToHighlight, properPrimaryColor) + itemName?.text = + if (textToHighlight.isEmpty()) { + fileName + } else { + fileName.highlightTextPart( + textToHighlight = textToHighlight, + color = properPrimaryColor + ) + } itemName?.setTextColor(textColor) - itemName?.setTextSize(TypedValue.COMPLEX_UNIT_PX, if (isListViewType) fontSize else smallerFontSize) + itemName?.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + if (isListViewType) fontSize else smallerFontSize + ) itemDetails?.setTextColor(textColor) itemDetails?.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize) @@ -947,7 +1078,10 @@ class ItemsAdapter( itemDate?.beVisible() itemDate?.text = listItem.modified.formatDate(activity, dateFormat, timeFormat) - val drawable = fileDrawables.getOrElse(fileName.substringAfterLast(".").lowercase(Locale.getDefault()), { fileDrawable }) + val drawable = fileDrawables.getOrElse( + key = fileName.substringAfterLast(".").lowercase(Locale.getDefault()), + defaultValue = { fileDrawable } + ) val options = RequestOptions() .signature(listItem.getKey()) .diskCacheStrategy(DiskCacheStrategy.RESOURCE) @@ -972,12 +1106,16 @@ class ItemsAdapter( return activity.resources.getQuantityString(R.plurals.items, children, children) } - private fun getOTGPublicPath(itemToLoad: String) = - "${baseConfig.OTGTreeUri}/document/${baseConfig.OTGPartition}%3A${itemToLoad.substring(baseConfig.OTGPath.length).replace("/", "%2F")}" + private fun getOTGPublicPath(itemToLoad: String): String { + return "${baseConfig.OTGTreeUri}/document/${baseConfig.OTGPartition}%3A${ + itemToLoad.substring(baseConfig.OTGPath.length).replace("/", "%2F") + }" + } private fun getImagePathToLoad(path: String): Any { var itemToLoad = if (path.endsWith(".apk", true)) { - val packageInfo = activity.packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES) + val packageInfo = + activity.packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES) if (packageInfo != null) { val appInfo = packageInfo.applicationInfo appInfo.sourceDir = path @@ -1000,13 +1138,16 @@ class ItemsAdapter( } fun initDrawables() { - folderDrawable = resources.getColoredDrawableWithColor(R.drawable.ic_folder_vector, properPrimaryColor) + folderDrawable = + resources.getColoredDrawableWithColor(R.drawable.ic_folder_vector, properPrimaryColor) folderDrawable.alpha = 180 fileDrawable = resources.getDrawable(R.drawable.ic_file_generic) fileDrawables = getFilePlaceholderDrawables(activity) } - override fun onChange(position: Int) = listItems.getOrNull(position)?.getBubbleText(activity, dateFormat, timeFormat) ?: "" + override fun onChange(position: Int): String { + return listItems.getOrNull(position)?.getBubbleText(activity, dateFormat, timeFormat) ?: "" + } private sealed interface Binding { companion object { @@ -1027,12 +1168,23 @@ class ItemsAdapter( } } - fun inflate(layoutInflater: LayoutInflater, viewGroup: ViewGroup, attachToRoot: Boolean): ItemViewBinding + fun inflate( + layoutInflater: LayoutInflater, + viewGroup: ViewGroup, + attachToRoot: Boolean + ): ItemViewBinding + fun bind(view: View): ItemViewBinding data object ItemSection : Binding { - override fun inflate(layoutInflater: LayoutInflater, viewGroup: ViewGroup, attachToRoot: Boolean): ItemViewBinding { - return ItemSectionBindingAdapter(ItemSectionBinding.inflate(layoutInflater, viewGroup, attachToRoot)) + override fun inflate( + layoutInflater: LayoutInflater, + viewGroup: ViewGroup, + attachToRoot: Boolean + ): ItemViewBinding { + return ItemSectionBindingAdapter( + ItemSectionBinding.inflate(layoutInflater, viewGroup, attachToRoot) + ) } override fun bind(view: View): ItemViewBinding { @@ -1041,8 +1193,14 @@ class ItemsAdapter( } data object ItemEmpty : Binding { - override fun inflate(layoutInflater: LayoutInflater, viewGroup: ViewGroup, attachToRoot: Boolean): ItemViewBinding { - return ItemEmptyBindingAdapter(ItemEmptyBinding.inflate(layoutInflater, viewGroup, attachToRoot)) + override fun inflate( + layoutInflater: LayoutInflater, + viewGroup: ViewGroup, + attachToRoot: Boolean + ): ItemViewBinding { + return ItemEmptyBindingAdapter( + ItemEmptyBinding.inflate(layoutInflater, viewGroup, attachToRoot) + ) } override fun bind(view: View): ItemViewBinding { @@ -1051,8 +1209,14 @@ class ItemsAdapter( } data object ItemFileDirList : Binding { - override fun inflate(layoutInflater: LayoutInflater, viewGroup: ViewGroup, attachToRoot: Boolean): ItemViewBinding { - return ItemFileDirListBindingAdapter(ItemFileDirListBinding.inflate(layoutInflater, viewGroup, attachToRoot)) + override fun inflate( + layoutInflater: LayoutInflater, + viewGroup: ViewGroup, + attachToRoot: Boolean + ): ItemViewBinding { + return ItemFileDirListBindingAdapter( + ItemFileDirListBinding.inflate(layoutInflater, viewGroup, attachToRoot) + ) } override fun bind(view: View): ItemViewBinding { @@ -1061,8 +1225,14 @@ class ItemsAdapter( } data object ItemDirGrid : Binding { - override fun inflate(layoutInflater: LayoutInflater, viewGroup: ViewGroup, attachToRoot: Boolean): ItemViewBinding { - return ItemDirGridBindingAdapter(ItemDirGridBinding.inflate(layoutInflater, viewGroup, attachToRoot)) + override fun inflate( + layoutInflater: LayoutInflater, + viewGroup: ViewGroup, + attachToRoot: Boolean + ): ItemViewBinding { + return ItemDirGridBindingAdapter( + ItemDirGridBinding.inflate(layoutInflater, viewGroup, attachToRoot) + ) } override fun bind(view: View): ItemViewBinding { @@ -1071,8 +1241,14 @@ class ItemsAdapter( } data object ItemFileGrid : Binding { - override fun inflate(layoutInflater: LayoutInflater, viewGroup: ViewGroup, attachToRoot: Boolean): ItemViewBinding { - return ItemFileGridBindingAdapter(ItemFileGridBinding.inflate(layoutInflater, viewGroup, attachToRoot)) + override fun inflate( + layoutInflater: LayoutInflater, + viewGroup: ViewGroup, + attachToRoot: Boolean + ): ItemViewBinding { + return ItemFileGridBindingAdapter( + ItemFileGridBinding.inflate(layoutInflater, viewGroup, attachToRoot) + ) } override fun bind(view: View): ItemViewBinding { @@ -1114,7 +1290,9 @@ class ItemsAdapter( override fun getRoot(): View = binding.root } - private class ItemFileDirListBindingAdapter(val binding: ItemFileDirListBinding) : ItemViewBinding { + private class ItemFileDirListBindingAdapter( + val binding: ItemFileDirListBinding + ) : ItemViewBinding { override val itemFrame: FrameLayout = binding.itemFrame override val itemName: TextView = binding.itemName override val itemIcon: ImageView = binding.itemIcon