diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3fd92bd..1a6d1a5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -128,6 +128,16 @@
android:foregroundServiceType="dataSync"
android:exported="false" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/wirelessalien/zipxtract/constant/BroadcastConstants.kt b/app/src/main/java/com/wirelessalien/zipxtract/constant/BroadcastConstants.kt
index bd1741f..17db20e 100644
--- a/app/src/main/java/com/wirelessalien/zipxtract/constant/BroadcastConstants.kt
+++ b/app/src/main/java/com/wirelessalien/zipxtract/constant/BroadcastConstants.kt
@@ -25,6 +25,8 @@ object BroadcastConstants {
const val ACTION_EXTRACTION_ERROR = "ACTION_EXTRACTION_ERROR"
const val ARCHIVE_NOTIFICATION_CHANNEL_ID = "archive_notification_channel"
const val EXTRACTION_NOTIFICATION_CHANNEL_ID = "extraction_notification_channel"
+ const val DELETE_NOTIFICATION_CHANNEL_ID = "delete_notification_channel"
+ const val COPY_MOVE_NOTIFICATION_CHANNEL_ID = "copy_move_notification_channel"
const val ACTION_EXTRACTION_PROGRESS = "ACTION_EXTRACTION_PROGRESS"
const val ACTION_ARCHIVE_PROGRESS = "ACTION_ARCHIVE_PROGRESS"
const val EXTRA_PROGRESS = "progress"
diff --git a/app/src/main/java/com/wirelessalien/zipxtract/fragment/ArchiveFragment.kt b/app/src/main/java/com/wirelessalien/zipxtract/fragment/ArchiveFragment.kt
index f9a1e23..a6d9460 100644
--- a/app/src/main/java/com/wirelessalien/zipxtract/fragment/ArchiveFragment.kt
+++ b/app/src/main/java/com/wirelessalien/zipxtract/fragment/ArchiveFragment.kt
@@ -63,6 +63,7 @@ import com.wirelessalien.zipxtract.activity.SettingsActivity
import com.wirelessalien.zipxtract.adapter.FileAdapter
import com.wirelessalien.zipxtract.constant.BroadcastConstants
import com.wirelessalien.zipxtract.databinding.FragmentArchiveBinding
+import com.wirelessalien.zipxtract.service.DeleteFilesService
import com.wirelessalien.zipxtract.service.ExtractArchiveService
import com.wirelessalien.zipxtract.service.ExtractCsArchiveService
import com.wirelessalien.zipxtract.service.ExtractMultipart7zService
@@ -450,12 +451,11 @@ class ArchiveFragment : Fragment(), FileAdapter.OnItemClickListener {
.setTitle(getString(R.string.confirm_delete))
.setMessage(getString(R.string.confirm_delete_message))
.setPositiveButton(getString(R.string.delete)) { _, _ ->
- if (file.delete()) {
- Toast.makeText(requireContext(), getString(R.string.file_deleted), Toast.LENGTH_SHORT).show()
- updateAdapterWithFullList()
- } else {
- Toast.makeText(requireContext(), getString(R.string.general_error_msg), Toast.LENGTH_SHORT).show()
+ val filesToDelete = arrayListOf(file.absolutePath)
+ val intent = Intent(requireContext(), DeleteFilesService::class.java).apply {
+ putStringArrayListExtra(DeleteFilesService.EXTRA_FILES_TO_DELETE, filesToDelete)
}
+ ContextCompat.startForegroundService(requireContext(), intent)
bottomSheetDialog.dismiss()
}
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
diff --git a/app/src/main/java/com/wirelessalien/zipxtract/fragment/MainFragment.kt b/app/src/main/java/com/wirelessalien/zipxtract/fragment/MainFragment.kt
index 86c2451..9038491 100644
--- a/app/src/main/java/com/wirelessalien/zipxtract/fragment/MainFragment.kt
+++ b/app/src/main/java/com/wirelessalien/zipxtract/fragment/MainFragment.kt
@@ -93,6 +93,8 @@ import com.wirelessalien.zipxtract.service.ArchiveSplitZipService
import com.wirelessalien.zipxtract.service.ArchiveTarService
import com.wirelessalien.zipxtract.service.ArchiveZipService
import com.wirelessalien.zipxtract.service.CompressCsArchiveService
+import com.wirelessalien.zipxtract.service.CopyMoveService
+import com.wirelessalien.zipxtract.service.DeleteFilesService
import com.wirelessalien.zipxtract.service.ExtractArchiveService
import com.wirelessalien.zipxtract.service.ExtractCsArchiveService
import com.wirelessalien.zipxtract.service.ExtractMultipart7zService
@@ -730,36 +732,17 @@ class MainFragment : Fragment(), FileAdapter.OnItemClickListener, FileAdapter.On
private fun pasteFiles() {
val destinationPath = currentPath ?: return
- CoroutineScope(Dispatchers.IO).launch {
- for (file in fileOperationViewModel.filesToCopyMove) {
- if (file.exists()) {
- val destinationFile = File(destinationPath, file.name)
- if (fileOperationViewModel.isCopyAction) {
- file.copyRecursively(destinationFile, overwrite = true)
- } else {
- file.moveTo(destinationFile, overwrite = true)
- }
- } else {
- withContext(Dispatchers.Main) {
- Toast.makeText(requireContext(),
- getString(R.string.the_file_doesn_t_exist, file.name), Toast.LENGTH_SHORT).show()
- }
- }
- }
- withContext(Dispatchers.Main) {
- fileOperationViewModel.filesToCopyMove = emptyList()
- binding.pasteFab.visibility = View.GONE
- updateAdapterWithFullList()
- }
- }
- }
+ val filesToCopyMove = fileOperationViewModel.filesToCopyMove.map { it.absolutePath }
+ val isCopyAction = fileOperationViewModel.isCopyAction
- private fun File.moveTo(destination: File, overwrite: Boolean = false) {
- if (overwrite && destination.exists()) {
- destination.deleteRecursively()
+ val intent = Intent(requireContext(), CopyMoveService::class.java).apply {
+ putStringArrayListExtra(CopyMoveService.EXTRA_FILES_TO_COPY_MOVE, ArrayList(filesToCopyMove))
+ putExtra(CopyMoveService.EXTRA_DESTINATION_PATH, destinationPath)
+ putExtra(CopyMoveService.EXTRA_IS_COPY_ACTION, isCopyAction)
}
- this.copyRecursively(destination, overwrite)
- this.deleteRecursively()
+ ContextCompat.startForegroundService(requireContext(), intent)
+ fileOperationViewModel.filesToCopyMove = emptyList()
+ binding.pasteFab.visibility = View.GONE
}
private fun deleteSelectedFiles() {
@@ -767,15 +750,12 @@ class MainFragment : Fragment(), FileAdapter.OnItemClickListener, FileAdapter.On
.setTitle(getString(R.string.confirm_delete))
.setMessage(getString(R.string.confirm_delete_message))
.setPositiveButton(getString(R.string.delete)) { _, _ ->
- CoroutineScope(Dispatchers.IO).launch {
- for (file in selectedFiles) {
- file.deleteRecursively()
- }
- withContext(Dispatchers.Main) {
- unselectAllFiles()
- updateAdapterWithFullList()
- }
+ val filesToDelete = selectedFiles.map { it.absolutePath }
+ val intent = Intent(requireContext(), DeleteFilesService::class.java).apply {
+ putStringArrayListExtra(DeleteFilesService.EXTRA_FILES_TO_DELETE, ArrayList(filesToDelete))
}
+ ContextCompat.startForegroundService(requireContext(), intent)
+ unselectAllFiles()
}
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.dismiss()
@@ -1018,12 +998,11 @@ class MainFragment : Fragment(), FileAdapter.OnItemClickListener, FileAdapter.On
.setTitle(getString(R.string.confirm_delete))
.setMessage(getString(R.string.confirm_delete_message))
.setPositiveButton(getString(R.string.delete)) { _, _ ->
- if (file.delete()) {
- Toast.makeText(requireContext(), getString(R.string.file_deleted), Toast.LENGTH_SHORT).show()
- updateAdapterWithFullList()
- } else {
- Toast.makeText(requireContext(), getString(R.string.general_error_msg), Toast.LENGTH_SHORT).show()
+ val filesToDelete = arrayListOf(file.absolutePath)
+ val intent = Intent(requireContext(), DeleteFilesService::class.java).apply {
+ putStringArrayListExtra(DeleteFilesService.EXTRA_FILES_TO_DELETE, filesToDelete)
}
+ ContextCompat.startForegroundService(requireContext(), intent)
bottomSheetDialog.dismiss()
}
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
diff --git a/app/src/main/java/com/wirelessalien/zipxtract/service/CopyMoveService.kt b/app/src/main/java/com/wirelessalien/zipxtract/service/CopyMoveService.kt
new file mode 100644
index 0000000..2e3792e
--- /dev/null
+++ b/app/src/main/java/com/wirelessalien/zipxtract/service/CopyMoveService.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 WirelessAlien
+ *
+ * This program 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
+ * 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.wirelessalien.zipxtract.service
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+import com.wirelessalien.zipxtract.R
+import com.wirelessalien.zipxtract.constant.BroadcastConstants.COPY_MOVE_NOTIFICATION_CHANNEL_ID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.File
+
+class CopyMoveService : Service() {
+
+ companion object {
+ const val EXTRA_FILES_TO_COPY_MOVE = "extra_files_to_copy_move"
+ const val EXTRA_DESTINATION_PATH = "extra_destination_path"
+ const val EXTRA_IS_COPY_ACTION = "extra_is_copy_action"
+ const val NOTIFICATION_ID = 2
+ }
+
+ override fun onBind(intent: Intent?): IBinder? = null
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ val filesToCopyMove = intent?.getStringArrayListExtra(EXTRA_FILES_TO_COPY_MOVE)?.map { File(it) } ?: return START_NOT_STICKY
+ val destinationPath = intent.getStringExtra(EXTRA_DESTINATION_PATH) ?: return START_NOT_STICKY
+ val isCopyAction = intent.getBooleanExtra(EXTRA_IS_COPY_ACTION, true)
+
+ createNotificationChannel()
+ startForeground(NOTIFICATION_ID, createNotification(0, filesToCopyMove.size))
+
+ CoroutineScope(Dispatchers.IO).launch {
+ copyMoveFiles(filesToCopyMove, destinationPath, isCopyAction)
+ }
+
+ return START_STICKY
+ }
+
+ private fun copyMoveFiles(files: List, destinationPath: String, isCopyAction: Boolean) {
+ var processedFilesCount = 0
+ for (file in files) {
+ val destinationFile = File(destinationPath, file.name)
+ if (isCopyAction) {
+ file.copyRecursively(destinationFile, overwrite = true)
+ } else {
+ file.moveTo(destinationFile, overwrite = true)
+ }
+ processedFilesCount++
+ updateNotification(processedFilesCount, files.size)
+ }
+ stopForegroundService()
+ stopSelf()
+ }
+
+ private fun File.moveTo(destination: File, overwrite: Boolean = false) {
+ if (overwrite && destination.exists()) {
+ destination.deleteRecursively()
+ }
+ this.copyRecursively(destination, overwrite)
+ this.deleteRecursively()
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ COPY_MOVE_NOTIFICATION_CHANNEL_ID,
+ getString(R.string.copy_move_files_notification_name),
+ NotificationManager.IMPORTANCE_LOW
+ )
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
+ private fun createNotification(progress: Int, total: Int): Notification {
+ return NotificationCompat.Builder(this, COPY_MOVE_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(getString(R.string.copying_moving_files))
+ .setContentText(getString(R.string.copying_moving_files_progress, progress, total))
+ .setSmallIcon(R.drawable.ic_notification_icon)
+ .setProgress(total, progress, false)
+ .setOngoing(true)
+ .build()
+ }
+
+ private fun updateNotification(progress: Int, total: Int) {
+ val notification = createNotification(progress, total)
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager.notify(NOTIFICATION_ID, notification)
+ }
+
+ private fun stopForegroundService() {
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager.cancel(Archive7zService.NOTIFICATION_ID)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/wirelessalien/zipxtract/service/DeleteFilesService.kt b/app/src/main/java/com/wirelessalien/zipxtract/service/DeleteFilesService.kt
new file mode 100644
index 0000000..0ee923f
--- /dev/null
+++ b/app/src/main/java/com/wirelessalien/zipxtract/service/DeleteFilesService.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 WirelessAlien
+ *
+ * This program 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
+ * 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.wirelessalien.zipxtract.service
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+import com.wirelessalien.zipxtract.R
+import com.wirelessalien.zipxtract.constant.BroadcastConstants.DELETE_NOTIFICATION_CHANNEL_ID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.File
+
+class DeleteFilesService : Service() {
+
+ companion object {
+ const val EXTRA_FILES_TO_DELETE = "extra_files_to_delete"
+ const val NOTIFICATION_ID = 23
+ }
+
+ override fun onBind(intent: Intent?): IBinder? = null
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ val filesToDelete = intent?.getStringArrayListExtra(EXTRA_FILES_TO_DELETE)?.map { File(it) } ?: return START_NOT_STICKY
+
+ createNotificationChannel()
+ startForeground(NOTIFICATION_ID, createNotification(0, filesToDelete.size))
+
+ CoroutineScope(Dispatchers.IO).launch {
+ deleteFiles(filesToDelete)
+ }
+
+ return START_STICKY
+ }
+
+ private fun deleteFiles(files: List) {
+ val totalFilesCount = countTotalFiles(files)
+ var deletedFilesCount = 0
+
+ fun deleteFile(file: File) {
+ if (file.isDirectory) {
+ file.listFiles()?.forEach { deleteFile(it) }
+ }
+ file.deleteRecursively()
+ deletedFilesCount++
+ updateNotification(deletedFilesCount, totalFilesCount)
+ }
+
+ for (file in files) {
+ deleteFile(file)
+ }
+
+ stopForegroundService()
+ stopSelf()
+ }
+
+ private fun countTotalFiles(files: List): Int {
+ var count = 0
+ for (file in files) {
+ if (file.isDirectory) {
+ count += countTotalFiles(file.listFiles()?.toList() ?: emptyList())
+ } else {
+ count++
+ }
+ }
+ return count
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ DELETE_NOTIFICATION_CHANNEL_ID,
+ getString(R.string.delete_files_notification_name),
+ NotificationManager.IMPORTANCE_LOW
+ )
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
+ private fun createNotification(progress: Int, total: Int): Notification {
+ return NotificationCompat.Builder(this, DELETE_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(getString(R.string.delete_ongoing))
+ .setContentText(getString(R.string.deleting_files, progress, total))
+ .setSmallIcon(R.drawable.ic_notification_icon)
+ .setProgress(total, progress, false)
+ .setOngoing(true)
+ .build()
+ }
+
+ private fun updateNotification(progress: Int, total: Int) {
+ val notification = createNotification(progress, total)
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager.notify(NOTIFICATION_ID, notification)
+ }
+
+ private fun stopForegroundService() {
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager.cancel(Archive7zService.NOTIFICATION_ID)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6da6c83..1be06a6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -171,4 +171,10 @@
Copy to
Paste
The file doesn\'t exist: %1$s
+ Delete File
+ Deleting files: %1$d/%2$d
+ Deleting...
+ Copy/Move Files
+ Progress...
+ Progress %1$d/%2$d
\ No newline at end of file