Added - file encryption, custom key creation.
This commit is contained in:
@@ -13,7 +13,6 @@ import android.widget.Toast
|
|||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.android.material.color.DynamicColors
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import devs.org.calculator.R
|
import devs.org.calculator.R
|
||||||
import devs.org.calculator.adapters.FolderAdapter
|
import devs.org.calculator.adapters.FolderAdapter
|
||||||
@@ -138,7 +137,7 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
|
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
@@ -158,25 +157,25 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
|
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle("Enter Folder Name To Create")
|
.setTitle(getString(R.string.enter_folder_name_to_create))
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setPositiveButton("Create") { dialog, _ ->
|
.setPositiveButton(getString(R.string.create)) { dialog, _ ->
|
||||||
val newName = inputEditText.text.toString().trim()
|
val newName = inputEditText.text.toString().trim()
|
||||||
if (newName.isNotEmpty()) {
|
if (newName.isNotEmpty()) {
|
||||||
try {
|
try {
|
||||||
folderManager.createFolder(hiddenDir, newName)
|
folderManager.createFolder(hiddenDir, newName)
|
||||||
refreshCurrentView()
|
refreshCurrentView()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HiddenActivity,
|
this@HiddenActivity,
|
||||||
"Failed to create folder",
|
getString(R.string.failed_to_create_folder),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
|
||||||
dialog.cancel()
|
dialog.cancel()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -214,7 +213,7 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,7 +433,8 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selectedFolders.size != 1) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,20 +449,21 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
inputEditText.selectAll()
|
inputEditText.selectAll()
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle("Rename Folder")
|
.setTitle(getString(R.string.rename_folder))
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setPositiveButton("Rename") { dialog, _ ->
|
.setPositiveButton(getString(R.string.rename)) { dialog, _ ->
|
||||||
val newName = inputEditText.text.toString().trim()
|
val newName = inputEditText.text.toString().trim()
|
||||||
if (newName.isNotEmpty() && newName != folder.name) {
|
if (newName.isNotEmpty() && newName != folder.name) {
|
||||||
if (isValidFolderName(newName)) {
|
if (isValidFolderName(newName)) {
|
||||||
renameFolder(folder, newName)
|
renameFolder(folder, newName)
|
||||||
} else {
|
} 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()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
|
||||||
dialog.cancel()
|
dialog.cancel()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -481,7 +482,8 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
if (parentDir != null) {
|
if (parentDir != null) {
|
||||||
val newFolder = File(parentDir, newName)
|
val newFolder = File(parentDir, newName)
|
||||||
if (newFolder.exists()) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +494,7 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
refreshCurrentView()
|
refreshCurrentView()
|
||||||
} else {
|
} 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import devs.org.calculator.utils.PrefsUtil
|
|||||||
import net.objecthunter.exp4j.ExpressionBuilder
|
import net.objecthunter.exp4j.ExpressionBuilder
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.google.android.material.color.DynamicColors
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
|
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
@@ -37,7 +36,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
private val dialogUtil = DialogUtil(this)
|
private val dialogUtil = DialogUtil(this)
|
||||||
private val fileManager = FileManager(this, this)
|
private val fileManager = FileManager(this, this)
|
||||||
private val sp by lazy { getSharedPreferences("app", MODE_PRIVATE) }
|
private val sp by lazy { getSharedPreferences("app", MODE_PRIVATE) }
|
||||||
private val prefs:PrefsUtil by lazy { PrefsUtil(this) }
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -276,7 +274,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
private fun evaluateExpression(expression: String): Double {
|
private fun evaluateExpression(expression: String): Double {
|
||||||
return try {
|
return try {
|
||||||
ExpressionBuilder(expression).build().evaluate()
|
ExpressionBuilder(expression).build().evaluate()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
expression.toDouble()
|
expression.toDouble()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,7 +357,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
if (sp.getBoolean("isFirst", true) && (currentExpression == "123456" || binding.display.text.toString() == "123456")){
|
if (sp.getBoolean("isFirst", true) && (currentExpression == "123456" || binding.display.text.toString() == "123456")){
|
||||||
binding.total.text = getString(R.string.now_enter_button)
|
binding.total.text = getString(R.string.now_enter_button)
|
||||||
}else binding.total.text = formattedResult
|
}else binding.total.text = formattedResult
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
binding.total.text = ""
|
binding.total.text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,7 +370,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
val lastChar = currentExpression.last()
|
val lastChar = currentExpression.last()
|
||||||
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
|
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
|
||||||
|
|
||||||
// Update flags based on what was removed
|
|
||||||
if (lastChar == '%') {
|
if (lastChar == '%') {
|
||||||
lastWasPercent = false
|
lastWasPercent = false
|
||||||
} else if (isOperator(lastChar.toString())) {
|
} else if (isOperator(lastChar.toString())) {
|
||||||
|
|||||||
@@ -6,18 +6,16 @@ import android.view.WindowManager
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.google.android.material.color.DynamicColors
|
|
||||||
import devs.org.calculator.R
|
import devs.org.calculator.R
|
||||||
import devs.org.calculator.adapters.ImagePreviewAdapter
|
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.databinding.ActivityPreviewBinding
|
||||||
import devs.org.calculator.utils.DialogUtil
|
import devs.org.calculator.utils.DialogUtil
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
import devs.org.calculator.utils.PrefsUtil
|
import devs.org.calculator.utils.PrefsUtil
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import devs.org.calculator.database.AppDatabase
|
|
||||||
import devs.org.calculator.database.HiddenFileRepository
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
class PreviewActivity : AppCompatActivity() {
|
class PreviewActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -35,10 +33,6 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
HiddenFileRepository(AppDatabase.getDatabase(this).hiddenFileDao())
|
HiddenFileRepository(AppDatabase.getDatabase(this).hiddenFileDao())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "PreviewActivity"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityPreviewBinding.inflate(layoutInflater)
|
binding = ActivityPreviewBinding.inflate(layoutInflater)
|
||||||
@@ -73,7 +67,6 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupFlagSecure() {
|
private fun setupFlagSecure() {
|
||||||
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
|
||||||
if (prefs.getBoolean("screenshot_restriction", true)) {
|
if (prefs.getBoolean("screenshot_restriction", true)) {
|
||||||
window.setFlags(
|
window.setFlags(
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
@@ -188,18 +181,15 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
override fun onPositiveButtonClicked() {
|
override fun onPositiveButtonClicked() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
// First delete from database
|
|
||||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(currentFile.absolutePath)
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(currentFile.absolutePath)
|
||||||
hiddenFile?.let {
|
hiddenFile?.let {
|
||||||
hiddenFileRepository.deleteHiddenFile(it)
|
hiddenFileRepository.deleteHiddenFile(it)
|
||||||
Log.d(TAG, "Deleted file metadata from database: ${it.filePath}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then delete the actual file
|
|
||||||
fileManager.deletePhotoFromExternalStorage(fileUri)
|
fileManager.deletePhotoFromExternalStorage(fileUri)
|
||||||
removeFileFromList(currentPosition)
|
removeFileFromList(currentPosition)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error deleting file: ${e.message}", e)
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,19 +218,17 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
override fun onPositiveButtonClicked() {
|
override fun onPositiveButtonClicked() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
// First copy the file to normal directory
|
|
||||||
val result = fileManager.copyFileToNormalDir(fileUri)
|
val result = fileManager.copyFileToNormalDir(fileUri)
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(currentFile.absolutePath)
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(currentFile.absolutePath)
|
||||||
hiddenFile?.let {
|
hiddenFile?.let {
|
||||||
hiddenFileRepository.deleteHiddenFile(it)
|
hiddenFileRepository.deleteHiddenFile(it)
|
||||||
Log.d(TAG, "Deleted file metadata from database: ${it.filePath}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFileFromList(currentPosition)
|
removeFileFromList(currentPosition)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error unhiding file: ${e.message}", e)
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.net.toUri
|
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.R
|
||||||
import devs.org.calculator.databinding.ActivitySettingsBinding
|
import devs.org.calculator.databinding.ActivitySettingsBinding
|
||||||
import devs.org.calculator.utils.PrefsUtil
|
import devs.org.calculator.utils.PrefsUtil
|
||||||
|
import devs.org.calculator.utils.SecurityUtils
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivitySettingsBinding
|
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 DEV_GITHUB_URL = ""
|
||||||
private var GITHUB_URL = ""
|
private var GITHUB_URL = ""
|
||||||
|
|
||||||
@@ -25,6 +28,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
prefs = PrefsUtil(this)
|
||||||
DEV_GITHUB_URL = getString(R.string.github_profile)
|
DEV_GITHUB_URL = getString(R.string.github_profile)
|
||||||
GITHUB_URL = getString(R.string.calculator_hide_files, DEV_GITHUB_URL)
|
GITHUB_URL = getString(R.string.calculator_hide_files, DEV_GITHUB_URL)
|
||||||
setupUI()
|
setupUI()
|
||||||
@@ -52,6 +56,8 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
else -> binding.systemThemeRadio.isChecked = true
|
else -> binding.systemThemeRadio.isChecked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isUsingCustomKey = SecurityUtils.isUsingCustomKey(this)
|
||||||
|
binding.customKeyStatus.isChecked = isUsingCustomKey
|
||||||
binding.screenshotRestrictionSwitch.isChecked = prefs.getBoolean("screenshot_restriction", true)
|
binding.screenshotRestrictionSwitch.isChecked = prefs.getBoolean("screenshot_restriction", true)
|
||||||
binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true)
|
binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true)
|
||||||
binding.encryptionSwitch.isChecked = prefs.getBoolean("encryption", false)
|
binding.encryptionSwitch.isChecked = prefs.getBoolean("encryption", false)
|
||||||
@@ -113,6 +119,10 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
binding.showFileNames.setOnCheckedChangeListener { _, isChecked ->
|
binding.showFileNames.setOnCheckedChangeListener { _, isChecked ->
|
||||||
prefs.setBoolean("showFileName", isChecked)
|
prefs.setBoolean("showFileName", isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.customKeyStatus.setOnClickListener {
|
||||||
|
showCustomKeyDialog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateThemeModeVisibility() {
|
private fun updateThemeModeVisibility() {
|
||||||
@@ -154,4 +164,57 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
getString(R.string.could_not_open_url), Snackbar.LENGTH_SHORT).show()
|
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<EditText>(R.id.keyInput)
|
||||||
|
val confirmKeyInput = dialogView.findViewById<EditText>(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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,9 @@ import devs.org.calculator.utils.PrefsUtil
|
|||||||
import devs.org.calculator.utils.SecurityUtils
|
import devs.org.calculator.utils.SecurityUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.CompoundButton
|
||||||
|
import android.app.AlertDialog
|
||||||
|
|
||||||
class ViewFolderActivity : AppCompatActivity() {
|
class ViewFolderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -166,7 +169,6 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
object : FileProcessCallback {
|
object : FileProcessCallback {
|
||||||
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
// Add files to Room database
|
|
||||||
copiedFiles.forEach { file ->
|
copiedFiles.forEach { file ->
|
||||||
val fileType = fileManager.getFileType(file)
|
val fileType = fileManager.getFileType(file)
|
||||||
var finalFile = file
|
var finalFile = file
|
||||||
@@ -331,14 +333,19 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
if (selectedFiles.isEmpty()) return
|
if (selectedFiles.isEmpty()) return
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
// Check if any files are encrypted
|
|
||||||
var hasEncryptedFiles = false
|
var hasEncryptedFiles = false
|
||||||
var hasDecryptedFiles = false
|
var hasDecryptedFiles = false
|
||||||
|
var hasEncFilesWithoutMetadata = false
|
||||||
|
|
||||||
for (file in selectedFiles) {
|
for (file in selectedFiles) {
|
||||||
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
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 {
|
} else {
|
||||||
hasDecryptedFiles = true
|
hasDecryptedFiles = true
|
||||||
}
|
}
|
||||||
@@ -351,14 +358,14 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
getString(R.string.move_to_another_folder)
|
getString(R.string.move_to_another_folder)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add encryption/decryption options based on file status
|
|
||||||
if (hasDecryptedFiles) {
|
if (hasDecryptedFiles) {
|
||||||
options.add(getString(R.string.encrypt_file))
|
options.add(getString(R.string.encrypt_file))
|
||||||
}
|
}
|
||||||
if (hasEncryptedFiles) {
|
if (hasEncryptedFiles || hasEncFilesWithoutMetadata) {
|
||||||
options.add(getString(R.string.decrypt_file))
|
options.add(getString(R.string.decrypt_file))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this@ViewFolderActivity)
|
MaterialAlertDialogBuilder(this@ViewFolderActivity)
|
||||||
.setTitle(getString(R.string.file_options))
|
.setTitle(getString(R.string.file_options))
|
||||||
.setItems(options.toTypedArray()) { _, which ->
|
.setItems(options.toTypedArray()) { _, which ->
|
||||||
@@ -371,7 +378,20 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
val option = options[which]
|
val option = options[which]
|
||||||
when (option) {
|
when (option) {
|
||||||
getString(R.string.encrypt_file) -> fileAdapter?.encryptSelectedFiles()
|
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<File>) {
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.dialog_file_type_selection, null)
|
||||||
|
val imageCheckbox = dialogView.findViewById<CheckBox>(R.id.checkboxImage)
|
||||||
|
val videoCheckbox = dialogView.findViewById<CheckBox>(R.id.checkboxVideo)
|
||||||
|
val audioCheckbox = dialogView.findViewById<CheckBox>(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<File>, 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<File>) {
|
private fun moveToAnotherFolder(selectedFiles: List<File>) {
|
||||||
showFolderSelectionDialog { destinationFolder ->
|
showFolderSelectionDialog { destinationFolder ->
|
||||||
moveFilesToFolder(selectedFiles, destinationFolder)
|
moveFilesToFolder(selectedFiles, destinationFolder)
|
||||||
@@ -437,7 +629,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
if (files.isNotEmpty()) {
|
if (files.isNotEmpty()) {
|
||||||
binding.recyclerView.visibility = View.VISIBLE
|
binding.recyclerView.visibility = View.VISIBLE
|
||||||
binding.noItems.visibility = View.GONE
|
binding.noItems.visibility = View.GONE
|
||||||
// Submit new list directly
|
|
||||||
fileAdapter?.submitList(files.toMutableList())
|
fileAdapter?.submitList(files.toMutableList())
|
||||||
fileAdapter?.let { adapter ->
|
fileAdapter?.let { adapter ->
|
||||||
if (adapter.isInSelectionMode()) {
|
if (adapter.isInSelectionMode()) {
|
||||||
@@ -451,7 +643,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ViewFolderActivity", "Error refreshing folder: ${e.message}")
|
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
@@ -610,7 +802,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ViewFolderActivity", "Error unhiding file: ${e.message}")
|
|
||||||
allUnhidden = false
|
allUnhidden = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -642,7 +834,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
allDeleted = false
|
allDeleted = false
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ViewFolderActivity", "Error deleting file: ${e.message}")
|
|
||||||
allDeleted = false
|
allDeleted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -689,7 +881,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ViewFolderActivity", "Error copying file: ${e.message}")
|
|
||||||
allCopied = false
|
allCopied = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -723,7 +915,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
file.delete()
|
file.delete()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ViewFolderActivity", "Error moving file: ${e.message}")
|
|
||||||
allMoved = false
|
allMoved = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,6 @@ class FileAdapter(
|
|||||||
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "FileAdapter"
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FileOperationCallback {
|
interface FileOperationCallback {
|
||||||
fun onFileDeleted(file: File)
|
fun onFileDeleted(file: File)
|
||||||
fun onFileRenamed(oldFile: File, newFile: File)
|
fun onFileRenamed(oldFile: File, newFile: File)
|
||||||
@@ -76,6 +72,7 @@ class FileAdapter(
|
|||||||
val fileNameTextView: TextView = view.findViewById(R.id.fileNameTextView)
|
val fileNameTextView: TextView = view.findViewById(R.id.fileNameTextView)
|
||||||
val playIcon: ImageView = view.findViewById(R.id.videoPlay)
|
val playIcon: ImageView = view.findViewById(R.id.videoPlay)
|
||||||
val selectedLayer: View = view.findViewById(R.id.selectedLayer)
|
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 selected: ImageView = view.findViewById(R.id.selected)
|
||||||
val encryptedIcon: ImageView = view.findViewById(R.id.encrypted)
|
val encryptedIcon: ImageView = view.findViewById(R.id.encrypted)
|
||||||
|
|
||||||
@@ -91,6 +88,7 @@ class FileAdapter(
|
|||||||
setupFileDisplay(file, fileType, hiddenFile?.isEncrypted == true,hiddenFile)
|
setupFileDisplay(file, fileType, hiddenFile?.isEncrypted == true,hiddenFile)
|
||||||
setupClickListeners(file, fileType)
|
setupClickListeners(file, fileType)
|
||||||
fileNameTextView.visibility = if (showFileName) View.VISIBLE else View.GONE
|
fileNameTextView.visibility = if (showFileName) View.VISIBLE else View.GONE
|
||||||
|
shade.visibility = if (showFileName) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
val position = adapterPosition
|
val position = adapterPosition
|
||||||
if (position != RecyclerView.NO_POSITION) {
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
@@ -99,11 +97,11 @@ class FileAdapter(
|
|||||||
}
|
}
|
||||||
encryptedIcon.visibility = if (hiddenFile?.isEncrypted == true) View.VISIBLE else View.GONE
|
encryptedIcon.visibility = if (hiddenFile?.isEncrypted == true) View.VISIBLE else View.GONE
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error binding file: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun bind(file: File, payloads: List<Any>) {
|
fun bind(file: File, payloads: List<Any>) {
|
||||||
if (payloads.isEmpty()) {
|
if (payloads.isEmpty()) {
|
||||||
bind(file)
|
bind(file)
|
||||||
@@ -163,7 +161,6 @@ class FileAdapter(
|
|||||||
showEncryptedIcon()
|
showEncryptedIcon()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error loading encrypted image preview: ${e.message}")
|
|
||||||
showEncryptedIcon()
|
showEncryptedIcon()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -200,7 +197,6 @@ class FileAdapter(
|
|||||||
showEncryptedIcon()
|
showEncryptedIcon()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error loading encrypted video preview: ${e.message}")
|
|
||||||
showEncryptedIcon()
|
showEncryptedIcon()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -260,7 +256,8 @@ class FileAdapter(
|
|||||||
|
|
||||||
private fun openFile(file: File, fileType: FileManager.FileType) {
|
private fun openFile(file: File, fileType: FileManager.FileType) {
|
||||||
if (!file.exists()) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,46 +267,45 @@ class FileAdapter(
|
|||||||
lifecycleOwner.lifecycleScope.launch {
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
if (hiddenFile?.isEncrypted == true) {
|
if (hiddenFile?.isEncrypted == true || file.extension == FileManager.ENCRYPTED_EXTENSION) {
|
||||||
val tempFile = File(context.cacheDir, "preview_${file.name}")
|
if (file.extension == FileManager.ENCRYPTED_EXTENSION && hiddenFile == null) {
|
||||||
Log.d(TAG, "Attempting to decrypt file for preview: ${file.absolutePath}")
|
showDecryptionTypeDialog(file)
|
||||||
|
} else {
|
||||||
|
val tempFile = File(context.cacheDir, "preview_${file.name}")
|
||||||
|
|
||||||
if (SecurityUtils.decryptFile(context, file, tempFile)) {
|
if (SecurityUtils.decryptFile(context, file, tempFile)) {
|
||||||
Log.d(TAG, "Successfully decrypted file for preview: ${tempFile.absolutePath}")
|
if (tempFile.exists() && tempFile.length() > 0) {
|
||||||
if (tempFile.exists() && tempFile.length() > 0) {
|
mainHandler.post {
|
||||||
mainHandler.post {
|
val fileTypeString = when (fileType) {
|
||||||
val fileTypeString = when (fileType) {
|
FileManager.FileType.IMAGE -> context.getString(R.string.image)
|
||||||
FileManager.FileType.IMAGE -> context.getString(R.string.image)
|
FileManager.FileType.VIDEO -> context.getString(R.string.video)
|
||||||
FileManager.FileType.VIDEO -> context.getString(R.string.video)
|
else -> "unknown"
|
||||||
else -> "unknown"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val intent = Intent(context, PreviewActivity::class.java).apply {
|
val intent = Intent(context, PreviewActivity::class.java).apply {
|
||||||
putExtra("type", fileTypeString)
|
putExtra("type", fileTypeString)
|
||||||
putExtra("folder", currentFolder.toString())
|
putExtra("folder", currentFolder.toString())
|
||||||
putExtra("position", adapterPosition)
|
putExtra("position", adapterPosition)
|
||||||
putExtra("isEncrypted", true)
|
putExtra("isEncrypted", true)
|
||||||
putExtra("tempFile", tempFile.absolutePath)
|
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 {
|
} else {
|
||||||
Log.e(TAG, "Decrypted preview file is empty or doesn't exist")
|
|
||||||
mainHandler.post {
|
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 {
|
} else {
|
||||||
openInPreview(fileType)
|
openInPreview(fileType)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error preparing file for preview: ${e.message}", e)
|
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
Toast.makeText(context, "Error preparing file for preview", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Error preparing file for preview", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
@@ -337,7 +333,6 @@ class FileAdapter(
|
|||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to open audio file: ${e.message}")
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.no_audio_player_found),
|
context.getString(R.string.no_audio_player_found),
|
||||||
@@ -360,7 +355,7 @@ class FileAdapter(
|
|||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to open document file: ${e.message}")
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.no_suitable_app_found_to_open_this_document),
|
context.getString(R.string.no_suitable_app_found_to_open_this_document),
|
||||||
@@ -435,7 +430,6 @@ class FileAdapter(
|
|||||||
val success = try {
|
val success = try {
|
||||||
file.renameTo(newFile)
|
file.renameTo(newFile)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to rename file: ${e.message}")
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,6 +463,86 @@ class FileAdapter(
|
|||||||
imageView.setImageResource(R.drawable.encrypted)
|
imageView.setImageResource(R.drawable.encrypted)
|
||||||
imageView.setPadding(50, 50, 50, 50)
|
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 {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
|
||||||
@@ -591,7 +665,6 @@ class FileAdapter(
|
|||||||
fileExecutor.shutdown()
|
fileExecutor.shutdown()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error shutting down executor: ${e.message}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileOperationCallback?.clear()
|
fileOperationCallback?.clear()
|
||||||
@@ -669,7 +742,6 @@ class FileAdapter(
|
|||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error encrypting file: ${e.message}")
|
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -748,7 +820,6 @@ class FileAdapter(
|
|||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error decrypting file: ${e.message}")
|
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,21 +10,19 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.MediaController
|
import android.widget.MediaController
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.AsyncListDiffer
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import devs.org.calculator.databinding.ViewpagerItemsBinding
|
|
||||||
import devs.org.calculator.utils.FileManager
|
|
||||||
import java.io.File
|
|
||||||
import devs.org.calculator.R
|
import devs.org.calculator.R
|
||||||
import devs.org.calculator.database.AppDatabase
|
import devs.org.calculator.database.AppDatabase
|
||||||
import devs.org.calculator.database.HiddenFileRepository
|
import devs.org.calculator.database.HiddenFileRepository
|
||||||
import devs.org.calculator.utils.SecurityUtils
|
import devs.org.calculator.databinding.ViewpagerItemsBinding
|
||||||
import android.util.Log
|
import devs.org.calculator.utils.FileManager
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import devs.org.calculator.utils.SecurityUtils.getDecryptedPreviewFile
|
import devs.org.calculator.utils.SecurityUtils.getDecryptedPreviewFile
|
||||||
import devs.org.calculator.utils.SecurityUtils.getUriForPreviewFile
|
import devs.org.calculator.utils.SecurityUtils.getUriForPreviewFile
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class ImagePreviewAdapter(
|
class ImagePreviewAdapter(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -38,10 +36,6 @@ class ImagePreviewAdapter(
|
|||||||
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "ImagePreviewAdapter"
|
|
||||||
}
|
|
||||||
|
|
||||||
var images: List<File>
|
var images: List<File>
|
||||||
get() = differ.currentList
|
get() = differ.currentList
|
||||||
set(value) = differ.submitList(value)
|
set(value) = differ.submitList(value)
|
||||||
@@ -53,10 +47,10 @@ class ImagePreviewAdapter(
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
||||||
val imageUrl = images[position]
|
val imageUrl = images[position]
|
||||||
|
val fileType = FileManager(context,lifecycleOwner).getFileType(imageUrl)
|
||||||
stopAndResetCurrentAudio()
|
stopAndResetCurrentAudio()
|
||||||
|
|
||||||
holder.bind(imageUrl, position)
|
holder.bind(imageUrl, position,fileType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = images.size
|
override fun getItemCount(): Int = images.size
|
||||||
@@ -77,36 +71,42 @@ class ImagePreviewAdapter(
|
|||||||
private var currentPosition = 0
|
private var currentPosition = 0
|
||||||
private var tempDecryptedFile: File? = null
|
private var tempDecryptedFile: File? = null
|
||||||
|
|
||||||
fun bind(file: File, position: Int) {
|
fun bind(file: File, position: Int,decryptedFileType: FileManager.FileType) {
|
||||||
currentPosition = position
|
currentPosition = position
|
||||||
|
|
||||||
releaseMediaPlayer()
|
releaseMediaPlayer()
|
||||||
resetAudioUI()
|
resetAudioUI()
|
||||||
cleanupTempFile()
|
cleanupTempFile()
|
||||||
lifecycleOwner.lifecycleScope.launch {
|
try {
|
||||||
try {
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
val isEncrypted = hiddenFile?.isEncrypted == true
|
if (hiddenFile != null){
|
||||||
val fileType = hiddenFile!!.fileType
|
val isEncrypted = hiddenFile.isEncrypted
|
||||||
if (isEncrypted) {
|
val fileType = hiddenFile.fileType
|
||||||
|
if (isEncrypted) {
|
||||||
val tempDecryptedFile = getDecryptedPreviewFile(context, hiddenFile)
|
val tempDecryptedFile = getDecryptedPreviewFile(context, hiddenFile)
|
||||||
if (tempDecryptedFile != null && tempDecryptedFile.exists() && tempDecryptedFile.length() > 0) {
|
if (tempDecryptedFile != null && tempDecryptedFile.exists() && tempDecryptedFile.length() > 0) {
|
||||||
displayFile(tempDecryptedFile, fileType)
|
displayFile(tempDecryptedFile, fileType,true)
|
||||||
|
} else {
|
||||||
|
showEncryptedError()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showEncryptedError()
|
displayFile(file, decryptedFileType,false)
|
||||||
}
|
}
|
||||||
} else {
|
}else{
|
||||||
displayFile(file, fileType)
|
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)
|
val uri = getUriForPreviewFile(context, file)
|
||||||
when (fileType) {
|
when (fileType) {
|
||||||
FileManager.FileType.VIDEO -> {
|
FileManager.FileType.VIDEO -> {
|
||||||
@@ -114,7 +114,11 @@ class ImagePreviewAdapter(
|
|||||||
binding.audioBg.visibility = View.GONE
|
binding.audioBg.visibility = View.GONE
|
||||||
binding.videoView.visibility = View.VISIBLE
|
binding.videoView.visibility = View.VISIBLE
|
||||||
|
|
||||||
val videoUri = uri
|
val videoUri = if (isEncrypted){
|
||||||
|
uri
|
||||||
|
}else{
|
||||||
|
Uri.fromFile(file)
|
||||||
|
}
|
||||||
binding.videoView.setVideoURI(videoUri)
|
binding.videoView.setVideoURI(videoUri)
|
||||||
binding.videoView.start()
|
binding.videoView.start()
|
||||||
|
|
||||||
@@ -139,21 +143,30 @@ class ImagePreviewAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
FileManager.FileType.IMAGE -> {
|
FileManager.FileType.IMAGE -> {
|
||||||
|
val imageUri = if (isEncrypted){
|
||||||
|
uri
|
||||||
|
}else{
|
||||||
|
Uri.fromFile(file)
|
||||||
|
}
|
||||||
binding.imageView.visibility = View.VISIBLE
|
binding.imageView.visibility = View.VISIBLE
|
||||||
binding.videoView.visibility = View.GONE
|
binding.videoView.visibility = View.GONE
|
||||||
binding.audioBg.visibility = View.GONE
|
binding.audioBg.visibility = View.GONE
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.load(uri)
|
.load(imageUri)
|
||||||
.into(binding.imageView)
|
.into(binding.imageView)
|
||||||
}
|
}
|
||||||
FileManager.FileType.AUDIO -> {
|
FileManager.FileType.AUDIO -> {
|
||||||
|
val audioFile: File? = if (isEncrypted) {
|
||||||
|
getFileFromUri(context, uri!!)
|
||||||
|
} else {
|
||||||
|
file
|
||||||
|
}
|
||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
binding.audioBg.visibility = View.VISIBLE
|
binding.audioBg.visibility = View.VISIBLE
|
||||||
binding.videoView.visibility = View.GONE
|
binding.videoView.visibility = View.GONE
|
||||||
binding.audioTitle.text = file.name
|
binding.audioTitle.text = file.name
|
||||||
|
|
||||||
setupAudioPlayer(file)
|
setupAudioPlayer(audioFile!!)
|
||||||
setupPlaybackControls()
|
setupPlaybackControls()
|
||||||
}
|
}
|
||||||
else -> {
|
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() {
|
private fun showEncryptedError() {
|
||||||
binding.imageView.visibility = View.VISIBLE
|
binding.imageView.visibility = View.VISIBLE
|
||||||
binding.videoView.visibility = View.GONE
|
binding.videoView.visibility = View.GONE
|
||||||
@@ -176,9 +203,7 @@ class ImagePreviewAdapter(
|
|||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
try {
|
try {
|
||||||
file.delete()
|
file.delete()
|
||||||
Log.d(TAG, "Cleaned up temporary decrypted file: ${file.absolutePath}")
|
} catch (_: Exception) {
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error cleaning up temporary file: ${e.message}", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tempDecryptedFile = null
|
tempDecryptedFile = null
|
||||||
@@ -188,7 +213,7 @@ class ImagePreviewAdapter(
|
|||||||
private fun resetAudioUI() {
|
private fun resetAudioUI() {
|
||||||
binding.playPause.setImageResource(R.drawable.play)
|
binding.playPause.setImageResource(R.drawable.play)
|
||||||
binding.audioSeekBar.value = 0f
|
binding.audioSeekBar.value = 0f
|
||||||
binding.audioSeekBar.valueTo = 100f // Default value
|
binding.audioSeekBar.valueTo = 100f
|
||||||
seekRunnable?.let { seekHandler.removeCallbacks(it) }
|
seekRunnable?.let { seekHandler.removeCallbacks(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
const val ENCRYPTED_EXTENSION = ".enc"
|
const val ENCRYPTED_EXTENSION = ".enc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getHiddenDirectory(): File {
|
fun getHiddenDirectory(): File {
|
||||||
val dir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
val dir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
@@ -58,7 +59,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
if (!created) {
|
if (!created) {
|
||||||
throw RuntimeException("Failed to create hidden directory: ${dir.absolutePath}")
|
throw RuntimeException("Failed to create hidden directory: ${dir.absolutePath}")
|
||||||
}
|
}
|
||||||
// Create .nomedia file to hide from media scanners
|
|
||||||
val nomediaFile = File(dir, ".nomedia")
|
val nomediaFile = File(dir, ".nomedia")
|
||||||
if (!nomediaFile.exists()) {
|
if (!nomediaFile.exists()) {
|
||||||
nomediaFile.createNewFile()
|
nomediaFile.createNewFile()
|
||||||
@@ -399,4 +399,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
DOCUMENT(DOCS_DIR),
|
DOCUMENT(DOCS_DIR),
|
||||||
ALL("all")
|
ALL("all")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package devs.org.calculator.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@@ -17,27 +16,38 @@ import javax.crypto.spec.SecretKeySpec
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import devs.org.calculator.database.HiddenFileEntity
|
import devs.org.calculator.database.HiddenFileEntity
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
object SecurityUtils {
|
object SecurityUtils {
|
||||||
private const val ALGORITHM = "AES"
|
private const val ALGORITHM = "AES"
|
||||||
private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
|
private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
|
||||||
private const val KEY_SIZE = 256
|
private const val KEY_SIZE = 256
|
||||||
private const val TAG = "SecurityUtils"
|
|
||||||
|
|
||||||
private fun getSecretKey(context: Context): SecretKey {
|
private fun getSecretKey(context: Context): SecretKey {
|
||||||
val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE)
|
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) {
|
return if (encodedKey != null) {
|
||||||
try {
|
try {
|
||||||
val decodedKey = android.util.Base64.decode(encodedKey, android.util.Base64.DEFAULT)
|
val decodedKey = android.util.Base64.decode(encodedKey, android.util.Base64.DEFAULT)
|
||||||
SecretKeySpec(decodedKey, ALGORITHM)
|
SecretKeySpec(decodedKey, ALGORITHM)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.e(TAG, "Error decoding stored key, generating new key", e)
|
|
||||||
generateAndStoreNewKey(keyStore)
|
generateAndStoreNewKey(keyStore)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "No stored key found, generating new key")
|
|
||||||
generateAndStoreNewKey(keyStore)
|
generateAndStoreNewKey(keyStore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,14 +57,13 @@ object SecurityUtils {
|
|||||||
keyGenerator.init(KEY_SIZE, SecureRandom())
|
keyGenerator.init(KEY_SIZE, SecureRandom())
|
||||||
val key = keyGenerator.generateKey()
|
val key = keyGenerator.generateKey()
|
||||||
val encodedKey = android.util.Base64.encodeToString(key.encoded, android.util.Base64.DEFAULT)
|
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
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encryptFile(context: Context, inputFile: File, outputFile: File): Boolean {
|
fun encryptFile(context: Context, inputFile: File, outputFile: File): Boolean {
|
||||||
return try {
|
return try {
|
||||||
if (!inputFile.exists()) {
|
if (!inputFile.exists()) {
|
||||||
Log.e(TAG, "Input file does not exist: ${inputFile.absolutePath}")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +75,6 @@ object SecurityUtils {
|
|||||||
|
|
||||||
FileInputStream(inputFile).use { input ->
|
FileInputStream(inputFile).use { input ->
|
||||||
FileOutputStream(outputFile).use { output ->
|
FileOutputStream(outputFile).use { output ->
|
||||||
// Write IV at the beginning of the file
|
|
||||||
output.write(iv)
|
output.write(iv)
|
||||||
CipherOutputStream(output, cipher).use { cipherOutput ->
|
CipherOutputStream(output, cipher).use { cipherOutput ->
|
||||||
input.copyTo(cipherOutput)
|
input.copyTo(cipherOutput)
|
||||||
@@ -74,26 +82,22 @@ object SecurityUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the encrypted file exists and has content
|
|
||||||
if (!outputFile.exists() || outputFile.length() == 0L) {
|
if (!outputFile.exists() || outputFile.length() == 0L) {
|
||||||
Log.e(TAG, "Encrypted file is empty or does not exist: ${outputFile.absolutePath}")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify we can read the IV from the encrypted file
|
|
||||||
FileInputStream(outputFile).use { input ->
|
FileInputStream(outputFile).use { input ->
|
||||||
val iv = ByteArray(16)
|
val iv = ByteArray(16)
|
||||||
val bytesRead = input.read(iv)
|
val bytesRead = input.read(iv)
|
||||||
if (bytesRead != 16) {
|
if (bytesRead != 16) {
|
||||||
Log.e(TAG, "Failed to verify IV in encrypted file: expected 16 bytes but got $bytesRead")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.e(TAG, "Error encrypting file: ${e.message}", e)
|
|
||||||
// Clean up the output file if it exists
|
|
||||||
if (outputFile.exists()) {
|
if (outputFile.exists()) {
|
||||||
outputFile.delete()
|
outputFile.delete()
|
||||||
}
|
}
|
||||||
@@ -105,60 +109,30 @@ object SecurityUtils {
|
|||||||
try {
|
try {
|
||||||
val encryptedFile = File(meta.filePath)
|
val encryptedFile = File(meta.filePath)
|
||||||
if (!encryptedFile.exists()) {
|
if (!encryptedFile.exists()) {
|
||||||
Log.e(TAG, "Encrypted file does not exist: ${meta.filePath}")
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a unique temp file name using the original file name
|
|
||||||
val tempDir = File(context.cacheDir, "preview_temp")
|
val tempDir = File(context.cacheDir, "preview_temp")
|
||||||
if (!tempDir.exists()) tempDir.mkdirs()
|
if (!tempDir.exists()) tempDir.mkdirs()
|
||||||
|
|
||||||
// Use the original extension from metadata
|
|
||||||
val tempFile = File(tempDir, "preview_${System.currentTimeMillis()}_${meta.fileName}")
|
val tempFile = File(tempDir, "preview_${System.currentTimeMillis()}_${meta.fileName}")
|
||||||
|
|
||||||
// Clean up any existing temp files
|
|
||||||
tempDir.listFiles()?.forEach { it.delete() }
|
tempDir.listFiles()?.forEach { it.delete() }
|
||||||
|
|
||||||
// Attempt to decrypt the file
|
|
||||||
val success = decryptFile(context, encryptedFile, tempFile)
|
val success = decryptFile(context, encryptedFile, tempFile)
|
||||||
|
|
||||||
if (success && tempFile.exists() && tempFile.length() > 0) {
|
if (success && tempFile.exists() && tempFile.length() > 0) {
|
||||||
Log.d(TAG, "Successfully created preview file: ${tempFile.absolutePath}")
|
|
||||||
return tempFile
|
return tempFile
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Failed to create preview file or file is empty")
|
|
||||||
if (tempFile.exists()) tempFile.delete()
|
if (tempFile.exists()) tempFile.delete()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.e(TAG, "Error creating preview file: ${e.message}", e)
|
|
||||||
return null
|
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? {
|
fun getUriForPreviewFile(context: Context, file: File): Uri? {
|
||||||
return try {
|
return try {
|
||||||
FileProvider.getUriForFile(
|
FileProvider.getUriForFile(
|
||||||
@@ -166,8 +140,7 @@ object SecurityUtils {
|
|||||||
"${context.packageName}.provider", // Must match AndroidManifest
|
"${context.packageName}.provider", // Must match AndroidManifest
|
||||||
file
|
file
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.e("PreviewUtils", "Error getting URI", e)
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,32 +150,25 @@ object SecurityUtils {
|
|||||||
fun decryptFile(context: Context, inputFile: File, outputFile: File): Boolean {
|
fun decryptFile(context: Context, inputFile: File, outputFile: File): Boolean {
|
||||||
return try {
|
return try {
|
||||||
if (!inputFile.exists()) {
|
if (!inputFile.exists()) {
|
||||||
Log.e(TAG, "Input file does not exist: ${inputFile.absolutePath}")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputFile.length() == 0L) {
|
if (inputFile.length() == 0L) {
|
||||||
Log.e(TAG, "Input file is empty: ${inputFile.absolutePath}")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val secretKey = getSecretKey(context)
|
val secretKey = getSecretKey(context)
|
||||||
val cipher = Cipher.getInstance(TRANSFORMATION)
|
val cipher = Cipher.getInstance(TRANSFORMATION)
|
||||||
|
|
||||||
// First verify we can read the IV
|
|
||||||
FileInputStream(inputFile).use { input ->
|
FileInputStream(inputFile).use { input ->
|
||||||
val iv = ByteArray(16)
|
val iv = ByteArray(16)
|
||||||
val bytesRead = input.read(iv)
|
val bytesRead = input.read(iv)
|
||||||
if (bytesRead != 16) {
|
if (bytesRead != 16) {
|
||||||
Log.e(TAG, "Failed to read IV: expected 16 bytes but got $bytesRead")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv))
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv))
|
||||||
|
|
||||||
// Create a new input stream for the actual decryption
|
|
||||||
FileInputStream(inputFile).use { decInput ->
|
FileInputStream(inputFile).use { decInput ->
|
||||||
// Skip the IV
|
|
||||||
decInput.skip(16)
|
decInput.skip(16)
|
||||||
|
|
||||||
FileOutputStream(outputFile).use { output ->
|
FileOutputStream(outputFile).use { output ->
|
||||||
@@ -213,16 +179,12 @@ object SecurityUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the decrypted file exists and has content
|
|
||||||
if (!outputFile.exists() || outputFile.length() == 0L) {
|
if (!outputFile.exists() || outputFile.length() == 0L) {
|
||||||
Log.e(TAG, "Decrypted file is empty or does not exist: ${outputFile.absolutePath}")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.e(TAG, "Error decrypting file: ${e.message}", e)
|
|
||||||
// Clean up the output file if it exists
|
|
||||||
if (outputFile.exists()) {
|
if (outputFile.exists()) {
|
||||||
outputFile.delete()
|
outputFile.delete()
|
||||||
}
|
}
|
||||||
@@ -250,4 +212,30 @@ object SecurityUtils {
|
|||||||
}
|
}
|
||||||
return File(file.parent, newName)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
5
app/src/main/res/drawable/bottom_shade.xml
Normal file
5
app/src/main/res/drawable/bottom_shade.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<gradient android:startColor="#CC000000" android:centerColor="#00ffffff" android:endColor="#00ffffff" android:angle="90"/>
|
||||||
|
</shape>
|
||||||
@@ -265,6 +265,14 @@
|
|||||||
android:text="@string/encrypt_file_when_hiding"
|
android:text="@string/encrypt_file_when_hiding"
|
||||||
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/customKeyStatus"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/set_custom_encryption_key"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
|||||||
47
app/src/main/res/layout/dialog_custom_key.xml
Normal file
47
app/src/main/res/layout/dialog_custom_key.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?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:layout_marginBottom="8dp"
|
||||||
|
android:hint="Enter encryption key"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/keyInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Confirm encryption key"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/confirmKeyInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Note: Make sure to remember your key. If you lose it, you won't be able to decrypt your files."
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="?android:textColorSecondary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
29
app/src/main/res/layout/dialog_file_type_selection.xml
Normal file
29
app/src/main/res/layout/dialog_file_type_selection.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkboxImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Image"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkboxVideo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Video"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkboxAudio"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Audio"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:cardCornerRadius="10dp"
|
app:cardCornerRadius="10dp"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
|
android:backgroundTint="#404040"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -36,6 +37,11 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/add_image" />
|
android:src="@drawable/add_image" />
|
||||||
|
<View
|
||||||
|
android:id="@+id/shade"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/bottom_shade"/>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/selectedLayer"
|
android:id="@+id/selectedLayer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -50,6 +56,20 @@
|
|||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fileNameTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="13dp"
|
||||||
|
android:paddingVertical="6dp"
|
||||||
|
android:textColor="#fff"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/cardView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/videoPlay"
|
android:id="@+id/videoPlay"
|
||||||
@@ -79,14 +99,4 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</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>
|
</LinearLayout>
|
||||||
@@ -157,6 +157,22 @@
|
|||||||
<string name="decrypt_files">Decrypt Files</string>
|
<string name="decrypt_files">Decrypt Files</string>
|
||||||
<string name="encrypt">Encrypt</string>
|
<string name="encrypt">Encrypt</string>
|
||||||
<string name="decrypt">Decrypt</string>
|
<string name="decrypt">Decrypt</string>
|
||||||
<string name="encryption_disclaimer">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?</string>
|
<string name="encryption_disclaimer">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?</string>
|
||||||
<string name="decryption_disclaimer">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?</string>
|
<string name="decryption_disclaimer">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?</string>
|
||||||
|
<string name="custom_encryption_key_cleared">Custom encryption key cleared</string>
|
||||||
|
<string name="failed_to_set_custom_key">Failed to set custom key</string>
|
||||||
|
<string name="custom_key_set_successfully">Custom key set successfully</string>
|
||||||
|
<string name="keys_do_not_match">Keys do not match</string>
|
||||||
|
<string name="key_cannot_be_empty">Key cannot be empty</string>
|
||||||
|
<string name="set_custom_encryption_key">Set Custom Encryption Key</string>
|
||||||
|
<string name="set">Set</string>
|
||||||
|
<string name="delete_key">Delete Key</string>
|
||||||
|
<string name="enter_folder_name_to_create">Enter Folder Name To Create</string>
|
||||||
|
<string name="please_select_exactly_one_folder_to_edit">Please select exactly one folder to edit</string>
|
||||||
|
<string name="invalid_folder_name">Invalid folder name</string>
|
||||||
|
<string name="folder_with_this_name_already_exists">Folder with this name already exists</string>
|
||||||
|
<string name="failed_to_rename_folder">Failed to rename folder</string>
|
||||||
|
<string name="select_file_type">Select File Type!</string>
|
||||||
|
<string name="please_select_the_type_of_file_to_decrypt">Please select the type of file to decrypt</string>
|
||||||
|
<string name="file_no_longer_exists">File no longer exists</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user