8 Commits
1.2 ... 1.3

Author SHA1 Message Date
Binondi
970cde737a Changes For Folder Feature 2025-06-03 15:25:24 +05:30
Binondi
eb7f61fbc8 Changes For Folder Feature 2025-06-03 15:09:19 +05:30
Binondi
4069ddc200 Changes For Folder Feature 2025-06-03 00:52:41 +05:30
Binondi
88dcc844c8 Changes For Folder Feature 2025-06-02 12:44:48 +05:30
Binondi
f8575da2a9 Changes For Folder Feature 2025-06-01 21:37:13 +05:30
Binondi
ab737511a7 Changes For Folder Feature 2025-06-01 16:47:45 +05:30
Binondi
48c4e04a28 Bug Fixes, Added Empty item message. 2025-04-14 21:10:51 +05:30
Binondi
713fd3540f Bug Fixes, Added Empty item message. 2025-04-14 12:44:06 +05:30
95 changed files with 4456 additions and 1453 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

@@ -11,28 +11,28 @@ android {
applicationId = "devs.org.calculator"
minSdk = 26
targetSdk = 34
versionCode = 3
versionName = "1.2"
versionCode = 4
versionName = "1.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
debug {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
@@ -43,6 +43,21 @@ android {
buildFeatures{
viewBinding = true
}
packaging {
resources {
excludes += listOf(
"META-INF/DEPENDENCIES",
"META-INF/LICENSE",
"META-INF/LICENSE.txt",
"META-INF/license.txt",
"META-INF/NOTICE",
"META-INF/NOTICE.txt",
"META-INF/notice.txt",
"META-INF/ASL2.0"
)
}
}
}
dependencies {
@@ -64,4 +79,5 @@ dependencies {
implementation(libs.photoview)
implementation(libs.androidx.viewpager)
implementation(libs.zoomage)
implementation(libs.lottie)
}

View File

@@ -67,4 +67,4 @@
# Keep specific activities with special code in onCreate
-keepclassmembers class * extends android.app.Activity {
public void onCreate(android.os.Bundle);
}
}

View File

@@ -1,64 +1,61 @@
<?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:name=".activities.ViewFolderActivity"
android:exported="false" />
<activity
android:name=".activities.AudioGalleryActivity"
android:name=".activities.SettingsActivity"
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" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activities.HiddenVaultActivity"
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 +66,6 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,23 @@
package devs.org.calculator
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.google.android.material.color.DynamicColors
class CalculatorApp : Application() {
override fun onCreate() {
super.onCreate()
// Apply dynamic colors to enable Material You theming
DynamicColors.applyToActivitiesIfAvailable(this)
// Initialize theme settings
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
// Apply saved theme mode
val themeMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
AppCompatDelegate.setDefaultNightMode(themeMode)
// Apply dynamic colors only if dynamic theme is enabled
if (prefs.getBoolean("dynamic_theme", true)) {
DynamicColors.applyToActivitiesIfAvailable(this)
}
}
}

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,536 @@
package devs.org.calculator.activities
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.EditText
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import devs.org.calculator.R
import devs.org.calculator.adapters.FolderAdapter
import devs.org.calculator.adapters.ListFolderAdapter
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 devs.org.calculator.utils.PrefsUtil
import java.io.File
class HiddenActivity : AppCompatActivity() {
private lateinit var binding: ActivityHiddenBinding
private lateinit var fileManager: FileManager
private lateinit var folderManager: FolderManager
private lateinit var dialogUtil: DialogUtil
private var currentFolder: File? = null
private var folderAdapter: FolderAdapter? = null
private var listFolderAdapter: ListFolderAdapter? = null
private val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
private val mainHandler = Handler(Looper.getMainLooper())
companion object {
private const val TAG = "HiddenActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHiddenBinding.inflate(layoutInflater)
setContentView(binding.root)
fileManager = FileManager(this, this)
folderManager = FolderManager(this)
dialogUtil = DialogUtil(this)
setupInitialUIState()
setupClickListeners()
setupBackPressedHandler()
fileManager.askPermission(this)
refreshCurrentView()
}
private fun setupInitialUIState() {
binding.addFolder.visibility = View.VISIBLE
binding.settings.visibility = View.VISIBLE
binding.folderOrientation.visibility = View.VISIBLE
binding.deleteSelected.visibility = View.GONE
binding.delete.visibility = View.GONE
binding.menuButton.visibility = View.GONE
}
private fun setupClickListeners() {
binding.settings.setOnClickListener {
startActivity(Intent(this, SettingsActivity::class.java))
}
binding.back.setOnClickListener {
handleBackPress()
}
binding.addFolder.setOnClickListener {
createNewFolder()
}
binding.deleteSelected.setOnClickListener {
deleteSelectedItems()
}
binding.delete.setOnClickListener {
deleteSelectedItems()
}
binding.edit.setOnClickListener {
editSelectedFolder()
}
binding.folderOrientation.setOnClickListener {
// Switch between grid mode and list mode
val currentIsList = PrefsUtil(this).getBoolean("isList", false)
val newIsList = !currentIsList
if (newIsList) {
// Switch to list view
showListUI()
PrefsUtil(this).setBoolean("isList", true)
binding.folderOrientation.setImageResource(R.drawable.ic_grid)
} else {
// Switch to grid view
showGridUI()
PrefsUtil(this).setBoolean("isList", false)
binding.folderOrientation.setImageResource(R.drawable.ic_list)
}
}
}
private fun showGridUI() {
listFoldersInHiddenDirectory()
}
private fun showListUI() {
listFoldersInHiddenDirectoryListStyle()
}
private fun listFoldersInHiddenDirectoryListStyle() {
try {
if (!hiddenDir.exists()) {
fileManager.getHiddenDirectory()
}
if (hiddenDir.exists() && hiddenDir.isDirectory) {
val folders = folderManager.getFoldersInDirectory(hiddenDir)
if (folders.isNotEmpty()) {
showFolderListStyle(folders)
} else {
showEmptyState()
}
} else {
Log.e(TAG, "Hidden directory is not accessible: ${hiddenDir.absolutePath}")
showEmptyState()
}
} catch (e: Exception) {
Log.e(TAG, "Error listing folders: ${e.message}")
showEmptyState()
}
}
private fun setupBackPressedHandler() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
handleBackPress()
}
})
}
private fun createNewFolder() {
val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_input, null)
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
MaterialAlertDialogBuilder(this)
.setTitle("Enter Folder Name To Create")
.setView(dialogView)
.setPositiveButton("Create") { dialog, _ ->
val newName = inputEditText.text.toString().trim()
if (newName.isNotEmpty()) {
try {
folderManager.createFolder(hiddenDir, newName)
refreshCurrentView()
} catch (e: Exception) {
Log.e(TAG, "Error creating folder: ${e.message}")
Toast.makeText(
this@HiddenActivity,
"Failed to create folder",
Toast.LENGTH_SHORT
).show()
}
}
dialog.dismiss()
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.cancel()
}
.show()
}
override fun onResume() {
super.onResume()
setupFlagSecure()
}
private fun setupFlagSecure() {
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
if (prefs.getBoolean("screenshot_restriction", true)) {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
}
}
private fun listFoldersInHiddenDirectory() {
try {
if (!hiddenDir.exists()) {
fileManager.getHiddenDirectory()
}
if (hiddenDir.exists() && hiddenDir.isDirectory) {
val folders = folderManager.getFoldersInDirectory(hiddenDir)
if (folders.isNotEmpty()) {
showFolderList(folders)
} else {
showEmptyState()
}
} else {
Log.e(TAG, "Hidden directory is not accessible: ${hiddenDir.absolutePath}")
showEmptyState()
}
} catch (e: Exception) {
Log.e(TAG, "Error listing folders: ${e.message}")
showEmptyState()
}
}
private fun showFolderList(folders: List<File>) {
binding.noItems.visibility = View.GONE
binding.recyclerView.visibility = View.VISIBLE
// Clear the existing adapter to avoid conflicts
listFolderAdapter = null
binding.recyclerView.layoutManager = GridLayoutManager(this, 2)
folderAdapter = FolderAdapter(
onFolderClick = { clickedFolder ->
startActivity(Intent(this,ViewFolderActivity::class.java).putExtra("folder",clickedFolder.toString()))
},
onFolderLongClick = {
enterFolderSelectionMode()
},
onSelectionModeChanged = { isSelectionMode ->
handleFolderSelectionModeChange(isSelectionMode)
},
onSelectionCountChanged = { selectedCount ->
updateEditButtonVisibility()
}
)
binding.recyclerView.adapter = folderAdapter
folderAdapter?.submitList(folders)
// Ensure proper icon state for folder view
if (folderAdapter?.isInSelectionMode() != true) {
showFolderViewIcons()
}
}
private fun showFolderListStyle(folders: List<File>) {
binding.noItems.visibility = View.GONE
binding.recyclerView.visibility = View.VISIBLE
// Clear the existing adapter to avoid conflicts
folderAdapter = null
binding.recyclerView.layoutManager = GridLayoutManager(this, 1)
listFolderAdapter = ListFolderAdapter(
onFolderClick = { clickedFolder ->
startActivity(Intent(this,ViewFolderActivity::class.java).putExtra("folder",clickedFolder.toString()))
},
onFolderLongClick = {
enterFolderSelectionMode()
},
onSelectionModeChanged = { isSelectionMode ->
handleFolderSelectionModeChange(isSelectionMode)
},
onSelectionCountChanged = { selectedCount ->
updateEditButtonVisibility()
}
)
binding.recyclerView.adapter = listFolderAdapter
listFolderAdapter?.submitList(folders)
// Ensure proper icon state for folder view
if (listFolderAdapter?.isInSelectionMode() != true) {
showFolderViewIcons()
}
}
private fun updateEditButtonVisibility() {
val selectedCount = when {
folderAdapter != null -> folderAdapter?.getSelectedItems()?.size ?: 0
listFolderAdapter != null -> listFolderAdapter?.getSelectedItems()?.size ?: 0
else -> 0
}
binding.edit.visibility = if (selectedCount == 1) View.VISIBLE else View.GONE
}
private fun showEmptyState() {
binding.noItems.visibility = View.VISIBLE
binding.recyclerView.visibility = View.GONE
}
private fun enterFolderSelectionMode() {
showFolderSelectionIcons()
}
private fun refreshCurrentView() {
val isList = PrefsUtil(this).getBoolean("isList", false)
if (isList) {
binding.folderOrientation.setImageResource(R.drawable.ic_grid)
listFoldersInHiddenDirectoryListStyle()
} else {
binding.folderOrientation.setImageResource(R.drawable.ic_list)
listFoldersInHiddenDirectory()
}
}
private fun deleteSelectedItems() {
deleteSelectedFolders()
}
private fun deleteSelectedFolders() {
val selectedFolders = when {
folderAdapter != null -> folderAdapter?.getSelectedItems() ?: emptyList()
listFolderAdapter != null -> listFolderAdapter?.getSelectedItems() ?: emptyList()
else -> emptyList()
}
if (selectedFolders.isNotEmpty()) {
dialogUtil.showMaterialDialog(
getString(R.string.delete_items),
getString(R.string.are_you_sure_you_want_to_delete_selected_items),
getString(R.string.delete),
getString(R.string.cancel),
object : DialogUtil.DialogCallback {
override fun onPositiveButtonClicked() {
performFolderDeletion(selectedFolders)
}
override fun onNegativeButtonClicked() {
// Do nothing
}
override fun onNaturalButtonClicked() {
// Do nothing
}
}
)
}
}
private fun performFolderDeletion(selectedFolders: List<File>) {
var allDeleted = true
selectedFolders.forEach { folder ->
if (!folderManager.deleteFolder(folder)) {
allDeleted = false
Log.e(TAG, "Failed to delete folder: ${folder.name}")
}
}
val message = if (allDeleted) {
getString(R.string.folder_deleted_successfully)
} else {
getString(R.string.some_items_could_not_be_deleted)
}
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// Clear selection from both adapters
folderAdapter?.clearSelection()
listFolderAdapter?.clearSelection()
// This will trigger the selection mode change callback and show proper icons
exitFolderSelectionMode()
// Refresh the current view based on orientation
refreshCurrentView()
}
private fun handleBackPress() {
// Check if folder adapters are in selection mode
if (folderAdapter?.onBackPressed() == true || listFolderAdapter?.onBackPressed() == true) {
return
}
// Handle navigation back
if (currentFolder != null) {
navigateBackToFolders()
} else {
finish()
}
}
private fun navigateBackToFolders() {
currentFolder = null
// Clean up file adapter
refreshCurrentView()
binding.folderName.text = getString(R.string.hidden_space)
// Set proper icons for folder view
showFolderViewIcons()
}
override fun onDestroy() {
super.onDestroy()
// Remove any pending callbacks
mainHandler.removeCallbacksAndMessages(null)
}
//visibility related code
private fun showFolderViewIcons() {
binding.folderOrientation.visibility = View.VISIBLE
binding.settings.visibility = View.VISIBLE
binding.delete.visibility = View.GONE
binding.deleteSelected.visibility = View.GONE
binding.menuButton.visibility = View.GONE
binding.addFolder.visibility = View.VISIBLE
binding.edit.visibility = View.GONE
// Ensure FABs are properly managed
if (currentFolder == null) {
binding.addFolder.visibility = View.VISIBLE
}
}
private fun showFolderSelectionIcons() {
binding.folderOrientation.visibility = View.GONE
binding.settings.visibility = View.GONE
binding.delete.visibility = View.VISIBLE
binding.deleteSelected.visibility = View.VISIBLE
binding.menuButton.visibility = View.GONE
binding.addFolder.visibility = View.GONE
// Update edit button visibility based on current selection count
updateEditButtonVisibility()
}
private fun exitFolderSelectionMode() {
showFolderViewIcons()
}
private fun handleFolderSelectionModeChange(isSelectionMode: Boolean) {
if (!isSelectionMode) {
exitFolderSelectionMode()
} else {
enterFolderSelectionMode()
}
// Always update edit button visibility when selection mode changes
updateEditButtonVisibility()
}
private fun editSelectedFolder() {
val selectedFolders = when {
folderAdapter != null -> folderAdapter?.getSelectedItems() ?: emptyList()
listFolderAdapter != null -> listFolderAdapter?.getSelectedItems() ?: emptyList()
else -> emptyList()
}
if (selectedFolders.size != 1) {
Toast.makeText(this, "Please select exactly one folder to edit", Toast.LENGTH_SHORT).show()
return
}
val folder = selectedFolders[0]
showEditFolderDialog(folder)
}
private fun showEditFolderDialog(folder: File) {
val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_input, null)
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
inputEditText.setText(folder.name)
inputEditText.selectAll()
MaterialAlertDialogBuilder(this)
.setTitle("Rename Folder")
.setView(dialogView)
.setPositiveButton("Rename") { dialog, _ ->
val newName = inputEditText.text.toString().trim()
if (newName.isNotEmpty() && newName != folder.name) {
if (isValidFolderName(newName)) {
renameFolder(folder, newName)
} else {
Toast.makeText(this, "Invalid folder name", Toast.LENGTH_SHORT).show()
}
}
dialog.dismiss()
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.cancel()
}
.show()
}
private fun isValidFolderName(folderName: String): Boolean {
val forbiddenChars = charArrayOf('/', '\\', ':', '*', '?', '"', '<', '>', '|')
return folderName.isNotBlank() &&
folderName.none { it in forbiddenChars } &&
!folderName.startsWith(".") &&
folderName.length <= 255
}
private fun renameFolder(oldFolder: File, newName: String) {
val parentDir = oldFolder.parentFile
if (parentDir != null) {
val newFolder = File(parentDir, newName)
if (newFolder.exists()) {
Toast.makeText(this, "Folder with this name already exists", Toast.LENGTH_SHORT).show()
return
}
if (oldFolder.renameTo(newFolder)) {
// Clear selection from both adapters
folderAdapter?.clearSelection()
listFolderAdapter?.clearSelection()
// Exit selection mode
exitFolderSelectionMode()
refreshCurrentView()
} else {
Toast.makeText(this, "Failed to rename folder", Toast.LENGTH_SHORT).show()
}
}
}
}

