✨
This commit is contained in:
@@ -6,13 +6,17 @@ import android.os.Bundle
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
|
import devs.org.calculator.utils.FileProcessCallback
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class AudioGalleryActivity : BaseGalleryActivity() {
|
class AudioGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||||
override val fileType = FileManager.FileType.AUDIO
|
override val fileType = FileManager.FileType.AUDIO
|
||||||
private lateinit var pickAudioLauncher: ActivityResultLauncher<Intent>
|
private lateinit var pickAudioLauncher: ActivityResultLauncher<Intent>
|
||||||
private var selectedUri: Uri? = null
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -20,32 +24,43 @@ class AudioGalleryActivity : BaseGalleryActivity() {
|
|||||||
|
|
||||||
pickAudioLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
pickAudioLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == RESULT_OK) {
|
if (result.resultCode == RESULT_OK) {
|
||||||
val uri = result.data?.data
|
val clipData = result.data?.clipData
|
||||||
if (uri != null) {
|
val uriList = mutableListOf<Uri>()
|
||||||
selectedUri = uri
|
|
||||||
try {
|
if (clipData != null) {
|
||||||
val file = fileManager.copyFileToHiddenDir(selectedUri!!, fileType)
|
for (i in 0 until clipData.itemCount) {
|
||||||
if (file != null && file.exists()) {
|
val uri = clipData.getItemAt(i).uri
|
||||||
Toast.makeText(this, "File hidden successfully", Toast.LENGTH_SHORT).show()
|
uriList.add(uri)
|
||||||
loadFiles()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, "Failed to hide file", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "No audio selected", Toast.LENGTH_SHORT).show()
|
result.data?.data?.let { uriList.add(it) } // Single file selected
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uriList.isNotEmpty()) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
FileManager(this@AudioGalleryActivity, this@AudioGalleryActivity).processMultipleFiles(uriList, fileType,this@AudioGalleryActivity )
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||||
|
Toast.makeText(this@AudioGalleryActivity, "${copiedFiles.size} Audios hidden successfully", Toast.LENGTH_SHORT).show()
|
||||||
|
loadFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFileProcessFailed() {
|
||||||
|
Toast.makeText(this@AudioGalleryActivity, "Failed to hide Audios", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupFabButton() {
|
private fun setupFabButton() {
|
||||||
binding.fabAdd.setOnClickListener {
|
binding.fabAdd.setOnClickListener {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
type = "audio/*"
|
type = "audio/*"
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||||
@@ -57,4 +72,6 @@ class AudioGalleryActivity : BaseGalleryActivity() {
|
|||||||
override fun openPreview() {
|
override fun openPreview() {
|
||||||
// Implement audio preview
|
// Implement audio preview
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,21 +22,33 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
|
|||||||
protected lateinit var binding: ActivityGalleryBinding
|
protected lateinit var binding: ActivityGalleryBinding
|
||||||
protected lateinit var fileManager: FileManager
|
protected lateinit var fileManager: FileManager
|
||||||
protected lateinit var adapter: FileAdapter
|
protected lateinit var adapter: FileAdapter
|
||||||
|
protected lateinit var files: List<File>
|
||||||
|
|
||||||
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||||
|
private val storagePermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
|
) { permissions ->
|
||||||
|
val granted = permissions.values.all { it }
|
||||||
|
if (granted || Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
|
||||||
|
loadFiles()
|
||||||
|
} else {
|
||||||
|
// Handle permission denial case
|
||||||
|
showPermissionDeniedDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract val fileType: FileManager.FileType
|
abstract val fileType: FileManager.FileType
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setupIntentSenderLauncher()
|
setupIntentSenderLauncher()
|
||||||
checkPermissions()
|
|
||||||
binding = ActivityGalleryBinding.inflate(layoutInflater)
|
binding = ActivityGalleryBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
fileManager = FileManager(this, this)
|
fileManager = FileManager(this, this)
|
||||||
|
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
loadFiles()
|
checkPermissionsAndLoadFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupIntentSenderLauncher() {
|
private fun setupIntentSenderLauncher() {
|
||||||
@@ -51,52 +63,55 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
|
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
|
||||||
adapter = FileAdapter(
|
adapter = FileAdapter(fileType, this, this)
|
||||||
fileType,
|
|
||||||
this, this
|
|
||||||
)
|
|
||||||
binding.recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun loadFiles() {
|
private fun checkPermissionsAndLoadFiles() {
|
||||||
val files = fileManager.getFilesInHiddenDir(fileType)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
if (!Environment.isExternalStorageManager()) {
|
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
.addCategory("android.intent.category.DEFAULT")
|
||||||
|
.setData(Uri.parse("package:${applicationContext.packageName}"))
|
||||||
|
startActivityForResult(intent, 2296)
|
||||||
|
} else {
|
||||||
|
loadFiles()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val permissions = arrayOf(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
if (permissions.any { checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED }) {
|
||||||
|
storagePermissionLauncher.launch(permissions)
|
||||||
|
} else {
|
||||||
|
loadFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun loadFiles() {
|
||||||
|
files = fileManager.getFilesInHiddenDir(fileType)
|
||||||
adapter.submitList(files)
|
adapter.submitList(files)
|
||||||
adapter.notifyDataSetChanged()
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
loadFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun openPreview()
|
abstract fun openPreview()
|
||||||
|
|
||||||
private fun checkPermissions() {
|
private fun showPermissionDeniedDialog() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
// Show a dialog or a message informing the user about the importance of permissions
|
||||||
if (!Environment.isExternalStorageManager()) {
|
|
||||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
|
||||||
intent.addCategory("android.intent.category.DEFAULT")
|
|
||||||
intent.data = Uri.parse("package:${applicationContext.packageName}")
|
|
||||||
startActivityForResult(intent, 2296)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
|
|
||||||
checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
requestPermissions(
|
|
||||||
arrayOf(
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
),
|
|
||||||
1001
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
if (requestCode == 2296) {
|
if (requestCode == 2296 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Environment.isExternalStorageManager()) {
|
||||||
if (Environment.isExternalStorageManager()) {
|
loadFiles()
|
||||||
// Permission granted
|
|
||||||
loadFiles()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,51 +6,69 @@ import android.os.Bundle
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import devs.org.calculator.activities.AudioGalleryActivity
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
|
import devs.org.calculator.utils.FileProcessCallback
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class DocumentsActivity : BaseGalleryActivity() {
|
class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||||
override val fileType = FileManager.FileType.DOCUMENT
|
override val fileType = FileManager.FileType.DOCUMENT
|
||||||
private lateinit var pickDocumentLauncher: ActivityResultLauncher<Intent>
|
private lateinit var pickLauncher: ActivityResultLauncher<Intent>
|
||||||
private var selectedUri: Uri? = null
|
private var selectedUri: Uri? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setupFabButton()
|
setupFabButton()
|
||||||
|
|
||||||
pickDocumentLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
pickLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == RESULT_OK) {
|
if (result.resultCode == RESULT_OK) {
|
||||||
val uri = result.data?.data
|
val clipData = result.data?.clipData
|
||||||
if (uri != null) {
|
val uriList = mutableListOf<Uri>()
|
||||||
selectedUri = uri
|
|
||||||
try {
|
if (clipData != null) {
|
||||||
val file = fileManager.copyFileToHiddenDir(selectedUri!!, fileType)
|
for (i in 0 until clipData.itemCount) {
|
||||||
if (file != null && file.exists()) {
|
val uri = clipData.getItemAt(i).uri
|
||||||
Toast.makeText(this, "File hidden successfully", Toast.LENGTH_SHORT).show()
|
uriList.add(uri)
|
||||||
loadFiles()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, "Failed to hide file", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "No document selected", Toast.LENGTH_SHORT).show()
|
result.data?.data?.let { uriList.add(it) } // Single file selected
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uriList.isNotEmpty()) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
FileManager(this@DocumentsActivity, this@DocumentsActivity).processMultipleFiles(uriList, fileType,this@DocumentsActivity )
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||||
|
Toast.makeText(this@DocumentsActivity, "${copiedFiles.size} Documents hidden successfully", Toast.LENGTH_SHORT).show()
|
||||||
|
loadFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFileProcessFailed() {
|
||||||
|
Toast.makeText(this@DocumentsActivity, "Failed to hide Documents", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupFabButton() {
|
private fun setupFabButton() {
|
||||||
binding.fabAdd.setOnClickListener {
|
binding.fabAdd.setOnClickListener {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
type = "*/*"
|
type = "*/*"
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
pickDocumentLauncher.launch(intent)
|
pickLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,33 @@
|
|||||||
package devs.org.calculator.activities
|
package devs.org.calculator.activities
|
||||||
|
|
||||||
import android.app.RecoverableSecurityException
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.IntentSenderRequest
|
import androidx.activity.result.IntentSenderRequest
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import android.Manifest
|
||||||
|
import devs.org.calculator.activities.AudioGalleryActivity
|
||||||
|
import devs.org.calculator.utils.FileProcessCallback
|
||||||
|
|
||||||
class ImageGalleryActivity : BaseGalleryActivity() {
|
class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||||
override val fileType = FileManager.FileType.IMAGE
|
override val fileType = FileManager.FileType.IMAGE
|
||||||
|
private val STORAGE_PERMISSION_CODE = 100
|
||||||
|
|
||||||
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||||
private var selectedImageUri: Uri? = null
|
|
||||||
private lateinit var pickImageLauncher: ActivityResultLauncher<Intent>
|
private lateinit var pickImageLauncher: ActivityResultLauncher<Intent>
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -32,36 +37,48 @@ class ImageGalleryActivity : BaseGalleryActivity() {
|
|||||||
|
|
||||||
intentSenderLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){
|
intentSenderLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){
|
||||||
if (it.resultCode == RESULT_OK){
|
if (it.resultCode == RESULT_OK){
|
||||||
Toast.makeText(this, "Photo Deleted Successfully", Toast.LENGTH_SHORT).show()
|
// Toast.makeText(this, "Photo Deleted Successfully", Toast.LENGTH_SHORT).show()
|
||||||
}else{
|
}else{
|
||||||
Toast.makeText(this, "Failed to delete photo", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Failed to hide/unhide photo", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == RESULT_OK) {
|
if (result.resultCode == RESULT_OK) {
|
||||||
val uri = result.data?.data
|
val clipData = result.data?.clipData
|
||||||
if (uri != null) {
|
val uriList = mutableListOf<Uri>()
|
||||||
selectedImageUri = uri
|
|
||||||
try {
|
if (clipData != null) {
|
||||||
val file = fileManager.copyFileToHiddenDir(selectedImageUri!!, fileType)
|
for (i in 0 until clipData.itemCount) {
|
||||||
if (file != null && file.exists()) {
|
val uri = clipData.getItemAt(i).uri
|
||||||
Toast.makeText(this, "File hidden successfully", Toast.LENGTH_SHORT).show()
|
uriList.add(uri)
|
||||||
loadFiles()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, "Failed to hide file", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "No image selected", Toast.LENGTH_SHORT).show()
|
result.data?.data?.let { uriList.add(it) } // Single file selected
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uriList.isNotEmpty()) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
FileManager(this@ImageGalleryActivity, this@ImageGalleryActivity)
|
||||||
|
.processMultipleFiles(uriList, fileType,this@ImageGalleryActivity )
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
askPermissiom()
|
askPermissiom()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||||
|
Toast.makeText(this@ImageGalleryActivity, "${copiedFiles.size} Images hidden successfully", Toast.LENGTH_SHORT).show()
|
||||||
|
loadFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFileProcessFailed() {
|
||||||
|
Toast.makeText(this@ImageGalleryActivity, "Failed to hide images", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupIntentSenderLauncher() {
|
private fun setupIntentSenderLauncher() {
|
||||||
intentSenderLauncher = registerForActivityResult(
|
intentSenderLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.StartIntentSenderForResult()
|
ActivityResultContracts.StartIntentSenderForResult()
|
||||||
@@ -80,7 +97,45 @@ class ImageGalleryActivity : BaseGalleryActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Toast.makeText(this, "Android Version Lower", Toast.LENGTH_SHORT).show()
|
checkAndRequestStoragePermission()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAndRequestStoragePermission() {
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED ||
|
||||||
|
ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
this,
|
||||||
|
arrayOf(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
),
|
||||||
|
STORAGE_PERMISSION_CODE
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
//storage permission granted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == STORAGE_PERMISSION_CODE) {
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Toast.makeText(this, "Storage permissions granted", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Storage permissions denied", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +144,7 @@ class ImageGalleryActivity : BaseGalleryActivity() {
|
|||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
type = "image/*"
|
type = "image/*"
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
binding.btnDot.setOnClickListener { addDecimal() }
|
binding.btnDot.setOnClickListener { addDecimal() }
|
||||||
binding.btnEquals.setOnClickListener { calculateResult() }
|
binding.btnEquals.setOnClickListener { calculateResult() }
|
||||||
binding.btnPercent.setOnClickListener { calculatePercentage() }
|
binding.btnPercent.setOnClickListener { calculatePercentage() }
|
||||||
|
binding.cut.setOnClickListener { cutNumbers() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchBaseDirectoryPicker() {
|
private fun launchBaseDirectoryPicker() {
|
||||||
@@ -162,11 +163,23 @@ class MainActivity : AppCompatActivity() {
|
|||||||
hasDecimal = currentExpression.contains(".")
|
hasDecimal = currentExpression.contains(".")
|
||||||
updateDisplay()
|
updateDisplay()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
binding.display.text = "Error"
|
binding.display.text = "Invalid Value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDisplay() {
|
private fun updateDisplay() {
|
||||||
binding.display.text = currentExpression
|
binding.display.text = currentExpression
|
||||||
}
|
}
|
||||||
|
private fun cutNumbers() {
|
||||||
|
if (currentExpression.isNotEmpty()){
|
||||||
|
if (currentExpression.length == 1){
|
||||||
|
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
|
||||||
|
currentExpression = "0"
|
||||||
|
}else currentExpression = currentExpression.substring(0, currentExpression.length - 1)
|
||||||
|
}else currentExpression = "0"
|
||||||
|
updateDisplay()
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package devs.org.calculator.activities
|
package devs.org.calculator.activities
|
||||||
|
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import devs.org.calculator.adapters.ImagePreviewAdapter
|
import devs.org.calculator.adapters.ImagePreviewAdapter
|
||||||
import devs.org.calculator.databinding.ActivityPreviewBinding
|
import devs.org.calculator.databinding.ActivityPreviewBinding
|
||||||
import devs.org.calculator.utils.DialogUtil
|
import devs.org.calculator.utils.DialogUtil
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class PreviewActivity : AppCompatActivity() {
|
class PreviewActivity : AppCompatActivity() {
|
||||||
@@ -29,70 +30,102 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
fileManager = FileManager(this, this)
|
fileManager = FileManager(this, this)
|
||||||
|
|
||||||
currentPosition = intent.getIntExtra("position", 0)
|
currentPosition = intent.getIntExtra("position", 0)
|
||||||
|
|
||||||
|
|
||||||
type = intent.getStringExtra("type").toString()
|
type = intent.getStringExtra("type").toString()
|
||||||
|
|
||||||
clickListeners()
|
setupFileType()
|
||||||
|
|
||||||
when(type){
|
|
||||||
"IMAGE" ->{
|
|
||||||
filetype = FileManager.FileType.IMAGE
|
|
||||||
binding.title.text = "Preview Images"
|
|
||||||
}
|
|
||||||
|
|
||||||
"VIDEO" ->{
|
|
||||||
filetype = FileManager.FileType.VIDEO
|
|
||||||
binding.title.text = "Preview Videos"
|
|
||||||
}
|
|
||||||
|
|
||||||
"AUDIO" ->{
|
|
||||||
filetype = FileManager.FileType.AUDIO
|
|
||||||
binding.title.text = "Preview Audios"
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
filetype = FileManager.FileType.DOCUMENT
|
|
||||||
binding.title.text = "Preview Docomnts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files = fileManager.getFilesInHiddenDir(filetype)
|
files = fileManager.getFilesInHiddenDir(filetype)
|
||||||
|
|
||||||
setupImagePreview()
|
setupImagePreview()
|
||||||
|
clickListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clickListeners() {
|
private fun setupFileType() {
|
||||||
binding.delete.setOnClickListener{
|
when (type) {
|
||||||
var fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem])
|
"IMAGE" -> {
|
||||||
if (fileUri != null) {
|
filetype = FileManager.FileType.IMAGE
|
||||||
DialogUtil(this, this).showMaterialDialog(
|
binding.title.text = "Preview Images"
|
||||||
"Delete File",
|
}
|
||||||
"Are you sure you want to delete this file ?",
|
"VIDEO" -> {
|
||||||
"Delete",
|
filetype = FileManager.FileType.VIDEO
|
||||||
"Cancel",
|
binding.title.text = "Preview Videos"
|
||||||
fileUri!!
|
}
|
||||||
)
|
"AUDIO" -> {
|
||||||
|
filetype = FileManager.FileType.AUDIO
|
||||||
|
binding.title.text = "Preview Audios"
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
filetype = FileManager.FileType.DOCUMENT
|
||||||
|
binding.title.text = "Preview Documents"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
binding.unHide.setOnClickListener{
|
|
||||||
DialogUtil(this, this).showMaterialDialog("Unhide File","Are you sure you want to unhide this file ?", "Unhide", "Cancel")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupImagePreview() {
|
private fun setupImagePreview() {
|
||||||
adapter = ImagePreviewAdapter(this, files,filetype)
|
adapter = ImagePreviewAdapter(this, filetype)
|
||||||
|
adapter.images = files // Set initial data
|
||||||
binding.viewPager.adapter = adapter
|
binding.viewPager.adapter = adapter
|
||||||
|
|
||||||
val fileUri = Uri.fromFile(files[currentPosition])
|
|
||||||
val filesName = FileManager.FileName(this).getFileNameFromUri(fileUri!!).toString()
|
|
||||||
binding.viewPager.setCurrentItem(currentPosition, false)
|
binding.viewPager.setCurrentItem(currentPosition, false)
|
||||||
|
|
||||||
|
val fileUri = Uri.fromFile(files[currentPosition])
|
||||||
|
val fileName = FileManager.FileName(this).getFileNameFromUri(fileUri).toString()
|
||||||
|
binding.title.text = fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clickListeners() {
|
||||||
|
binding.delete.setOnClickListener {
|
||||||
|
val fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem])
|
||||||
|
if (fileUri != null) {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle("Delete File")
|
||||||
|
.setMessage("Are you sure you want to Delete this file?")
|
||||||
|
.setPositiveButton("Delete") { dialog, _ ->
|
||||||
|
lifecycleScope.launch {
|
||||||
|
FileManager(this@PreviewActivity, this@PreviewActivity).deletePhotoFromExternalStorage(fileUri)
|
||||||
|
removeFileFromList(binding.viewPager.currentItem)
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.unHide.setOnClickListener {
|
||||||
|
val fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem])
|
||||||
|
if (fileUri != null) {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle("Unhide File")
|
||||||
|
.setMessage("Are you sure you want to Unhide this file?")
|
||||||
|
.setPositiveButton("Unhide") { dialog, _ ->
|
||||||
|
lifecycleScope.launch {
|
||||||
|
FileManager(this@PreviewActivity, this@PreviewActivity).copyFileToNormalDir(fileUri)
|
||||||
|
removeFileFromList(binding.viewPager.currentItem)
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeFileFromList(position: Int) {
|
||||||
|
val updatedFiles = files.toMutableList().apply { removeAt(position) }
|
||||||
|
files = updatedFiles
|
||||||
|
adapter.images = updatedFiles // Update adapter with the new list
|
||||||
|
|
||||||
|
// Update the ViewPager's position
|
||||||
|
if (!updatedFiles.isNotEmpty()) finish()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,12 +6,18 @@ import android.os.Bundle
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import devs.org.calculator.activities.ImageGalleryActivity
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
|
import devs.org.calculator.utils.FileProcessCallback
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class VideoGalleryActivity : BaseGalleryActivity() {
|
class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||||
override val fileType = FileManager.FileType.VIDEO
|
override val fileType = FileManager.FileType.VIDEO
|
||||||
private lateinit var pickVideoLauncher: ActivityResultLauncher<Intent>
|
private lateinit var pickLauncher: ActivityResultLauncher<Intent>
|
||||||
private var selectedUri: Uri? = null
|
private var selectedUri: Uri? = null
|
||||||
|
|
||||||
|
|
||||||
@@ -19,39 +25,52 @@ class VideoGalleryActivity : BaseGalleryActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setupFabButton()
|
setupFabButton()
|
||||||
|
|
||||||
pickVideoLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
pickLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
if (result.resultCode == RESULT_OK) {
|
if (result.resultCode == RESULT_OK) {
|
||||||
val uri = result.data?.data
|
val clipData = result.data?.clipData
|
||||||
if (uri != null) {
|
val uriList = mutableListOf<Uri>()
|
||||||
selectedUri = uri
|
|
||||||
try {
|
if (clipData != null) {
|
||||||
val file = fileManager.copyFileToHiddenDir(selectedUri!!, fileType)
|
for (i in 0 until clipData.itemCount) {
|
||||||
if (file != null && file.exists()) {
|
val uri = clipData.getItemAt(i).uri
|
||||||
Toast.makeText(this, "File hidden successfully", Toast.LENGTH_SHORT).show()
|
uriList.add(uri)
|
||||||
loadFiles()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, "Failed to hide file", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "No video selected", Toast.LENGTH_SHORT).show()
|
result.data?.data?.let { uriList.add(it) } // Single file selected
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uriList.isNotEmpty()) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
FileManager(this@VideoGalleryActivity, this@VideoGalleryActivity)
|
||||||
|
.processMultipleFiles(uriList, fileType,this@VideoGalleryActivity )
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||||
|
Toast.makeText(this@VideoGalleryActivity, "${copiedFiles.size} Videos hidden successfully", Toast.LENGTH_SHORT).show()
|
||||||
|
loadFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFileProcessFailed() {
|
||||||
|
Toast.makeText(this@VideoGalleryActivity, "Failed to hide videos", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupFabButton() {
|
private fun setupFabButton() {
|
||||||
binding.fabAdd.setOnClickListener {
|
binding.fabAdd.setOnClickListener {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
type = "video/*"
|
type = "video/*"
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
pickVideoLauncher.launch(intent)
|
pickLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
|
|||||||
|
|
||||||
MaterialAlertDialogBuilder(context)
|
MaterialAlertDialogBuilder(context)
|
||||||
.setTitle("Details")
|
.setTitle("Details")
|
||||||
.setMessage("File Name: $filesName\\n\\nYou can delete or unghide this file\", \"Delete")
|
.setMessage("File Name: $filesName\n\nYou can delete or Unhide this file.")
|
||||||
.setPositiveButton("Delete") { dialog, _ ->
|
.setPositiveButton("Delete") { dialog, _ ->
|
||||||
// Handle positive button click
|
// Handle positive button click
|
||||||
lifecycleOwner.lifecycleScope.launch{
|
lifecycleOwner.lifecycleScope.launch{
|
||||||
@@ -100,7 +100,11 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
|
|||||||
}
|
}
|
||||||
.setNegativeButton("Unhide") { dialog, _ ->
|
.setNegativeButton("Unhide") { dialog, _ ->
|
||||||
// Handle negative button click
|
// Handle negative button click
|
||||||
|
FileManager(context, context as LifecycleOwner).copyFileToNormalDir(fileUri)
|
||||||
|
val currentList = currentList.toMutableList()
|
||||||
|
currentList.remove(file)
|
||||||
|
submitList(currentList)
|
||||||
|
dialog.dismiss()
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -110,11 +114,6 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun reloadList(file: File){
|
|
||||||
val currentList = currentList.toMutableList()
|
|
||||||
currentList.remove(file)
|
|
||||||
submitList(currentList)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
|
||||||
|
|||||||
@@ -6,28 +6,35 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.MediaController
|
import android.widget.MediaController
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import devs.org.calculator.adapters.FileAdapter.FileDiffCallback
|
||||||
import devs.org.calculator.databinding.ViewpagerItemsBinding
|
import devs.org.calculator.databinding.ViewpagerItemsBinding
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class ImagePreviewAdapter(
|
class ImagePreviewAdapter(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val images: List<File>,
|
|
||||||
private var fileType: FileManager.FileType
|
private var fileType: FileManager.FileType
|
||||||
) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() {
|
) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() {
|
||||||
|
|
||||||
|
// Use AsyncListDiffer for managing the list
|
||||||
|
private val differ = AsyncListDiffer(this, FileDiffCallback())
|
||||||
|
|
||||||
|
// Expose data management through differ
|
||||||
|
var images: List<File>
|
||||||
|
get() = differ.currentList
|
||||||
|
set(value) = differ.submitList(value)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
|
||||||
val binding = ViewpagerItemsBinding.inflate(LayoutInflater.from(context), parent, false
|
val binding = ViewpagerItemsBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||||
)
|
|
||||||
return ImageViewHolder(binding)
|
return ImageViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
||||||
val imageUrl = images[position]
|
val imageUrl = images[position]
|
||||||
holder.bind(imageUrl)
|
holder.bind(imageUrl)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = images.size
|
override fun getItemCount(): Int = images.size
|
||||||
@@ -39,31 +46,27 @@ class ImagePreviewAdapter(
|
|||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
binding.videoView.visibility = View.VISIBLE
|
binding.videoView.visibility = View.VISIBLE
|
||||||
|
|
||||||
// Set up the VideoView with the current video file
|
|
||||||
val videoUri = Uri.fromFile(file)
|
val videoUri = Uri.fromFile(file)
|
||||||
binding.videoView.setVideoURI(videoUri)
|
binding.videoView.setVideoURI(videoUri)
|
||||||
binding.videoView.start()
|
binding.videoView.start()
|
||||||
|
|
||||||
// Create and attach MediaController
|
|
||||||
val mediaController = MediaController(context)
|
val mediaController = MediaController(context)
|
||||||
mediaController.setAnchorView(binding.videoView)
|
mediaController.setAnchorView(binding.videoView)
|
||||||
binding.videoView.setMediaController(mediaController)
|
binding.videoView.setMediaController(mediaController)
|
||||||
|
|
||||||
// Handle the "Next" button logic
|
|
||||||
mediaController.setPrevNextListeners(
|
mediaController.setPrevNextListeners(
|
||||||
{ // Next button clicked
|
{
|
||||||
val nextPosition = (adapterPosition + 1) % images.size // Loop to start if last
|
val nextPosition = (adapterPosition + 1) % images.size
|
||||||
playVideoAtPosition(nextPosition)
|
playVideoAtPosition(nextPosition)
|
||||||
},
|
},
|
||||||
{ // Previous button clicked
|
{
|
||||||
val prevPosition = if (adapterPosition - 1 < 0) images.size - 1 else adapterPosition - 1
|
val prevPosition = if (adapterPosition - 1 < 0) images.size - 1 else adapterPosition - 1
|
||||||
playVideoAtPosition(prevPosition)
|
playVideoAtPosition(prevPosition)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Play next video automatically when the current one finishes
|
|
||||||
binding.videoView.setOnCompletionListener {
|
binding.videoView.setOnCompletionListener {
|
||||||
val nextPosition = (adapterPosition + 1) % images.size // Loop to start if last
|
val nextPosition = (adapterPosition + 1) % images.size
|
||||||
playVideoAtPosition(nextPosition)
|
playVideoAtPosition(nextPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +95,5 @@ class ImagePreviewAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class DialogUtil(private val context: Context, private var lifecycleOwner: Lifec
|
|||||||
val deleted = documentFile.delete()
|
val deleted = documentFile.delete()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
|
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, "Failed to delete file", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Failed to delete file", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import android.provider.ContactsContract.Directory
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
@@ -15,6 +17,7 @@ import androidx.activity.result.IntentSenderRequest
|
|||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import devs.org.calculator.activities.VideoGalleryActivity
|
||||||
import devs.org.calculator.adapters.FileAdapter
|
import devs.org.calculator.adapters.FileAdapter
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -95,6 +98,46 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun copyFileToNormalDir(uri: Uri): File? {
|
||||||
|
return try {
|
||||||
|
val contentResolver = context.contentResolver
|
||||||
|
|
||||||
|
// Get the target directory
|
||||||
|
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
targetDir.mkdirs()
|
||||||
|
|
||||||
|
// Create target file
|
||||||
|
val mimeType = contentResolver.getType(uri)
|
||||||
|
val extension = MimeTypeMap.getSingleton()
|
||||||
|
.getExtensionFromMimeType(mimeType) ?: ""
|
||||||
|
val fileName = "${System.currentTimeMillis()}.${extension}"
|
||||||
|
val targetFile = File(targetDir, fileName)
|
||||||
|
|
||||||
|
// Copy file using DocumentFile
|
||||||
|
contentResolver.openInputStream(uri)?.use { input ->
|
||||||
|
targetFile.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify copy success
|
||||||
|
if (!targetFile.exists() || targetFile.length() == 0L) {
|
||||||
|
throw Exception("File copy failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media scan the new file to hide it
|
||||||
|
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
|
||||||
|
mediaScanIntent.data = Uri.fromFile(targetDir)
|
||||||
|
context.sendBroadcast(mediaScanIntent)
|
||||||
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
|
deletePhotoFromExternalStorage(uri)
|
||||||
|
}
|
||||||
|
targetFile
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -108,9 +151,9 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
val deleted = documentFile.delete()
|
val deleted = documentFile.delete()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
|
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, "Failed to delete file", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Failed to hide/unhide file", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@withContext
|
return@withContext
|
||||||
@@ -120,7 +163,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
try {
|
try {
|
||||||
context.contentResolver.delete(photoUri, null, null)
|
context.contentResolver.delete(photoUri, null, null)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
|
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
// Handle security exception for Android 10 and above
|
// Handle security exception for Android 10 and above
|
||||||
@@ -145,7 +188,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
"Error deleting file: ${e.message}",
|
"Error hiding/unhiding file: ${e.message}",
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
@@ -196,6 +239,31 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun processMultipleFiles(
|
||||||
|
uriList: List<Uri>,
|
||||||
|
fileType: FileType,
|
||||||
|
callback: FileProcessCallback
|
||||||
|
) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val copiedFiles = mutableListOf<File>()
|
||||||
|
for (uri in uriList) {
|
||||||
|
try {
|
||||||
|
val file = copyFileToHiddenDir(uri, fileType)
|
||||||
|
file?.let { copiedFiles.add(it) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (copiedFiles.isNotEmpty()) {
|
||||||
|
callback.onFilesProcessedSuccessfully(copiedFiles)
|
||||||
|
} else {
|
||||||
|
callback.onFileProcessFailed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum class FileType(val dirName: String) {
|
enum class FileType(val dirName: String) {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package devs.org.calculator.utils
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface FileProcessCallback {
|
||||||
|
fun onFilesProcessedSuccessfully(copiedFiles: List<File>)
|
||||||
|
fun onFileProcessFailed()
|
||||||
|
}
|
||||||
15
app/src/main/res/drawable/backspace.xml
Normal file
15
app/src/main/res/drawable/backspace.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M7.7,6.36L3.533,11.36C3.224,11.731 3.224,12.269 3.533,12.64L7.7,17.64C7.89,17.868 8.172,18 8.468,18H18C19.657,18 21,16.657 21,15V9C21,7.343 19.657,6 18,6H8.468C8.172,6 7.89,6.132 7.7,6.36Z"
|
||||||
|
android:strokeColor="@color/textColor" android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M15,10L13,12M13,12L11,14M13,12L11,10M13,12L15,14"
|
||||||
|
android:strokeColor="@color/textColor"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -47,18 +47,6 @@
|
|||||||
app:cornerRadius="15dp"
|
app:cornerRadius="15dp"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/btnParentheses"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_rowWeight="1"
|
|
||||||
android:layout_columnWeight="1"
|
|
||||||
android:textSize="30sp"
|
|
||||||
android:layout_margin="4dp"
|
|
||||||
android:text="( )"
|
|
||||||
app:cornerRadius="15dp"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnPercent"
|
android:id="@+id/btnPercent"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -81,8 +69,20 @@
|
|||||||
android:layout_margin="4dp"
|
android:layout_margin="4dp"
|
||||||
android:text="÷"
|
android:text="÷"
|
||||||
app:cornerRadius="15dp"
|
app:cornerRadius="15dp"
|
||||||
style="@style/Widget.MaterialComponents.Button"/>
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/cut"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_rowWeight="1"
|
||||||
|
android:layout_columnWeight="1"
|
||||||
|
android:textSize="30sp"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
app:icon="@drawable/backspace"
|
||||||
|
|
||||||
|
app:iconSize="30dp"
|
||||||
|
app:cornerRadius="15dp"/>
|
||||||
<!-- Row 2 -->
|
<!-- Row 2 -->
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn7"
|
android:id="@+id/btn7"
|
||||||
|
|||||||
20
app/src/main/res/values-night/colors.xml
Normal file
20
app/src/main/res/values-night/colors.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
|
||||||
|
<!-- Material You dynamic colors -->
|
||||||
|
<color name="primary">@android:color/system_accent1_600</color>
|
||||||
|
<color name="on_primary">@android:color/system_accent1_0</color>
|
||||||
|
<color name="primary_container">@android:color/system_accent1_100</color>
|
||||||
|
<color name="on_primary_container">@android:color/system_accent1_900</color>
|
||||||
|
|
||||||
|
<color name="secondary">@android:color/system_accent2_600</color>
|
||||||
|
<color name="on_secondary">@android:color/system_accent2_0</color>
|
||||||
|
<color name="secondary_container">@android:color/system_accent2_100</color>
|
||||||
|
<color name="on_secondary_container">@android:color/system_accent2_900</color>
|
||||||
|
|
||||||
|
<color name="surface">@android:color/system_neutral1_50</color>
|
||||||
|
<color name="on_surface">@android:color/system_neutral1_900</color>
|
||||||
|
<color name="textColor">#ffffff</color>
|
||||||
|
</resources>
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<style name="Base.Theme.Calculator" parent="Theme.Material3.DayNight.NoActionBar">
|
|
||||||
<!-- Primary brand color -->
|
|
||||||
<item name="colorPrimary">@android:color/system_accent1_600</item>
|
|
||||||
<item name="colorPrimaryVariant">@android:color/system_accent1_700</item>
|
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
|
||||||
<item name="fontFamily">@font/ubuntu_regular</item>
|
|
||||||
|
|
||||||
<!-- Secondary brand color -->
|
|
||||||
<item name="colorSecondary">@android:color/system_accent2_600</item>
|
|
||||||
<item name="colorSecondaryVariant">@android:color/system_accent2_700</item>
|
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
|
||||||
|
|
||||||
<!-- Status bar color -->
|
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
||||||
|
|
||||||
<!-- Enable window decor fitting -->
|
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
|
||||||
<item name="android:windowLightNavigationBar">true</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<style name="Base.Theme.Calculator" parent="Theme.Material3.DayNight.NoActionBar">
|
|
||||||
<!-- Primary brand color -->
|
|
||||||
<item name="colorPrimary">@android:color/system_accent1_600</item>
|
|
||||||
<item name="colorPrimaryVariant">@android:color/system_accent1_700</item>
|
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
|
||||||
|
|
||||||
<!-- Secondary brand color -->
|
|
||||||
<item name="colorSecondary">@android:color/system_accent2_600</item>
|
|
||||||
<item name="colorSecondaryVariant">@android:color/system_accent2_700</item>
|
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
|
||||||
<item name="fontFamily">@font/ubuntu_regular</item>
|
|
||||||
|
|
||||||
<!-- Status bar color -->
|
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
||||||
|
|
||||||
<!-- Enable window decor fitting -->
|
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
|
||||||
<item name="android:windowLightNavigationBar">true</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
@@ -16,4 +16,5 @@
|
|||||||
|
|
||||||
<color name="surface">@android:color/system_neutral1_50</color>
|
<color name="surface">@android:color/system_neutral1_50</color>
|
||||||
<color name="on_surface">@android:color/system_neutral1_900</color>
|
<color name="on_surface">@android:color/system_neutral1_900</color>
|
||||||
|
<color name="textColor">#000000</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<item name="colorPrimary">@android:color/system_accent1_600</item>
|
<item name="colorPrimary">@android:color/system_accent1_600</item>
|
||||||
<item name="colorPrimaryVariant">@android:color/system_accent1_700</item>
|
<item name="colorPrimaryVariant">@android:color/system_accent1_700</item>
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
|
<item name="android:textColor"></item>
|
||||||
<item name="fontFamily">@font/ubuntu_regular</item>
|
<item name="fontFamily">@font/ubuntu_regular</item>
|
||||||
|
|
||||||
<!-- Secondary brand color -->
|
<!-- Secondary brand color -->
|
||||||
|
|||||||
Reference in New Issue
Block a user