Changes For Folder Feature
This commit is contained in:
6
.idea/AndroidProjectSystem.xml
generated
Normal file
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,52 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="devs.org.calculator"
|
||||
>
|
||||
package="devs.org.calculator">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_MEDIA_IMAGES"
|
||||
tools:ignore="SelectedPhotoAccess" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_MEDIA_VIDEO"
|
||||
tools:ignore="SelectedPhotoAccess" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
|
||||
<application
|
||||
android:name=".CalculatorApp"
|
||||
android:allowBackup="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Calculator"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".activities.DocumentsActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".activities.AudioGalleryActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".activities.VideoGalleryActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".activities.ImageGalleryActivity"
|
||||
android:name=".activities.HiddenActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".activities.SetupPasswordActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:exported="true" >
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -58,7 +52,7 @@
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".activities.PreviewActivity"
|
||||
android:configChanges="orientation|screenSize"/>
|
||||
android:configChanges="orientation|screenSize" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
@@ -69,9 +63,6 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
|
||||
</manifest>
|
||||
@@ -1,86 +0,0 @@
|
||||
package devs.org.calculator.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.callbacks.FileProcessCallback
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
class AudioGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||
override val fileType = FileManager.FileType.AUDIO
|
||||
private lateinit var pickAudioLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setupFabButton()
|
||||
|
||||
pickAudioLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val clipData = result.data?.clipData
|
||||
val uriList = mutableListOf<Uri>()
|
||||
|
||||
if (clipData != null) {
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
val uri = clipData.getItemAt(i).uri
|
||||
uriList.add(uri)
|
||||
}
|
||||
} else {
|
||||
result.data?.data?.let { uriList.add(it) }
|
||||
}
|
||||
|
||||
if (uriList.isNotEmpty()) {
|
||||
lifecycleScope.launch {
|
||||
FileManager(
|
||||
this@AudioGalleryActivity,
|
||||
this@AudioGalleryActivity
|
||||
).processMultipleFiles(uriList, fileType, this@AudioGalleryActivity)
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.no_files_selected), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||
Toast.makeText(
|
||||
this@AudioGalleryActivity,
|
||||
"${copiedFiles.size} ${getString(R.string.audio_hidded_successfully)} ",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
loadFiles()
|
||||
}
|
||||
|
||||
override fun onFileProcessFailed() {
|
||||
Toast.makeText(this@AudioGalleryActivity, "Failed to hide Audios", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setupFabButton() {
|
||||
binding.fabAdd.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
type = "audio/*"
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
}
|
||||
pickAudioLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun openPreview() {
|
||||
// Not implemented audio preview
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package devs.org.calculator.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.adapters.FileAdapter
|
||||
import devs.org.calculator.databinding.ActivityGalleryBinding
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import java.io.File
|
||||
|
||||
abstract class BaseGalleryActivity : AppCompatActivity() {
|
||||
protected lateinit var binding: ActivityGalleryBinding
|
||||
private lateinit var fileManager: FileManager
|
||||
private lateinit var adapter: FileAdapter
|
||||
private lateinit var files: List<File>
|
||||
|
||||
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 {
|
||||
showPermissionDeniedDialog()
|
||||
}
|
||||
}
|
||||
|
||||
abstract val fileType: FileManager.FileType
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setupIntentSenderLauncher()
|
||||
binding = ActivityGalleryBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
fileManager = FileManager(this, this)
|
||||
|
||||
when(fileType){
|
||||
FileManager.FileType.IMAGE -> {
|
||||
val image = getString(R.string.add_image)
|
||||
binding.fabAdd.text = image
|
||||
binding.noItemsTxt.text = "${getString(R.string.no_items_available_add_one_by_clicking_on_the_plus_button)} '$image' button"
|
||||
}
|
||||
FileManager.FileType.AUDIO -> {
|
||||
val text = getString(R.string.add_audio)
|
||||
binding.fabAdd.text = text
|
||||
binding.noItemsTxt.text = "${getString(R.string.no_items_available_add_one_by_clicking_on_the_plus_button)} '$text' button"
|
||||
|
||||
}
|
||||
FileManager.FileType.VIDEO -> {
|
||||
val text = getString(R.string.add_video)
|
||||
binding.fabAdd.text = text
|
||||
binding.noItemsTxt.text = "${getString(R.string.no_items_available_add_one_by_clicking_on_the_plus_button)} '$text' button"
|
||||
}
|
||||
FileManager.FileType.DOCUMENT -> {
|
||||
val text = getString(R.string.add_files)
|
||||
binding.fabAdd.text = text
|
||||
binding.noItemsTxt.text = "${getString(R.string.no_items_available_add_one_by_clicking_on_the_plus_button)} '$text' button"
|
||||
}
|
||||
}
|
||||
binding.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||
if (scrollY > oldScrollY && binding.fabAdd.isExtended) {
|
||||
|
||||
binding.fabAdd.shrink()
|
||||
} else if (scrollY < oldScrollY && !binding.fabAdd.isExtended) {
|
||||
|
||||
binding.fabAdd.extend()
|
||||
}
|
||||
}
|
||||
setupRecyclerView()
|
||||
checkPermissionsAndLoadFiles()
|
||||
}
|
||||
|
||||
private fun setupIntentSenderLauncher() {
|
||||
intentSenderLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartIntentSenderForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
loadFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
|
||||
adapter = FileAdapter(fileType, this, this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
private fun checkPermissionsAndLoadFiles() {
|
||||
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)
|
||||
if (files.isEmpty()){
|
||||
binding.recyclerView.visibility = View.GONE
|
||||
binding.loading.visibility = View.GONE
|
||||
binding.noItems.visibility = View.VISIBLE
|
||||
}else{
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
binding.loading.visibility = View.GONE
|
||||
binding.noItems.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
loadFiles()
|
||||
}
|
||||
|
||||
abstract fun openPreview()
|
||||
|
||||
private fun showPermissionDeniedDialog() {
|
||||
// permission denied
|
||||
}
|
||||
|
||||
@Deprecated("This method has been deprecated in favor of using the Activity Result API\n which brings increased type safety via an {@link ActivityResultContract} and the prebuilt\n contracts for common intents available in\n {@link androidx.activity.result.contract.ActivityResultContracts}, provides hooks for\n testing, and allow receiving results in separate, testable classes independent from your\n activity. Use\n {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}\n with the appropriate {@link ActivityResultContract} and handling the result in the\n {@link ActivityResultCallback#onActivityResult(Object) callback}.")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == 2296 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
loadFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package devs.org.calculator.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import devs.org.calculator.callbacks.FileProcessCallback
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||
override val fileType = FileManager.FileType.DOCUMENT
|
||||
private lateinit var pickLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setupFabButton()
|
||||
|
||||
pickLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val clipData = result.data?.clipData
|
||||
val uriList = mutableListOf<Uri>()
|
||||
|
||||
if (clipData != null) {
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
val uri = clipData.getItemAt(i).uri
|
||||
uriList.add(uri)
|
||||
}
|
||||
} else {
|
||||
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, getString(R.string.no_files_selected), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||
Toast.makeText(this@DocumentsActivity,"${copiedFiles.size} ${getString(R.string.documents_hidden_successfully )}"
|
||||
, Toast.LENGTH_SHORT).show()
|
||||
loadFiles()
|
||||
}
|
||||
|
||||
override fun onFileProcessFailed() {
|
||||
Toast.makeText(this@DocumentsActivity,
|
||||
getString(R.string.failed_to_hide_documents), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun setupFabButton() {
|
||||
binding.fabAdd.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
type = "*/*"
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
}
|
||||
pickLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun openPreview() {
|
||||
//Not implemented document preview
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
package devs.org.calculator.activities
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.adapters.FileAdapter
|
||||
import devs.org.calculator.adapters.FolderAdapter
|
||||
import devs.org.calculator.callbacks.DialogActionsCallback
|
||||
import devs.org.calculator.databinding.ActivityHiddenBinding
|
||||
import devs.org.calculator.utils.DialogUtil
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import devs.org.calculator.utils.FileManager.Companion.HIDDEN_DIR
|
||||
import devs.org.calculator.utils.FolderManager
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class HiddenActivity : AppCompatActivity() {
|
||||
|
||||
private var isFabOpen = false
|
||||
private lateinit var fabOpen: Animation
|
||||
private lateinit var fabClose: Animation
|
||||
private lateinit var rotateOpen: Animation
|
||||
private lateinit var rotateClose: Animation
|
||||
|
||||
private lateinit var binding: ActivityHiddenBinding
|
||||
private val fileManager = FileManager(this, this)
|
||||
private val folderManager = FolderManager(this)
|
||||
private val dialogUtil = DialogUtil(this)
|
||||
|
||||
private val STORAGE_PERMISSION_CODE = 101
|
||||
private val PICK_FILE_REQUEST_CODE = 102
|
||||
private var currentFolder: File? = null
|
||||
private var folderAdapter: FolderAdapter? = null
|
||||
val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
binding = ActivityHiddenBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
//initialized animations for fabs
|
||||
fabOpen = AnimationUtils.loadAnimation(this, R.anim.fab_open)
|
||||
fabClose = AnimationUtils.loadAnimation(this, R.anim.fab_close)
|
||||
rotateOpen = AnimationUtils.loadAnimation(this, R.anim.rotate_open)
|
||||
rotateClose = AnimationUtils.loadAnimation(this, R.anim.rotate_close)
|
||||
|
||||
binding.fabExpend.visibility = View.GONE
|
||||
binding.addImage.visibility = View.GONE
|
||||
binding.addVideo.visibility = View.GONE
|
||||
binding.addAudio.visibility = View.GONE
|
||||
binding.addDocument.visibility = View.GONE
|
||||
binding.addFolder.visibility = View.VISIBLE
|
||||
|
||||
binding.fabExpend.setOnClickListener {
|
||||
if (isFabOpen) {
|
||||
closeFabs()
|
||||
|
||||
} else {
|
||||
openFabs()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
binding.addImage.setOnClickListener { openFilePicker("image/*") }
|
||||
binding.addVideo.setOnClickListener { openFilePicker("video/*") }
|
||||
binding.addAudio.setOnClickListener { openFilePicker("audio/*") }
|
||||
binding.addDocument.setOnClickListener { openFilePicker("*/*") }
|
||||
binding.addFolder.setOnClickListener {
|
||||
dialogUtil.createInputDialog(
|
||||
title = "Enter Folder Name To Create",
|
||||
hint = "",
|
||||
callback = object : DialogUtil.InputDialogCallback {
|
||||
override fun onPositiveButtonClicked(input: String) {
|
||||
fileManager.askPermission(this@HiddenActivity)
|
||||
folderManager.createFolder( hiddenDir,input )
|
||||
listFoldersInHiddenDirectory()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fileManager.askPermission(this)
|
||||
listFoldersInHiddenDirectory()
|
||||
}
|
||||
|
||||
private fun openFilePicker(mimeType: String) {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
type = mimeType
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
}
|
||||
startActivityForResult(intent, PICK_FILE_REQUEST_CODE)
|
||||
}
|
||||
|
||||
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) {
|
||||
Log.d("HiddenActivity", "READ/WRITE_EXTERNAL_STORAGE permission granted via onRequestPermissionsResult")
|
||||
listFoldersInHiddenDirectory()
|
||||
} else {
|
||||
Log.d("HiddenActivity", "READ/WRITE_EXTERNAL_STORAGE permission denied via onRequestPermissionsResult")
|
||||
// Handle denied case, maybe show a message or disable functionality
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This method has been deprecated in favor of using the Activity Result API\n which brings increased type safety via an {@link ActivityResultContract} and the prebuilt\n contracts for common intents available in\n {@link androidx.activity.result.contract.ActivityResultContracts}, provides hooks for\n testing, and allow receiving results in separate, testable classes independent from your\n activity. Use\n {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}\n with the appropriate {@link ActivityResultContract} and handling the result in the\n {@link ActivityResultCallback#onActivityResult(Object) callback}.")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == STORAGE_PERMISSION_CODE) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
listFoldersInHiddenDirectory()
|
||||
} else {
|
||||
// Handle denied case
|
||||
}
|
||||
}
|
||||
} else if (requestCode == PICK_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
|
||||
data?.data?.let { uri ->
|
||||
Log.d("HiddenActivity", "Selected file URI: $uri")
|
||||
copyFileToHiddenDirectory(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun listFoldersInHiddenDirectory() {
|
||||
if (hiddenDir.exists() && hiddenDir.isDirectory) {
|
||||
val folders = folderManager.getFoldersInDirectory(hiddenDir)
|
||||
|
||||
if (folders.isNotEmpty()) {
|
||||
binding.noItems.visibility = View.GONE
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
|
||||
// Initialize adapter only once
|
||||
if (folderAdapter == null) {
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
|
||||
folderAdapter = FolderAdapter(
|
||||
onFolderClick = { clickedFolder ->
|
||||
openFolder(clickedFolder)
|
||||
},
|
||||
onFolderLongClick = { folder ->
|
||||
// go to selection mode
|
||||
}
|
||||
)
|
||||
binding.recyclerView.adapter = folderAdapter
|
||||
}
|
||||
|
||||
// Submit new list to adapter - DiffUtil will handle the comparison
|
||||
folderAdapter?.submitList(folders)
|
||||
} else {
|
||||
binding.noItems.visibility = View.VISIBLE
|
||||
binding.recyclerView.visibility = View.GONE
|
||||
}
|
||||
} else if (!hiddenDir.exists()) {
|
||||
fileManager.getHiddenDirectory()
|
||||
} else {
|
||||
Log.e("HiddenActivity", "Hidden directory is not a directory: ${hiddenDir.absolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun openFolder(folder: File) {
|
||||
Log.d("HiddenActivity", "Opening folder: ${folder.name}")
|
||||
currentFolder = folder
|
||||
binding.addFolder.visibility = View.GONE
|
||||
binding.fabExpend.visibility = View.VISIBLE
|
||||
|
||||
// Read files in the clicked folder and update RecyclerView
|
||||
val files = folderManager.getFilesInFolder(folder)
|
||||
Log.d("HiddenActivity", "Found ${files.size} files in ${folder.name}")
|
||||
|
||||
if (files.isNotEmpty()) {
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
|
||||
|
||||
val fileAdapter = FileAdapter(this, this, folder).apply {
|
||||
// Set up the callback for file operations
|
||||
fileOperationCallback = object : FileAdapter.FileOperationCallback {
|
||||
override fun onFileDeleted(file: File) {
|
||||
// Refresh the file list
|
||||
refreshCurrentFolder()
|
||||
}
|
||||
|
||||
override fun onFileRenamed(oldFile: File, newFile: File) {
|
||||
// Refresh the file list
|
||||
refreshCurrentFolder()
|
||||
}
|
||||
|
||||
override fun onRefreshNeeded() {
|
||||
// Refresh the file list
|
||||
refreshCurrentFolder()
|
||||
}
|
||||
}
|
||||
|
||||
submitList(files)
|
||||
}
|
||||
|
||||
binding.recyclerView.adapter = fileAdapter
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
binding.noItems.visibility = View.GONE
|
||||
} else {
|
||||
binding.recyclerView.visibility = View.GONE
|
||||
binding.noItems.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshCurrentFolder() {
|
||||
currentFolder?.let { folder ->
|
||||
val files = folderManager.getFilesInFolder(folder)
|
||||
(binding.recyclerView.adapter as? FileAdapter)?.submitList(files)
|
||||
|
||||
if (files.isEmpty()) {
|
||||
binding.recyclerView.visibility = View.GONE
|
||||
binding.noItems.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
binding.noItems.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openFabs() {
|
||||
binding.addImage.startAnimation(fabOpen)
|
||||
binding.addVideo.startAnimation(fabOpen)
|
||||
binding.addAudio.startAnimation(fabOpen)
|
||||
binding.addDocument.startAnimation(fabOpen)
|
||||
binding.addFolder.startAnimation(fabOpen)
|
||||
binding.fabExpend.startAnimation(rotateOpen)
|
||||
|
||||
binding.addImage.visibility = View.VISIBLE
|
||||
binding.addVideo.visibility = View.VISIBLE
|
||||
binding.addAudio.visibility = View.VISIBLE
|
||||
binding.addDocument.visibility = View.VISIBLE
|
||||
binding.addFolder.visibility = View.VISIBLE // Keep this visible if in folder list, but should be GONE when showing files
|
||||
|
||||
isFabOpen = true
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
binding.fabExpend.setImageResource(R.drawable.wrong)
|
||||
},200)
|
||||
}
|
||||
|
||||
private fun closeFabs() {
|
||||
binding.addImage.startAnimation(fabClose)
|
||||
binding.addVideo.startAnimation(fabClose)
|
||||
binding.addAudio.startAnimation(fabClose)
|
||||
binding.addDocument.startAnimation(fabClose)
|
||||
binding.addFolder.startAnimation(fabClose)
|
||||
binding.fabExpend.startAnimation(rotateClose)
|
||||
|
||||
binding.addImage.visibility = View.INVISIBLE
|
||||
binding.addVideo.visibility = View.INVISIBLE
|
||||
binding.addAudio.visibility = View.INVISIBLE
|
||||
binding.addDocument.visibility = View.INVISIBLE
|
||||
binding.addFolder.visibility = View.INVISIBLE
|
||||
|
||||
isFabOpen = false
|
||||
binding.fabExpend.setImageResource(R.drawable.ic_add)
|
||||
}
|
||||
|
||||
private fun copyFileToHiddenDirectory(uri: Uri) {
|
||||
currentFolder?.let { destinationFolder ->
|
||||
try {
|
||||
val inputStream: InputStream? = contentResolver.openInputStream(uri)
|
||||
val fileName = getFileNameFromUri(uri) ?: "unknown_file"
|
||||
val destinationFile = File(destinationFolder, fileName)
|
||||
|
||||
inputStream?.use { input ->
|
||||
val outputStream: OutputStream = FileOutputStream(destinationFile)
|
||||
outputStream.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
Log.d("HiddenActivity", "File copied to: ${destinationFile.absolutePath}")
|
||||
// Refresh the file list in the RecyclerView
|
||||
currentFolder?.let { openFolder(it) }
|
||||
} catch (e: Exception) {
|
||||
Log.e("HiddenActivity", "Error copying file", e)
|
||||
// TODO: Show error message to user
|
||||
}
|
||||
} ?: run {
|
||||
Log.e("HiddenActivity", "Current folder is null, cannot copy file")
|
||||
// TODO: Show error message to user
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileNameFromUri(uri: Uri): String? {
|
||||
var name: String? = null
|
||||
val cursor = contentResolver.query(uri, null, null, null, null)
|
||||
cursor?.use { it ->
|
||||
val nameIndex = it.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
|
||||
if (nameIndex != -1) {
|
||||
it.moveToFirst()
|
||||
name = it.getString(nameIndex)
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
private fun showFileOptionsDialog(file: File) {
|
||||
val options = arrayOf("Delete", "Rename", "Share")
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Choose an action for ${file.name}")
|
||||
.setItems(options) { dialog, which ->
|
||||
when (which) {
|
||||
0 -> deleteFile(file)
|
||||
1 -> renameFile(file)
|
||||
2 -> shareFile(file)
|
||||
}
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun deleteFile(file: File) {
|
||||
Log.d("HiddenActivity", "Deleting file: ${file.name}")
|
||||
if (file.exists()) {
|
||||
if (file.delete()) {
|
||||
Log.d("HiddenActivity", "File deleted successfully")
|
||||
// Refresh the file list in the RecyclerView
|
||||
currentFolder?.let { openFolder(it) }
|
||||
} else {
|
||||
Log.e("HiddenActivity", "Failed to delete file: ${file.absolutePath}")
|
||||
// TODO: Show error message to user
|
||||
}
|
||||
} else {
|
||||
Log.e("HiddenActivity", "File not found for deletion: ${file.absolutePath}")
|
||||
// TODO: Show error message to user
|
||||
}
|
||||
}
|
||||
|
||||
private fun renameFile(file: File) {
|
||||
Log.d("HiddenActivity", "Renaming file: ${file.name}")
|
||||
val inputEditText = EditText(this)
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Rename ${file.name}")
|
||||
.setView(inputEditText)
|
||||
.setPositiveButton("Rename") { dialog, _ ->
|
||||
val newName = inputEditText.text.toString().trim()
|
||||
if (newName.isNotEmpty()) {
|
||||
val parentDir = file.parentFile
|
||||
if (parentDir != null) {
|
||||
val newFile = File(parentDir, newName)
|
||||
if (file.renameTo(newFile)) {
|
||||
Log.d("HiddenActivity", "File renamed to: ${newFile.name}")
|
||||
currentFolder?.let { openFolder(it) }
|
||||
} else {
|
||||
Log.e("HiddenActivity", "Failed to rename file: ${file.absolutePath} to ${newFile.absolutePath}")
|
||||
}
|
||||
} else {
|
||||
Log.e("HiddenActivity", "Parent directory is null for renaming: ${file.absolutePath}")
|
||||
}
|
||||
} else {
|
||||
Log.d("HiddenActivity", "New file name is empty")
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton("Cancel") { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun shareFile(file: File) {
|
||||
val uri: Uri? = FileProvider.getUriForFile(this, "${packageName}.fileprovider", file)
|
||||
uri?.let { fileUri ->
|
||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = contentResolver.getType(fileUri) ?: "*/*"
|
||||
putExtra(Intent.EXTRA_STREAM, fileUri)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, "Share ${file.name}"))
|
||||
} ?: run {
|
||||
Log.e("HiddenActivity", "Could not get URI for sharing file: ${file.absolutePath}")
|
||||
//Show error message to user
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (currentFolder != null) {
|
||||
currentFolder = null
|
||||
if (isFabOpen) {
|
||||
closeFabs()
|
||||
}
|
||||
if (folderAdapter != null) {
|
||||
binding.recyclerView.adapter = folderAdapter
|
||||
}
|
||||
listFoldersInHiddenDirectory()
|
||||
binding.fabExpend.visibility = View.GONE
|
||||
binding.addImage.visibility = View.GONE
|
||||
binding.addVideo.visibility = View.GONE
|
||||
binding.addAudio.visibility = View.GONE
|
||||
binding.addDocument.visibility = View.GONE
|
||||
binding.addFolder.visibility = View.VISIBLE
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,17 +20,5 @@ class HiddenVaultActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun setupNavigation() {
|
||||
binding.btnImages.setOnClickListener {
|
||||
startActivity(Intent(this, ImageGalleryActivity::class.java))
|
||||
}
|
||||
binding.btnVideos.setOnClickListener {
|
||||
startActivity(Intent(this, VideoGalleryActivity::class.java))
|
||||
}
|
||||
binding.btnAudio.setOnClickListener {
|
||||
startActivity(Intent(this, AudioGalleryActivity::class.java))
|
||||
}
|
||||
binding.btnDocs.setOnClickListener {
|
||||
startActivity(Intent(this, DocumentsActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package devs.org.calculator.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import android.Manifest
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.callbacks.FileProcessCallback
|
||||
|
||||
class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||
override val fileType = FileManager.FileType.IMAGE
|
||||
private val STORAGE_PERMISSION_CODE = 100
|
||||
|
||||
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||
private lateinit var pickImageLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setupIntentSenderLauncher()
|
||||
setupFabButton()
|
||||
|
||||
intentSenderLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){
|
||||
if (it.resultCode != RESULT_OK) Toast.makeText(this,
|
||||
getString(R.string.failed_to_hide_unhide_photo), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val clipData = result.data?.clipData
|
||||
val uriList = mutableListOf<Uri>()
|
||||
|
||||
if (clipData != null) {
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
val uri = clipData.getItemAt(i).uri
|
||||
uriList.add(uri)
|
||||
}
|
||||
} else {
|
||||
result.data?.data?.let { uriList.add(it) }
|
||||
}
|
||||
|
||||
if (uriList.isNotEmpty()) {
|
||||
lifecycleScope.launch {
|
||||
FileManager(this@ImageGalleryActivity, this@ImageGalleryActivity)
|
||||
.processMultipleFiles(uriList, fileType,this@ImageGalleryActivity )
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.no_files_selected), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
askPermissiom()
|
||||
}
|
||||
|
||||
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||
Toast.makeText(this@ImageGalleryActivity, "${copiedFiles.size} ${getString(R.string.images_hidden_successfully)}", Toast.LENGTH_SHORT).show()
|
||||
loadFiles()
|
||||
}
|
||||
|
||||
override fun onFileProcessFailed() {
|
||||
Toast.makeText(this@ImageGalleryActivity,
|
||||
getString(R.string.failed_to_hide_images), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun setupIntentSenderLauncher() {
|
||||
intentSenderLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartIntentSenderForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
loadFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun askPermissiom() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
|
||||
if (!Environment.isExternalStorageManager()){
|
||||
val intent = Intent().setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
else {
|
||||
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,
|
||||
getString(R.string.storage_permissions_granted), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(this,
|
||||
getString(R.string.storage_permissions_denied), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFabButton() {
|
||||
binding.fabAdd.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
type = "image/*"
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
}
|
||||
pickImageLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun openPreview() {
|
||||
val intent = Intent(this, PreviewActivity::class.java).apply {
|
||||
putExtra("type", fileType)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import devs.org.calculator.utils.PrefsUtil
|
||||
import net.objecthunter.exp4j.ExpressionBuilder
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class MainActivity : AppCompatActivity(), DialogActionsCallback {
|
||||
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private var currentExpression = "0"
|
||||
private var lastWasOperator = false
|
||||
@@ -57,8 +57,24 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
|
||||
"\n" +
|
||||
"For devices running Android 11 or higher, you'll need to grant the 'All Files Access' permission.",
|
||||
"Grant",
|
||||
"Cancel",
|
||||
this
|
||||
"Later",
|
||||
object : DialogUtil.DialogCallback {
|
||||
override fun onPositiveButtonClicked() {
|
||||
fileManager.askPermission(this@MainActivity)
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
Toast.makeText(this@MainActivity,
|
||||
"Storage permission is required for the app to function properly",
|
||||
Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
Toast.makeText(this@MainActivity,
|
||||
"You can grant permission later from Settings",
|
||||
Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
setupNumberButton(binding.btn0, "0")
|
||||
@@ -298,7 +314,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
|
||||
}
|
||||
|
||||
if (PrefsUtil(this).validatePassword(currentExpression)) {
|
||||
val intent = Intent(this, HiddenVaultActivity::class.java)
|
||||
val intent = Intent(this, HiddenActivity::class.java)
|
||||
intent.putExtra("password", currentExpression)
|
||||
startActivity(intent)
|
||||
clearDisplay()
|
||||
@@ -395,15 +411,18 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
|
||||
}
|
||||
|
||||
override fun onPositiveButtonClicked() {
|
||||
// Handle positive button click for both DialogUtil and DialogActionsCallback
|
||||
fileManager.askPermission(this)
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
|
||||
// Handle negative button click
|
||||
Toast.makeText(this, "Storage permission is required for the app to function properly", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
|
||||
// Handle neutral button click
|
||||
Toast.makeText(this, "You can grant permission later from Settings", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.adapters.ImagePreviewAdapter
|
||||
import devs.org.calculator.callbacks.DialogActionsCallback
|
||||
import devs.org.calculator.databinding.ActivityPreviewBinding
|
||||
import devs.org.calculator.utils.DialogUtil
|
||||
import devs.org.calculator.utils.FileManager
|
||||
@@ -20,6 +19,7 @@ class PreviewActivity : AppCompatActivity() {
|
||||
private var currentPosition: Int = 0
|
||||
private lateinit var files: List<File>
|
||||
private lateinit var type: String
|
||||
private lateinit var folder: String
|
||||
private lateinit var filetype: FileManager.FileType
|
||||
private lateinit var adapter: ImagePreviewAdapter
|
||||
private lateinit var fileManager: FileManager
|
||||
@@ -34,9 +34,10 @@ class PreviewActivity : AppCompatActivity() {
|
||||
|
||||
currentPosition = intent.getIntExtra("position", 0)
|
||||
type = intent.getStringExtra("type").toString()
|
||||
folder = intent.getStringExtra("folder").toString()
|
||||
|
||||
setupFileType()
|
||||
files = fileManager.getFilesInHiddenDir(filetype)
|
||||
files = fileManager.getFilesInHiddenDirFromFolder(filetype, folder = folder)
|
||||
|
||||
setupImagePreview()
|
||||
clickListeners()
|
||||
@@ -72,7 +73,7 @@ class PreviewActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun setupImagePreview() {
|
||||
adapter = ImagePreviewAdapter(this, filetype)
|
||||
adapter = ImagePreviewAdapter(this, this)
|
||||
adapter.images = files
|
||||
binding.viewPager.adapter = adapter
|
||||
|
||||
@@ -110,7 +111,7 @@ class PreviewActivity : AppCompatActivity() {
|
||||
getString(R.string.are_you_sure_to_delete_this_file_permanently),
|
||||
getString(R.string.delete_permanently),
|
||||
getString(R.string.cancel),
|
||||
object : DialogActionsCallback{
|
||||
object : DialogUtil.DialogCallback {
|
||||
override fun onPositiveButtonClicked() {
|
||||
lifecycleScope.launch {
|
||||
FileManager(this@PreviewActivity, this@PreviewActivity).deletePhotoFromExternalStorage(fileUri)
|
||||
@@ -119,13 +120,12 @@ class PreviewActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
|
||||
// Handle negative button click
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
|
||||
// Handle neutral button click
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -139,7 +139,7 @@ class PreviewActivity : AppCompatActivity() {
|
||||
getString(R.string.are_you_sure_you_want_to_un_hide_this_file),
|
||||
getString(R.string.un_hide),
|
||||
getString(R.string.cancel),
|
||||
object : DialogActionsCallback{
|
||||
object : DialogUtil.DialogCallback {
|
||||
override fun onPositiveButtonClicked() {
|
||||
lifecycleScope.launch {
|
||||
FileManager(this@PreviewActivity, this@PreviewActivity).copyFileToNormalDir(fileUri)
|
||||
@@ -148,13 +148,12 @@ class PreviewActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
|
||||
// Handle negative button click
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
|
||||
// Handle neutral button click
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package devs.org.calculator.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import devs.org.calculator.callbacks.FileProcessCallback
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||
override val fileType = FileManager.FileType.VIDEO
|
||||
private lateinit var pickLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setupFabButton()
|
||||
|
||||
pickLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val clipData = result.data?.clipData
|
||||
val uriList = mutableListOf<Uri>()
|
||||
|
||||
if (clipData != null) {
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
val uri = clipData.getItemAt(i).uri
|
||||
uriList.add(uri)
|
||||
}
|
||||
} else {
|
||||
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, getString(R.string.no_files_selected), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||
Toast.makeText(this@VideoGalleryActivity, "${copiedFiles.size} ${getString(R.string.videos_hidden_successfully)}"
|
||||
, Toast.LENGTH_SHORT).show()
|
||||
loadFiles()
|
||||
}
|
||||
|
||||
override fun onFileProcessFailed() {
|
||||
Toast.makeText(this@VideoGalleryActivity,
|
||||
getString(R.string.failed_to_hide_videos), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun setupFabButton() {
|
||||
binding.fabAdd.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
type = "video/*"
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
}
|
||||
pickLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun openPreview() {
|
||||
val intent = Intent(this, PreviewActivity::class.java).apply {
|
||||
putExtra("type", fileType)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -1,197 +1,341 @@
|
||||
package devs.org.calculator.adapters
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.activities.PreviewActivity
|
||||
import devs.org.calculator.callbacks.DialogActionsCallback
|
||||
import devs.org.calculator.utils.DialogUtil
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
class FileAdapter(
|
||||
private val fileType: FileManager.FileType,
|
||||
var context: Context,
|
||||
private var lifecycleOwner: LifecycleOwner
|
||||
) :
|
||||
ListAdapter<File, FileAdapter.FileViewHolder>(FileDiffCallback()) {
|
||||
private val context: Context,
|
||||
private val lifecycleOwner: LifecycleOwner,
|
||||
private val currentFolder: File
|
||||
) : ListAdapter<File, FileAdapter.FileViewHolder>(FileDiffCallback()) {
|
||||
|
||||
private val selectedItems = mutableSetOf<Int>()
|
||||
private var isSelectionMode = false
|
||||
private var fileName = "Unknown File"
|
||||
private var fileTypes = when (fileType) {
|
||||
|
||||
FileManager.FileType.IMAGE -> {
|
||||
context.getString(R.string.image)
|
||||
}
|
||||
|
||||
FileManager.FileType.VIDEO -> {
|
||||
context.getString(R.string.video)
|
||||
}
|
||||
|
||||
FileManager.FileType.AUDIO -> {
|
||||
context.getString(R.string.audio)
|
||||
}
|
||||
|
||||
else -> context.getString(R.string.document)
|
||||
|
||||
// Callback interface for handling file operations
|
||||
interface FileOperationCallback {
|
||||
fun onFileDeleted(file: File)
|
||||
fun onFileRenamed(oldFile: File, newFile: File)
|
||||
fun onRefreshNeeded()
|
||||
}
|
||||
|
||||
var fileOperationCallback: FileOperationCallback? = null
|
||||
|
||||
inner class FileViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val imageView: ImageView = view.findViewById(R.id.imageView)
|
||||
val imageView: ImageView = view.findViewById(R.id.fileIconImageView)
|
||||
val fileNameTextView: TextView = view.findViewById(R.id.fileNameTextView)
|
||||
val playIcon: ImageView = view.findViewById(R.id.videoPlay)
|
||||
|
||||
fun bind(file: File) {
|
||||
val fileType = FileManager(context, lifecycleOwner).getFileType(file)
|
||||
setupFileDisplay(file, fileType)
|
||||
setupClickListeners(file, fileType)
|
||||
|
||||
// Handle selection state
|
||||
itemView.isSelected = selectedItems.contains(adapterPosition)
|
||||
}
|
||||
|
||||
fun bind(file: File, payloads: List<Any>) {
|
||||
if (payloads.isEmpty()) {
|
||||
bind(file)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle partial updates based on payload
|
||||
val changes = payloads.firstOrNull() as? List<String>
|
||||
changes?.forEach { change ->
|
||||
when (change) {
|
||||
"NAME_CHANGED" -> {
|
||||
fileNameTextView.text = file.name
|
||||
}
|
||||
"SIZE_CHANGED", "MODIFIED_DATE_CHANGED" -> {
|
||||
// Could update file info if displayed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFileDisplay(file: File, fileType: FileManager.FileType) {
|
||||
when (fileType) {
|
||||
FileManager.FileType.IMAGE -> {
|
||||
Glide.with(imageView)
|
||||
.load(file)
|
||||
.centerCrop()
|
||||
.into(imageView)
|
||||
loadImageThumbnail(file)
|
||||
fileNameTextView.visibility = View.GONE
|
||||
playIcon.visibility = View.GONE
|
||||
}
|
||||
|
||||
FileManager.FileType.VIDEO -> {
|
||||
Glide.with(imageView)
|
||||
.asBitmap()
|
||||
.load(file)
|
||||
.centerCrop()
|
||||
.into(imageView)
|
||||
loadVideoThumbnail(file)
|
||||
fileNameTextView.visibility = View.GONE
|
||||
playIcon.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
else -> {
|
||||
val resourceId = when (fileType) {
|
||||
FileManager.FileType.AUDIO -> R.drawable.ic_audio
|
||||
FileManager.FileType.DOCUMENT -> R.drawable.ic_document
|
||||
else -> R.drawable.ic_file
|
||||
}
|
||||
imageView.setImageResource(resourceId)
|
||||
loadFileIcon(fileType)
|
||||
fileNameTextView.visibility = View.VISIBLE
|
||||
playIcon.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
fileNameTextView.text = file.name
|
||||
}
|
||||
|
||||
private fun loadImageThumbnail(file: File) {
|
||||
Glide.with(imageView)
|
||||
.load(file)
|
||||
.thumbnail(0.1f)
|
||||
.centerCrop()
|
||||
.override(300, 300)
|
||||
.placeholder(R.drawable.ic_file)
|
||||
.error(R.drawable.ic_file)
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
private fun loadVideoThumbnail(file: File) {
|
||||
Glide.with(imageView)
|
||||
.asBitmap()
|
||||
.load(file)
|
||||
.thumbnail(0.1f)
|
||||
.centerCrop()
|
||||
.override(300, 300)
|
||||
.placeholder(R.drawable.ic_file)
|
||||
.error(R.drawable.ic_file)
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
private fun loadFileIcon(fileType: FileManager.FileType) {
|
||||
val resourceId = when (fileType) {
|
||||
FileManager.FileType.AUDIO -> R.drawable.ic_audio
|
||||
FileManager.FileType.DOCUMENT -> R.drawable.ic_document
|
||||
else -> R.drawable.ic_file
|
||||
}
|
||||
imageView.setImageResource(resourceId)
|
||||
}
|
||||
|
||||
private fun setupClickListeners(file: File, fileType: FileManager.FileType) {
|
||||
itemView.setOnClickListener {
|
||||
|
||||
|
||||
when(fileType){
|
||||
FileManager.FileType.AUDIO -> {
|
||||
// Create an intent to play audio using available audio players
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(FileManager.FileManager().getContentUriImage(context, file, fileType), "audio/*")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.no_audio_player_found), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
FileManager.FileType.DOCUMENT -> {
|
||||
// Create an intent to open the document using available viewers or file managers
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(FileManager.FileManager().getContentUriImage(context, file, fileType), "*/*")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.no_suitable_app_found_to_open_this_document), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val intent = Intent(context, PreviewActivity::class.java).apply {
|
||||
putExtra("type", fileTypes)
|
||||
putExtra("position", position)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
if (isSelectionMode) {
|
||||
toggleSelection(adapterPosition)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
|
||||
openFile(file, fileType)
|
||||
}
|
||||
|
||||
itemView.setOnLongClickListener {
|
||||
|
||||
val fileUri = FileManager.FileManager().getContentUriImage(context, file, fileType)
|
||||
if (fileUri == null) {
|
||||
Toast.makeText(context, "Unable to access file: $file", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
|
||||
return@setOnLongClickListener true
|
||||
|
||||
if (!isSelectionMode) {
|
||||
showFileOptionsDialog(file)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
fileName = FileManager.FileName(context).getFileNameFromUri(fileUri)?.toString()
|
||||
?: context.getString(R.string.unknown_file)
|
||||
}
|
||||
}
|
||||
|
||||
DialogUtil(context).showMaterialDialogWithNaturalButton(
|
||||
context.getString(R.string.details, fileTypes),
|
||||
"File Name: $fileName\n\nFile Path: $file\n\nYou can permanently delete or un-hide this file.",
|
||||
context.getString(R.string.delete_permanently),
|
||||
context.getString(R.string.un_hide),
|
||||
context.getString(R.string.cancel),
|
||||
object : DialogActionsCallback {
|
||||
override fun onPositiveButtonClicked() {
|
||||
lifecycleOwner.lifecycleScope.launch {
|
||||
FileManager(context, lifecycleOwner).deletePhotoFromExternalStorage(
|
||||
fileUri
|
||||
)
|
||||
}
|
||||
val currentList = currentList.toMutableList()
|
||||
currentList.remove(file)
|
||||
submitList(currentList)
|
||||
}
|
||||
private fun openFile(file: File, fileType: FileManager.FileType) {
|
||||
when (fileType) {
|
||||
FileManager.FileType.AUDIO -> openAudioFile(file)
|
||||
FileManager.FileType.IMAGE, FileManager.FileType.VIDEO -> openInPreview(fileType)
|
||||
FileManager.FileType.DOCUMENT -> openDocumentFile(file)
|
||||
else -> openDocumentFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
FileManager(context, lifecycleOwner).copyFileToNormalDir(fileUri)
|
||||
val currentList = currentList.toMutableList()
|
||||
currentList.remove(file)
|
||||
submitList(currentList)
|
||||
}
|
||||
private fun openAudioFile(file: File) {
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileprovider",
|
||||
file
|
||||
)
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, "audio/*")
|
||||
putExtra("folder", currentFolder.toString())
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.no_audio_player_found),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
private fun openDocumentFile(file: File) {
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileprovider",
|
||||
file
|
||||
)
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, "*/*")
|
||||
putExtra("folder", currentFolder.toString())
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.no_suitable_app_found_to_open_this_document),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return@setOnLongClickListener true
|
||||
private fun openInPreview(fileType: FileManager.FileType) {
|
||||
val fileTypeString = when (fileType) {
|
||||
FileManager.FileType.IMAGE -> context.getString(R.string.image)
|
||||
FileManager.FileType.VIDEO -> context.getString(R.string.video)
|
||||
else -> "unknown"
|
||||
}
|
||||
|
||||
val intent = Intent(context, PreviewActivity::class.java).apply {
|
||||
putExtra("type", fileTypeString)
|
||||
putExtra("folder", currentFolder.toString())
|
||||
putExtra("position", adapterPosition)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
private fun showFileOptionsDialog(file: File) {
|
||||
val options = arrayOf(
|
||||
context.getString(R.string.delete),
|
||||
context.getString(R.string.rename),
|
||||
context.getString(R.string.share)
|
||||
)
|
||||
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.file_options))
|
||||
.setItems(options) { dialog, which ->
|
||||
when (which) {
|
||||
0 -> deleteFile(file)
|
||||
1 -> renameFile(file)
|
||||
2 -> shareFile(file)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun deleteFile(file: File) {
|
||||
if (file.delete()) {
|
||||
fileOperationCallback?.onFileDeleted(file)
|
||||
Toast.makeText(context, "File deleted", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "Failed to delete file", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun renameFile(file: File) {
|
||||
val inputEditText = EditText(context).apply {
|
||||
setText(file.name)
|
||||
selectAll()
|
||||
}
|
||||
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.rename_file))
|
||||
.setView(inputEditText)
|
||||
.setPositiveButton(context.getString(R.string.rename)) { dialog, _ ->
|
||||
val newName = inputEditText.text.toString().trim()
|
||||
if (newName.isNotEmpty() && newName != file.name) {
|
||||
val parentDir = file.parentFile
|
||||
if (parentDir != null) {
|
||||
val newFile = File(parentDir, newName)
|
||||
if (file.renameTo(newFile)) {
|
||||
fileOperationCallback?.onFileRenamed(file, newFile)
|
||||
Toast.makeText(context, "File renamed", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "Failed to rename file", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(context.getString(R.string.cancel)) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun shareFile(file: File) {
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileprovider",
|
||||
file
|
||||
)
|
||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = context.contentResolver.getType(uri) ?: "*/*"
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
context.startActivity(
|
||||
Intent.createChooser(shareIntent, context.getString(R.string.share_file))
|
||||
)
|
||||
}
|
||||
|
||||
private fun toggleSelection(position: Int) {
|
||||
if (selectedItems.contains(position)) {
|
||||
selectedItems.remove(position)
|
||||
} else {
|
||||
selectedItems.add(position)
|
||||
}
|
||||
|
||||
if (selectedItems.isEmpty()) {
|
||||
isSelectionMode = false
|
||||
}
|
||||
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_file, parent, false)
|
||||
.inflate(R.layout.list_item_file, parent, false)
|
||||
return FileViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
val file = getItem(position)
|
||||
holder.bind(file)
|
||||
}
|
||||
|
||||
class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
||||
override fun areItemsTheSame(oldItem: File, newItem: File): Boolean {
|
||||
return oldItem.path == newItem.path
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: File, newItem: File): Boolean {
|
||||
return oldItem == newItem
|
||||
override fun onBindViewHolder(holder: FileViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
if (payloads.isEmpty()) {
|
||||
super.onBindViewHolder(holder, position, payloads)
|
||||
} else {
|
||||
val file = getItem(position)
|
||||
holder.bind(file, payloads)
|
||||
}
|
||||
}
|
||||
|
||||
// Public methods for external control
|
||||
fun clearSelection() {
|
||||
selectedItems.clear()
|
||||
isSelectionMode = false
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
}
|
||||
fun getSelectedItems(): List<File> {
|
||||
return selectedItems.mapNotNull { position ->
|
||||
if (position < itemCount) getItem(position) else null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package devs.org.calculator.adapters
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import java.io.File
|
||||
|
||||
class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: File, newItem: File): Boolean {
|
||||
// Compare by absolute path since File objects might be different instances
|
||||
// but represent the same file
|
||||
return oldItem.absolutePath == newItem.absolutePath
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: File, newItem: File): Boolean {
|
||||
// Compare all relevant properties that might change and affect the UI
|
||||
return oldItem.name == newItem.name &&
|
||||
oldItem.length() == newItem.length() &&
|
||||
oldItem.lastModified() == newItem.lastModified() &&
|
||||
oldItem.canRead() == newItem.canRead() &&
|
||||
oldItem.canWrite() == newItem.canWrite()
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: File, newItem: File): Any? {
|
||||
// Return a payload if only specific properties changed
|
||||
// This allows for partial updates instead of full rebinding
|
||||
val changes = mutableListOf<String>()
|
||||
|
||||
if (oldItem.name != newItem.name) {
|
||||
changes.add("NAME_CHANGED")
|
||||
}
|
||||
|
||||
if (oldItem.length() != newItem.length()) {
|
||||
changes.add("SIZE_CHANGED")
|
||||
}
|
||||
|
||||
if (oldItem.lastModified() != newItem.lastModified()) {
|
||||
changes.add("MODIFIED_DATE_CHANGED")
|
||||
}
|
||||
|
||||
return if (changes.isNotEmpty()) changes else null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package devs.org.calculator.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import devs.org.calculator.R
|
||||
import java.io.File
|
||||
|
||||
class FolderAdapter(
|
||||
private val onFolderClick: (File) -> Unit,
|
||||
private val onFolderLongClick: (File) -> Unit
|
||||
) : ListAdapter<File, FolderAdapter.FolderViewHolder>(FolderDiffCallback()) {
|
||||
|
||||
class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val folderNameTextView: TextView = itemView.findViewById(R.id.folderName)
|
||||
|
||||
fun bind(folder: File, onFolderClick: (File) -> Unit, onFolderLongClick: (File) -> Unit) {
|
||||
folderNameTextView.text = folder.name
|
||||
|
||||
itemView.setOnClickListener { onFolderClick(folder) }
|
||||
itemView.setOnLongClickListener {
|
||||
onFolderLongClick(folder)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FolderViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_folder, parent, false)
|
||||
return FolderViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FolderViewHolder, position: Int) {
|
||||
val folder = getItem(position)
|
||||
holder.bind(folder, onFolderClick, onFolderLongClick)
|
||||
}
|
||||
|
||||
private class FolderDiffCallback : DiffUtil.ItemCallback<File>() {
|
||||
override fun areItemsTheSame(oldItem: File, newItem: File): Boolean {
|
||||
return oldItem.absolutePath == newItem.absolutePath
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: File, newItem: File): Boolean {
|
||||
return oldItem.name == newItem.name &&
|
||||
oldItem.lastModified() == newItem.lastModified() &&
|
||||
oldItem.length() == newItem.length()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,10 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.MediaController
|
||||
import android.widget.SeekBar
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import devs.org.calculator.adapters.FileAdapter.FileDiffCallback
|
||||
import devs.org.calculator.databinding.ViewpagerItemsBinding
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import java.io.File
|
||||
@@ -21,7 +21,7 @@ import devs.org.calculator.R
|
||||
|
||||
class ImagePreviewAdapter(
|
||||
private val context: Context,
|
||||
private var fileType: FileManager.FileType
|
||||
private var lifecycleOwner: LifecycleOwner
|
||||
) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() {
|
||||
|
||||
private val differ = AsyncListDiffer(this, FileDiffCallback())
|
||||
@@ -42,7 +42,8 @@ class ImagePreviewAdapter(
|
||||
|
||||
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
||||
val imageUrl = images[position]
|
||||
holder.bind(imageUrl)
|
||||
val fileType = FileManager(context, lifecycleOwner).getFileType(images[position])
|
||||
holder.bind(imageUrl,fileType)
|
||||
currentViewHolder = holder
|
||||
|
||||
currentMediaPlayer?.let {
|
||||
@@ -67,7 +68,7 @@ class ImagePreviewAdapter(
|
||||
private var seekHandler = Handler(Looper.getMainLooper())
|
||||
private var seekRunnable: Runnable? = null
|
||||
|
||||
fun bind(file: File) {
|
||||
fun bind(file: File, fileType: FileManager.FileType) {
|
||||
when (fileType) {
|
||||
FileManager.FileType.VIDEO -> {
|
||||
binding.imageView.visibility = View.GONE
|
||||
@@ -228,6 +229,7 @@ class ImagePreviewAdapter(
|
||||
|
||||
private fun playVideoAtPosition(position: Int) {
|
||||
val nextFile = images[position]
|
||||
val fileType = FileManager(context, lifecycleOwner).getFileType(images[position])
|
||||
if (fileType == FileManager.FileType.VIDEO) {
|
||||
val videoUri = Uri.fromFile(nextFile)
|
||||
binding.videoView.setVideoURI(videoUri)
|
||||
|
||||
@@ -1,59 +1,57 @@
|
||||
package devs.org.calculator.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.EditText
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import devs.org.calculator.callbacks.DialogActionsCallback
|
||||
import devs.org.calculator.R
|
||||
|
||||
class DialogUtil(private val context: Context) {
|
||||
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||
fun showMaterialDialogWithNaturalButton(
|
||||
title: String,
|
||||
message: String,
|
||||
positiveButton: String,
|
||||
negativeButton: String,
|
||||
neutralButton: String,
|
||||
callback: DialogActionsCallback
|
||||
) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(positiveButton) { dialog, _ ->
|
||||
// Handle positive button click
|
||||
callback.onPositiveButtonClicked()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(negativeButton) { dialog, _ ->
|
||||
// Handle negative button click
|
||||
callback.onNegativeButtonClicked()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNeutralButton(neutralButton) { dialog, _ ->
|
||||
callback.onNaturalButtonClicked()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
fun showMaterialDialog(
|
||||
title: String,
|
||||
message: String,
|
||||
positiveButton: String,
|
||||
negativeButton: String,
|
||||
callback: DialogActionsCallback
|
||||
positiveButtonText: String,
|
||||
neutralButtonText: String,
|
||||
callback: DialogCallback
|
||||
) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(positiveButton) { _, _ ->
|
||||
// Handle positive button click
|
||||
callback.onPositiveButtonClicked()
|
||||
}
|
||||
.setNegativeButton(negativeButton) { dialog, _ ->
|
||||
// Handle negative button click
|
||||
callback.onNegativeButtonClicked()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setPositiveButton(positiveButtonText) { _, _ -> callback.onPositiveButtonClicked() }
|
||||
.setNegativeButton(neutralButtonText) { _, _ -> callback.onNegativeButtonClicked() }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun createInputDialog(
|
||||
title: String,
|
||||
hint: String,
|
||||
callback: InputDialogCallback
|
||||
) {
|
||||
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_input, null)
|
||||
val editText = dialogView.findViewById<EditText>(R.id.editText)
|
||||
editText.hint = hint
|
||||
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(title)
|
||||
.setView(dialogView)
|
||||
.setPositiveButton(R.string.create) { _, _ ->
|
||||
callback.onPositiveButtonClicked(editText.text.toString())
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
interface DialogCallback {
|
||||
fun onPositiveButtonClicked()
|
||||
fun onNegativeButtonClicked()
|
||||
fun onNaturalButtonClicked()
|
||||
}
|
||||
|
||||
interface InputDialogCallback {
|
||||
fun onPositiveButtonClicked(input: String)
|
||||
}
|
||||
}
|
||||
@@ -41,31 +41,43 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
fun getHiddenDirectory(): File {
|
||||
val dir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs()
|
||||
val created = dir.mkdirs()
|
||||
if (!created) {
|
||||
throw RuntimeException("Failed to create hidden directory: ${dir.absolutePath}")
|
||||
}
|
||||
// Create .nomedia file to hide from media scanners
|
||||
File(dir, ".nomedia").createNewFile()
|
||||
val nomediaFile = File(dir, ".nomedia")
|
||||
if (!nomediaFile.exists()) {
|
||||
nomediaFile.createNewFile()
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun getFilesInHiddenDir(type: FileType): List<File> {
|
||||
val hiddenDir = getHiddenDirectory()
|
||||
val typeDir = File(hiddenDir, type.dirName)
|
||||
return if (typeDir.exists()) {
|
||||
typeDir.listFiles()?.filterNotNull()?.filter { it.name != ".nomedia" } ?: emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
if (!typeDir.exists()) {
|
||||
typeDir.mkdirs()
|
||||
File(typeDir, ".nomedia").createNewFile()
|
||||
}
|
||||
return typeDir.listFiles()?.filterNotNull()?.filter { it.name != ".nomedia" } ?: emptyList()
|
||||
}
|
||||
fun getFilesInHiddenDirFromFolder(type: FileType, folder: String): List<File> {
|
||||
val typeDir = File(folder)
|
||||
if (!typeDir.exists()) {
|
||||
typeDir.mkdirs()
|
||||
File(typeDir, ".nomedia").createNewFile()
|
||||
}
|
||||
return typeDir.listFiles()?.filterNotNull()?.filter { it.name != ".nomedia" } ?: emptyList()
|
||||
}
|
||||
|
||||
private fun copyFileToHiddenDir(uri: Uri, type: FileType): File? {
|
||||
private fun copyFileToHiddenDir(uri: Uri, type: FileType, currentDir: File? = null): File? {
|
||||
return try {
|
||||
val contentResolver = context.contentResolver
|
||||
|
||||
// Get the target directory
|
||||
val targetDir = File(Environment.getExternalStorageDirectory(), "$HIDDEN_DIR/${type.dirName}")
|
||||
val targetDir = currentDir ?: File(Environment.getExternalStorageDirectory(), "$HIDDEN_DIR/${type.dirName}")
|
||||
targetDir.mkdirs()
|
||||
File(targetDir, ".nomedia").createNewFile()
|
||||
|
||||
@@ -258,8 +270,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(activity, "Unable to open settings. Please grant permission manually.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(activity, "Permission already granted", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
// For Android 10 and below
|
||||
@@ -275,13 +285,14 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
suspend fun processMultipleFiles(
|
||||
uriList: List<Uri>,
|
||||
fileType: FileType,
|
||||
callback: FileProcessCallback
|
||||
callback: FileProcessCallback,
|
||||
currentDir: File? = null
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val copiedFiles = mutableListOf<File>()
|
||||
for (uri in uriList) {
|
||||
try {
|
||||
val file = copyFileToHiddenDir(uri, fileType)
|
||||
val file = copyFileToHiddenDir(uri, fileType, currentDir)
|
||||
file?.let { copiedFiles.add(it) }
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -297,12 +308,22 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
}
|
||||
}
|
||||
|
||||
fun getFileType(file: File): FileType {
|
||||
val extension = file.extension.lowercase()
|
||||
return when (extension) {
|
||||
"jpg", "jpeg", "png", "gif", "bmp", "webp" -> FileType.IMAGE
|
||||
"mp4", "avi", "mkv", "mov", "wmv", "flv", "webm", "3gp" -> FileType.VIDEO
|
||||
"mp3", "wav", "flac", "aac", "ogg", "m4a" -> FileType.AUDIO
|
||||
else -> FileType.DOCUMENT
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum class FileType(val dirName: String) {
|
||||
IMAGE(IMAGES_DIR),
|
||||
VIDEO(VIDEOS_DIR),
|
||||
AUDIO(AUDIO_DIR),
|
||||
DOCUMENT(DOCS_DIR)
|
||||
DOCUMENT(DOCS_DIR),
|
||||
ALL("all")
|
||||
}
|
||||
}
|
||||
75
app/src/main/java/devs/org/calculator/utils/FolderManager.kt
Normal file
75
app/src/main/java/devs/org/calculator/utils/FolderManager.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
package devs.org.calculator.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import java.io.File
|
||||
|
||||
class FolderManager(private val context: Context) {
|
||||
companion object {
|
||||
const val HIDDEN_DIR = ".CalculatorHide"
|
||||
}
|
||||
|
||||
fun createFolder(parentDir: File, folderName: String): Boolean {
|
||||
val newFolder = File(parentDir, folderName)
|
||||
return if (!newFolder.exists()) {
|
||||
newFolder.mkdirs()
|
||||
// Create .nomedia file to hide from media scanners
|
||||
File(newFolder, ".nomedia").createNewFile()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteFolder(folder: File): Boolean {
|
||||
return try {
|
||||
if (folder.exists() && folder.isDirectory) {
|
||||
// Delete all files in the folder first
|
||||
folder.listFiles()?.forEach { file ->
|
||||
if (file.isFile) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
// Then delete the folder itself
|
||||
folder.delete()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun getFoldersInDirectory(directory: File): List<File> {
|
||||
return if (directory.exists() && directory.isDirectory) {
|
||||
directory.listFiles()?.filter { it.isDirectory && it.name != ".nomedia" } ?: emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun getFilesInFolder(folder: File): List<File> {
|
||||
return if (folder.exists() && folder.isDirectory) {
|
||||
folder.listFiles()?.filter { it.isFile && it.name != ".nomedia" } ?: emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun moveFileToFolder(file: File, targetFolder: File): Boolean {
|
||||
return try {
|
||||
if (!targetFolder.exists()) {
|
||||
targetFolder.mkdirs()
|
||||
File(targetFolder, ".nomedia").createNewFile()
|
||||
}
|
||||
val newFile = File(targetFolder, file.name)
|
||||
file.copyTo(newFile, overwrite = true)
|
||||
file.delete()
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
9
app/src/main/res/anim/fab_close.xml
Normal file
9
app/src/main/res/anim/fab_close.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<scale xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="200"
|
||||
android:fromXScale="1.0"
|
||||
android:fromYScale="1.0"
|
||||
android:toXScale="0.0"
|
||||
android:toYScale="0.0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%" />
|
||||
9
app/src/main/res/anim/fab_open.xml
Normal file
9
app/src/main/res/anim/fab_open.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<scale xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="200"
|
||||
android:fromXScale="0.0"
|
||||
android:fromYScale="0.0"
|
||||
android:toXScale="1.0"
|
||||
android:toYScale="1.0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%" />
|
||||
7
app/src/main/res/anim/rotate_close.xml
Normal file
7
app/src/main/res/anim/rotate_close.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="200"
|
||||
android:fromDegrees="45"
|
||||
android:toDegrees="0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%" />
|
||||
7
app/src/main/res/anim/rotate_open.xml
Normal file
7
app/src/main/res/anim/rotate_open.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="200"
|
||||
android:fromDegrees="0"
|
||||
android:toDegrees="45"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%" />
|
||||
9
app/src/main/res/drawable/add_image.xml
Normal file
9
app/src/main/res/drawable/add_image.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M23,4v2h-3v3h-2L18,6h-3L15,4h3L18,1h2v3h3zM14.5,11c0.828,0 1.5,-0.672 1.5,-1.5S15.328,8 14.5,8 13,8.672 13,9.5s0.672,1.5 1.5,1.5zM18,14.234l-0.513,-0.57c-0.794,-0.885 -2.18,-0.885 -2.976,0l-0.655,0.73L9,9l-3,3.333L6,6h7L13,4L6,4c-1.105,0 -2,0.895 -2,2v12c0,1.105 0.895,2 2,2h12c1.105,0 2,-0.895 2,-2v-7h-2v3.234z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/document_add.xml
Normal file
9
app/src/main/res/drawable/document_add.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="205dp"
|
||||
android:height="205dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M15,12h-2v-2c0,-0.553 -0.447,-1 -1,-1s-1,0.447 -1,1v2h-2c-0.553,0 -1,0.447 -1,1s0.447,1 1,1h2v2c0,0.553 0.447,1 1,1s1,-0.447 1,-1v-2h2c0.553,0 1,-0.447 1,-1s-0.447,-1 -1,-1zM19.707,7.293l-4,-4c-0.187,-0.188 -0.441,-0.293 -0.707,-0.293h-8c-1.654,0 -3,1.346 -3,3v12c0,1.654 1.346,3 3,3h10c1.654,0 3,-1.346 3,-3v-10c0,-0.266 -0.105,-0.52 -0.293,-0.707zM17.586,8h-1.086c-0.827,0 -1.5,-0.673 -1.5,-1.5v-1.086l2.586,2.586zM17,19h-10c-0.552,0 -1,-0.448 -1,-1v-12c0,-0.552 0.448,-1 1,-1h7v1.5c0,1.379 1.121,2.5 2.5,2.5h1.5v9c0,0.552 -0.448,1 -1,1z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_add.xml
Normal file
10
app/src/main/res/drawable/ic_add.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/textColor"
|
||||
android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z"/>
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_back.xml
Normal file
12
app/src/main/res/drawable/ic_back.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M224,480h640a32,32 0,1 1,0 64H224a32,32 0,0 1,0 -64z"
|
||||
android:fillColor="@color/textColor"/>
|
||||
<path
|
||||
android:pathData="m237.2,512 l265.4,265.3a32,32 0,0 1,-45.3 45.3l-288,-288a32,32 0,0 1,0 -45.3l288,-288a32,32 0,1 1,45.3 45.3L237.2,512z"
|
||||
android:fillColor="@color/textColor"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_close.xml
Normal file
10
app/src/main/res/drawable/ic_close.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_create_new_folder.xml
Normal file
10
app/src/main/res/drawable/ic_create_new_folder.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M20,6h-8l-2,-2L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18L4,8h16v10zM14,12h-2v-2h-2v2L8,12v2h2v2h2v-2h2z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_delete.xml
Normal file
10
app/src/main/res/drawable/ic_delete.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
||||
</vector>
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/textColor"
|
||||
android:pathData="M14,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8L14,2zM16,18H8v-2h8V18zM16,14H8v-2h8V14zM13,9V3.5L18.5,9H13z"/>
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_folder.xml
Normal file
10
app/src/main/res/drawable/ic_folder.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorPrimary"
|
||||
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z"/>
|
||||
</vector>
|
||||
13
app/src/main/res/drawable/ic_folder_add.xml
Normal file
13
app/src/main/res/drawable/ic_folder_add.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M15,13h-2v2h-2v-2H9v-2h2V9h2v2h2v2z"/>
|
||||
</vector>
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/textColor"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_no_items.xml
Normal file
10
app/src/main/res/drawable/ic_no_items.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_video.xml
Normal file
10
app/src/main/res/drawable/ic_video.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17,10.5V7c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-3.5l4,4v-11l-4,4z"/>
|
||||
</vector>
|
||||
41
app/src/main/res/drawable/music_add.xml
Normal file
41
app/src/main/res/drawable/music_add.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M17,6.5H20M23,6.5H20M20,6.5V3.5M20,6.5V9.5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M6,16V5L14,4"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M15,14V10"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,19H13C14.105,19 15,18.105 15,17V14H12C10.895,14 10,14.895 10,16V17C10,18.105 10.895,19 12,19Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M3,21H4C5.105,21 6,20.105 6,19V16H3C1.895,16 1,16.895 1,18V19C1,20.105 1.895,21 3,21Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
@@ -2,7 +2,7 @@
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@color/textColor"
|
||||
<path android:fillColor="?attr/colorPrimary"
|
||||
android:pathData="M21.409,9.353C23.531,10.507 23.531,13.493 21.409,14.647L8.597,21.615C6.534,22.736 4,21.276 4,18.967L4,5.033C4,2.724 6.534,1.264 8.597,2.385L21.409,9.353Z"/>
|
||||
|
||||
</vector>
|
||||
|
||||
9
app/src/main/res/drawable/video_add.xml
Normal file
9
app/src/main/res/drawable/video_add.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M16,4c0.552,0 1,0.448 1,1v4.2l5.213,-3.65c0.226,-0.158 0.538,-0.103 0.697,0.124 0.058,0.084 0.09,0.184 0.09,0.286v12.08c0,0.276 -0.224,0.5 -0.5,0.5 -0.103,0 -0.203,-0.032 -0.287,-0.09L17,14.8V19c0,0.552 -0.448,1 -1,1H2c-0.552,0 -1,-0.448 -1,-1V5c0,-0.552 0.448,-1 1,-1h14zM8,8v3H5v2h2.999L8,16h2l-0.001,-3H13v-2h-3V8H8z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/wrong.xml
Normal file
9
app/src/main/res/drawable/wrong.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:pathData="M142,128l49,-49a9.9,9.9 0,0 0,-14 -14L128,114 79,65A9.9,9.9 0,0 0,65 79l49,49L65,177a9.9,9.9 0,0 0,14 14l49,-49 49,49a9.9,9.9 0,0 0,14 -14Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.AudioGalleryActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,10 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.DocumentsActivity">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -3,58 +3,143 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:visibility="gone"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="visible"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:layout_marginTop="50dp"
|
||||
android:padding="8dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/noItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/noItemsTxt"
|
||||
<LinearLayout
|
||||
android:id="@+id/noItems"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:padding="25dp"
|
||||
android:text="@string/no_items_available_add_one_by_clicking_on_the_plus_button"/>
|
||||
</LinearLayout>
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_no_items" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noItemsTxt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:text="@string/no_items_available_add_one_by_clicking_on_the_plus_button"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/actionModeBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/selectedCount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnDelete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:tint="@android:color/white" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_close"
|
||||
android:tint="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabCreateFolder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/create_folder"
|
||||
app:srcCompat="@drawable/ic_create_new_folder" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAdd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginBottom="35dp"
|
||||
android:contentDescription="Compose"
|
||||
app:iconPadding="8dp"
|
||||
app:icon="@android:drawable/ic_input_add"
|
||||
android:text="Add file"/>
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/add_files"
|
||||
app:srcCompat="@drawable/ic_add" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAddImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/add_image"
|
||||
app:srcCompat="@drawable/ic_image" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAddVideo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/add_video"
|
||||
app:srcCompat="@drawable/ic_video" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAddAudio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/add_audio"
|
||||
app:srcCompat="@drawable/ic_audio" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAddDocument"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/add_document"
|
||||
app:srcCompat="@drawable/ic_document" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
156
app/src/main/res/layout/activity_hidden.xml
Normal file
156
app/src/main/res/layout/activity_hidden.xml
Normal file
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.HiddenActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_back"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="#00000000"
|
||||
android:padding="7dp"
|
||||
android:id="@+id/back"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Folder Name"
|
||||
android:textSize="18sp"
|
||||
android:id="@+id/folderName"/>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/noItems"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_no_items" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noItemsTxt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:text="@string/no_items_available_add_one_by_clicking_on_the_plus_button"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/addImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/add_image"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/add_image"
|
||||
android:visibility="gone"
|
||||
app:fabCustomSize="48dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/addVideo"
|
||||
app:layout_constraintEnd_toEndOf="@+id/addVideo"
|
||||
app:layout_constraintStart_toStartOf="@+id/addVideo" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/addVideo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:src="@drawable/video_add"
|
||||
android:text="@string/add_image"
|
||||
android:visibility="gone"
|
||||
app:fabCustomSize="49dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/addAudio"
|
||||
app:layout_constraintEnd_toEndOf="@+id/addAudio"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/addAudio" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/addAudio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:src="@drawable/music_add"
|
||||
android:text="@string/add_image"
|
||||
app:fabCustomSize="51dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/addDocument"
|
||||
app:layout_constraintEnd_toEndOf="@+id/fabExpend"
|
||||
app:layout_constraintStart_toStartOf="@+id/fabExpend" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/addDocument"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/document_add"
|
||||
android:text="@string/add_image"
|
||||
android:layout_marginBottom="10dp"
|
||||
app:fabCustomSize="54dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/fabExpend"
|
||||
app:layout_constraintEnd_toEndOf="@+id/addFolder"
|
||||
app:layout_constraintStart_toStartOf="@+id/addFolder" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/addFolder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_folder_add"
|
||||
android:text="@string/add_image"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
app:fabCustomSize="57dp"
|
||||
/>
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabExpend"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:src="@drawable/ic_add"
|
||||
android:visibility="gone"
|
||||
android:text="@string/add_image"
|
||||
app:fabCustomSize="60dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.ImageGalleryActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.VideoGalleryActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
22
app/src/main/res/layout/dialog_input.xml
Normal file
22
app/src/main/res/layout/dialog_input.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/enter_folder_name">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:maxLines="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
48
app/src/main/res/layout/item_folder.xml
Normal file
48
app/src/main/res/layout/item_folder.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/folderIcon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_folder" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folderName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
60
app/src/main/res/layout/list_item_file.xml
Normal file
60
app/src/main/res/layout/list_item_file.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/fileIconImageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/add_image" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<ImageView
|
||||
android:id="@+id/videoPlay"
|
||||
android:layout_width="43dp"
|
||||
android:layout_height="43dp"
|
||||
android:visibility="gone"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/play"
|
||||
android:layout_gravity="center"/>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fileNameTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="5dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:text="File Name" />
|
||||
|
||||
</LinearLayout>
|
||||
15
app/src/main/res/layout/list_item_folder.xml
Normal file
15
app/src/main/res/layout/list_item_folder.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folderNameTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
android:text="Folder Name" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,6 +5,12 @@
|
||||
<string name="add_audio">Add Audio</string>
|
||||
<string name="add_video">Add Video</string>
|
||||
<string name="add_files">Add Files</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="file_options">File Options</string>
|
||||
<string name="rename_file">Rename File</string>
|
||||
<string name="share_file">Share File</string>
|
||||
<string name="add_document">Add Document</string>
|
||||
<string name="failed_to_hide_audio">Failed to hide Audios</string>
|
||||
<string name="failed_to_hide_documents">Failed to hide Documents</string>
|
||||
<string name="no_files_selected">No files selected</string>
|
||||
<string name="documents_hidden_successfully"> Documents hidden successfully</string>
|
||||
@@ -46,6 +52,11 @@
|
||||
<string name="image">IMAGE</string>
|
||||
<string name="video">VIDEO</string>
|
||||
<string name="audio">AUDIO</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="create">Create</string>
|
||||
<string name="delete_folder">Delete Folder</string>
|
||||
<string name="rename">Rename</string>
|
||||
<string name="cannot_delete_folder">Cannot Delete Folder</string>
|
||||
<string name="document">DOCUMENT</string>
|
||||
<string name="no_audio_player_found">No audio player found!</string>
|
||||
<string name="no_suitable_app_found_to_open_this_document">No suitable app found to open this document!</string>
|
||||
@@ -55,4 +66,18 @@
|
||||
<string name="no_items_available_add_one_by_clicking_on_the_plus_button">No Items Available, Add one by clicking on the</string>
|
||||
<string name="now_enter_button">Now Enter \'=\' button</string>
|
||||
<string name="enter_123456">Enter 123456</string>
|
||||
<string name="create_folder">Create Folder</string>
|
||||
<string name="enter_folder_name">Enter folder name</string>
|
||||
<string name="folder_already_exists">Folder already exists</string>
|
||||
<string name="folder_options">Folder Options</string>
|
||||
<string name="rename_folder">Rename Folder</string>
|
||||
<string name="enter_new_folder_name">Enter new folder name</string>
|
||||
<string name="failed_to_create_folder">Failed to create folder</string>
|
||||
<string name="are_you_sure_you_want_to_delete_this_folder">Are you sure you want to delete this folder?</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="error_loading_files">Error loading files</string>
|
||||
<string name="delete_items">Delete Items</string>
|
||||
<string name="are_you_sure_you_want_to_delete_selected_items">Are you sure you want to delete selected items?</string>
|
||||
<string name="items_deleted_successfully">Items deleted successfully</string>
|
||||
<string name="some_items_could_not_be_deleted">Some items could not be deleted</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user