View File

@@ -1,36 +0,0 @@
package devs.org.calculator.activities
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import devs.org.calculator.databinding.ActivityHiddenVaultBinding
import devs.org.calculator.utils.FileManager
class HiddenVaultActivity : AppCompatActivity() {
private lateinit var binding: ActivityHiddenVaultBinding
private lateinit var fileManager: FileManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHiddenVaultBinding.inflate(layoutInflater)
setContentView(binding.root)
fileManager = FileManager(this, this)
setupNavigation()
}
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
@@ -33,6 +33,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
private lateinit var baseDocumentTreeUri: Uri
private val dialogUtil = DialogUtil(this)
private val fileManager = FileManager(this, this)
private val sp by lazy { getSharedPreferences("app", MODE_PRIVATE) }
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
@@ -44,16 +45,36 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
handleActivityResult(result)
}
if (sp.getBoolean("isFirst", true)){
binding.display.text = getString(R.string.enter_123456)
}
// Ask permission
if(!Environment.isExternalStorageManager()) {
dialogUtil.showMaterialDialog(
"Storage Permission",
"To ensure the app works properly and allows you to easily hide or unhide your private files, please grant storage access permission.\n" +
"To ensure the app works properly and allows you to easily hide or un-hide your private files, please grant storage access permission.\n" +
"\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")
@@ -285,6 +306,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
private fun calculateResult() {
if (currentExpression == "123456") {
val intent = Intent(this, SetupPasswordActivity::class.java)
sp.edit().putBoolean("isFirst", false).apply()
intent.putExtra("password", currentExpression)
startActivity(intent)
clearDisplay()
@@ -292,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()
@@ -300,10 +322,8 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
}
try {
// Replace '×' with '*' for the expression evaluator
var processedExpression = currentExpression.replace("×", "*")
// Process percentages in the expression
if (processedExpression.contains("%")) {
processedExpression = preprocessExpression(processedExpression)
}
@@ -338,8 +358,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
}
try {
// Don't show preview result if the expression ends with an operator
// (but allow percentage at the end)
if (currentExpression.isEmpty() ||
(isOperator(currentExpression.last().toString()) && currentExpression.last() != '%')) {
binding.total.text = ""
@@ -361,8 +379,9 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
} else {
String.format("%.2f", result)
}
binding.total.text = formattedResult
if (sp.getBoolean("isFirst", true) && (currentExpression == "123456" || binding.display.text.toString() == "123456")){
binding.total.text = getString(R.string.now_enter_button)
}else binding.total.text = formattedResult
} catch (e: Exception) {
binding.total.text = ""
}
@@ -392,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

@@ -2,12 +2,12 @@ package devs.org.calculator.activities
import android.net.Uri
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
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 +20,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
@@ -30,13 +31,15 @@ class PreviewActivity : AppCompatActivity() {
binding = ActivityPreviewBinding.inflate(layoutInflater)
setContentView(binding.root)
fileManager = FileManager(this, this)
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()
@@ -48,6 +51,25 @@ class PreviewActivity : AppCompatActivity() {
}
})
binding.back.setOnClickListener {
finish()
}
}
override fun onResume() {
super.onResume()
setupFlagSecure()
}
private fun setupFlagSecure() {
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
if (prefs.getBoolean("screenshot_restriction", true)) {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
}
}
private fun setupFileType() {
@@ -72,7 +94,7 @@ class PreviewActivity : AppCompatActivity() {
}
private fun setupImagePreview() {
adapter = ImagePreviewAdapter(this, filetype)
adapter = ImagePreviewAdapter(this, this)
adapter.images = files
binding.viewPager.adapter = adapter
@@ -103,14 +125,14 @@ class PreviewActivity : AppCompatActivity() {
private fun clickListeners() {
binding.delete.setOnClickListener {
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype)
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem])
if (fileUri != null) {
dialogUtil.showMaterialDialog(
getString(R.string.delete_file),
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,27 +141,26 @@ class PreviewActivity : AppCompatActivity() {
}
override fun onNegativeButtonClicked() {
// Handle negative button click
}
override fun onNaturalButtonClicked() {
// Handle neutral button click
}
}
)
}
}
binding.unHide.setOnClickListener {
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype)
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem])
if (fileUri != null) {
dialogUtil.showMaterialDialog(
getString(R.string.un_hide_file),
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 +169,12 @@ class PreviewActivity : AppCompatActivity() {
}
override fun onNegativeButtonClicked() {
// Handle negative button click
}
override fun onNaturalButtonClicked() {
// Handle neutral button click
}
}
)
}

View File

@@ -0,0 +1,154 @@
package devs.org.calculator.activities
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import devs.org.calculator.R
import devs.org.calculator.databinding.ActivitySettingsBinding
class SettingsActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsBinding
private lateinit var prefs: SharedPreferences
private val DEV_GITHUB_URL = "https://github.com/binondi"
private val GITHUB_URL = "$DEV_GITHUB_URL/calculator-hide-files"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
setupUI()
loadSettings()
setupListeners()
}
private fun setupUI() {
binding.back.setOnClickListener {
onBackPressed()
}
}
private fun loadSettings() {
binding.dynamicThemeSwitch.isChecked = prefs.getBoolean("dynamic_theme", true)
val themeMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
binding.themeModeSwitch.isChecked = themeMode != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
when (themeMode) {
AppCompatDelegate.MODE_NIGHT_YES -> binding.darkThemeRadio.isChecked = true
AppCompatDelegate.MODE_NIGHT_NO -> binding.lightThemeRadio.isChecked = true
else -> binding.systemThemeRadio.isChecked = true
}
binding.screenshotRestrictionSwitch.isChecked = prefs.getBoolean("screenshot_restriction", true)
binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true)
updateThemeModeVisibility()
}
private fun setupListeners() {
binding.githubButton.setOnClickListener {
openUrl(GITHUB_URL)
}
binding.devGithubButton.setOnClickListener {
openUrl(DEV_GITHUB_URL)
}
binding.dynamicThemeSwitch.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean("dynamic_theme", isChecked).apply()
if (!isChecked) {
showThemeModeDialog()
}
}
binding.themeModeSwitch.setOnCheckedChangeListener { _, isChecked ->
binding.themeRadioGroup.visibility = if (isChecked) View.VISIBLE else View.GONE
if (!isChecked) {
binding.systemThemeRadio.isChecked = true
applyThemeMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
binding.themeRadioGroup.setOnCheckedChangeListener { _, checkedId ->
val themeMode = when (checkedId) {
R.id.lightThemeRadio -> AppCompatDelegate.MODE_NIGHT_NO
R.id.darkThemeRadio -> AppCompatDelegate.MODE_NIGHT_YES
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
applyThemeMode(themeMode)
}
binding.screenshotRestrictionSwitch.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean("screenshot_restriction", isChecked).apply()
if (isChecked) {
enableScreenshotRestriction()
} else {
disableScreenshotRestriction()
}
}
binding.showFileNames.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean("showFileName", isChecked).apply()
}
}
private fun updateThemeModeVisibility() {
binding.themeRadioGroup.visibility = if (binding.themeModeSwitch.isChecked) View.VISIBLE else View.GONE
}
private fun showThemeModeDialog() {
MaterialAlertDialogBuilder(this)
.setTitle("Theme Mode")
.setMessage("Would you like to set a specific theme mode?")
.setPositiveButton("Yes") { _, _ ->
binding.themeModeSwitch.isChecked = true
}
.setNegativeButton("No") { _, _ ->
binding.systemThemeRadio.isChecked = true
applyThemeMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
.show()
}
private fun applyThemeMode(themeMode: Int) {
prefs.edit().putInt("theme_mode", themeMode).apply()
AppCompatDelegate.setDefaultNightMode(themeMode)
}
private fun enableScreenshotRestriction() {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
}
private fun disableScreenshotRestriction() {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
private fun openUrl(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
Snackbar.make(binding.root, "Could not open URL", Snackbar.LENGTH_SHORT).show()
}
}
}

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

