Changes For Folder Feature

This commit is contained in:
Binondi
2025-06-01 16:47:45 +05:30
parent 48c4e04a28
commit ab737511a7
51 changed files with 1671 additions and 895 deletions

6
.idea/AndroidProjectSystem.xml generated Normal file
View 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>

View File

@@ -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>

View File

@@ -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
}
}

View File

@@ -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()
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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)
}
}

View File

@@ -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) {

View File

@@ -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
}
}
)
}

View File

@@ -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)
}
}

View File

@@ -1,76 +1,123 @@
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)
// Callback interface for handling file operations
interface FileOperationCallback {
fun onFileDeleted(file: File)
fun onFileRenamed(oldFile: File, newFile: File)
fun onRefreshNeeded()
}
FileManager.FileType.VIDEO -> {
context.getString(R.string.video)
}
FileManager.FileType.AUDIO -> {
context.getString(R.string.audio)
}
else -> context.getString(R.string.document)
}
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 -> {
loadImageThumbnail(file)
fileNameTextView.visibility = View.GONE
playIcon.visibility = View.GONE
}
FileManager.FileType.VIDEO -> {
loadVideoThumbnail(file)
fileNameTextView.visibility = View.GONE
playIcon.visibility = View.VISIBLE
}
else -> {
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)
}
FileManager.FileType.VIDEO -> {
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)
}
else -> {
private fun loadFileIcon(fileType: FileManager.FileType) {
val resourceId = when (fileType) {
FileManager.FileType.AUDIO -> R.drawable.ic_audio
FileManager.FileType.DOCUMENT -> R.drawable.ic_document
@@ -78,120 +125,217 @@ class FileAdapter(
}
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()
if (isSelectionMode) {
toggleSelection(adapterPosition)
return@setOnClickListener
}
openFile(file, fileType)
}
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)
}
}
}
itemView.setOnLongClickListener {
if (!isSelectionMode) {
showFileOptionsDialog(file)
true
} else {
false
}
}
}
val fileUri = FileManager.FileManager().getContentUriImage(context, file, fileType)
if (fileUri == null) {
Toast.makeText(context, "Unable to access file: $file", Toast.LENGTH_SHORT)
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)
}
}
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()
}
}
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()
}
}
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()
return@setOnLongClickListener true
}
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
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))
)
}
val currentList = currentList.toMutableList()
currentList.remove(file)
submitList(currentList)
private fun toggleSelection(position: Int) {
if (selectedItems.contains(position)) {
selectedItems.remove(position)
} else {
selectedItems.add(position)
}
override fun onNegativeButtonClicked() {
FileManager(context, lifecycleOwner).copyFileToNormalDir(fileUri)
val currentList = currentList.toMutableList()
currentList.remove(file)
submitList(currentList)
if (selectedItems.isEmpty()) {
isSelectionMode = false
}
override fun onNaturalButtonClicked() {
notifyItemChanged(position)
}
}
)
return@setOnLongClickListener true
}
}
}
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
}
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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")
}
}

View 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
}
}
}

View 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%" />

View 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%" />

View 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%" />

View 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%" />

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:layout_marginTop="50dp"
android:padding="8dp" />
<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>
<LinearLayout
android:id="@+id/noItems"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
android:orientation="vertical">
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:textSize="16sp"
android:layout_marginTop="8dp"
android:gravity="center"
android:padding="25dp"
android:text="@string/no_items_available_add_one_by_clicking_on_the_plus_button"/>
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.ExtendedFloatingActionButton
<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>

View 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>

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>