diff --git a/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt b/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt index c7049d5..925d6f0 100644 --- a/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt @@ -13,7 +13,6 @@ import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.GridLayoutManager -import com.google.android.material.color.DynamicColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import devs.org.calculator.R import devs.org.calculator.adapters.FolderAdapter @@ -138,7 +137,7 @@ class HiddenActivity : AppCompatActivity() { } else { showEmptyState() } - } catch (e: Exception) { + } catch (_: Exception) { showEmptyState() } @@ -158,25 +157,25 @@ class HiddenActivity : AppCompatActivity() { val inputEditText = dialogView.findViewById(R.id.editText) MaterialAlertDialogBuilder(this) - .setTitle("Enter Folder Name To Create") + .setTitle(getString(R.string.enter_folder_name_to_create)) .setView(dialogView) - .setPositiveButton("Create") { dialog, _ -> + .setPositiveButton(getString(R.string.create)) { dialog, _ -> val newName = inputEditText.text.toString().trim() if (newName.isNotEmpty()) { try { folderManager.createFolder(hiddenDir, newName) refreshCurrentView() - } catch (e: Exception) { + } catch (_: Exception) { Toast.makeText( this@HiddenActivity, - "Failed to create folder", + getString(R.string.failed_to_create_folder), Toast.LENGTH_SHORT ).show() } } dialog.dismiss() } - .setNegativeButton("Cancel") { dialog, _ -> + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() } .show() @@ -214,7 +213,7 @@ class HiddenActivity : AppCompatActivity() { } else { showEmptyState() } - } catch (e: Exception) { + } catch (_: Exception) { showEmptyState() } } @@ -434,7 +433,8 @@ class HiddenActivity : AppCompatActivity() { } if (selectedFolders.size != 1) { - Toast.makeText(this, "Please select exactly one folder to edit", Toast.LENGTH_SHORT).show() + Toast.makeText(this, + getString(R.string.please_select_exactly_one_folder_to_edit), Toast.LENGTH_SHORT).show() return } @@ -449,20 +449,21 @@ class HiddenActivity : AppCompatActivity() { inputEditText.selectAll() MaterialAlertDialogBuilder(this) - .setTitle("Rename Folder") + .setTitle(getString(R.string.rename_folder)) .setView(dialogView) - .setPositiveButton("Rename") { dialog, _ -> + .setPositiveButton(getString(R.string.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() + Toast.makeText(this, + getString(R.string.invalid_folder_name), Toast.LENGTH_SHORT).show() } } dialog.dismiss() } - .setNegativeButton("Cancel") { dialog, _ -> + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() } .show() @@ -481,7 +482,8 @@ class HiddenActivity : AppCompatActivity() { if (parentDir != null) { val newFolder = File(parentDir, newName) if (newFolder.exists()) { - Toast.makeText(this, "Folder with this name already exists", Toast.LENGTH_SHORT).show() + Toast.makeText(this, + getString(R.string.folder_with_this_name_already_exists), Toast.LENGTH_SHORT).show() return } @@ -492,7 +494,7 @@ class HiddenActivity : AppCompatActivity() { refreshCurrentView() } else { - Toast.makeText(this, "Failed to rename folder", Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.failed_to_rename_folder), Toast.LENGTH_SHORT).show() } } } diff --git a/app/src/main/java/devs/org/calculator/activities/MainActivity.kt b/app/src/main/java/devs/org/calculator/activities/MainActivity.kt index b26a4e0..e2a4d2d 100644 --- a/app/src/main/java/devs/org/calculator/activities/MainActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/MainActivity.kt @@ -24,7 +24,6 @@ import devs.org.calculator.utils.PrefsUtil import net.objecthunter.exp4j.ExpressionBuilder import java.util.regex.Pattern import androidx.core.content.edit -import com.google.android.material.color.DynamicColors class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback { private lateinit var binding: ActivityMainBinding @@ -37,7 +36,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial private val dialogUtil = DialogUtil(this) private val fileManager = FileManager(this, this) private val sp by lazy { getSharedPreferences("app", MODE_PRIVATE) } - private val prefs:PrefsUtil by lazy { PrefsUtil(this) } @RequiresApi(Build.VERSION_CODES.R) override fun onCreate(savedInstanceState: Bundle?) { @@ -276,7 +274,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial private fun evaluateExpression(expression: String): Double { return try { ExpressionBuilder(expression).build().evaluate() - } catch (e: Exception) { + } catch (_: Exception) { expression.toDouble() } } @@ -359,7 +357,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial 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) { + } catch (_: Exception) { binding.total.text = "" } } @@ -372,7 +370,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial val lastChar = currentExpression.last() currentExpression = currentExpression.substring(0, currentExpression.length - 1) - // Update flags based on what was removed if (lastChar == '%') { lastWasPercent = false } else if (isOperator(lastChar.toString())) { diff --git a/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt b/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt index 66f32c8..a3c8f29 100644 --- a/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt @@ -6,18 +6,16 @@ import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.color.DynamicColors import devs.org.calculator.R import devs.org.calculator.adapters.ImagePreviewAdapter +import devs.org.calculator.database.AppDatabase +import devs.org.calculator.database.HiddenFileRepository import devs.org.calculator.databinding.ActivityPreviewBinding import devs.org.calculator.utils.DialogUtil import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.PrefsUtil import kotlinx.coroutines.launch import java.io.File -import devs.org.calculator.database.AppDatabase -import devs.org.calculator.database.HiddenFileRepository -import android.util.Log class PreviewActivity : AppCompatActivity() { @@ -35,10 +33,6 @@ class PreviewActivity : AppCompatActivity() { HiddenFileRepository(AppDatabase.getDatabase(this).hiddenFileDao()) } - companion object { - private const val TAG = "PreviewActivity" - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityPreviewBinding.inflate(layoutInflater) @@ -73,7 +67,6 @@ class PreviewActivity : AppCompatActivity() { } private fun setupFlagSecure() { - val prefs = getSharedPreferences("app_settings", MODE_PRIVATE) if (prefs.getBoolean("screenshot_restriction", true)) { window.setFlags( WindowManager.LayoutParams.FLAG_SECURE, @@ -188,18 +181,15 @@ class PreviewActivity : AppCompatActivity() { override fun onPositiveButtonClicked() { lifecycleScope.launch { try { - // First delete from database val hiddenFile = hiddenFileRepository.getHiddenFileByPath(currentFile.absolutePath) hiddenFile?.let { hiddenFileRepository.deleteHiddenFile(it) - Log.d(TAG, "Deleted file metadata from database: ${it.filePath}") } - // Then delete the actual file fileManager.deletePhotoFromExternalStorage(fileUri) removeFileFromList(currentPosition) } catch (e: Exception) { - Log.e(TAG, "Error deleting file: ${e.message}", e) + e.printStackTrace() } } } @@ -228,19 +218,17 @@ class PreviewActivity : AppCompatActivity() { override fun onPositiveButtonClicked() { lifecycleScope.launch { try { - // First copy the file to normal directory val result = fileManager.copyFileToNormalDir(fileUri) if (result != null) { val hiddenFile = hiddenFileRepository.getHiddenFileByPath(currentFile.absolutePath) hiddenFile?.let { hiddenFileRepository.deleteHiddenFile(it) - Log.d(TAG, "Deleted file metadata from database: ${it.filePath}") } removeFileFromList(currentPosition) } } catch (e: Exception) { - Log.e(TAG, "Error unhiding file: ${e.message}", e) + e.printStackTrace() } } } diff --git a/app/src/main/java/devs/org/calculator/activities/SettingsActivity.kt b/app/src/main/java/devs/org/calculator/activities/SettingsActivity.kt index 6663419..036e807 100644 --- a/app/src/main/java/devs/org/calculator/activities/SettingsActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/SettingsActivity.kt @@ -4,6 +4,8 @@ import android.content.Intent import android.os.Bundle import android.view.View import android.view.WindowManager +import android.widget.EditText +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.net.toUri @@ -13,11 +15,12 @@ import com.google.android.material.snackbar.Snackbar import devs.org.calculator.R import devs.org.calculator.databinding.ActivitySettingsBinding import devs.org.calculator.utils.PrefsUtil +import devs.org.calculator.utils.SecurityUtils class SettingsActivity : AppCompatActivity() { private lateinit var binding: ActivitySettingsBinding - private val prefs:PrefsUtil by lazy { PrefsUtil(this) } + private lateinit var prefs: PrefsUtil private var DEV_GITHUB_URL = "" private var GITHUB_URL = "" @@ -25,6 +28,7 @@ class SettingsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) binding = ActivitySettingsBinding.inflate(layoutInflater) setContentView(binding.root) + prefs = PrefsUtil(this) DEV_GITHUB_URL = getString(R.string.github_profile) GITHUB_URL = getString(R.string.calculator_hide_files, DEV_GITHUB_URL) setupUI() @@ -52,6 +56,8 @@ class SettingsActivity : AppCompatActivity() { else -> binding.systemThemeRadio.isChecked = true } + val isUsingCustomKey = SecurityUtils.isUsingCustomKey(this) + binding.customKeyStatus.isChecked = isUsingCustomKey binding.screenshotRestrictionSwitch.isChecked = prefs.getBoolean("screenshot_restriction", true) binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true) binding.encryptionSwitch.isChecked = prefs.getBoolean("encryption", false) @@ -113,6 +119,10 @@ class SettingsActivity : AppCompatActivity() { binding.showFileNames.setOnCheckedChangeListener { _, isChecked -> prefs.setBoolean("showFileName", isChecked) } + + binding.customKeyStatus.setOnClickListener { + showCustomKeyDialog() + } } private fun updateThemeModeVisibility() { @@ -154,4 +164,57 @@ class SettingsActivity : AppCompatActivity() { getString(R.string.could_not_open_url), Snackbar.LENGTH_SHORT).show() } } + + private fun showCustomKeyDialog() { + val dialogView = layoutInflater.inflate(R.layout.dialog_custom_key, null) + val keyInput = dialogView.findViewById(R.id.keyInput) + val confirmKeyInput = dialogView.findViewById(R.id.confirmKeyInput) + + MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.set_custom_encryption_key)) + .setView(dialogView) + .setPositiveButton(getString(R.string.set)) { dialog, _ -> + val key = keyInput.text.toString() + val confirmKey = confirmKeyInput.text.toString() + + if (key.isEmpty()) { + Toast.makeText(this, getString(R.string.key_cannot_be_empty), Toast.LENGTH_SHORT).show() + updateUI() + return@setPositiveButton + } + + if (key != confirmKey) { + Toast.makeText(this, getString(R.string.keys_do_not_match), Toast.LENGTH_SHORT).show() + updateUI() + return@setPositiveButton + } + + if (SecurityUtils.setCustomKey(this, key)) { + Toast.makeText(this, + getString(R.string.custom_key_set_successfully), Toast.LENGTH_SHORT).show() + updateUI() + } else { + Toast.makeText(this, + getString(R.string.failed_to_set_custom_key), Toast.LENGTH_SHORT).show() + updateUI() + } + } + .setNegativeButton(getString(R.string.cancel)){ _, _ -> + updateUI() + } + .setNeutralButton(getString(R.string.delete_key)) { _, _ -> + SecurityUtils.clearCustomKey(this) + Toast.makeText(this, + getString(R.string.custom_encryption_key_cleared), Toast.LENGTH_SHORT).show() + updateUI() + } + .show() + } + + private fun updateUI() { + binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true) + binding.encryptionSwitch.isChecked = prefs.getBoolean("encryption", false) + val isUsingCustomKey = SecurityUtils.isUsingCustomKey(this) + binding.customKeyStatus.isChecked = isUsingCustomKey + } } \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt b/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt index 9c2dda9..502cde2 100644 --- a/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt @@ -38,6 +38,9 @@ import devs.org.calculator.utils.PrefsUtil import devs.org.calculator.utils.SecurityUtils import kotlinx.coroutines.launch import java.io.File +import android.widget.CheckBox +import android.widget.CompoundButton +import android.app.AlertDialog class ViewFolderActivity : AppCompatActivity() { @@ -166,7 +169,6 @@ class ViewFolderActivity : AppCompatActivity() { object : FileProcessCallback { override fun onFilesProcessedSuccessfully(copiedFiles: List) { mainHandler.post { - // Add files to Room database copiedFiles.forEach { file -> val fileType = fileManager.getFileType(file) var finalFile = file @@ -331,14 +333,19 @@ class ViewFolderActivity : AppCompatActivity() { if (selectedFiles.isEmpty()) return lifecycleScope.launch { - // Check if any files are encrypted var hasEncryptedFiles = false var hasDecryptedFiles = false + var hasEncFilesWithoutMetadata = false for (file in selectedFiles) { val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath) - if (hiddenFile?.isEncrypted == true) { - hasEncryptedFiles = true + + if (file.name.endsWith(ENCRYPTED_EXTENSION)) { + if (hiddenFile?.isEncrypted == true) { + hasEncryptedFiles = true + } else { + hasEncFilesWithoutMetadata = true + } } else { hasDecryptedFiles = true } @@ -351,14 +358,14 @@ class ViewFolderActivity : AppCompatActivity() { getString(R.string.move_to_another_folder) ) - // Add encryption/decryption options based on file status if (hasDecryptedFiles) { options.add(getString(R.string.encrypt_file)) } - if (hasEncryptedFiles) { + if (hasEncryptedFiles || hasEncFilesWithoutMetadata) { options.add(getString(R.string.decrypt_file)) } + MaterialAlertDialogBuilder(this@ViewFolderActivity) .setTitle(getString(R.string.file_options)) .setItems(options.toTypedArray()) { _, which -> @@ -371,7 +378,20 @@ class ViewFolderActivity : AppCompatActivity() { val option = options[which] when (option) { getString(R.string.encrypt_file) -> fileAdapter?.encryptSelectedFiles() - getString(R.string.decrypt_file) -> fileAdapter?.decryptSelectedFiles() + getString(R.string.decrypt_file) -> { + lifecycleScope.launch { + val filesWithoutMetadata = selectedFiles.filter { file -> + file.name.endsWith(ENCRYPTED_EXTENSION) && + fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)?.isEncrypted != true + } + + if (filesWithoutMetadata.isNotEmpty()) { + showDecryptionTypeDialog(filesWithoutMetadata) + } else { + fileAdapter?.decryptSelectedFiles() + } + } + } } } } @@ -380,6 +400,178 @@ class ViewFolderActivity : AppCompatActivity() { } } + private fun showDecryptionTypeDialog(selectedFiles: List) { + val dialogView = layoutInflater.inflate(R.layout.dialog_file_type_selection, null) + val imageCheckbox = dialogView.findViewById(R.id.checkboxImage) + val videoCheckbox = dialogView.findViewById(R.id.checkboxVideo) + val audioCheckbox = dialogView.findViewById(R.id.checkboxAudio) + val checkboxes = listOf(imageCheckbox, videoCheckbox, audioCheckbox) + checkboxes.forEach { checkbox -> + checkbox.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + checkboxes.filter { it != checkbox }.forEach { it.isChecked = false } + } + } + } + + val dialog = MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.select_file_type)) + .setMessage(getString(R.string.please_select_the_type_of_file_to_decrypt)) + .setView(dialogView) + .setNegativeButton(getString(R.string.cancel), null) + .setPositiveButton(getString(R.string.decrypt), null) + .create() + + dialog.setOnShowListener { + val positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) + positiveButton.isEnabled = false + val checkboxListener = CompoundButton.OnCheckedChangeListener { _, _ -> + positiveButton.isEnabled = checkboxes.any { it.isChecked } + } + checkboxes.forEach { it.setOnCheckedChangeListener(checkboxListener) } + } + + dialog.setOnDismissListener { + checkboxes.forEach { it.setOnCheckedChangeListener(null) } + } + + dialog.show() + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val selectedType = when { + imageCheckbox.isChecked -> FileManager.FileType.IMAGE + videoCheckbox.isChecked -> FileManager.FileType.VIDEO + audioCheckbox.isChecked -> FileManager.FileType.AUDIO + else -> return@setOnClickListener + } + + lifecycleScope.launch { + performDecryptionWithType(selectedFiles, selectedType) + } + dialog.dismiss() + } + } + + private fun performDecryptionWithType(selectedFiles: List, fileType: FileManager.FileType) { + lifecycleScope.launch { + var successCount = 0 + var failCount = 0 + + for (file in selectedFiles) { + try { + val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath) + + + if (hiddenFile?.isEncrypted == true) { + + val originalExtension = hiddenFile.originalExtension + val decryptedFile = SecurityUtils.changeFileExtension(file, originalExtension) + + + if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) { + if (decryptedFile.exists() && decryptedFile.length() > 0) { + + hiddenFile.let { + fileAdapter?.hiddenFileRepository?.updateEncryptionStatus( + filePath = file.absolutePath, + newFilePath = decryptedFile.absolutePath, + encryptedFileName = decryptedFile.name, + isEncrypted = false + ) + } + if (file.delete()) { + + successCount++ + } else { + + decryptedFile.delete() + failCount++ + } + } else { + + decryptedFile.delete() + failCount++ + } + } else { + + if (decryptedFile.exists()) { + decryptedFile.delete() + } + failCount++ + } + } else if (file.name.endsWith(ENCRYPTED_EXTENSION) && hiddenFile == null) { + + val extension = when (fileType) { + FileManager.FileType.IMAGE -> ".jpg" + FileManager.FileType.VIDEO -> ".mp4" + FileManager.FileType.AUDIO -> ".mp3" + else -> ".txt" + } + + val decryptedFile = SecurityUtils.changeFileExtension(file, extension) + + + if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) { + if (decryptedFile.exists() && decryptedFile.length() > 0) { + + + fileAdapter?.hiddenFileRepository?.insertHiddenFile( + HiddenFileEntity( + filePath = decryptedFile.absolutePath, + fileName = decryptedFile.name, + encryptedFileName = file.name, + fileType = fileType, + originalExtension = extension, + isEncrypted = false + ) + ) + if (file.delete()) { + + successCount++ + } else { + + decryptedFile.delete() + failCount++ + } + } else { + + decryptedFile.delete() + failCount++ + } + } else { + + if (decryptedFile.exists()) { + decryptedFile.delete() + } + failCount++ + } + } else { + + failCount++ + } + } catch (e: Exception) { + + failCount++ + } + } + + mainHandler.post { + fileAdapter?.exitSelectionMode() + when { + successCount > 0 && failCount == 0 -> { + Toast.makeText(this@ViewFolderActivity, "Decrypted $successCount file(s)", Toast.LENGTH_SHORT).show() + } + successCount > 0 && failCount > 0 -> { + Toast.makeText(this@ViewFolderActivity, "Decrypted $successCount file(s), failed to decrypt $failCount", Toast.LENGTH_LONG).show() + } + failCount > 0 -> { + Toast.makeText(this@ViewFolderActivity, "Failed to decrypt $failCount file(s)", Toast.LENGTH_SHORT).show() + } + } + refreshCurrentFolder() + } + } + } + private fun moveToAnotherFolder(selectedFiles: List) { showFolderSelectionDialog { destinationFolder -> moveFilesToFolder(selectedFiles, destinationFolder) @@ -437,7 +629,7 @@ class ViewFolderActivity : AppCompatActivity() { if (files.isNotEmpty()) { binding.recyclerView.visibility = View.VISIBLE binding.noItems.visibility = View.GONE - // Submit new list directly + fileAdapter?.submitList(files.toMutableList()) fileAdapter?.let { adapter -> if (adapter.isInSelectionMode()) { @@ -451,7 +643,7 @@ class ViewFolderActivity : AppCompatActivity() { } } } catch (e: Exception) { - Log.e("ViewFolderActivity", "Error refreshing folder: ${e.message}") + mainHandler.post { showEmptyState() } @@ -610,7 +802,7 @@ class ViewFolderActivity : AppCompatActivity() { } } } catch (e: Exception) { - Log.e("ViewFolderActivity", "Error unhiding file: ${e.message}") + allUnhidden = false } } @@ -642,7 +834,7 @@ class ViewFolderActivity : AppCompatActivity() { allDeleted = false } } catch (e: Exception) { - Log.e("ViewFolderActivity", "Error deleting file: ${e.message}") + allDeleted = false } } @@ -689,7 +881,7 @@ class ViewFolderActivity : AppCompatActivity() { ) } } catch (e: Exception) { - Log.e("ViewFolderActivity", "Error copying file: ${e.message}") + allCopied = false } } @@ -723,7 +915,7 @@ class ViewFolderActivity : AppCompatActivity() { file.delete() } catch (e: Exception) { - Log.e("ViewFolderActivity", "Error moving file: ${e.message}") + allMoved = false } } diff --git a/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt b/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt index 2fc1909..9882dcb 100644 --- a/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt +++ b/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt @@ -55,10 +55,6 @@ class FileAdapter( HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao()) } - companion object { - private const val TAG = "FileAdapter" - } - interface FileOperationCallback { fun onFileDeleted(file: File) fun onFileRenamed(oldFile: File, newFile: File) @@ -76,6 +72,7 @@ class FileAdapter( 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 shade: View = view.findViewById(R.id.shade) val selected: ImageView = view.findViewById(R.id.selected) val encryptedIcon: ImageView = view.findViewById(R.id.encrypted) @@ -91,6 +88,7 @@ class FileAdapter( setupFileDisplay(file, fileType, hiddenFile?.isEncrypted == true,hiddenFile) setupClickListeners(file, fileType) fileNameTextView.visibility = if (showFileName) View.VISIBLE else View.GONE + shade.visibility = if (showFileName) View.VISIBLE else View.GONE val position = adapterPosition if (position != RecyclerView.NO_POSITION) { @@ -99,11 +97,11 @@ class FileAdapter( } encryptedIcon.visibility = if (hiddenFile?.isEncrypted == true) View.VISIBLE else View.GONE } catch (e: Exception) { - Log.e(TAG, "Error binding file: ${e.message}") } } } + fun bind(file: File, payloads: List) { if (payloads.isEmpty()) { bind(file) @@ -163,7 +161,6 @@ class FileAdapter( showEncryptedIcon() } } catch (e: Exception) { - Log.e(TAG, "Error loading encrypted image preview: ${e.message}") showEncryptedIcon() } } else { @@ -200,7 +197,6 @@ class FileAdapter( showEncryptedIcon() } } catch (e: Exception) { - Log.e(TAG, "Error loading encrypted video preview: ${e.message}") showEncryptedIcon() } } else { @@ -260,7 +256,8 @@ class FileAdapter( private fun openFile(file: File, fileType: FileManager.FileType) { if (!file.exists()) { - Toast.makeText(context, "File no longer exists", Toast.LENGTH_SHORT).show() + Toast.makeText(context, + context.getString(R.string.file_no_longer_exists), Toast.LENGTH_SHORT).show() return } @@ -270,46 +267,45 @@ class FileAdapter( lifecycleOwner.lifecycleScope.launch { try { val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath) - if (hiddenFile?.isEncrypted == true) { - val tempFile = File(context.cacheDir, "preview_${file.name}") - Log.d(TAG, "Attempting to decrypt file for preview: ${file.absolutePath}") - - if (SecurityUtils.decryptFile(context, file, tempFile)) { - Log.d(TAG, "Successfully decrypted file for preview: ${tempFile.absolutePath}") - if (tempFile.exists() && tempFile.length() > 0) { - mainHandler.post { - val fileTypeString = when (fileType) { - FileManager.FileType.IMAGE -> context.getString(R.string.image) - FileManager.FileType.VIDEO -> context.getString(R.string.video) - else -> "unknown" - } + if (hiddenFile?.isEncrypted == true || file.extension == FileManager.ENCRYPTED_EXTENSION) { + if (file.extension == FileManager.ENCRYPTED_EXTENSION && hiddenFile == null) { + showDecryptionTypeDialog(file) + } else { + val tempFile = File(context.cacheDir, "preview_${file.name}") + + if (SecurityUtils.decryptFile(context, file, tempFile)) { + if (tempFile.exists() && tempFile.length() > 0) { + mainHandler.post { + 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) - putExtra("isEncrypted", true) - putExtra("tempFile", tempFile.absolutePath) + val intent = Intent(context, PreviewActivity::class.java).apply { + putExtra("type", fileTypeString) + putExtra("folder", currentFolder.toString()) + putExtra("position", adapterPosition) + putExtra("isEncrypted", true) + putExtra("tempFile", tempFile.absolutePath) + } + context.startActivity(intent) + } + } else { + mainHandler.post { + Toast.makeText(context, "Failed to prepare file for preview", Toast.LENGTH_SHORT).show() } - context.startActivity(intent) } } else { - Log.e(TAG, "Decrypted preview file is empty or doesn't exist") mainHandler.post { - Toast.makeText(context, "Failed to prepare file for preview", Toast.LENGTH_SHORT).show() + Toast.makeText(context, "Failed to decrypt file for preview", Toast.LENGTH_SHORT).show() } } - } else { - Log.e(TAG, "Failed to decrypt file for preview") - mainHandler.post { - Toast.makeText(context, "Failed to decrypt file for preview", Toast.LENGTH_SHORT).show() - } } } else { openInPreview(fileType) } } catch (e: Exception) { - Log.e(TAG, "Error preparing file for preview: ${e.message}", e) mainHandler.post { Toast.makeText(context, "Error preparing file for preview", Toast.LENGTH_SHORT).show() } @@ -337,7 +333,6 @@ class FileAdapter( } 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), @@ -360,7 +355,7 @@ class FileAdapter( } 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), @@ -435,7 +430,6 @@ class FileAdapter( val success = try { file.renameTo(newFile) } catch (e: Exception) { - Log.e(TAG, "Failed to rename file: ${e.message}") false } @@ -469,6 +463,86 @@ class FileAdapter( imageView.setImageResource(R.drawable.encrypted) imageView.setPadding(50, 50, 50, 50) } + + private fun showDecryptionTypeDialog(file: File) { + val options = arrayOf("Image", "Video", "Audio") + MaterialAlertDialogBuilder(context) + .setTitle("Select File Type") + .setMessage("Please select the type of file to decrypt") + .setItems(options) { _, which -> + val selectedType = when (which) { + 0 -> FileManager.FileType.IMAGE + 1 -> FileManager.FileType.VIDEO + 2 -> FileManager.FileType.AUDIO + else -> FileManager.FileType.DOCUMENT + } + performDecryptionWithType(file, selectedType) + } + .setNegativeButton("Cancel", null) + .show() + } + + private fun performDecryptionWithType(file: File, fileType: FileManager.FileType) { + lifecycleOwner.lifecycleScope.launch { + try { + val extension = when (fileType) { + FileManager.FileType.IMAGE -> ".jpg" + FileManager.FileType.VIDEO -> ".mp4" + FileManager.FileType.AUDIO -> ".mp3" + else -> ".txt" + } + + val decryptedFile = SecurityUtils.changeFileExtension(file, extension) + if (SecurityUtils.decryptFile(context, file, decryptedFile)) { + if (decryptedFile.exists() && decryptedFile.length() > 0) { + hiddenFileRepository.insertHiddenFile( + HiddenFileEntity( + filePath = decryptedFile.absolutePath, + fileName = decryptedFile.name, + encryptedFileName = file.name, + fileType = fileType, + originalExtension = extension, + isEncrypted = false + ) + ) + + when (fileType) { + FileManager.FileType.IMAGE, FileManager.FileType.VIDEO -> { + val intent = Intent(context, PreviewActivity::class.java).apply { + putExtra("type", if (fileType == FileManager.FileType.IMAGE) "image" else "video") + putExtra("folder", currentFolder.toString()) + putExtra("position", adapterPosition) + putExtra("isEncrypted", false) + putExtra("file", decryptedFile.absolutePath) + } + context.startActivity(intent) + } + FileManager.FileType.AUDIO -> openAudioFile(decryptedFile) + else -> openDocumentFile(decryptedFile) + } + + file.delete() + } else { + decryptedFile.delete() + mainHandler.post { + Toast.makeText(context, "Failed to decrypt file", Toast.LENGTH_SHORT).show() + } + } + } else { + if (decryptedFile.exists()) { + decryptedFile.delete() + } + mainHandler.post { + Toast.makeText(context, "Failed to decrypt file", Toast.LENGTH_SHORT).show() + } + } + } catch (e: Exception) { + mainHandler.post { + Toast.makeText(context, "Error decrypting file", Toast.LENGTH_SHORT).show() + } + } + } + } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder { @@ -591,7 +665,6 @@ class FileAdapter( fileExecutor.shutdown() } } catch (e: Exception) { - Log.e(TAG, "Error shutting down executor: ${e.message}") } fileOperationCallback?.clear() @@ -669,7 +742,6 @@ class FileAdapter( failCount++ } } catch (e: Exception) { - Log.e(TAG, "Error encrypting file: ${e.message}") failCount++ } } @@ -748,7 +820,6 @@ class FileAdapter( failCount++ } } catch (e: Exception) { - Log.e(TAG, "Error decrypting file: ${e.message}") failCount++ } } diff --git a/app/src/main/java/devs/org/calculator/adapters/ImagePreviewAdapter.kt b/app/src/main/java/devs/org/calculator/adapters/ImagePreviewAdapter.kt index 2113af1..2ac21ec 100644 --- a/app/src/main/java/devs/org/calculator/adapters/ImagePreviewAdapter.kt +++ b/app/src/main/java/devs/org/calculator/adapters/ImagePreviewAdapter.kt @@ -10,21 +10,19 @@ import android.view.View import android.view.ViewGroup import android.widget.MediaController import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide -import devs.org.calculator.databinding.ViewpagerItemsBinding -import devs.org.calculator.utils.FileManager -import java.io.File import devs.org.calculator.R import devs.org.calculator.database.AppDatabase import devs.org.calculator.database.HiddenFileRepository -import devs.org.calculator.utils.SecurityUtils -import android.util.Log -import androidx.lifecycle.lifecycleScope +import devs.org.calculator.databinding.ViewpagerItemsBinding +import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.SecurityUtils.getDecryptedPreviewFile import devs.org.calculator.utils.SecurityUtils.getUriForPreviewFile import kotlinx.coroutines.launch +import java.io.File class ImagePreviewAdapter( private val context: Context, @@ -38,10 +36,6 @@ class ImagePreviewAdapter( HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao()) } - companion object { - private const val TAG = "ImagePreviewAdapter" - } - var images: List get() = differ.currentList set(value) = differ.submitList(value) @@ -53,10 +47,10 @@ class ImagePreviewAdapter( override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { val imageUrl = images[position] - + val fileType = FileManager(context,lifecycleOwner).getFileType(imageUrl) stopAndResetCurrentAudio() - holder.bind(imageUrl, position) + holder.bind(imageUrl, position,fileType) } override fun getItemCount(): Int = images.size @@ -77,36 +71,42 @@ class ImagePreviewAdapter( private var currentPosition = 0 private var tempDecryptedFile: File? = null - fun bind(file: File, position: Int) { + fun bind(file: File, position: Int,decryptedFileType: FileManager.FileType) { currentPosition = position releaseMediaPlayer() resetAudioUI() cleanupTempFile() - lifecycleOwner.lifecycleScope.launch { - try { + try { + lifecycleOwner.lifecycleScope.launch { val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath) - val isEncrypted = hiddenFile?.isEncrypted == true - val fileType = hiddenFile!!.fileType - if (isEncrypted) { - - val tempDecryptedFile = getDecryptedPreviewFile(context, hiddenFile) - if (tempDecryptedFile != null && tempDecryptedFile.exists() && tempDecryptedFile.length() > 0) { - displayFile(tempDecryptedFile, fileType) + if (hiddenFile != null){ + val isEncrypted = hiddenFile.isEncrypted + val fileType = hiddenFile.fileType + if (isEncrypted) { + val tempDecryptedFile = getDecryptedPreviewFile(context, hiddenFile) + if (tempDecryptedFile != null && tempDecryptedFile.exists() && tempDecryptedFile.length() > 0) { + displayFile(tempDecryptedFile, fileType,true) + } else { + showEncryptedError() + } } else { - showEncryptedError() + displayFile(file, decryptedFileType,false) } - } else { - displayFile(file, fileType) + }else{ + displayFile(file, decryptedFileType,false) } - } catch (e: Exception) { - Log.e(TAG, "Error binding file: ${e.message}", e) - showEncryptedError() + } + + } catch (_: Exception) { + + displayFile(file, decryptedFileType,false) } + } - private fun displayFile(file: File, fileType: FileManager.FileType) { + private fun displayFile(file: File, fileType: FileManager.FileType,isEncrypted: Boolean = false) { val uri = getUriForPreviewFile(context, file) when (fileType) { FileManager.FileType.VIDEO -> { @@ -114,7 +114,11 @@ class ImagePreviewAdapter( binding.audioBg.visibility = View.GONE binding.videoView.visibility = View.VISIBLE - val videoUri = uri + val videoUri = if (isEncrypted){ + uri + }else{ + Uri.fromFile(file) + } binding.videoView.setVideoURI(videoUri) binding.videoView.start() @@ -139,21 +143,30 @@ class ImagePreviewAdapter( } } FileManager.FileType.IMAGE -> { - + val imageUri = if (isEncrypted){ + uri + }else{ + Uri.fromFile(file) + } binding.imageView.visibility = View.VISIBLE binding.videoView.visibility = View.GONE binding.audioBg.visibility = View.GONE Glide.with(context) - .load(uri) + .load(imageUri) .into(binding.imageView) } FileManager.FileType.AUDIO -> { + val audioFile: File? = if (isEncrypted) { + getFileFromUri(context, uri!!) + } else { + file + } binding.imageView.visibility = View.GONE binding.audioBg.visibility = View.VISIBLE binding.videoView.visibility = View.GONE binding.audioTitle.text = file.name - setupAudioPlayer(file) + setupAudioPlayer(audioFile!!) setupPlaybackControls() } else -> { @@ -164,6 +177,20 @@ class ImagePreviewAdapter( } } + fun getFileFromUri(context: Context, uri: Uri): File? { + return try { + val inputStream = context.contentResolver.openInputStream(uri) + val tempFile = File.createTempFile("temp_audio", null, context.cacheDir) + tempFile.outputStream().use { output -> + inputStream?.copyTo(output) + } + tempFile + } catch (e: Exception) { + e.printStackTrace() + null + } + } + private fun showEncryptedError() { binding.imageView.visibility = View.VISIBLE binding.videoView.visibility = View.GONE @@ -176,9 +203,7 @@ class ImagePreviewAdapter( if (file.exists()) { try { file.delete() - Log.d(TAG, "Cleaned up temporary decrypted file: ${file.absolutePath}") - } catch (e: Exception) { - Log.e(TAG, "Error cleaning up temporary file: ${e.message}", e) + } catch (_: Exception) { } } tempDecryptedFile = null @@ -188,7 +213,7 @@ class ImagePreviewAdapter( private fun resetAudioUI() { binding.playPause.setImageResource(R.drawable.play) binding.audioSeekBar.value = 0f - binding.audioSeekBar.valueTo = 100f // Default value + binding.audioSeekBar.valueTo = 100f seekRunnable?.let { seekHandler.removeCallbacks(it) } } diff --git a/app/src/main/java/devs/org/calculator/utils/FileManager.kt b/app/src/main/java/devs/org/calculator/utils/FileManager.kt index b3ba48d..a6acd12 100644 --- a/app/src/main/java/devs/org/calculator/utils/FileManager.kt +++ b/app/src/main/java/devs/org/calculator/utils/FileManager.kt @@ -51,6 +51,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life const val ENCRYPTED_EXTENSION = ".enc" } + fun getHiddenDirectory(): File { val dir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR) if (!dir.exists()) { @@ -58,7 +59,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life if (!created) { throw RuntimeException("Failed to create hidden directory: ${dir.absolutePath}") } - // Create .nomedia file to hide from media scanners val nomediaFile = File(dir, ".nomedia") if (!nomediaFile.exists()) { nomediaFile.createNewFile() @@ -399,4 +399,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life DOCUMENT(DOCS_DIR), ALL("all") } + + } \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/utils/SecurityUtils.kt b/app/src/main/java/devs/org/calculator/utils/SecurityUtils.kt index 5ec3013..205d056 100644 --- a/app/src/main/java/devs/org/calculator/utils/SecurityUtils.kt +++ b/app/src/main/java/devs/org/calculator/utils/SecurityUtils.kt @@ -2,7 +2,6 @@ package devs.org.calculator.utils import android.content.Context import android.net.Uri -import android.util.Log import java.io.File import java.io.FileInputStream import java.io.FileOutputStream @@ -17,27 +16,38 @@ import javax.crypto.spec.SecretKeySpec import android.content.SharedPreferences import androidx.core.content.FileProvider import devs.org.calculator.database.HiddenFileEntity +import androidx.core.content.edit object SecurityUtils { private const val ALGORITHM = "AES" private const val TRANSFORMATION = "AES/CBC/PKCS5Padding" private const val KEY_SIZE = 256 - private const val TAG = "SecurityUtils" private fun getSecretKey(context: Context): SecretKey { val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE) - val encodedKey = keyStore.getString("secret_key", null) + val useCustomKey = keyStore.getBoolean("use_custom_key", false) + + if (useCustomKey) { + val customKey = keyStore.getString("custom_key", null) + if (customKey != null) { + try { + val messageDigest = java.security.MessageDigest.getInstance("SHA-256") + val keyBytes = messageDigest.digest(customKey.toByteArray()) + return SecretKeySpec(keyBytes, ALGORITHM) + } catch (_: Exception) { + } + } + } + val encodedKey = keyStore.getString("secret_key", null) return if (encodedKey != null) { try { val decodedKey = android.util.Base64.decode(encodedKey, android.util.Base64.DEFAULT) SecretKeySpec(decodedKey, ALGORITHM) - } catch (e: Exception) { - Log.e(TAG, "Error decoding stored key, generating new key", e) + } catch (_: Exception) { generateAndStoreNewKey(keyStore) } } else { - Log.d(TAG, "No stored key found, generating new key") generateAndStoreNewKey(keyStore) } } @@ -47,14 +57,13 @@ object SecurityUtils { keyGenerator.init(KEY_SIZE, SecureRandom()) val key = keyGenerator.generateKey() val encodedKey = android.util.Base64.encodeToString(key.encoded, android.util.Base64.DEFAULT) - keyStore.edit().putString("secret_key", encodedKey).apply() + keyStore.edit { putString("secret_key", encodedKey) } return key } fun encryptFile(context: Context, inputFile: File, outputFile: File): Boolean { return try { if (!inputFile.exists()) { - Log.e(TAG, "Input file does not exist: ${inputFile.absolutePath}") return false } @@ -66,7 +75,6 @@ object SecurityUtils { FileInputStream(inputFile).use { input -> FileOutputStream(outputFile).use { output -> - // Write IV at the beginning of the file output.write(iv) CipherOutputStream(output, cipher).use { cipherOutput -> input.copyTo(cipherOutput) @@ -74,26 +82,22 @@ object SecurityUtils { } } - // Verify the encrypted file exists and has content if (!outputFile.exists() || outputFile.length() == 0L) { - Log.e(TAG, "Encrypted file is empty or does not exist: ${outputFile.absolutePath}") return false } - // Verify we can read the IV from the encrypted file FileInputStream(outputFile).use { input -> val iv = ByteArray(16) val bytesRead = input.read(iv) if (bytesRead != 16) { - Log.e(TAG, "Failed to verify IV in encrypted file: expected 16 bytes but got $bytesRead") + return false } } true - } catch (e: Exception) { - Log.e(TAG, "Error encrypting file: ${e.message}", e) - // Clean up the output file if it exists + } catch (_: Exception) { + if (outputFile.exists()) { outputFile.delete() } @@ -105,60 +109,30 @@ object SecurityUtils { try { val encryptedFile = File(meta.filePath) if (!encryptedFile.exists()) { - Log.e(TAG, "Encrypted file does not exist: ${meta.filePath}") return null } - // Create a unique temp file name using the original file name val tempDir = File(context.cacheDir, "preview_temp") if (!tempDir.exists()) tempDir.mkdirs() - // Use the original extension from metadata val tempFile = File(tempDir, "preview_${System.currentTimeMillis()}_${meta.fileName}") - - // Clean up any existing temp files + tempDir.listFiles()?.forEach { it.delete() } - // Attempt to decrypt the file val success = decryptFile(context, encryptedFile, tempFile) if (success && tempFile.exists() && tempFile.length() > 0) { - Log.d(TAG, "Successfully created preview file: ${tempFile.absolutePath}") return tempFile } else { - Log.e(TAG, "Failed to create preview file or file is empty") if (tempFile.exists()) tempFile.delete() return null } - } catch (e: Exception) { - Log.e(TAG, "Error creating preview file: ${e.message}", e) + } catch (_: Exception) { return null } } - fun getDecryptedFileUri(context: Context, encryptedFile: File): Uri? { - return try { - // Create temp file in cache dir with same extension - val extension = getFileExtension(encryptedFile) - val tempFile = File.createTempFile("decrypted_", extension, context.cacheDir) - - if (decryptFile(context, encryptedFile, tempFile)) { - val uri = FileProvider.getUriForFile( - context, - "${context.packageName}.provider", - tempFile - ) - uri - } else { - null - } - } catch (e: Exception) { - Log.e(TAG, "Failed to get decrypted file URI: ${e.message}", e) - null - } - } - fun getUriForPreviewFile(context: Context, file: File): Uri? { return try { FileProvider.getUriForFile( @@ -166,8 +140,7 @@ object SecurityUtils { "${context.packageName}.provider", // Must match AndroidManifest file ) - } catch (e: Exception) { - Log.e("PreviewUtils", "Error getting URI", e) + } catch (_: Exception) { null } } @@ -177,32 +150,25 @@ object SecurityUtils { fun decryptFile(context: Context, inputFile: File, outputFile: File): Boolean { return try { if (!inputFile.exists()) { - Log.e(TAG, "Input file does not exist: ${inputFile.absolutePath}") return false } if (inputFile.length() == 0L) { - Log.e(TAG, "Input file is empty: ${inputFile.absolutePath}") return false } val secretKey = getSecretKey(context) val cipher = Cipher.getInstance(TRANSFORMATION) - - // First verify we can read the IV FileInputStream(inputFile).use { input -> val iv = ByteArray(16) val bytesRead = input.read(iv) if (bytesRead != 16) { - Log.e(TAG, "Failed to read IV: expected 16 bytes but got $bytesRead") return false } cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv)) - // Create a new input stream for the actual decryption FileInputStream(inputFile).use { decInput -> - // Skip the IV decInput.skip(16) FileOutputStream(outputFile).use { output -> @@ -213,16 +179,12 @@ object SecurityUtils { } } - // Verify the decrypted file exists and has content if (!outputFile.exists() || outputFile.length() == 0L) { - Log.e(TAG, "Decrypted file is empty or does not exist: ${outputFile.absolutePath}") return false } true - } catch (e: Exception) { - Log.e(TAG, "Error decrypting file: ${e.message}", e) - // Clean up the output file if it exists + } catch (_: Exception) { if (outputFile.exists()) { outputFile.delete() } @@ -250,4 +212,30 @@ object SecurityUtils { } return File(file.parent, newName) } + + fun setCustomKey(context: Context, key: String): Boolean { + return try { + val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE) + keyStore.edit { + putString("custom_key", key) + putBoolean("use_custom_key", true) + } + true + } catch (_: Exception) { + false + } + } + + fun clearCustomKey(context: Context) { + val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE) + keyStore.edit { + remove("custom_key") + putBoolean("use_custom_key", false) + } + } + + fun isUsingCustomKey(context: Context): Boolean { + val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE) + return keyStore.getBoolean("use_custom_key", false) + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_shade.xml b/app/src/main/res/drawable/bottom_shade.xml new file mode 100644 index 0000000..00ef37b --- /dev/null +++ b/app/src/main/res/drawable/bottom_shade.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index cff1632..fd41efc 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -265,6 +265,14 @@ android:text="@string/encrypt_file_when_hiding" android:textAppearance="?attr/textAppearanceBodyLarge" /> + + diff --git a/app/src/main/res/layout/dialog_custom_key.xml b/app/src/main/res/layout/dialog_custom_key.xml new file mode 100644 index 0000000..40d3582 --- /dev/null +++ b/app/src/main/res/layout/dialog_custom_key.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_file_type_selection.xml b/app/src/main/res/layout/dialog_file_type_selection.xml new file mode 100644 index 0000000..5a862c1 --- /dev/null +++ b/app/src/main/res/layout/dialog_file_type_selection.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_file.xml b/app/src/main/res/layout/list_item_file.xml index e9c9237..f7dde4e 100644 --- a/app/src/main/res/layout/list_item_file.xml +++ b/app/src/main/res/layout/list_item_file.xml @@ -21,6 +21,7 @@ android:layout_height="0dp" app:cardCornerRadius="10dp" android:layout_margin="5dp" + android:backgroundTint="#404040" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1:1" app:layout_constraintEnd_toEndOf="parent" @@ -36,6 +37,11 @@ android:layout_gravity="center" android:scaleType="centerCrop" android:src="@drawable/add_image" /> + + + - - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be4f726..a42dfa4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -157,6 +157,22 @@ Decrypt Files Encrypt Decrypt - Warning: This will encrypt the selected files. The original files will be deleted after successful encryption. Make sure to decrypt all the files if you are uninstalling the application otherwise all your encrypted files will be lost. Do you want to continue? + Warning: This will encrypt the selected files. Please use a custom encryption key from Settings, you can only decrypt the encrypted files with the same key so if you use default key and uninstall the app without decrypting the file you cannot decrypt the file later!. Do you want to continue? Warning: This will decrypt the selected files. The encrypted files will be deleted after successful decryption. Make sure to do this. Do you want to continue? + Custom encryption key cleared + Failed to set custom key + Custom key set successfully + Keys do not match + Key cannot be empty + Set Custom Encryption Key + Set + Delete Key + Enter Folder Name To Create + Please select exactly one folder to edit + Invalid folder name + Folder with this name already exists + Failed to rename folder + Select File Type! + Please select the type of file to decrypt + File no longer exists \ No newline at end of file