@@ -0,0 +1,623 @@
package devs.org.calculator.activities
import android.annotation.SuppressLint
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.bottomsheet.BottomSheetDialog
import devs.org.calculator.R
import devs.org.calculator.adapters.FileAdapter
import devs.org.calculator.adapters.FolderSelectionAdapter
import devs.org.calculator.callbacks.FileProcessCallback
import devs.org.calculator.databinding.ActivityViewFolderBinding
import devs.org.calculator.databinding.ProccessingDialogBinding
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 kotlinx.coroutines.launch
import java.io.File
class ViewFolderActivity : 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: ActivityViewFolderBinding
private val mainHandler = Handler(Looper.getMainLooper())
private lateinit var fileManager: FileManager
private lateinit var folderManager: FolderManager
private lateinit var dialogUtil: DialogUtil
private var fileAdapter: FileAdapter? = null
private var currentFolder: File? = null
private val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
private lateinit var pickImageLauncher: ActivityResultLauncher<Intent>
private lateinit var prefs: SharedPreferences
private var customDialog: androidx.appcompat.app.AlertDialog? = null
private var dialogShowTime: Long = 0
private val MINIMUM_DIALOG_DURATION = 1200L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityViewFolderBinding.inflate(layoutInflater)
setContentView(binding.root)
setupAnimations()
initialize()
setupClickListeners()
closeFabs()
val folder = intent.getStringExtra("folder").toString()
currentFolder = File(folder)
if (currentFolder != null){
openFolder(currentFolder!!)
}else {
showEmptyState()
}
setupActivityResultLaunchers()
}
private fun initialize() {
fileManager = FileManager(this, this)
folderManager = FolderManager(this)
dialogUtil = DialogUtil(this)
prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
}
private fun setupActivityResultLaunchers() {
pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
handleFilePickerResult(result.data)
}
}
}
private fun handleFilePickerResult(data: Intent?) {
val clipData = 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 {
data?.data?.let { uriList.add(it) }
}
if (uriList.isNotEmpty()) {
processSelectedFiles(uriList)
} else {
Toast.makeText(this, getString(R.string.no_files_selected), Toast.LENGTH_SHORT).show()
}
}
@SuppressLint("SetTextI18n")
private fun showCustomDialog(count: Int) {
val dialogView = ProccessingDialogBinding.inflate(layoutInflater)
customDialog = MaterialAlertDialogBuilder(this)
.setView(dialogView.root)
.setCancelable(false)
.create()
dialogView.title.text = "Hiding $count files"
customDialog?.show()
dialogShowTime = System.currentTimeMillis()
}
private fun dismissCustomDialog() {
val currentTime = System.currentTimeMillis()
val elapsedTime = currentTime - dialogShowTime
if (elapsedTime < MINIMUM_DIALOG_DURATION) {
val remainingTime = MINIMUM_DIALOG_DURATION - elapsedTime
mainHandler.postDelayed({
customDialog?.dismiss()
customDialog = null
updateFilesToAdapter()
}, remainingTime)
} else {
customDialog?.dismiss()
customDialog = null
updateFilesToAdapter()
}
}
private fun updateFilesToAdapter() {
openFolder(currentFolder!!)
}
private fun processSelectedFiles(uriList: List<Uri>) {
val targetFolder = currentFolder ?: hiddenDir
if (!targetFolder.exists()) {
targetFolder.mkdirs()
File(targetFolder, ".nomedia").createNewFile()
}
showCustomDialog(uriList.size)
lifecycleScope.launch {
try {
fileManager.processMultipleFiles(uriList, targetFolder,
object : FileProcessCallback {
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
mainHandler.post {
mainHandler.postDelayed({
dismissCustomDialog()
}, 1000)
}
}
override fun onFileProcessFailed() {
mainHandler.post {
Toast.makeText(
this@ViewFolderActivity,
getString(R.string.failed_to_hide_files),
Toast.LENGTH_SHORT
).show()
dismissCustomDialog()
}
}
})
} catch (e: Exception) {
mainHandler.post {
Toast.makeText(
this@ViewFolderActivity,
getString(R.string.there_was_a_problem_in_the_folder),
Toast.LENGTH_SHORT
).show()
dismissCustomDialog()
}
}
}
}
private fun refreshCurrentView() {
if (currentFolder != null) {
refreshCurrentFolder()
}
}
override fun onResume() {
super.onResume()
refreshCurrentFolder()
}
private fun openFolder(folder: File) {
// Ensure folder exists and has .nomedia file
if (!folder.exists()) {
folder.mkdirs()
File(folder, ".nomedia").createNewFile()
}
val files = folderManager.getFilesInFolder(folder)
binding.folderName.text = folder.name
if (files.isNotEmpty()) {
showFileList(files, folder)
} else {
showEmptyState()
}
}
private fun showEmptyState() {
binding.noItems.visibility = View.VISIBLE
binding.recyclerView.visibility = View.GONE
}
private fun showFileList(files: List<File>, folder: File) {
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
// Clean up previous adapter
fileAdapter?.cleanup()
fileAdapter = FileAdapter(this, this, folder, prefs.getBoolean("showFileName", true),
onFolderLongClick = { isSelected ->
handleFileSelectionModeChange(isSelected)
}).apply {
setFileOperationCallback(object : FileAdapter.FileOperationCallback {
override fun onFileDeleted(file: File) {
refreshCurrentFolder()
}
override fun onFileRenamed(oldFile: File, newFile: File) {
refreshCurrentFolder()
}
override fun onRefreshNeeded() {
refreshCurrentFolder()
}
override fun onSelectionModeChanged(isSelectionMode: Boolean, selectedCount: Int) {
handleFileSelectionModeChange(isSelectionMode)
}
override fun onSelectionCountChanged(selectedCount: Int) {
updateSelectionCountDisplay(selectedCount)
}
})
submitList(files)
}
binding.recyclerView.adapter = fileAdapter
binding.recyclerView.visibility = View.VISIBLE
binding.noItems.visibility = View.GONE
binding.menuButton.setOnClickListener {
fileAdapter?.let { adapter ->
showFileOptionsMenu(adapter.getSelectedItems())
}
}
showFileViewIcons()
}
@Deprecated("This method has been deprecated in favor of using the\n {@link OnBackPressedDispatcher} via {@link #getOnBackPressedDispatcher()}.\n The OnBackPressedDispatcher controls how back button events are dispatched\n to one or more {@link OnBackPressedCallback} objects.")
@SuppressLint("MissingSuperCall")
override fun onBackPressed() {
handleBackPress()
}
private fun handleBackPress() {
if (fileAdapter?.onBackPressed() == true) {
return
}
super.onBackPressed()
}
private fun handleFileSelectionModeChange(isSelectionMode: Boolean) {
if (isSelectionMode) {
showFileSelectionIcons()
} else {
showFileViewIcons()
}
}
private fun updateSelectionCountDisplay(selectedCount: Int) {
if (selectedCount > 0) {
showFileSelectionIcons()
} else {
showFileViewIcons()
}
}
private fun showFileOptionsMenu(selectedFiles: List<File>) {
if (selectedFiles.isEmpty()) return
val options = arrayOf(
getString(R.string.un_hide),
getString(R.string.delete),
getString(R.string.copy_to_another_folder),
getString(R.string.move_to_another_folder)
)
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.file_options))
.setItems(options) { _, which ->
when (which) {
0 -> unhideSelectedFiles(selectedFiles)
1 -> deleteSelectedFiles(selectedFiles)
2 -> copyToAnotherFolder(selectedFiles)
3 -> moveToAnotherFolder(selectedFiles)
}
}
.show()
}
private fun moveToAnotherFolder(selectedFiles: List<File>) {
showFolderSelectionDialog { destinationFolder ->
moveFilesToFolder(selectedFiles, destinationFolder)
}
}
private fun unhideSelectedFiles(selectedFiles: List<File>) {
dialogUtil.showMaterialDialog(
getString(R.string.un_hide_files),
getString(R.string.are_you_sure_you_want_to_un_hide_selected_files),
getString(R.string.un_hide),
getString(R.string.cancel),
object : DialogUtil.DialogCallback {
override fun onPositiveButtonClicked() {
performFileUnhiding(selectedFiles)
}
override fun onNegativeButtonClicked() {
// Do nothing
}
override fun onNaturalButtonClicked() {
// Do nothing
}
}
)
}
private fun deleteSelectedFiles(selectedFiles: List<File>) {
dialogUtil.showMaterialDialog(
getString(R.string.delete_items),
getString(R.string.are_you_sure_you_want_to_delete_selected_items),
getString(R.string.delete),
getString(R.string.cancel),
object : DialogUtil.DialogCallback {
override fun onPositiveButtonClicked() {
performFileDeletion(selectedFiles)
}
override fun onNegativeButtonClicked() {
// Do nothing
}
override fun onNaturalButtonClicked() {
// Do nothing
}
}
)
}
private fun refreshCurrentFolder() {
currentFolder?.let { folder ->
val files = folderManager.getFilesInFolder(folder)
if (files.isNotEmpty()) {
binding.recyclerView.visibility = View.VISIBLE
binding.noItems.visibility = View.GONE
fileAdapter?.submitList(files.toMutableList())
fileAdapter?.let { adapter ->
if (adapter.isInSelectionMode()) {
showFileSelectionIcons()
} else {
showFileViewIcons()
}
}
} else {
showEmptyState()
}
}
}
private fun setupClickListeners() {
binding.fabExpend.setOnClickListener {
if (isFabOpen) closeFabs()
else openFabs()
}
binding.back.setOnClickListener {
finish()
}
binding.addImage.setOnClickListener { openFilePicker("image/*") }
binding.addVideo.setOnClickListener { openFilePicker("video/*") }
binding.addAudio.setOnClickListener { openFilePicker("audio/*") }
binding.addDocument.setOnClickListener { openFilePicker("*/*") }
}
private fun setupAnimations() {
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)
}
private fun openFilePicker(mimeType: String) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
type = mimeType
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)
}
closeFabs()
pickImageLauncher.launch(intent)
}
private fun openFabs() {
if (!isFabOpen) {
binding.addImage.startAnimation(fabOpen)
binding.addVideo.startAnimation(fabOpen)
binding.addAudio.startAnimation(fabOpen)
binding.addDocument.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
isFabOpen = true
mainHandler.postDelayed({
binding.fabExpend.setImageResource(R.drawable.wrong)
}, 200)
}
}
private fun closeFabs() {
if (isFabOpen) {
binding.addImage.startAnimation(fabClose)
binding.addVideo.startAnimation(fabClose)
binding.addAudio.startAnimation(fabClose)
binding.addDocument.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
isFabOpen = false
binding.fabExpend.setImageResource(R.drawable.ic_add)
}
}
private fun showFileViewIcons() {
binding.menuButton.visibility = View.GONE
binding.fabExpend.visibility = View.VISIBLE
binding.addImage.visibility = View.INVISIBLE
binding.addVideo.visibility = View.INVISIBLE
binding.addAudio.visibility = View.INVISIBLE
binding.addDocument.visibility = View.INVISIBLE
isFabOpen = false
binding.fabExpend.setImageResource(R.drawable.ic_add)
}
private fun showFileSelectionIcons() {
binding.menuButton.visibility = View.VISIBLE
binding.fabExpend.visibility = View.GONE
binding.addImage.visibility = View.INVISIBLE
binding.addVideo.visibility = View.INVISIBLE
binding.addAudio.visibility = View.INVISIBLE
binding.addDocument.visibility = View.INVISIBLE
isFabOpen = false
}
private fun performFileUnhiding(selectedFiles: List<File>) {
lifecycleScope.launch {
var allUnhidden = true
selectedFiles.forEach { file ->
try {
val fileUri = FileManager.FileManager().getContentUriImage(this@ViewFolderActivity, file)
if (fileUri != null) {
val result = fileManager.copyFileToNormalDir(fileUri)
if (result == null) {
allUnhidden = false
}
} else {
allUnhidden = false
}
} catch (e: Exception) {
allUnhidden = false
}
}
mainHandler.post {
val message = if (allUnhidden) {
getString(R.string.files_unhidden_successfully)
} else {
getString(R.string.some_files_could_not_be_unhidden)
}
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
// Fixed: Ensure proper order of operations
fileAdapter?.exitSelectionMode()
refreshCurrentFolder()
}
}
}
private fun performFileDeletion(selectedFiles: List<File>) {
var allDeleted = true
selectedFiles.forEach { file ->
if (!file.delete()) {
allDeleted = false
}
}
val message = if (allDeleted) {
getString(R.string.files_deleted_successfully)
} else {
getString(R.string.some_items_could_not_be_deleted)
}
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// Fixed: Ensure proper order of operations
fileAdapter?.exitSelectionMode()
refreshCurrentFolder()
}
private fun copyToAnotherFolder(selectedFiles: List<File>) {
showFolderSelectionDialog { destinationFolder ->
copyFilesToFolder(selectedFiles, destinationFolder)
}
}
private fun copyFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
var allCopied = true
selectedFiles.forEach { file ->
try {
val newFile = File(destinationFolder, file.name)
file.copyTo(newFile, overwrite = true)
} catch (e: Exception) {
allCopied = false
}
}
val message = if (allCopied) "Files copied successfully" else "Some files could not be copied"
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// Fixed: Ensure proper order of operations
fileAdapter?.exitSelectionMode()
refreshCurrentFolder()
}
private fun moveFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
var allMoved = true
selectedFiles.forEach { file ->
try {
val newFile = File(destinationFolder, file.name)
file.copyTo(newFile, overwrite = true)
file.delete()
} catch (e: Exception) {
allMoved = false
}
}
val message = if (allMoved) "Files moved successfully" else "Some files could not be moved"
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// Fixed: Ensure proper order of operations
fileAdapter?.exitSelectionMode()
refreshCurrentFolder()
}
private fun showFolderSelectionDialog(onFolderSelected: (File) -> Unit) {
val folders = folderManager.getFoldersInDirectory(hiddenDir)
.filter { it != currentFolder } // Exclude current folder
if (folders.isEmpty()) {
Toast.makeText(this, getString(R.string.no_folders_available), Toast.LENGTH_SHORT).show()
return
}
val bottomSheetView = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_folder_selection, null)
val recyclerView = bottomSheetView.findViewById<RecyclerView>(R.id.folderRecyclerView)
val bottomSheetDialog = BottomSheetDialog(this)
bottomSheetDialog.setContentView(bottomSheetView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = FolderSelectionAdapter(folders) { selectedFolder ->
bottomSheetDialog.dismiss()
onFolderSelected(selectedFolder)
}
bottomSheetDialog.show()
}
}

View File

@@ -1,197 +1,737 @@
package devs.org.calculator.adapters
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
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 com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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
import java.lang.ref.WeakReference
import java.util.concurrent.Executors
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,
private val showFileName: Boolean,
private val onFolderLongClick: (Boolean) -> Unit
) : 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)
}
// Use WeakReference to prevent memory leaks
private var fileOperationCallback: WeakReference<FileOperationCallback>? = null
FileManager.FileType.VIDEO -> {
context.getString(R.string.video)
}
// Background executor for file operations
private val fileExecutor = Executors.newSingleThreadExecutor()
private val mainHandler = Handler(Looper.getMainLooper())
FileManager.FileType.AUDIO -> {
context.getString(R.string.audio)
}
companion object {
private const val TAG = "FileAdapter"
}
else -> context.getString(R.string.document)
// Callback interface for handling file operations and selection changes
interface FileOperationCallback {
fun onFileDeleted(file: File)
fun onFileRenamed(oldFile: File, newFile: File)
fun onRefreshNeeded()
fun onSelectionModeChanged(isSelectionMode: Boolean, selectedCount: Int)
fun onSelectionCountChanged(selectedCount: Int)
}
fun setFileOperationCallback(callback: FileOperationCallback?) {
fileOperationCallback = callback?.let { WeakReference(it) }
}
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)
val selectedLayer: View = view.findViewById(R.id.selectedLayer)
val selected: ImageView = view.findViewById(R.id.selected)
fun bind(file: File) {
val fileType = FileManager(context, lifecycleOwner).getFileType(file)
setupFileDisplay(file, fileType)
setupClickListeners(file, fileType)
fileNameTextView.visibility = if (showFileName) View.VISIBLE else View.GONE
// Update selection state
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
val isSelected = selectedItems.contains(position)
updateSelectionUI(isSelected)
}
}
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
}
"SELECTION_CHANGED" -> {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
val isSelected = selectedItems.contains(position)
updateSelectionUI(isSelected)
// Notify activity about selection change
notifySelectionModeChange()
}
}
}
}
}
private fun updateSelectionUI(isSelected: Boolean) {
selectedLayer.visibility = if (isSelected) View.VISIBLE else View.GONE
selected.visibility = if (isSelected) View.VISIBLE else View.GONE
}
private fun setupFileDisplay(file: File, fileType: FileManager.FileType) {
fileNameTextView.text = file.name
when (fileType) {
FileManager.FileType.IMAGE -> {
Glide.with(imageView)
playIcon.visibility = View.GONE
Glide.with(context)
.load(file)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.error(R.drawable.ic_document)
.placeholder(R.drawable.ic_document)
.into(imageView)
}
FileManager.FileType.VIDEO -> {
Glide.with(imageView)
.asBitmap()
playIcon.visibility = View.VISIBLE
Glide.with(context)
.load(file)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.error(R.drawable.ic_document)
.placeholder(R.drawable.ic_document)
.into(imageView)
}
FileManager.FileType.AUDIO -> {
playIcon.visibility = View.GONE
imageView.setImageResource(R.drawable.ic_audio)
}
else -> {
val resourceId = when (fileType) {
FileManager.FileType.AUDIO -> R.drawable.ic_audio
FileManager.FileType.DOCUMENT -> R.drawable.ic_document
else -> R.drawable.ic_file
}
imageView.setImageResource(resourceId)
playIcon.visibility = View.GONE
imageView.setImageResource(R.drawable.ic_document)
}
}
}
private fun setupClickListeners(file: File, fileType: FileManager.FileType) {
itemView.setOnClickListener {
val position = adapterPosition
if (position == RecyclerView.NO_POSITION) return@setOnClickListener
when(fileType){
FileManager.FileType.AUDIO -> {
// Create an intent to play audio using available audio players
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(FileManager.FileManager().getContentUriImage(context, file, fileType), "audio/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
try {
context.startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context,
context.getString(R.string.no_audio_player_found), Toast.LENGTH_SHORT).show()
}
}
FileManager.FileType.DOCUMENT -> {
// Create an intent to open the document using available viewers or file managers
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(FileManager.FileManager().getContentUriImage(context, file, fileType), "*/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
try {
context.startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context,
context.getString(R.string.no_suitable_app_found_to_open_this_document), Toast.LENGTH_SHORT).show()
}
}
else -> {
val intent = Intent(context, PreviewActivity::class.java).apply {
putExtra("type", fileTypes)
putExtra("position", position)
}
context.startActivity(intent)
}
if (isSelectionMode) {
toggleSelection(position)
} else {
openFile(file, fileType)
}
}
itemView.setOnLongClickListener {
val position = adapterPosition
if (position == RecyclerView.NO_POSITION) return@setOnLongClickListener false
val fileUri = FileManager.FileManager().getContentUriImage(context, file, fileType)
if (fileUri == null) {
Toast.makeText(context, "Unable to access file: $file", Toast.LENGTH_SHORT)
.show()
return@setOnLongClickListener true
if (!isSelectionMode) {
enterSelectionMode()
toggleSelection(position)
}
fileName = FileManager.FileName(context).getFileNameFromUri(fileUri)?.toString()
?: context.getString(R.string.unknown_file)
true
}
}
DialogUtil(context).showMaterialDialogWithNaturalButton(
context.getString(R.string.details, fileTypes),
"File Name: $fileName\n\nFile Path: $file\n\nYou can permanently delete or un-hide this file.",
context.getString(R.string.delete_permanently),
context.getString(R.string.un_hide),
context.getString(R.string.cancel),
object : DialogActionsCallback {
override fun onPositiveButtonClicked() {
lifecycleOwner.lifecycleScope.launch {
FileManager(context, lifecycleOwner).deletePhotoFromExternalStorage(
fileUri
)
}
val currentList = currentList.toMutableList()
currentList.remove(file)
submitList(currentList)
}
override fun onNegativeButtonClicked() {
FileManager(context, lifecycleOwner).copyFileToNormalDir(fileUri)
val currentList = currentList.toMutableList()
currentList.remove(file)
submitList(currentList)
}
override fun onNaturalButtonClicked() {
}
}
)
return@setOnLongClickListener true
private fun openFile(file: File, fileType: FileManager.FileType) {
if (!file.exists()) {
Toast.makeText(context, "File no longer exists", Toast.LENGTH_SHORT).show()
return
}
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) {
try {
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)
}
context.startActivity(intent)
} catch (e: Exception) {
Log.e(TAG, "Failed to open audio file: ${e.message}")
Toast.makeText(
context,
context.getString(R.string.no_audio_player_found),
Toast.LENGTH_SHORT
).show()
}
}
private fun openDocumentFile(file: File) {
try {
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)
}
context.startActivity(intent)
} catch (e: Exception) {
Log.e(TAG, "Failed to open document file: ${e.message}")
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 = if (isSelectionMode) {
arrayOf(
context.getString(R.string.un_hide),
context.getString(R.string.delete),
context.getString(R.string.copy_to_another_folder),
context.getString(R.string.move_to_another_folder)
)
} else {
arrayOf(
context.getString(R.string.un_hide),
context.getString(R.string.select_multiple),
context.getString(R.string.rename),
context.getString(R.string.delete),
context.getString(R.string.share)
)
}
MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.file_options))
.setItems(options) { dialog, which ->
if (isSelectionMode) {
when (which) {
0 -> unHideFile(file)
1 -> deleteFile(file)
2 -> copyToAnotherFolder(file)
3 -> moveToAnotherFolder(file)
}
} else {
when (which) {
0 -> unHideFile(file)
1 -> enableSelectMultipleFiles()
2 -> renameFile(file)
3 -> deleteFile(file)
4 -> shareFile(file)
}
}
dialog.dismiss()
}
.create()
.show()
}
private fun enableSelectMultipleFiles() {
val position = adapterPosition
if (position == RecyclerView.NO_POSITION) return
enterSelectionMode()
selectedItems.add(position)
notifyItemChanged(position, listOf("SELECTION_CHANGED"))
}
private fun unHideFile(file: File) {
FileManager(context, lifecycleOwner).unHideFile(
file = file,
onSuccess = {
fileOperationCallback?.get()?.onFileDeleted(file)
},
onError = { errorMessage ->
Toast.makeText(context, "Failed to unhide: $errorMessage", Toast.LENGTH_SHORT).show()
}
)
}
private fun deleteFile(file: File) {
// Show confirmation dialog first
MaterialAlertDialogBuilder(context)
.setTitle("Delete File")
.setMessage("Are you sure you want to delete ${file.name}?")
.setPositiveButton("Delete") { _, _ ->
deleteFileAsync(file)
}
.setNegativeButton("Cancel", null)
.show()
}
private fun deleteFileAsync(file: File) {
fileExecutor.execute {
val success = try {
file.delete()
} catch (e: Exception) {
Log.e(TAG, "Failed to delete file: ${e.message}")
false
}
mainHandler.post {
if (success) {
fileOperationCallback?.get()?.onFileDeleted(file)
Toast.makeText(context, "File deleted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to delete file", Toast.LENGTH_SHORT).show()
}
}
}
}
@SuppressLint("MissingInflatedId")
private fun renameFile(file: File) {
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_input, null)
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
inputEditText.setText(file.name)
inputEditText.selectAll()
MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.rename_file))
.setView(dialogView)
.setPositiveButton(context.getString(R.string.rename)) { dialog, _ ->
val newName = inputEditText.text.toString().trim()
if (newName.isNotEmpty() && newName != file.name) {
if (isValidFileName(newName)) {
renameFileAsync(file, newName)
} else {
Toast.makeText(context, "Invalid file name", Toast.LENGTH_SHORT).show()
}
}
dialog.dismiss()
}
.setNegativeButton(context.getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
.create()
.show()
}
private fun isValidFileName(fileName: String): Boolean {
val forbiddenChars = charArrayOf('/', '\\', ':', '*', '?', '"', '<', '>', '|')
return fileName.isNotBlank() &&
fileName.none { it in forbiddenChars } &&
!fileName.startsWith(".") &&
fileName.length <= 255
}
private fun renameFileAsync(file: File, newName: String) {
fileExecutor.execute {
val parentDir = file.parentFile
if (parentDir != null) {
val newFile = File(parentDir, newName)
if (newFile.exists()) {
mainHandler.post {
Toast.makeText(context, "File with this name already exists", Toast.LENGTH_SHORT).show()
}
return@execute
}
val success = try {
file.renameTo(newFile)
} catch (e: Exception) {
Log.e(TAG, "Failed to rename file: ${e.message}")
false
}
mainHandler.post {
if (success) {
fileOperationCallback?.get()?.onFileRenamed(file, newFile)
Toast.makeText(context, "File renamed", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to rename file", Toast.LENGTH_SHORT).show()
}
}
}
}
}
private fun shareFile(file: File) {
try {
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))
)
} catch (e: Exception) {
Log.e(TAG, "Failed to share file: ${e.message}")
Toast.makeText(context, "Failed to share file", Toast.LENGTH_SHORT).show()
}
}
private fun copyToAnotherFolder(file: File) {
// This will be handled by the activity
fileOperationCallback?.get()?.onRefreshNeeded()
}
private fun moveToAnotherFolder(file: File) {
// This will be handled by the activity
fileOperationCallback?.get()?.onRefreshNeeded()
}
private fun toggleSelection(position: Int) {
if (selectedItems.contains(position)) {
selectedItems.remove(position)
if (selectedItems.isEmpty()) {
exitSelectionMode()
}
} else {
selectedItems.add(position)
}
onSelectionCountChanged(selectedItems.size)
notifyItemChanged(position)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_file, parent, false)
.inflate(R.layout.list_item_file, parent, false)
return FileViewHolder(view)
}
override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
holder.bind(getItem(position))
if (position < itemCount) {
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 onBindViewHolder(holder: FileViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
} else {
if (position < itemCount) {
val file = getItem(position)
holder.bind(file, payloads)
}
}
}
override fun submitList(list: List<File>?) {
val currentList = currentList.toMutableList()
if (list == null) {
currentList.clear()
super.submitList(null)
} else {
// Create a new list to force update
val newList = list.toMutableList()
super.submitList(newList)
}
}
fun enterSelectionMode() {
if (!isSelectionMode) {
isSelectionMode = true
notifySelectionModeChange()
}
}
fun exitSelectionMode() {
if (isSelectionMode) {
isSelectionMode = false
selectedItems.clear()
notifySelectionModeChange()
notifyDataSetChanged()
}
}
fun clearSelection() {
if (selectedItems.isNotEmpty()) {
val previouslySelected = selectedItems.toSet()
selectedItems.clear()
fileOperationCallback?.get()?.onSelectionCountChanged(0)
previouslySelected.forEach { position ->
if (position < itemCount) {
notifyItemChanged(position, listOf("SELECTION_CHANGED"))
}
}
}
}
fun selectAll() {
if (!isSelectionMode) {
enterSelectionMode()
}
override fun areContentsTheSame(oldItem: File, newItem: File): Boolean {
return oldItem == newItem
val previouslySelected = selectedItems.toSet()
selectedItems.clear()
// Add all positions to selection
for (i in 0 until itemCount) {
selectedItems.add(i)
}
// Notify callback about selection change
fileOperationCallback?.get()?.onSelectionCountChanged(selectedItems.size)
// Update UI for changed items efficiently
updateSelectionItems(selectedItems.toSet(), previouslySelected)
}
private fun updateSelectionItems(newSelections: Set<Int>, oldSelections: Set<Int>) {
val changedItems = (oldSelections - newSelections) + (newSelections - oldSelections)
changedItems.forEach { position ->
if (position < itemCount) {
notifyItemChanged(position, listOf("SELECTION_CHANGED"))
}
}
}
private fun notifySelectionModeChange() {
fileOperationCallback?.get()?.onSelectionModeChanged(isSelectionMode, selectedItems.size)
onFolderLongClick(isSelectionMode)
}
fun getSelectedItems(): List<File> {
return selectedItems.mapNotNull { position ->
if (position < itemCount) getItem(position) else null
}
}
}
fun getSelectedCount(): Int = selectedItems.size
fun isInSelectionMode(): Boolean = isSelectionMode
fun deleteSelectedFiles() {
val selectedFiles = getSelectedItems()
if (selectedFiles.isEmpty()) return
// Show confirmation dialog
MaterialAlertDialogBuilder(context)
.setTitle("Delete Files")
.setMessage("Are you sure you want to delete ${selectedFiles.size} file(s)?")
.setPositiveButton("Delete") { _, _ ->
deleteFilesAsync(selectedFiles)
}
.setNegativeButton("Cancel", null)
.show()
}
private fun deleteFilesAsync(selectedFiles: List<File>) {
fileExecutor.execute {
var deletedCount = 0
var failedCount = 0
val failedFiles = mutableListOf<String>()
selectedFiles.forEach { file ->
try {
if (file.delete()) {
deletedCount++
mainHandler.post {
fileOperationCallback?.get()?.onFileDeleted(file)
}
} else {
failedCount++
failedFiles.add(file.name)
}
} catch (e: Exception) {
failedCount++
failedFiles.add(file.name)
Log.e(TAG, "Failed to delete ${file.name}: ${e.message}")
}
}
mainHandler.post {
// Exit selection mode first
exitSelectionMode()
// Show detailed result message
when {
deletedCount > 0 && failedCount == 0 -> {
Toast.makeText(context, "Deleted $deletedCount file(s)", Toast.LENGTH_SHORT).show()
}
deletedCount > 0 && failedCount > 0 -> {
Toast.makeText(context,
"Deleted $deletedCount file(s), failed to delete $failedCount",
Toast.LENGTH_LONG).show()
}
failedCount > 0 -> {
Toast.makeText(context,
"Failed to delete $failedCount file(s)",
Toast.LENGTH_SHORT).show()
}
}
}
}
}
fun shareSelectedFiles() {
val selectedFiles = getSelectedItems()
if (selectedFiles.isEmpty()) return
try {
if (selectedFiles.size == 1) {
// Share single file
val file = selectedFiles.first()
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))
)
} else {
// Share multiple files
val uris = selectedFiles.mapNotNull { file ->
try {
FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
file
)
} catch (e: Exception) {
Log.e(TAG, "Failed to get URI for file ${file.name}: ${e.message}")
null
}
}
if (uris.isNotEmpty()) {
val shareIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
type = "*/*"
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(
Intent.createChooser(shareIntent, "Share ${selectedFiles.size} files")
)
} else {
Toast.makeText(context, "No files could be shared", Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to share files: ${e.message}")
Toast.makeText(context, "Failed to share files", Toast.LENGTH_SHORT).show()
}
exitSelectionMode()
}
fun onBackPressed(): Boolean {
return if (isSelectionMode) {
exitSelectionMode()
true
} else {
false
}
}
fun refreshSelectionStates() {
if (isSelectionMode) {
selectedItems.forEach { position ->
if (position < itemCount) {
notifyItemChanged(position, listOf("SELECTION_CHANGED"))
}
}
// Ensure callback is notified
notifySelectionModeChange()
}
}
fun cleanup() {
try {
if (!fileExecutor.isShutdown) {
fileExecutor.shutdown()
}
} catch (e: Exception) {
Log.e(TAG, "Error shutting down executor: ${e.message}")
}
fileOperationCallback?.clear()
fileOperationCallback = null
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
cleanup()
}
private fun onSelectionCountChanged(count: Int) {
fileOperationCallback?.get()?.onSelectionCountChanged(count)
}
}

View File

@@ -0,0 +1,47 @@
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() &&
oldItem.exists() == newItem.exists()
}
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")
}
if (oldItem.exists() != newItem.exists()) {
changes.add("EXISTENCE_CHANGED")
}
return if (changes.isNotEmpty()) changes else null
}
}

View File

@@ -0,0 +1,125 @@
package devs.org.calculator.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
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,
private val onSelectionModeChanged: (Boolean) -> Unit,
private val onSelectionCountChanged: (Int) -> Unit
) : ListAdapter<File, FolderAdapter.FolderViewHolder>(FolderDiffCallback()) {
private val selectedItems = mutableSetOf<Int>()
private var isSelectionMode = false
inner class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val folderNameTextView: TextView = itemView.findViewById(R.id.folderName)
val selectedView: ImageView = itemView.findViewById(R.id.selected)
val selectedLayer: View = itemView.findViewById(R.id.selectedLayer)
fun bind(folder: File, onFolderClick: (File) -> Unit, onFolderLongClick: (File) -> Unit, isSelected: Boolean) {
folderNameTextView.text = folder.name
selectedView.visibility = if (isSelected) View.VISIBLE else View.GONE
selectedLayer.visibility = if (isSelected) View.VISIBLE else View.GONE
itemView.setOnClickListener {
if (isSelectionMode) {
toggleSelection(adapterPosition)
} else {
onFolderClick(folder)
}
}
itemView.setOnLongClickListener {
if (!isSelectionMode) {
enterSelectionMode()
onFolderLongClick(folder)
toggleSelection(adapterPosition)
}
true
}
}
private fun toggleSelection(position: Int) {
if (selectedItems.contains(position)) {
selectedItems.remove(position)
if (selectedItems.isEmpty()) {
exitSelectionMode()
}
} else {
selectedItems.add(position)
}
onSelectionCountChanged(selectedItems.size)
notifyItemChanged(position)
}
}
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, selectedItems.contains(position))
}
fun getSelectedItems(): List<File> {
return selectedItems.mapNotNull { position ->
if (position < itemCount) getItem(position) else null
}
}
fun clearSelection() {
val wasInSelectionMode = isSelectionMode
selectedItems.clear()
if (wasInSelectionMode) {
exitSelectionMode()
}
onSelectionCountChanged(0)
notifyDataSetChanged()
}
fun onBackPressed(): Boolean {
return if (isInSelectionMode()) {
clearSelection()
true
} else {
false
}
}
fun isInSelectionMode(): Boolean {
return isSelectionMode
}
private fun enterSelectionMode() {
isSelectionMode = true
onSelectionModeChanged(true)
}
private fun exitSelectionMode() {
isSelectionMode = false
onSelectionModeChanged(false)
}
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
}
}
}

View File

@@ -0,0 +1,41 @@
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.RecyclerView
import devs.org.calculator.R
import java.io.File
class FolderSelectionAdapter(
private val folders: List<File>,
private val onFolderSelected: (File) -> Unit
) : RecyclerView.Adapter<FolderSelectionAdapter.FolderViewHolder>() {
inner class FolderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val folderName: TextView = view.findViewById(R.id.folderName)
init {
view.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
onFolderSelected(folders[position])
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FolderViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_folder_selection, parent, false)
return FolderViewHolder(view)
}
override fun onBindViewHolder(holder: FolderViewHolder, position: Int) {
val folder = folders[position]
holder.folderName.text = folder.name
}
override fun getItemCount() = folders.size
}

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
@@ -128,13 +129,15 @@ class ImagePreviewAdapter(
mediaPlayer = MediaPlayer().apply {
setDataSource(file.absolutePath)
setOnPreparedListener { mp ->
binding.audioSeekBar.max = mp.duration
binding.audioSeekBar.valueTo = mp.duration.toFloat()
isMediaPlayerPrepared = true
}
setOnCompletionListener {
// isPlaying = false
binding.playPause.setImageResource(R.drawable.play)
binding.audioSeekBar.progress = 0
binding.audioSeekBar.value = 0f
seekHandler.removeCallbacks(seekRunnable!!)
}
prepareAsync()
@@ -142,27 +145,23 @@ class ImagePreviewAdapter(
}
private fun setupSeekBar() {
binding.audioSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
mediaPlayer?.seekTo(progress)
}
binding.audioSeekBar.addOnChangeListener { slider, value, fromUser ->
if (fromUser && mediaPlayer != null && isMediaPlayerPrepared) {
mediaPlayer?.seekTo(value.toInt())
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
}
seekRunnable = Runnable {
mediaPlayer?.let { mp ->
if (mp.isPlaying) {
binding.audioSeekBar.progress = mp.currentPosition
binding.audioSeekBar.value = mp.currentPosition.toFloat()
seekHandler.postDelayed(seekRunnable!!, 100)
}
}
}
}
private fun setupPlaybackControls() {
binding.playPause.setOnClickListener {
if (isPlaying) {
@@ -176,7 +175,7 @@ class ImagePreviewAdapter(
mediaPlayer?.let { mp ->
val newPosition = mp.currentPosition - 10000
mp.seekTo(maxOf(0, newPosition))
binding.audioSeekBar.progress = mp.currentPosition
binding.audioSeekBar.value = mp.currentPosition.toFloat()
}
}
@@ -184,7 +183,7 @@ class ImagePreviewAdapter(
mediaPlayer?.let { mp ->
val newPosition = mp.currentPosition + 10000
mp.seekTo(minOf(mp.duration, newPosition))
binding.audioSeekBar.progress = mp.currentPosition
binding.audioSeekBar.value = mp.currentPosition.toFloat()
}
}
}
@@ -228,6 +227,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

@@ -0,0 +1,124 @@
package devs.org.calculator.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
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 ListFolderAdapter(
private val onFolderClick: (File) -> Unit,
private val onFolderLongClick: (File) -> Unit,
private val onSelectionModeChanged: (Boolean) -> Unit,
private val onSelectionCountChanged: (Int) -> Unit
) : ListAdapter<File, ListFolderAdapter.FolderViewHolder>(FolderDiffCallback()) {
private val selectedItems = mutableSetOf<Int>()
private var isSelectionMode = false
inner class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val folderNameTextView: TextView = itemView.findViewById(R.id.folderName)
val selectedLayer: View = itemView.findViewById(R.id.selectedLayer)
fun bind(folder: File, onFolderClick: (File) -> Unit, onFolderLongClick: (File) -> Unit, isSelected: Boolean) {
folderNameTextView.text = folder.name
selectedLayer.visibility = if (isSelected) View.VISIBLE else View.GONE
itemView.setOnClickListener {
if (isSelectionMode) {
toggleSelection(adapterPosition)
} else {
onFolderClick(folder)
}
}
itemView.setOnLongClickListener {
if (!isSelectionMode) {
enterSelectionMode()
onFolderLongClick(folder)
toggleSelection(adapterPosition)
}
true
}
}
private fun toggleSelection(position: Int) {
if (selectedItems.contains(position)) {
selectedItems.remove(position)
if (selectedItems.isEmpty()) {
exitSelectionMode()
}
} else {
selectedItems.add(position)
}
onSelectionCountChanged(selectedItems.size)
notifyItemChanged(position)
}
}
private fun enterSelectionMode() {
isSelectionMode = true
onSelectionModeChanged(true)
}
private fun exitSelectionMode() {
isSelectionMode = false
onSelectionModeChanged(false)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FolderViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_folder_list_style, parent, false)
return FolderViewHolder(view)
}
override fun onBindViewHolder(holder: FolderViewHolder, position: Int) {
val folder = getItem(position)
holder.bind(folder, onFolderClick, onFolderLongClick, selectedItems.contains(position))
}
fun getSelectedItems(): List<File> {
return selectedItems.mapNotNull { position ->
if (position < itemCount) getItem(position) else null
}
}
fun clearSelection() {
val wasInSelectionMode = isSelectionMode
selectedItems.clear()
if (wasInSelectionMode) {
exitSelectionMode()
}
onSelectionCountChanged(0)
notifyDataSetChanged()
}
fun onBackPressed(): Boolean {
return if (isInSelectionMode()) {
clearSelection()
true
} else {
false
}
}
fun isInSelectionMode(): Boolean {
return isSelectionMode
}
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
}
}
}

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

@@ -20,11 +20,13 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.callbacks.FileProcessCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import android.Manifest
import androidx.core.content.FileProvider
import devs.org.calculator.R
class FileManager(private val context: Context, private val lifecycleOwner: LifecycleOwner) {
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
@@ -41,33 +43,49 @@ 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, folderName: File, currentDir: File? = null): File? {
return try {
val contentResolver = context.contentResolver
// Get the target directory
val targetDir = File(Environment.getExternalStorageDirectory(), "$HIDDEN_DIR/${type.dirName}")
targetDir.mkdirs()
File(targetDir, ".nomedia").createNewFile()
// Get the target directory (i am using the current opened folder as target folder)
val targetDir = folderName
// Ensure target directory exists and has .nomedia file
if (!targetDir.exists()) {
targetDir.mkdirs()
File(targetDir, ".nomedia").createNewFile()
}
// Create target file
val mimeType = contentResolver.getType(uri)
@@ -141,6 +159,64 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
null
}
}
fun unHideFile(file: File, onSuccess: (() -> Unit)? = null, onError: ((String) -> Unit)? = null) {
lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
try {
// Create target directory (Downloads)
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
targetDir.mkdirs()
// Create target file with same name or timestamp
val targetFile = File(targetDir, file.name)
// If file with same name exists, add timestamp
val finalTargetFile = if (targetFile.exists()) {
val nameWithoutExt = file.nameWithoutExtension
val extension = file.extension
File(targetDir, "${nameWithoutExt}_${System.currentTimeMillis()}.${extension}")
} else {
targetFile
}
// Copy file content
file.copyTo(finalTargetFile, overwrite = false)
// Verify copy success
if (finalTargetFile.exists() && finalTargetFile.length() > 0) {
// Delete original hidden file
if (file.delete()) {
// Trigger media scan for the new file
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
mediaScanIntent.data = Uri.fromFile(finalTargetFile)
context.sendBroadcast(mediaScanIntent)
withContext(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.file_unhidden_successfully), Toast.LENGTH_SHORT).show()
onSuccess?.invoke() // Call success callback
}
} else {
withContext(Dispatchers.Main) {
Toast.makeText(context, "File copied but failed to remove from hidden folder", Toast.LENGTH_SHORT).show()
onError?.invoke("Failed to remove from hidden folder")
}
}
} else {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Failed to copy file", Toast.LENGTH_SHORT).show()
onError?.invoke("Failed to copy file")
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Error unhiding file: ${e.message}", Toast.LENGTH_LONG).show()
onError?.invoke(e.message ?: "Unknown error")
}
e.printStackTrace()
}
}
}
suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
withContext(Dispatchers.IO) {
@@ -150,11 +226,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
if (documentFile?.exists() == true && documentFile.canWrite()) {
val deleted = documentFile.delete()
withContext(Dispatchers.Main) {
if (deleted) {
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to hide/unhide file", Toast.LENGTH_SHORT).show()
}
}
return@withContext
}
@@ -163,7 +235,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
try {
context.contentResolver.delete(photoUri, null, null)
withContext(Dispatchers.Main) {
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
}
} catch (e: SecurityException) {
// Handle security exception for Android 10 and above
@@ -221,8 +293,8 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
}
}
class FileManager(){
fun getContentUriImage(context: Context, file: File, fileType: FileType): Uri? {
class FileManager{
fun getContentUriImage(context: Context, file: File): Uri? {
// Query MediaStore for the file
val projection = arrayOf(MediaStore.MediaColumns._ID)
@@ -258,8 +330,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
@@ -274,19 +344,22 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
suspend fun processMultipleFiles(
uriList: List<Uri>,
fileType: FileType,
callback: FileProcessCallback
targetFolder: File,
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, targetFolder, currentDir)
file?.let { copiedFiles.add(it) }
} catch (e: Exception) {
e.printStackTrace()
}
}
delay(500)
withContext(Dispatchers.Main) {
if (copiedFiles.isNotEmpty()) {
callback.onFilesProcessedSuccessfully(copiedFiles)
@@ -297,12 +370,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,73 @@
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) {
folder.listFiles()?.forEach { file ->
if (file.isFile) {
file.delete()
}
}
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

@@ -18,6 +18,15 @@ class PrefsUtil(context: Context) {
.apply()
}
fun setBoolean(key:String, value: Boolean){
return prefs.edit().putBoolean(key,value).apply()
}
fun getBoolean(key: String, defValue: Boolean = false): Boolean{
return prefs.getBoolean(key,defValue)
}
fun resetPassword(){
prefs.edit()
.remove("password")

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

@@ -2,12 +2,12 @@
<path android:fillColor="#00000000"
android:pathData="M7.7,6.36L3.533,11.36C3.224,11.731 3.224,12.269 3.533,12.64L7.7,17.64C7.89,17.868 8.172,18 8.468,18H18C19.657,18 21,16.657 21,15V9C21,7.343 19.657,6 18,6H8.468C8.172,6 7.89,6.132 7.7,6.36Z"
android:strokeColor="@color/textColor" android:strokeLineCap="round"
android:strokeColor="@color/white" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#00000000"
android:pathData="M15,10L13,12M13,12L11,14M13,12L11,10M13,12L15,14"
android:strokeColor="@color/textColor"
android:strokeColor="@color/white"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="2"/>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:bottomLeftRadius="15dp" android:bottomRightRadius="15dp"/>
<solid android:color="?attr/cardForegroundColor"/>
</shape>

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,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="15dp"/>
<gradient android:startColor="?attr/colorPrimary" android:endColor="?attr/colorPrimary" android:angle="75"/>
</shape>

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,5 @@
<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"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="?attr/colorPrimary" android:pathData="M13.75,2C13.75,1.586 13.414,1.25 13,1.25C12.586,1.25 12.25,1.586 12.25,2V14.536C11.4,13.738 10.257,13.25 9,13.25C6.377,13.25 4.25,15.377 4.25,18C4.25,20.623 6.377,22.75 9,22.75C11.623,22.75 13.75,20.623 13.75,18V6.243C14.988,7.772 16.879,8.75 19,8.75C19.414,8.75 19.75,8.414 19.75,8C19.75,7.586 19.414,7.25 19,7.25C16.101,7.25 13.75,4.899 13.75,2Z"/>
</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/svgTintColor"/>
<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/svgTintColor"/>
</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="#fff"
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,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:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"
android:fillColor="#fff"/>
</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

@@ -0,0 +1,37 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="800dp"
android:height="800dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M2.909,3.5L11.283,3.5C12.441,3.5 13.551,3.96 14.369,4.778L14.722,5.131C15.54,5.949 16.65,6.409 17.807,6.409L23.273,6.409C24.076,6.409 24.762,6.693 25.33,7.261C25.898,7.829 26.182,8.515 26.182,9.318L26.182,25.318C26.182,26.122 25.898,26.807 25.33,27.375C24.762,27.943 24.076,28.227 23.273,28.227L2.909,28.227C2.106,28.227 1.42,27.943 0.852,27.375C0.284,26.807 0,26.122 0,25.318L0,6.409C0,5.606 0.284,4.92 0.852,4.352C1.42,3.784 2.106,3.5 2.909,3.5Z"
android:fillType="evenOdd">
<aapt:attr name="android:fillColor">
<gradient
android:startX="16"
android:startY="3.5"
android:endX="16"
android:endY="19.854"
android:type="linear">
<item android:offset="0" android:color="#FFFBA200"/>
<item android:offset="1" android:color="#FFFF7300"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M5.818,25.318L5.818,12.227C5.818,11.424 6.102,10.738 6.67,10.17C7.238,9.602 7.924,9.318 8.727,9.318L29.091,9.318C29.894,9.318 30.58,9.602 31.148,10.17C31.716,10.738 32,11.424 32,12.227L32,25.318C32,26.122 31.716,26.807 31.148,27.375C30.58,27.943 29.894,28.227 29.091,28.227L2.909,28.227C3.712,28.227 4.398,27.943 4.966,27.375C5.534,26.807 5.818,26.122 5.818,25.318Z"
android:fillType="evenOdd">
<aapt:attr name="android:fillColor">
<gradient
android:startX="17.454"
android:startY="9.318"
android:endX="17.454"
android:endY="28.227"
android:type="linear">
<item android:offset="0" android:color="#FFFAC227"/>
<item android:offset="1" android:color="#FFFAA627"/>
</gradient>
</aapt:attr>
</path>
</vector>

View File

@@ -0,0 +1,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="20"
android:viewportWidth="20"
android:width="24dp">
<path android:fillColor="@color/black" android:fillType="evenOdd" android:pathData="M10,0C15.523,0 20,4.59 20,10.253C20,14.782 17.138,18.624 13.167,19.981C12.66,20.082 12.48,19.762 12.48,19.489C12.48,19.151 12.492,18.047 12.492,16.675C12.492,15.719 12.172,15.095 11.813,14.777C14.04,14.523 16.38,13.656 16.38,9.718C16.38,8.598 15.992,7.684 15.35,6.966C15.454,6.707 15.797,5.664 15.252,4.252C15.252,4.252 14.414,3.977 12.505,5.303C11.706,5.076 10.85,4.962 10,4.958C9.15,4.962 8.295,5.076 7.497,5.303C5.586,3.977 4.746,4.252 4.746,4.252C4.203,5.664 4.546,6.707 4.649,6.966C4.01,7.684 3.619,8.598 3.619,9.718C3.619,13.646 5.954,14.526 8.175,14.785C7.889,15.041 7.63,15.493 7.54,16.156C6.97,16.418 5.522,16.871 4.63,15.304C4.63,15.304 4.101,14.319 3.097,14.247C3.097,14.247 2.122,14.234 3.029,14.87C3.029,14.87 3.684,15.185 4.139,16.37C4.139,16.37 4.726,18.2 7.508,17.58C7.513,18.437 7.522,19.245 7.522,19.489C7.522,19.76 7.338,20.077 6.839,19.982C2.865,18.627 0,14.783 0,10.253C0,4.59 4.478,0 10,0" android:strokeColor="#00000000" android:strokeWidth="1"/>
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="169dp"
android:height="169dp"
android:viewportWidth="35.28"
android:viewportHeight="35.28">
<path
android:pathData="M27.64,3.64L23.64,3.64C21.431,3.64 19.64,5.431 19.64,7.64L19.64,11.64C19.64,13.849 21.431,15.64 23.64,15.64L27.64,15.64C29.849,15.64 31.64,13.849 31.64,11.64L31.64,7.64C31.64,5.431 29.849,3.64 27.64,3.64L27.64,3.64ZM27.64,19.64L23.64,19.64C21.431,19.64 19.64,21.431 19.64,23.64L19.64,27.64C19.64,29.849 21.431,31.64 23.64,31.64L27.64,31.64C29.849,31.64 31.64,29.849 31.64,27.64L31.64,23.64C31.64,21.431 29.849,19.64 27.64,19.64L27.64,19.64ZM11.64,19.64L7.64,19.64C5.431,19.64 3.64,21.431 3.64,23.64L3.64,27.64C3.64,29.849 5.431,31.64 7.64,31.64L11.64,31.64C13.849,31.64 15.64,29.849 15.64,27.64L15.64,23.64C15.64,21.431 13.849,19.64 11.64,19.64L11.64,19.64ZM11.64,3.64L7.64,3.64C5.431,3.64 3.64,5.431 3.64,7.64L3.64,11.64C3.64,13.849 5.431,15.64 7.64,15.64L11.64,15.64C13.849,15.64 15.64,13.849 15.64,11.64L15.64,7.64C15.64,5.431 13.849,3.64 11.64,3.64L11.64,3.64Z"
android:strokeWidth="1"
android:fillColor="?attr/colorPrimary"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</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,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M4,13L2,13L2,11L4,11ZM14,11L6,11v2h8ZM4,7L2,7L2,9L4,9ZM14,7L6,7L6,9h8ZM4,3L2,3L2,5L4,5ZM14,3L6,3L6,5h8Z"
android:fillColor="#ffffff"/>
</vector>

View File

@@ -0,0 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp">
<path android:fillColor="#fff"
android:pathData="M12,12H12.01M12,6H12.01M12,18H12.01M13,12C13,12.552 12.552,13 12,13C11.448,13 11,12.552 11,12C11,11.448 11.448,11 12,11C12.552,11 13,11.448 13,12ZM13,18C13,18.552 12.552,19 12,19C11.448,19 11,18.552 11,18C11,17.448 11.448,17 12,17C12.552,17 13,17.448 13,18ZM13,6C13,6.552 12.552,7 12,7C11.448,7 11,6.552 11,6C11,5.448 11.448,5 12,5C12.552,5 13,5.448 13,6Z" android:strokeColor="#fff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2"/>
</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,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M34.6,23.3 L18.1,13.2c-0.6,-0.4 -1.1,-0.1 -1.1,0.6V34.2c0,0.7 0.5,1 1.1,0.6L34.6,24.7A0.8,0.8 0,0 0,34.6 23.3Z"/>
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M24,2A22,22 0,1 0,46 24,21.9 21.9,0 0,0 24,2ZM24,42A18,18 0,1 1,42 24,18.1 18.1,0 0,1 24,42Z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillAlpha="1" android:fillColor="?attr/colorPrimary" android:pathData="M17.88,18.6C17.88,19.926 16.806,21 15.48,21C14.155,21 13.08,19.926 13.08,18.6C13.08,17.274 14.155,16.2 15.48,16.2C16.806,16.2 17.88,17.274 17.88,18.6Z"/>
<path android:fillAlpha="1" android:fillColor="?attr/colorPrimary" android:pathData="M9.48,12C9.48,13.325 8.405,14.4 7.08,14.4C5.755,14.4 4.68,13.325 4.68,12C4.68,10.675 5.755,9.6 7.08,9.6C8.405,9.6 9.48,10.675 9.48,12Z"/>
<path android:fillAlpha="1" android:fillColor="?attr/colorPrimary" android:pathData="M17.88,5.4C17.88,6.725 16.806,7.8 15.48,7.8C14.155,7.8 13.08,6.725 13.08,5.4C13.08,4.075 14.155,3 15.48,3C16.806,3 17.88,4.075 17.88,5.4Z"/>
<path android:fillColor="#00000000" android:pathData="M18.48,18.537H21M4.68,12L3,12.044M4.68,12C4.68,13.325 5.755,14.4 7.08,14.4C8.405,14.4 9.48,13.325 9.48,12C9.48,10.675 8.405,9.6 7.08,9.6C5.755,9.6 4.68,10.675 4.68,12ZM10.169,12.044H21M12.801,5.551L3,5.551M21,5.551H18.48M3,18.537H12.801M17.88,18.6C17.88,19.926 16.806,21 15.48,21C14.155,21 13.08,19.926 13.08,18.6C13.08,17.274 14.155,16.2 15.48,16.2C16.806,16.2 17.88,17.274 17.88,18.6ZM17.88,5.4C17.88,6.725 16.806,7.8 15.48,7.8C14.155,7.8 13.08,6.725 13.08,5.4C13.08,4.075 14.155,3 15.48,3C16.806,3 17.88,4.075 17.88,5.4Z" android:strokeColor="?attr/colorPrimary" android:strokeLineCap="round" android:strokeWidth="1.5"/>
</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:pathData="M4,13.743l-1,0.579a1,1 0,0 0,-0.366 1.366l1.488,2.578a1,1 0,0 0,1.366 0.366L6.5,18.05a1.987,1.987 0,0 1,1.986 0l0.02,0.011a1.989,1.989 0,0 1,1 1.724V21a1,1 0,0 0,1 1h3a1,1 0,0 0,1 -1V19.782a1.985,1.985 0,0 1,0.995 -1.721l0.021,-0.012a1.987,1.987 0,0 1,1.986 0l1.008,0.582a1,1 0,0 0,1.366 -0.366l1.488,-2.578A1,1 0,0 0,21 14.322l-1,-0.579a1.994,1.994 0,0 1,-1 -1.733v-0.021a1.991,1.991 0,0 1,1 -1.732l1,-0.579a1,1 0,0 0,0.366 -1.366L19.876,5.734a1,1 0,0 0,-1.366 -0.366L17.5,5.95a1.987,1.987 0,0 1,-1.986 0L15.5,5.94a1.989,1.989 0,0 1,-1 -1.724V3a1,1 0,0 0,-1 -1h-3a1,1 0,0 0,-1 1V4.294A1.856,1.856 0,0 1,8.57 5.9l-0.153,0.088a1.855,1.855 0,0 1,-1.853 0L5.49,5.368a1,1 0,0 0,-1.366 0.366L2.636,8.312A1,1 0,0 0,3 9.678l1,0.579A1.994,1.994 0,0 1,5 11.99v0.021A1.991,1.991 0,0 1,4 13.743ZM12,9a3,3 0,1 1,-3 3A3,3 0,0 1,12 9Z"
android:fillColor="#fff"/>
</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

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M13.75,2C13.75,1.586 13.414,1.25 13,1.25C12.586,1.25 12.25,1.586 12.25,2V14.536C11.4,13.738 10.257,13.25 9,13.25C6.377,13.25 4.25,15.377 4.25,18C4.25,20.623 6.377,22.75 9,22.75C11.623,22.75 13.75,20.623 13.75,18V6.243C14.988,7.772 16.879,8.75 19,8.75C19.414,8.75 19.75,8.414 19.75,8C19.75,7.586 19.414,7.25 19,7.25C16.101,7.25 13.75,4.899 13.75,2Z"
android:fillColor="@color/textColor"/>
</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

@@ -5,6 +5,6 @@
android:viewportHeight="24">
<path
android:pathData="M5,7.766c0,-1.554 1.696,-2.515 3.029,-1.715l7.056,4.234c1.295,0.777 1.295,2.653 0,3.43l-7.056,4.234c-1.333,0.8 -3.029,-0.16 -3.029,-1.715V7.766zM14.056,12L7,7.766v8.468L14.056,12zM18,6a1,1 0,0 0,-1 1v10a1,1 0,1 0,2 0V7a1,1 0,0 0,-1 -1z"
android:fillColor="@color/textColor"
android:fillColor="?attr/colorPrimary"
android:fillType="evenOdd"/>
</vector>

View File

@@ -5,8 +5,8 @@
android:viewportHeight="24">
<path
android:pathData="M2,6C2,4.114 2,3.172 2.586,2.586C3.172,2 4.114,2 6,2C7.886,2 8.828,2 9.414,2.586C10,3.172 10,4.114 10,6V18C10,19.886 10,20.828 9.414,21.414C8.828,22 7.886,22 6,22C4.114,22 3.172,22 2.586,21.414C2,20.828 2,19.886 2,18V6Z"
android:fillColor="@color/textColor"/>
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M14,6C14,4.114 14,3.172 14.586,2.586C15.172,2 16.114,2 18,2C19.886,2 20.828,2 21.414,2.586C22,3.172 22,4.114 22,6V18C22,19.886 22,20.828 21.414,21.414C20.828,22 19.886,22 18,22C16.114,22 15.172,22 14.586,21.414C14,20.828 14,19.886 14,18V6Z"
android:fillColor="@color/textColor"/>
android:fillColor="?attr/colorPrimary"/>
</vector>

View File

@@ -1,8 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="512" android:viewportWidth="512" android:width="24dp">
<path android:fillColor="@color/textColor"
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"/>
<path android:fillColor="?attr/colorPrimary" android:pathData="M464.7,221.5L86.1,7.3C52.5,-11.7 25,7.5 25,50v412c0,42.5 27.5,61.7 61.1,42.7l378.6,-214.1C498.2,271.5 498.2,240.5 464.7,221.5z"/>
</vector>

View File

@@ -5,6 +5,6 @@
android:viewportHeight="24">
<path
android:pathData="M19,7.766c0,-1.554 -1.696,-2.515 -3.029,-1.715l-7.056,4.234c-1.295,0.777 -1.295,2.653 0,3.43l7.056,4.234c1.333,0.8 3.029,-0.16 3.029,-1.715V7.766zM9.944,12L17,7.766v8.468L9.944,12zM6,6a1,1 0,0 1,1 1v10a1,1 0,1 1,-2 0V7a1,1 0,0 1,1 -1z"
android:fillColor="@color/textColor"
android:fillColor="?attr/colorPrimary"
android:fillType="evenOdd"/>
</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="M512,512m-448,0a448,448 0,1 0,896 0,448 448,0 1,0 -896,0Z"
android:fillColor="#00ffffff"/>
<path
android:pathData="M738.1,311.5L448,601.6l-119.5,-119.5 -59.7,59.7 179.2,179.2 349.9,-349.9z"
android:fillColor="@color/black"/>
</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

@@ -12,7 +12,7 @@
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Change Password"
android:text="@string/change_password"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
@@ -24,7 +24,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:hint="Enter Old Password"
android:hint="@string/enter_old_password"
app:endIconMode="password_toggle"
app:layout_constraintTop_toBottomOf="@id/tvTitle">
@@ -42,7 +42,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Enter New Password"
android:hint="@string/enter_new_password"
app:endIconMode="password_toggle"
app:layout_constraintTop_toBottomOf="@id/tilOldPassword">
@@ -61,7 +61,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:padding="12dp"
android:text="Change Password"
android:text="@string/change_password"
app:layout_constraintTop_toBottomOf="@id/tilNewPassword" />
<com.google.android.material.button.MaterialButton
@@ -70,7 +70,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Forgot Password?"
android:text="@string/forgot_password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnChangePassword" />

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.DocumentsActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
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:gravity="center"
android:visibility="gone"
android:orientation="vertical">
<TextView
android:id="@+id/noItemsTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:gravity="center"
android:padding="25dp"
android:text="@string/no_items_available_add_one_by_clicking_on_the_plus_button"/>
</LinearLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
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"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAdd"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="bottom|end"
android:layout_marginEnd="25dp"
android:layout_marginBottom="35dp"
android:src="@android:drawable/ic_input_add" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,164 @@
<?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"
android:id="@+id/toolBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_back"
app:tint="@color/svgTintColor"
android:scaleType="fitCenter"
android:background="#00000000"
android:padding="8dp"
android:id="@+id/back"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/hidden_space"
android:textSize="22sp"
android:singleLine="true"
android:padding="4dp"
android:textStyle="bold"
android:layout_weight="1"
android:id="@+id/folderName"/>
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_edit"
app:tint="?attr/colorPrimary"
android:scaleType="fitCenter"
android:padding="9dp"
android:visibility="gone"
android:background="#00000000"
android:id="@+id/edit"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/delete"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#00000000"
android:padding="9dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_delete"
app:tint="?attr/colorPrimary"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/menuButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_more"
app:tint="?attr/colorPrimary"
android:scaleType="fitCenter"
android:padding="9dp"
android:visibility="gone"
android:background="#00000000"/>
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_list"
android:scaleType="fitCenter"
app:tint="?attr/colorPrimary"
android:background="#00000000"
android:padding="8dp"
android:id="@+id/folderOrientation"/>
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_settings"
android:scaleType="fitCenter"
app:tint="?attr/colorPrimary"
android:background="#00000000"
android:padding="8dp"
android:id="@+id/settings"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolBar">
</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/addFolder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_folder_add"
android:text="@string/add_image"
android:backgroundTint="?attr/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
app:tint="@color/white"
android:layout_marginBottom="20dp"
app:fabCustomSize="57dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/deleteSelected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_delete"
android:visibility="gone"
app:tint="@color/white"
android:backgroundTint="?attr/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
app:fabCustomSize="57dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,121 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:minHeight="300dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal|bottom"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView
android:id="@+id/btnImages"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:layout_constraintBottom_toTopOf="@id/btnAudio"
app:layout_constraintEnd_toStartOf="@id/btnVideos"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Images"
android:textSize="18sp"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/btnVideos"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:layout_constraintBottom_toTopOf="@id/btnDocs"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnImages"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Videos"
android:textSize="18sp"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal|top"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView
android:id="@+id/btnAudio"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btnDocs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnImages">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Audio"
android:textSize="18sp"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/btnDocs"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnAudio"
app:layout_constraintTop_toBottomOf="@id/btnVideos">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Documents"
android:textSize="18sp"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>

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

@@ -7,12 +7,12 @@
android:layout_height="match_parent"
tools:context=".activities.MainActivity">
<!-- Calculator Display -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/displayContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:layout_margin="0dp"
android:background="@drawable/bottom_corner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.3"
app:layout_constraintStart_toStartOf="parent"
@@ -41,25 +41,24 @@
android:id="@+id/display"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoSizeMaxTextSize="48sp"
android:autoSizeMaxTextSize="70sp"
android:autoSizeMinTextSize="16sp"
android:autoSizeStepGranularity="2sp"
android:gravity="end|bottom"
android:autoSizeTextType="uniform"
android:padding="10dp"
android:text="0"
android:textSize="48sp"
android:text=""
android:textSize="70sp"
tools:ignore="Suspicious0dp" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/total"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autoSizeMaxTextSize="26sp"
android:autoSizeMaxTextSize="40sp"
android:autoSizeMinTextSize="24sp"
android:autoSizeStepGranularity="2sp"
android:autoSizeTextType="uniform"
@@ -67,7 +66,7 @@
android:paddingRight="10dp"
android:paddingBottom="10dp"
android:text=""
android:textSize="26sp"
android:textSize="40sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -75,255 +74,274 @@
tools:ignore="Suspicious0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Calculator Buttons -->
<androidx.gridlayout.widget.GridLayout
android:id="@+id/buttonGrid"
<LinearLayout
android:id="@+id/buttonContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="8dp"
app:columnCount="4"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/displayContainer"
app:rowCount="5">
app:layout_constraintTop_toBottomOf="@id/displayContainer">
<!-- Row 1 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClear"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="C"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
android:layout_weight="1"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPercent"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClear"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="C"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPercent"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="%"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDivide"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="÷"
android:textSize="30sp"
app:cornerRadius="15dp" />
<ImageButton
android:id="@+id/cut"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:layout_marginVertical="8dp"
android:background="@drawable/gradient_bg"
android:src="@drawable/backspace"
android:scaleType="centerInside"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="%"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
android:layout_weight="1"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDivide"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
<com.google.android.material.button.MaterialButton
android:id="@+id/btn7"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="7"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn8"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="8"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn9"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="9"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnMultiply"
style="@style/CustomMaterialButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:text="×"
android:textSize="30sp"
app:cornerRadius="15dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="÷"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
android:layout_weight="1"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/cut"
android:layout_width="0dp"
<com.google.android.material.button.MaterialButton
android:id="@+id/btn4"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="4"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn5"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="5"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn6"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="6"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnMinus"
style="@style/CustomMaterialButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:text="-"
android:textSize="30sp"
app:cornerRadius="15dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="4dp"
android:gravity="center"
android:textAlignment="center"
android:textSize="30sp"
app:cornerRadius="15dp"
app:icon="@drawable/backspace"
app:iconSize="32dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
android:layout_weight="1"
android:orientation="horizontal">
<!-- Row 2 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn7"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
<com.google.android.material.button.MaterialButton
android:id="@+id/btn1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="1"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn2"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="2"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn3"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="3"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPlus"
style="@style/CustomMaterialButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:text="+"
android:textSize="30sp"
app:cornerRadius="15dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="7"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
android:layout_weight="1"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn8"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="8"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn0"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="2"
android:text="0"
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn9"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="9"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDot"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:layout_weight="1"
android:text="."
android:textSize="30sp"
app:cornerRadius="15dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnMultiply"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="×"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEquals"
style="@style/CustomMaterialButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:text="="
android:textSize="30sp"
app:cornerRadius="15dp" />
<!-- Row 3 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn4"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="4"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn5"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="5"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn6"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="6"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnMinus"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="-"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<!-- Row 4 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="1"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn2"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="2"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn3"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="3"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPlus"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="+"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<!-- Row 5 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn0"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="0"
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnSpan="2"
app:layout_columnWeight="2"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDot"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="."
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEquals"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="4dp"
android:text="="
android:textSize="30sp"
app:cornerRadius="15dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
</androidx.gridlayout.widget.GridLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -15,13 +15,21 @@
android:paddingLeft="15dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_back"
android:scaleType="fitCenter"
android:background="#00000000"
android:padding="8dp"
android:id="@+id/back"/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:textSize="22sp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="Preview File"/>
android:text="@string/preview_file"/>
</LinearLayout>
<androidx.viewpager2.widget.ViewPager2
@@ -46,19 +54,22 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/unHide"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:minWidth="120dp"
android:text="Unhide" />
android:layout_weight="1"
style="@style/Widget.Material3.Button.OutlinedButton"
android:text="@string/un_hide" />
<com.google.android.material.button.MaterialButton
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="6dp"
android:minWidth="120dp"
android:text="Delete" />
android:text="@string/delete" />
</LinearLayout>

View File

@@ -0,0 +1,291 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.SettingsActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp"
android:id="@+id/toolBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_back"
android:scaleType="fitCenter"
android:background="#00000000"
android:padding="8dp"
android:id="@+id/back"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/settings"
android:textSize="22sp"
android:singleLine="true"
android:padding="4dp"
android:textStyle="bold"
android:layout_weight="1"
android:id="@+id/folderName"/>
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- App Details Section -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardElevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_details"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="10dp"
app:cardElevation="5dp">
<ImageView
android:id="@+id/appLogo"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@mipmap/ic_launcher" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/full_app_name"
android:textAppearance="?attr/textAppearanceBodyLarge" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/version"
android:textAppearance="?attr/textAppearanceBodyMedium" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/githubButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_github" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Developer Details Section -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardElevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/developer_details"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/binondi_borthakur"
android:textAppearance="?attr/textAppearanceBodyLarge" />
<com.google.android.material.button.MaterialButton
android:id="@+id/devGithubButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_github" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Theme Settings Section -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardElevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/theme_settings"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textStyle="bold" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/dynamicThemeSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/dynamic_theme"
android:textAppearance="?attr/textAppearanceBodyLarge" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/themeModeSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/theme_mode"
android:textAppearance="?attr/textAppearanceBodyLarge" />
<RadioGroup
android:id="@+id/themeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/lightThemeRadio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/light" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/darkThemeRadio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dark" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/systemThemeRadio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/system_default" />
</RadioGroup>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Security Settings Section -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/security_settings"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textStyle="bold" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/screenshotRestrictionSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/restrict_screenshots_in_hidden_section"
android:textAppearance="?attr/textAppearanceBodyLarge" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
app:cardElevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_settings"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textStyle="bold" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/showFileNames"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/show_file_names"
android:textAppearance="?attr/textAppearanceBodyLarge" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -12,7 +12,7 @@
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Setup Password"
android:text="@string/setup_password"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
@@ -24,7 +24,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:hint="Enter Password"
android:hint="@string/enter_password"
app:endIconMode="password_toggle"
app:layout_constraintTop_toBottomOf="@id/tvTitle">
@@ -42,7 +42,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Confirm Password"
android:hint="@string/confirm_password"
app:endIconMode="password_toggle"
app:layout_constraintTop_toBottomOf="@id/tilPassword">
@@ -60,7 +60,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Security Question (For Password Reset)"
android:hint="@string/security_question_for_password_reset"
app:layout_constraintTop_toBottomOf="@id/tilConfirmPassword">
<com.google.android.material.textfield.TextInputEditText
@@ -75,7 +75,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Security Answer"
android:hint="@string/security_answer"
app:layout_constraintTop_toBottomOf="@id/tilSecurityQuestion">
<com.google.android.material.textfield.TextInputEditText
@@ -91,7 +91,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:padding="12dp"
android:text="Save Password"
android:text="@string/save_password"
app:layout_constraintTop_toBottomOf="@id/tilSecurityAnswer" />
<com.google.android.material.button.MaterialButton
@@ -100,7 +100,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Forgot Password?"
android:text="@string/forgot_password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnSavePassword" />

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,166 @@
<?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.ViewFolderActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp"
android:id="@+id/toolBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_back"
app:tint="@color/svgTintColor"
android:scaleType="fitCenter"
android:background="#00000000"
android:padding="8dp"
android:id="@+id/back"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/hidden_space"
android:textSize="22sp"
android:singleLine="true"
android:padding="4dp"
android:textStyle="bold"
android:layout_weight="1"
android:id="@+id/folderName"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/menuButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_more"
app:tint="?attr/colorPrimary"
android:scaleType="fitCenter"
android:padding="7dp"
android:visibility="gone"
android:background="#00000000"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolBar">
</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"
app:tint="@color/white"
android:visibility="gone"
android:backgroundTint="?attr/colorPrimary"
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:tint="@color/white"
android:backgroundTint="?attr/colorPrimary"
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"
android:visibility="gone"
app:tint="@color/white"
android:backgroundTint="?attr/colorPrimary"
app:layout_constraintBottom_toTopOf="@+id/addDocument"
app:layout_constraintEnd_toEndOf="@+id/addDocument"
app:layout_constraintStart_toStartOf="@+id/addDocument" />
<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:tint="@color/white"
android:visibility="gone"
android:backgroundTint="?attr/colorPrimary"
app:layout_constraintBottom_toTopOf="@+id/fabExpend"
app:layout_constraintEnd_toEndOf="@+id/fabExpend"
app:layout_constraintStart_toStartOf="@+id/fabExpend" />
<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:backgroundTint="?attr/colorPrimary"
android:text="@string/add_image"
app:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:padding="16dp">
<androidx.cardview.widget.CardView
android:layout_width="100dp"
android:layout_height="3dp"
android:layout_gravity="center"
app:cardCornerRadius="100dp"
android:backgroundTint="#808080"
app:cardElevation="0dp"
android:layout_marginBottom="10dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/select_destination_folder"
android:textSize="20sp"
android:padding="8dp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/folderRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="400dp"/>
</LinearLayout>

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

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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">
<devs.org.calculator.views.SquareImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"/>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
android:layout_marginHorizontal="2dp"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
app:cardCornerRadius="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/selectedLayer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:alpha="0.3"
android:background="?attr/colorControlNormal"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"
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/folderIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
app:tint="?attr/colorPrimary"
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:singleLine="true"
android:textColor="?attr/colorPrimary"
android:maxLines="1"
android:textSize="14sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<ImageView
android:id="@+id/selected"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/gradient_bg"
android:padding="3dp"
android:src="@drawable/selected"
android:layout_gravity="end"
android:visibility="visible"
app:tint="#fff" />
</FrameLayout>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
android:layout_marginHorizontal="8dp"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
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="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/selectedLayer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:alpha="0.3"
android:background="?attr/colorControlNormal"
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" />
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp"
android:gravity="center_vertical"
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/folderIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="8dp"
android:layout_gravity="center"
app:tint="?attr/colorPrimary"
android:src="@drawable/ic_folder" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/folderName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:textColor="?attr/colorPrimary"
android:maxLines="1"
android:singleLine="true"
android:textSize="18sp" />
<TextView
android:id="@+id/timeModified"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:text=""
android:visibility="gone"
android:textColor="#767676"
android:maxLines="1"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/selected"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/gradient_bg"
android:padding="3dp"
android:src="@drawable/selected"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="#fff" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="horizontal"
android:padding="12dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/folderIcon"
android:layout_width="34dp"
android:layout_height="34dp"
android:src="@drawable/ic_folder"
app:tint="?attr/colorPrimary" />
<TextView
android:id="@+id/folderName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textSize="18sp"/>
</LinearLayout>

View File

@@ -0,0 +1,85 @@
<?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">
<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"
android:layout_margin="5dp"
app:cardElevation="3dp"
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">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_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" />
<LinearLayout
android:id="@+id/selectedLayer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.3"
android:background="?attr/colorControlNormal"
android:orientation="horizontal"
android:visibility="gone" />
</FrameLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/videoPlay"
android:layout_width="35dp"
android:layout_height="35dp"
android:visibility="gone"
android:scaleType="fitCenter"
android:src="@drawable/ic_play_circle"
android:layout_gravity="center"/>
<ImageView
android:id="@+id/selected"
android:layout_width="20dp"
android:layout_height="20dp"
android:padding="3dp"
app:tint="#fff"
android:visibility="gone"
android:src="@drawable/selected"
android:background="@drawable/gradient_bg"
android:layout_gravity="end"/>
</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" />
</LinearLayout>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:paddingVertical="15dp"
android:paddingHorizontal="10dp"
android:gravity="center_horizontal"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hiding_files"
android:padding="8dp"
android:textSize="20sp"/>
<com.airbnb.lottie.LottieAnimationView
android:layout_width="match_parent"
android:layout_height="200dp"
app:lottie_fileName="hiding_files.json"
app:lottie_autoPlay="true"
android:scaleX="1.2"
android:scaleY="1.2"
android:scaleType="centerCrop"
app:lottie_loop="true"/>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="25dp"
android:layout_marginBottom="15dp"
android:indeterminate="true"/>
</LinearLayout>

View File

@@ -8,35 +8,42 @@
android:layout_margin="4dp"
app:cardCornerRadius="8dp">
<com.jsibbold.zoomage.ZoomageView
android:id="@+id/imageView"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:zoomage_restrictBounds="false"
app:zoomage_animateOnReset="true"
app:zoomage_autoResetMode="UNDER"
app:zoomage_autoCenter="true"
android:visibility="gone"
app:zoomage_zoomable="true"
app:zoomage_translatable="true"
app:zoomage_minScale="0.6"
app:zoomage_maxScale="8"
/>
android:gravity="center"
android:orientation="vertical">
<com.jsibbold.zoomage.ZoomageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:zoomage_restrictBounds="false"
app:zoomage_animateOnReset="true"
app:zoomage_autoResetMode="UNDER"
app:zoomage_autoCenter="true"
android:visibility="gone"
app:zoomage_zoomable="true"
app:zoomage_translatable="true"
app:zoomage_minScale="0.6"
app:zoomage_maxScale="8"
/>
<VideoView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:id="@+id/videoView"/>
<VideoView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:visibility="gone"
android:layout_gravity="center"
android:id="@+id/videoView"/>
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/audioBg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
style="@style/Widget.Material3.CardView.Outlined">
app:cardCornerRadius="15dp"
app:cardElevation="10dp"
android:layout_margin="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -47,7 +54,7 @@
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="5dp"
android:src="@drawable/music"
android:src="@drawable/ic_audio"
android:padding="3dp"/>
<TextView
@@ -55,11 +62,13 @@
android:layout_height="wrap_content"
android:text="audio.mp3"
android:layout_margin="5dp"
android:textColor="?attr/colorPrimary"
android:id="@+id/audioTitle"/>
<SeekBar
<com.google.android.material.slider.Slider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:scaleY="0.6"
android:id="@+id/audioSeekBar"/>
<LinearLayout
android:layout_width="match_parent"

View File

@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="white">#FF000000</color>
<color name="black">#FFFFFFFF</color>
<!-- Material You dynamic colors -->
<color name="primary">#00B43B</color>
<color name="colorPrimaryVariant">#00C15C</color>
<color name="colorSecondary">#39FF97</color>
<color name="textColor">#ffffff</color>
<color name="svgTintColor" tools:ignore="MissingDefaultResource">#ffffff</color>
</resources>

View File

@@ -1,21 +1,21 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Calculator" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Primary brand color -->
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariant</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color -->
<item name="colorAccent">@color/primary</item>
<item name="colorSecondary">@color/colorSecondary</item>
<item name="colorSecondaryVariant">@color/colorSecondary</item>
<item name="colorOnSecondary">@color/black</item>
<item name="fontFamily">@font/ubuntu_regular</item>
<!-- Status bar color -->
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<!-- Enable window decor fitting -->
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
</style>
<style name="CustomMaterialButton" parent="Widget.MaterialComponents.Button">
<item name="android:background">@drawable/gradient_bg</item>
<item name="backgroundTint">@null</item>
</style>
</resources>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
@@ -8,4 +8,5 @@
<color name="colorPrimaryVariant">#00C15C</color>
<color name="colorSecondary">#39FF97</color>
<color name="textColor">#000000</color>
<color name="svgTintColor" tools:ignore="MissingDefaultResource">#000000</color>
</resources>

View File

@@ -5,12 +5,19 @@
<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>
<string name="documents_hidden_successfully"> Files hidden successfully</string>
<string name="failed_to_hide_unhide_photo">Failed to hide/unhide photo</string>
<string name="images_hidden_successfully"> Images hidden successfully</string>
<string name="failed_to_hide_images">Failed to hide images</string>
<string name="failed_to_hide_files">Failed to hide files</string>
<string name="storage_permissions_granted">Storage permissions granted</string>
<string name="storage_permissions_denied">Storage permissions denied</string>
<string name="preview_images">Preview Images</string>
@@ -46,6 +53,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>
@@ -53,4 +65,65 @@
<string name="details"> DETAILS</string>
<string name="audio_hidded_successfully">Audios hidden successfully</string>
<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 Folder</string>
<string name="are_you_sure_you_want_to_delete_selected_items">Are you sure you want to delete selected Folders?</string>
<string name="items_deleted_successfully">Items deleted successfully</string>
<string name="folder_deleted_successfully">Folder deleted successfully</string>
<string name="some_items_could_not_be_deleted">Some items could not be deleted</string>
<string name="hidden_space">Hidden Space</string>
<string name="there_was_a_problem_in_the_folder">There was a problem in the Folder</string>
<string name="hiding_files">Hiding Files</string>
<string name="file_unhidden_successfully">File Will Now Show In Gallery</string>
<string name="file_unhidden_but_failed_to_remove_from_hidden_folder">File unhidden but failed to remove from hidden folder</string>
<string name="failed_to_unhidden_file">Failed to unhidden file</string>
<string name="failed_to_create_uri_for_file">Failed to create URI for file</string>
<string name="error_unhiding_file">Error unhiding file: %1$s</string>
<string name="select_multiple">Select Multiple</string>
<string name="settings">Settings</string>
<string name="binondi_borthakur">Binondi Borthakur</string>
<string name="dynamic_theme">Dynamic Theme</string>
<string name="theme_mode">Theme Mode</string>
<string name="light">Light</string>
<string name="dark">Dark</string>
<string name="system_default">System Default</string>
<string name="full_app_name">Calculator Hide Files</string>
<string name="developer_details">Developer Details</string>
<string name="theme_settings">Theme Settings</string>
<string name="files_deleted_successfully">Files deleted successfully</string>
<string name="some_files_could_not_be_deleted">Some files could not be deleted</string>
<string name="un_hide_files">Unhide Files</string>
<string name="are_you_sure_you_want_to_un_hide_selected_files">Are you sure you want to unhide the selected files?</string>
<string name="files_unhidden_successfully">Files unhidden successfully</string>
<string name="some_files_could_not_be_unhidden">Some files could not be unhidden</string>
<string name="copy_to_another_folder">Copy to Another Folder</string>
<string name="move_to_another_folder">Move to Another Folder</string>
<string name="select_destination_folder">Select Destination Folder</string>
<string name="no_folders_available">No other folders available</string>
<string name="change_password">Change Password</string>
<string name="enter_old_password">Enter Old Password</string>
<string name="enter_new_password">Enter New Password</string>
<string name="forgot_password">Forgot Password?</string>
<string name="preview_file">Preview File</string>
<string name="show_file_names">Show File Names</string>
<string name="app_settings">App Settings</string>
<string name="restrict_screenshots_in_hidden_section">Restrict Screenshots in Hidden Section</string>
<string name="security_settings">Security Settings</string>
<string name="version">Version 1.3</string>
<string name="app_details">App Details</string>
<string name="setup_password">Setup Password</string>
<string name="security_question_for_password_reset">Security Question (For Password Reset)</string>
<string name="security_answer">Security Answer</string>
<string name="save_password">Save Password</string>
</resources>

View File

@@ -1,23 +1,26 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Calculator" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Primary brand color -->
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariant</item>
<item name="colorAccent">@color/primary</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color -->
<item name="colorSecondary">@color/colorSecondary</item>
<item name="colorSecondaryVariant">@color/colorSecondary</item>
<item name="colorOnSecondary">@color/white</item>
<item name="fontFamily">@font/ubuntu_regular</item>
<!-- Status bar color -->
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="colorControlNormal">#483CFF61</item>
<!-- Enable window decor fitting -->
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
</style>
<style name="Theme.Calculator" parent="Base.Theme.Calculator" />
<style name="CustomMaterialButton" parent="Widget.MaterialComponents.Button">
<item name="android:background">@drawable/gradient_bg</item>
<item name="backgroundTint">@null</item>
</style>
</resources>

View File

@@ -9,6 +9,7 @@ junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
lottie = "6.2.0"
material = "1.12.0"
activity = "1.9.3"
constraintlayout = "2.2.0"
@@ -28,6 +29,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }