Compare commits
33 Commits
1.4.0
...
92dcfbd0ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92dcfbd0ae | ||
|
|
db2326f3e7 | ||
|
|
5efeb6876b | ||
|
|
82982aa221 | ||
|
|
6f151554f0 | ||
|
|
5ecd3ec7d1 | ||
|
|
946953d4eb | ||
|
|
18172f5191 | ||
|
|
603d96b672 | ||
|
|
78ea4596ea | ||
|
|
a70a569b57 | ||
|
|
df0db1a479 | ||
|
|
9cdc6eb1a4 | ||
|
|
913af83b90 | ||
|
|
7ceb599d9f | ||
|
|
dd1767e55d | ||
|
|
28ccdda1bf | ||
|
|
27a538f7c6 | ||
|
|
22c2a64450 | ||
|
|
3af3f81f3c | ||
|
|
f2e206f208 | ||
|
|
2de1b28afe | ||
|
|
ccf291d2e2 | ||
|
|
0968d5c19b | ||
|
|
d64904fbe7 | ||
|
|
2da2c944a3 | ||
|
|
13e1fca28f | ||
|
|
5c5e0e4be8 | ||
|
|
aad939463c | ||
|
|
e4e2983acd | ||
|
|
8702491f85 | ||
|
|
5263f89cd3 | ||
|
|
0787d6dd5b |
51
README.md
@@ -1,23 +1,31 @@
|
|||||||
|

|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="app/src/main/assets/logo.png" alt="Calculator Hide File App Logo" width="200" />
|
|
||||||
|
|
||||||
# 📂 Calculator Hide File App for Android 📂
|
# 📂 Calculator Hide File App for Android 📂
|
||||||
|
|
||||||
<a href="https://github.com/Binondi/Calculator-Hide-Files/releases/latest">
|
<a href="https://github.com/Binondi/Calculator-Hide-Files/releases/latest">
|
||||||
<img alt="Latest release" src="https://img.shields.io/badge/Releases-v1.0-blue?logo=github&style=for-the-badge">
|
<img alt="Latest release" src="https://img.shields.io/badge/Releases-v1.4.2-blue?logo=github&style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="https://github.com/Binondi/Calculator-Hide-Files/releases/latest">
|
<a href="https://github.com/Binondi/Calculator-Hide-Files/releases/latest">
|
||||||
<img alt="Downloads" src="https://img.shields.io/badge/Downloads-1.3k-blue?logo=github&style=for-the-badge">
|
<img alt="Downloads" src="https://img.shields.io/badge/downloads-1.1k-blue?logo=github&style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="LICENSE">
|
<a href="LICENSE">
|
||||||
<img alt="Apache License 2.0" src="https://img.shields.io/badge/License-Apache_2.0-blue?logo=github&style=for-the-badge">
|
<img alt="Apache License 2.0" src="https://img.shields.io/badge/License-Apache_2.0-blue?logo=github&style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
<br>
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<h4>Download</h4>
|
||||||
|
|
||||||
|
<a>[<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png" alt="Get it on GitHub" height="80">](https://github.com/binondi/Calculator-Hide-Files/releases) </a><a href="https://apt.izzysoft.de/fdroid/index/apk/devs.org.calculator"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="80"></a> <a href="https://www.androidfreeware.net/download-apk-devs-org-calculator.html"><img src="https://www.androidfreeware.net/images/androidfreeware-badge.png" height="80"></a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## 😍 Why Choose This App?
|
## 😍 Why Choose This App?
|
||||||
The **Calculator Hide File App** is an **open-source** application, allowing you to inspect the code yourself. This ensures **complete transparency** and guarantees that your **privacy remains uncompromised**. 🔒✅
|
The **Calculator Hide File App** is an **open-source** application, allowing you to inspect the code yourself. This ensures **complete transparency** and guarantees that your **privacy remains uncompromised**. 🔒✅
|
||||||
@@ -33,37 +41,37 @@ The **Calculator Hide File App** is an innovative **Android file-hiding app** th
|
|||||||
> - Hide images, videos, documents & other files securely.
|
> - Hide images, videos, documents & other files securely.
|
||||||
> - Works like a **real calculator** with hidden storage mode.
|
> - Works like a **real calculator** with hidden storage mode.
|
||||||
> - No one will suspect it’s a file vault!
|
> - No one will suspect it’s a file vault!
|
||||||
|
> - **Encrypt** files with your **custom key** to secure your hidden files.
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
|
|
||||||
✅ **Dual Functionality** – A working **calculator** & a **file vault** in one app.
|
✅ **Dual Functionality** – A working **calculator** & a **file vault** in one app.
|
||||||
✅ **Secret Passcode** – Unlock hidden files by entering a secret code.
|
✅ **Secret Passcode** – Unlock hidden files by entering a secret code.
|
||||||
✅ **Secure File Manager** – Hide/unhide files easily.
|
✅ **Secure File Manager** – Hide/Unhide files easily.
|
||||||
✅ **Fast & Lightweight** – Smooth performance on all Android devices.
|
✅ **Fast & Lightweight** – Smooth performance on all Android devices.
|
||||||
✅ **No Root Required** – Works without rooting your phone.
|
✅ **No Root Required** – Works without rooting your phone.
|
||||||
|
✅ **Security** – Encrypt & Decrypt files.
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🖼️ Screenshots
|
## 🖼️ Screenshots
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="app/src/main/assets/Screenshot_1.jpg" alt="Calculator Hide File App - Home Screen" width="32%">
|
<img src="media/Screenshot_1.jpg" alt="Calculator Hide File App - Home Screen" width="32%">
|
||||||
<img src="app/src/main/assets/Screenshot_2.jpg" alt="Calculator Hide File App - Secure File Storage" width="32%">
|
<img src="media/Screenshot_2.jpg" alt="Calculator Hide File App - Secure File Storage" width="32%">
|
||||||
<img src="app/src/main/assets/Screenshot_3.jpg" alt="Calculator Hidle File App - Passcode Protection" width="32%">
|
<img src="media/Screenshot_3.jpg" alt="Calculator Hide File App - Passcode Protection" width="32%">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="app/src/main/assets/Screenshot_4.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
<img src="media/Screenshot_4.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
||||||
<img src="app/src/main/assets/Screenshot_5.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
<img src="media/Screenshot_5.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
||||||
<img src="app/src/main/assets/Screenshot_6.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
<img src="media/Screenshot_6.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="app/src/main/assets/Screenshot_7.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
<img src="media/Screenshot_7.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
||||||
<img src="app/src/main/assets/Screenshot_8.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
<img src="media/Screenshot_8.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
||||||
<img src="app/src/main/assets/Screenshot_9.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
<img src="media/Screenshot_9.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -106,7 +114,7 @@ git clone https://github.com/Binondi/Calculator-Hide-Files.git
|
|||||||
## 🛠️ Technologies Used
|
## 🛠️ Technologies Used
|
||||||
|
|
||||||
- **Programming Language**: Kotlin
|
- **Programming Language**: Kotlin
|
||||||
- **UI Framework**: XML (For UI)
|
- **UI Framework**: XML
|
||||||
- **File Storage**: Secure internal storage & MediaStore API
|
- **File Storage**: Secure internal storage & MediaStore API
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -125,7 +133,12 @@ If you find this app useful, please consider supporting the development. 🙏
|
|||||||
|
|
||||||
[](https://github.com/sponsors/Binondi)
|
[](https://github.com/sponsors/Binondi)
|
||||||
[](https://paypal.me/BinondiBorthakur56)
|
[](https://paypal.me/BinondiBorthakur56)
|
||||||
|
[](https://buymeacoffee.com/binondi)
|
||||||
|
|
||||||
|
- **UPI ID** 📱
|
||||||
|
``
|
||||||
|
binondi@naviaxis
|
||||||
|
``
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 Contributing
|
## 🔧 Contributing
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "devs.org.calculator"
|
applicationId = "devs.org.calculator"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
|
//noinspection OldTargetApi
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 5
|
versionCode = 7
|
||||||
versionName = "1.4.0"
|
versionName = "1.4.2"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -69,6 +70,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.activity)
|
implementation(libs.androidx.activity)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.androidx.constraintlayout)
|
||||||
implementation(libs.androidx.gridlayout)
|
implementation(libs.androidx.gridlayout)
|
||||||
|
implementation(libs.androidx.lifecycle.process)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
@@ -81,9 +83,11 @@ dependencies {
|
|||||||
implementation(libs.androidx.viewpager)
|
implementation(libs.androidx.viewpager)
|
||||||
implementation(libs.zoomage)
|
implementation(libs.zoomage)
|
||||||
implementation(libs.lottie)
|
implementation(libs.lottie)
|
||||||
|
implementation(libs.androidx.swiperefreshlayout)
|
||||||
|
|
||||||
// Room dependencies
|
// Room dependencies
|
||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
|
//noinspection KaptUsageInsteadOfKsp
|
||||||
kapt(libs.androidx.room.compiler)
|
kapt(libs.androidx.room.compiler)
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"versionCode": 4,
|
"versionCode": 6,
|
||||||
"versionName": "1.3",
|
"versionName": "1.4.1",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 237 KiB |
@@ -11,7 +11,10 @@ import android.view.WindowManager
|
|||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import devs.org.calculator.R
|
import devs.org.calculator.R
|
||||||
@@ -47,7 +50,12 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
fileManager = FileManager(this, this)
|
fileManager = FileManager(this, this)
|
||||||
folderManager = FolderManager()
|
folderManager = FolderManager()
|
||||||
dialogUtil = DialogUtil(this)
|
dialogUtil = DialogUtil(this)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
|
||||||
setupInitialUIState()
|
setupInitialUIState()
|
||||||
setupClickListeners()
|
setupClickListeners()
|
||||||
@@ -110,6 +118,13 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
binding.folderOrientation.setIconResource(R.drawable.ic_list)
|
binding.folderOrientation.setIconResource(R.drawable.ic_list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.random.setOnClickListener {
|
||||||
|
val folders = folderManager.getFoldersInDirectory(hiddenDir)
|
||||||
|
val randomIndex = (Math.random() * folders.size).toInt()
|
||||||
|
val folder = folders[randomIndex]
|
||||||
|
startActivity(Intent(this,ViewFolderActivity::class.java).putExtra("folder",folder.toString()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showGridUI() {
|
private fun showGridUI() {
|
||||||
@@ -391,6 +406,7 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showFolderViewIcons() {
|
private fun showFolderViewIcons() {
|
||||||
|
binding.random.visibility = View.VISIBLE
|
||||||
binding.folderOrientation.visibility = View.VISIBLE
|
binding.folderOrientation.visibility = View.VISIBLE
|
||||||
binding.settings.visibility = View.VISIBLE
|
binding.settings.visibility = View.VISIBLE
|
||||||
binding.delete.visibility = View.GONE
|
binding.delete.visibility = View.GONE
|
||||||
@@ -404,6 +420,7 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun showFolderSelectionIcons() {
|
private fun showFolderSelectionIcons() {
|
||||||
|
binding.random.visibility = View.GONE
|
||||||
binding.folderOrientation.visibility = View.GONE
|
binding.folderOrientation.visibility = View.GONE
|
||||||
binding.settings.visibility = View.GONE
|
binding.settings.visibility = View.GONE
|
||||||
binding.delete.visibility = View.VISIBLE
|
binding.delete.visibility = View.VISIBLE
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ 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 androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import devs.org.calculator.utils.StoragePermissionUtil
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
|
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
@@ -36,6 +40,8 @@ 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 lateinit var storagePermissionUtil: StoragePermissionUtil
|
||||||
|
private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -43,15 +49,30 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, 0, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
|
) { permissions ->
|
||||||
|
storagePermissionUtil.handlePermissionResult(permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize StoragePermissionUtil
|
||||||
|
storagePermissionUtil = StoragePermissionUtil(this)
|
||||||
|
|
||||||
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
handleActivityResult(result)
|
handleActivityResult(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp.getBoolean("isFirst", true)){
|
if (sp.getBoolean("isFirst", true)){
|
||||||
binding.display.text = getString(R.string.enter_123456)
|
binding.display.text = getString(R.string.enter_123456)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Environment.isExternalStorageManager()) {
|
|
||||||
|
if (!storagePermissionUtil.hasStoragePermission()) {
|
||||||
dialogUtil.showMaterialDialog(
|
dialogUtil.showMaterialDialog(
|
||||||
getString(R.string.storage_permission),
|
getString(R.string.storage_permission),
|
||||||
getString(R.string.to_ensure_the_app_works_properly_and_allows_you_to_easily_hide_or_un_hide_your_private_files_please_grant_storage_access_permission) +
|
getString(R.string.to_ensure_the_app_works_properly_and_allows_you_to_easily_hide_or_un_hide_your_private_files_please_grant_storage_access_permission) +
|
||||||
@@ -61,7 +82,10 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
getString(R.string.later),
|
getString(R.string.later),
|
||||||
object : DialogUtil.DialogCallback {
|
object : DialogUtil.DialogCallback {
|
||||||
override fun onPositiveButtonClicked() {
|
override fun onPositiveButtonClicked() {
|
||||||
fileManager.askPermission(this@MainActivity)
|
storagePermissionUtil.requestStoragePermission(permissionLauncher) {
|
||||||
|
Toast.makeText(this@MainActivity, getString(R.string.permission_granted), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNegativeButtonClicked() {
|
override fun onNegativeButtonClicked() {
|
||||||
@@ -75,9 +99,11 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
getString(R.string.you_can_grant_permission_later_from_settings),
|
getString(R.string.you_can_grant_permission_later_from_settings),
|
||||||
Toast.LENGTH_LONG).show()
|
Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setupNumberButton(binding.btn0, "0")
|
setupNumberButton(binding.btn0, "0")
|
||||||
setupNumberButton(binding.btn00, "00")
|
setupNumberButton(binding.btn00, "00")
|
||||||
setupNumberButton(binding.btn1, "1")
|
setupNumberButton(binding.btn1, "1")
|
||||||
@@ -150,7 +176,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun clearDisplay() {
|
private fun clearDisplay() {
|
||||||
currentExpression = ""
|
currentExpression = "0"
|
||||||
binding.total.text = ""
|
binding.total.text = ""
|
||||||
lastWasOperator = false
|
lastWasOperator = false
|
||||||
lastWasPercent = false
|
lastWasPercent = false
|
||||||
@@ -335,13 +361,17 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (currentExpression.isEmpty() ||
|
if (currentExpression.isEmpty()) {
|
||||||
(isOperator(currentExpression.last().toString()) && currentExpression.last() != '%')) {
|
|
||||||
binding.total.text = ""
|
binding.total.text = ""
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var processedExpression = currentExpression.replace("×", "*")
|
var processedExpression = currentExpression.replace("×", "*")
|
||||||
|
|
||||||
|
if (isOperator(processedExpression.last().toString())) {
|
||||||
|
processedExpression = processedExpression.substring(0, processedExpression.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
if (processedExpression.contains("%")) {
|
if (processedExpression.contains("%")) {
|
||||||
processedExpression = preprocessExpression(processedExpression)
|
processedExpression = preprocessExpression(processedExpression)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import devs.org.calculator.R
|
import devs.org.calculator.R
|
||||||
@@ -18,6 +22,7 @@ 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 devs.org.calculator.utils.SecurityUtils
|
import devs.org.calculator.utils.SecurityUtils
|
||||||
|
import kotlinx.coroutines.Runnable
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -37,12 +42,33 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
private val hiddenFileRepository: HiddenFileRepository by lazy {
|
private val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||||
HiddenFileRepository(AppDatabase.getDatabase(this).hiddenFileDao())
|
HiddenFileRepository(AppDatabase.getDatabase(this).hiddenFileDao())
|
||||||
}
|
}
|
||||||
|
private var slideshowRunning = false
|
||||||
|
private val slideshowDelay = 2000L
|
||||||
|
|
||||||
|
private val slideshowRunner = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
if (slideshowRunning) {
|
||||||
|
if (currentPosition + 1 >= files.size) {
|
||||||
|
currentPosition = 0
|
||||||
|
} else {
|
||||||
|
currentPosition += 1
|
||||||
|
}
|
||||||
|
binding.viewPager.setCurrentItem(currentPosition, true)
|
||||||
|
mainHandler.postDelayed(this, slideshowDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityPreviewBinding.inflate(layoutInflater)
|
binding = ActivityPreviewBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
fileManager = FileManager(this, this)
|
fileManager = FileManager(this, this)
|
||||||
|
|
||||||
currentPosition = intent.getIntExtra("position", 0)
|
currentPosition = intent.getIntExtra("position", 0)
|
||||||
@@ -126,6 +152,11 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
adapter = ImagePreviewAdapter(this, this)
|
adapter = ImagePreviewAdapter(this, this)
|
||||||
adapter.images = files
|
adapter.images = files
|
||||||
|
adapter.onSlideshowShouldStop = {
|
||||||
|
if (slideshowRunning) {
|
||||||
|
stopSlideshow()
|
||||||
|
}
|
||||||
|
}
|
||||||
binding.viewPager.adapter = adapter
|
binding.viewPager.adapter = adapter
|
||||||
if (currentPosition < files.size) {
|
if (currentPosition < files.size) {
|
||||||
binding.viewPager.setCurrentItem(currentPosition, false)
|
binding.viewPager.setCurrentItem(currentPosition, false)
|
||||||
@@ -171,6 +202,34 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
currentPosition = position
|
currentPosition = position
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if (filetype != FileManager.FileType.IMAGE) {
|
||||||
|
binding.startSlideshow.visibility = View.GONE
|
||||||
|
}
|
||||||
|
binding.startSlideshow.setOnClickListener {
|
||||||
|
startSlideshow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startSlideshow() {
|
||||||
|
if (!slideshowRunning) {
|
||||||
|
slideshowRunning = true
|
||||||
|
toggleToolbars()
|
||||||
|
mainHandler.postDelayed(slideshowRunner, slideshowDelay)
|
||||||
|
adapter.setImageZoomable(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopSlideshow() {
|
||||||
|
slideshowRunning = false
|
||||||
|
toggleToolbars()
|
||||||
|
mainHandler.removeCallbacks(slideshowRunner)
|
||||||
|
adapter.setImageZoomable(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleToolbars() {
|
||||||
|
val visibility = if (slideshowRunning) View.GONE else View.VISIBLE
|
||||||
|
binding.toolbar.visibility = visibility
|
||||||
|
binding.footer.visibility = visibility
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDeleteFile() {
|
private fun handleDeleteFile() {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package devs.org.calculator.activities
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
|
||||||
|
abstract class SecureBaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
class AppLifecycleObserver : DefaultLifecycleObserver {
|
||||||
|
companion object {
|
||||||
|
var wasInBackground = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
|
wasInBackground = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
wasInBackground = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
if (AppLifecycleObserver.wasInBackground) {
|
||||||
|
AppLifecycleObserver.wasInBackground = false
|
||||||
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,9 +6,12 @@ import android.view.View
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
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
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
@@ -29,6 +32,12 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
prefs = PrefsUtil(this)
|
prefs = PrefsUtil(this)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
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()
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
@@ -26,7 +29,12 @@ class SetupPasswordActivity : AppCompatActivity() {
|
|||||||
binding = ActivitySetupPasswordBinding.inflate(layoutInflater)
|
binding = ActivitySetupPasswordBinding.inflate(layoutInflater)
|
||||||
binding2 = ActivityChangePasswordBinding.inflate(layoutInflater)
|
binding2 = ActivityChangePasswordBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
prefsUtil = PrefsUtil(this)
|
prefsUtil = PrefsUtil(this)
|
||||||
hasPassword = prefsUtil.hasPassword()
|
hasPassword = prefsUtil.hasPassword()
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.os.Bundle
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.animation.Animation
|
import android.view.animation.Animation
|
||||||
@@ -23,7 +22,6 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
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.FileAdapter
|
|
||||||
import devs.org.calculator.adapters.FolderSelectionAdapter
|
import devs.org.calculator.adapters.FolderSelectionAdapter
|
||||||
import devs.org.calculator.callbacks.FileProcessCallback
|
import devs.org.calculator.callbacks.FileProcessCallback
|
||||||
import devs.org.calculator.database.HiddenFileEntity
|
import devs.org.calculator.database.HiddenFileEntity
|
||||||
@@ -41,6 +39,11 @@ import java.io.File
|
|||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import devs.org.calculator.adapters.FileAdapter
|
||||||
|
|
||||||
class ViewFolderActivity : AppCompatActivity() {
|
class ViewFolderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -71,6 +74,12 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
binding = ActivityViewFolderBinding.inflate(layoutInflater)
|
binding = ActivityViewFolderBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
setupAnimations()
|
setupAnimations()
|
||||||
initialize()
|
initialize()
|
||||||
setupClickListeners()
|
setupClickListeners()
|
||||||
@@ -142,16 +151,19 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
customDialog?.dismiss()
|
customDialog?.dismiss()
|
||||||
customDialog = null
|
customDialog = null
|
||||||
updateFilesToAdapter()
|
updateFilesToAdapter()
|
||||||
|
refreshCurrentFolder()
|
||||||
}, remainingTime)
|
}, remainingTime)
|
||||||
} else {
|
} else {
|
||||||
customDialog?.dismiss()
|
customDialog?.dismiss()
|
||||||
customDialog = null
|
customDialog = null
|
||||||
|
refreshCurrentFolder()
|
||||||
updateFilesToAdapter()
|
updateFilesToAdapter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFilesToAdapter() {
|
private fun updateFilesToAdapter() {
|
||||||
openFolder(currentFolder!!)
|
val files = folderManager.getFilesInFolder(currentFolder!!)
|
||||||
|
fileAdapter?.submitList(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -199,6 +211,16 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
mainHandler.postDelayed({
|
mainHandler.postDelayed({
|
||||||
dismissCustomDialog()
|
dismissCustomDialog()
|
||||||
|
val files = folderManager.getFilesInFolder(targetFolder)
|
||||||
|
if (files.isNotEmpty()) {
|
||||||
|
binding.swipeLayout.visibility = View.VISIBLE
|
||||||
|
binding.noItems.visibility = View.GONE
|
||||||
|
if (fileAdapter == null) {
|
||||||
|
showFileList(files, targetFolder)
|
||||||
|
} else {
|
||||||
|
fileAdapter?.submitList(files.toMutableList())
|
||||||
|
}
|
||||||
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,6 +245,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
).show()
|
).show()
|
||||||
dismissCustomDialog()
|
dismissCustomDialog()
|
||||||
}
|
}
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,9 +253,19 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
setupFlagSecure()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openFolder(folder: File) {
|
private fun setupFlagSecure() {
|
||||||
|
if (prefs.getBoolean("screenshot_restriction", true)) {
|
||||||
|
window.setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openFolder(folder: File) {
|
||||||
if (!folder.exists()) {
|
if (!folder.exists()) {
|
||||||
folder.mkdirs()
|
folder.mkdirs()
|
||||||
File(folder, ".nomedia").createNewFile()
|
File(folder, ".nomedia").createNewFile()
|
||||||
@@ -246,11 +279,12 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
|
binding.swipeLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showEmptyState() {
|
private fun showEmptyState() {
|
||||||
binding.noItems.visibility = View.VISIBLE
|
binding.noItems.visibility = View.VISIBLE
|
||||||
binding.recyclerView.visibility = View.GONE
|
binding.swipeLayout.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showFileList(files: List<File>, folder: File) {
|
private fun showFileList(files: List<File>, folder: File) {
|
||||||
@@ -261,17 +295,20 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
onFolderLongClick = { isSelected ->
|
onFolderLongClick = { isSelected ->
|
||||||
handleFileSelectionModeChange(isSelected)
|
handleFileSelectionModeChange(isSelected)
|
||||||
}).apply {
|
}).apply {
|
||||||
setFileOperationCallback(object : FileAdapter.FileOperationCallback {
|
setFilesOperationCallback(object : FileAdapter.FilesOperationCallback {
|
||||||
override fun onFileDeleted(file: File) {
|
override fun onFileDeleted(file: File) {
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
updateFilesToAdapter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFileRenamed(oldFile: File, newFile: File) {
|
override fun onFileRenamed(oldFile: File, newFile: File) {
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
updateFilesToAdapter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRefreshNeeded() {
|
override fun onRefreshNeeded() {
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
updateFilesToAdapter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSelectionModeChanged(isSelectionMode: Boolean, selectedCount: Int) {
|
override fun onSelectionModeChanged(isSelectionMode: Boolean, selectedCount: Int) {
|
||||||
@@ -287,9 +324,13 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.recyclerView.adapter = fileAdapter
|
binding.recyclerView.adapter = fileAdapter
|
||||||
binding.recyclerView.visibility = View.VISIBLE
|
binding.swipeLayout.visibility = View.VISIBLE
|
||||||
binding.noItems.visibility = View.GONE
|
binding.noItems.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.selectAllButton.setOnClickListener {
|
||||||
|
fileAdapter?.toggleSelectAll()
|
||||||
|
}
|
||||||
|
|
||||||
binding.menuButton.setOnClickListener {
|
binding.menuButton.setOnClickListener {
|
||||||
fileAdapter?.let { adapter ->
|
fileAdapter?.let { adapter ->
|
||||||
showFileOptionsMenu(adapter.getSelectedItems())
|
showFileOptionsMenu(adapter.getSelectedItems())
|
||||||
@@ -455,21 +496,18 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var successCount = 0
|
var successCount = 0
|
||||||
var failCount = 0
|
var failCount = 0
|
||||||
|
val decryptedFiles = mutableMapOf<File, File>()
|
||||||
|
|
||||||
for (file in selectedFiles) {
|
for (file in selectedFiles) {
|
||||||
try {
|
try {
|
||||||
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
|
|
||||||
|
|
||||||
if (hiddenFile?.isEncrypted == true) {
|
if (hiddenFile?.isEncrypted == true) {
|
||||||
|
|
||||||
val originalExtension = hiddenFile.originalExtension
|
val originalExtension = hiddenFile.originalExtension
|
||||||
val decryptedFile = SecurityUtils.changeFileExtension(file, originalExtension)
|
val decryptedFile = SecurityUtils.changeFileExtension(file, originalExtension)
|
||||||
|
|
||||||
|
|
||||||
if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) {
|
if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) {
|
||||||
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
|
||||||
hiddenFile.let {
|
hiddenFile.let {
|
||||||
fileAdapter?.hiddenFileRepository?.updateEncryptionStatus(
|
fileAdapter?.hiddenFileRepository?.updateEncryptionStatus(
|
||||||
filePath = file.absolutePath,
|
filePath = file.absolutePath,
|
||||||
@@ -479,27 +517,23 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (file.delete()) {
|
if (file.delete()) {
|
||||||
|
decryptedFiles[file] = decryptedFile
|
||||||
successCount++
|
successCount++
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
decryptedFile.delete()
|
decryptedFile.delete()
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
decryptedFile.delete()
|
decryptedFile.delete()
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (decryptedFile.exists()) {
|
if (decryptedFile.exists()) {
|
||||||
decryptedFile.delete()
|
decryptedFile.delete()
|
||||||
}
|
}
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} else if (file.name.endsWith(ENCRYPTED_EXTENSION) && hiddenFile == null) {
|
} else if (file.name.endsWith(ENCRYPTED_EXTENSION) && hiddenFile == null) {
|
||||||
|
|
||||||
val extension = when (fileType) {
|
val extension = when (fileType) {
|
||||||
FileManager.FileType.IMAGE -> ".jpg"
|
FileManager.FileType.IMAGE -> ".jpg"
|
||||||
FileManager.FileType.VIDEO -> ".mp4"
|
FileManager.FileType.VIDEO -> ".mp4"
|
||||||
@@ -509,11 +543,8 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val decryptedFile = SecurityUtils.changeFileExtension(file, extension)
|
val decryptedFile = SecurityUtils.changeFileExtension(file, extension)
|
||||||
|
|
||||||
|
|
||||||
if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) {
|
if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) {
|
||||||
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
|
||||||
|
|
||||||
fileAdapter?.hiddenFileRepository?.insertHiddenFile(
|
fileAdapter?.hiddenFileRepository?.insertHiddenFile(
|
||||||
HiddenFileEntity(
|
HiddenFileEntity(
|
||||||
filePath = decryptedFile.absolutePath,
|
filePath = decryptedFile.absolutePath,
|
||||||
@@ -525,37 +556,32 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (file.delete()) {
|
if (file.delete()) {
|
||||||
|
decryptedFiles[file] = decryptedFile
|
||||||
successCount++
|
successCount++
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
decryptedFile.delete()
|
decryptedFile.delete()
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
decryptedFile.delete()
|
decryptedFile.delete()
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (decryptedFile.exists()) {
|
if (decryptedFile.exists()) {
|
||||||
decryptedFile.delete()
|
decryptedFile.delete()
|
||||||
}
|
}
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failCount++
|
failCount++
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
failCount++
|
failCount++
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
when {
|
when {
|
||||||
successCount > 0 && failCount == 0 -> {
|
successCount > 0 && failCount == 0 -> {
|
||||||
Toast.makeText(this@ViewFolderActivity, "Decrypted $successCount file(s)", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ViewFolderActivity, "Decrypted $successCount file(s)", Toast.LENGTH_SHORT).show()
|
||||||
@@ -567,7 +593,10 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
Toast.makeText(this@ViewFolderActivity, "Failed to decrypt $failCount file(s)", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ViewFolderActivity, "Failed to decrypt $failCount file(s)", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshCurrentFolder()
|
if (successCount > 0) {
|
||||||
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -627,10 +656,19 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
val files = folderManager.getFilesInFolder(folder)
|
val files = folderManager.getFilesInFolder(folder)
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
if (files.isNotEmpty()) {
|
if (files.isNotEmpty()) {
|
||||||
binding.recyclerView.visibility = View.VISIBLE
|
binding.swipeLayout.visibility = View.VISIBLE
|
||||||
binding.noItems.visibility = View.GONE
|
binding.noItems.visibility = View.GONE
|
||||||
|
|
||||||
fileAdapter?.submitList(files.toMutableList())
|
val currentFiles = fileAdapter?.currentList ?: emptyList()
|
||||||
|
val hasChanges = files.size != currentFiles.size ||
|
||||||
|
files.any { newFile ->
|
||||||
|
currentFiles.none { it.absolutePath == newFile.absolutePath }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
fileAdapter?.submitList(files.toMutableList())
|
||||||
|
}
|
||||||
|
|
||||||
fileAdapter?.let { adapter ->
|
fileAdapter?.let { adapter ->
|
||||||
if (adapter.isInSelectionMode()) {
|
if (adapter.isInSelectionMode()) {
|
||||||
showFileSelectionIcons()
|
showFileSelectionIcons()
|
||||||
@@ -642,8 +680,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
|
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
@@ -659,6 +696,9 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
binding.back.setOnClickListener {
|
binding.back.setOnClickListener {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
binding.swipeLayout.setOnRefreshListener {
|
||||||
|
openFolder(currentFolder!!)
|
||||||
|
}
|
||||||
|
|
||||||
binding.addImage.setOnClickListener { openFilePicker("image/*") }
|
binding.addImage.setOnClickListener { openFilePicker("image/*") }
|
||||||
binding.addVideo.setOnClickListener { openFilePicker("video/*") }
|
binding.addVideo.setOnClickListener { openFilePicker("video/*") }
|
||||||
@@ -726,6 +766,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun showFileViewIcons() {
|
private fun showFileViewIcons() {
|
||||||
binding.menuButton.visibility = View.GONE
|
binding.menuButton.visibility = View.GONE
|
||||||
|
binding.selectAllButton.visibility = View.GONE
|
||||||
binding.fabExpend.visibility = View.VISIBLE
|
binding.fabExpend.visibility = View.VISIBLE
|
||||||
binding.addImage.visibility = View.INVISIBLE
|
binding.addImage.visibility = View.INVISIBLE
|
||||||
binding.addVideo.visibility = View.INVISIBLE
|
binding.addVideo.visibility = View.INVISIBLE
|
||||||
@@ -737,6 +778,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun showFileSelectionIcons() {
|
private fun showFileSelectionIcons() {
|
||||||
binding.menuButton.visibility = View.VISIBLE
|
binding.menuButton.visibility = View.VISIBLE
|
||||||
|
binding.selectAllButton.visibility = View.VISIBLE
|
||||||
binding.fabExpend.visibility = View.GONE
|
binding.fabExpend.visibility = View.GONE
|
||||||
binding.addImage.visibility = View.INVISIBLE
|
binding.addImage.visibility = View.INVISIBLE
|
||||||
binding.addVideo.visibility = View.INVISIBLE
|
binding.addVideo.visibility = View.INVISIBLE
|
||||||
@@ -748,6 +790,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
private fun performFileUnhiding(selectedFiles: List<File>) {
|
private fun performFileUnhiding(selectedFiles: List<File>) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var allUnhidden = true
|
var allUnhidden = true
|
||||||
|
val unhiddenFiles = mutableListOf<File>()
|
||||||
selectedFiles.forEach { file ->
|
selectedFiles.forEach { file ->
|
||||||
try {
|
try {
|
||||||
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
@@ -767,6 +810,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
file.delete()
|
file.delete()
|
||||||
decryptedFile.delete()
|
decryptedFile.delete()
|
||||||
|
unhiddenFiles.add(file)
|
||||||
} else {
|
} else {
|
||||||
decryptedFile.delete()
|
decryptedFile.delete()
|
||||||
allUnhidden = false
|
allUnhidden = false
|
||||||
@@ -794,6 +838,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
fileAdapter?.hiddenFileRepository?.deleteHiddenFile(it)
|
fileAdapter?.hiddenFileRepository?.deleteHiddenFile(it)
|
||||||
}
|
}
|
||||||
file.delete()
|
file.delete()
|
||||||
|
unhiddenFiles.add(file)
|
||||||
} else {
|
} else {
|
||||||
allUnhidden = false
|
allUnhidden = false
|
||||||
}
|
}
|
||||||
@@ -802,8 +847,8 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
allUnhidden = false
|
allUnhidden = false
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -815,8 +860,8 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -824,17 +869,19 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
private fun performFileDeletion(selectedFiles: List<File>) {
|
private fun performFileDeletion(selectedFiles: List<File>) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var allDeleted = true
|
var allDeleted = true
|
||||||
|
val deletedFiles = mutableListOf<File>()
|
||||||
selectedFiles.forEach { file ->
|
selectedFiles.forEach { file ->
|
||||||
try {
|
try {
|
||||||
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
hiddenFile?.let {
|
hiddenFile?.let {
|
||||||
fileAdapter?.hiddenFileRepository?.deleteHiddenFile(it)
|
fileAdapter?.hiddenFileRepository?.deleteHiddenFile(it)
|
||||||
}
|
}
|
||||||
if (!file.delete()) {
|
if (file.delete()) {
|
||||||
|
deletedFiles.add(file)
|
||||||
|
} else {
|
||||||
allDeleted = false
|
allDeleted = false
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
|
|
||||||
allDeleted = false
|
allDeleted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -847,8 +894,8 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -862,6 +909,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
private fun copyFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
private fun copyFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var allCopied = true
|
var allCopied = true
|
||||||
|
val copiedFiles = mutableListOf<File>()
|
||||||
selectedFiles.forEach { file ->
|
selectedFiles.forEach { file ->
|
||||||
try {
|
try {
|
||||||
val newFile = File(destinationFolder, file.name)
|
val newFile = File(destinationFolder, file.name)
|
||||||
@@ -880,17 +928,18 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
copiedFiles.add(file)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
allCopied = false
|
allCopied = false
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
val message = if (allCopied) getString(R.string.files_copied_successfully) else getString(R.string.some_files_could_not_be_copied)
|
val message = if (allCopied) getString(R.string.files_copied_successfully) else getString(R.string.some_files_could_not_be_copied)
|
||||||
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -898,6 +947,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
private fun moveFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
private fun moveFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var allMoved = true
|
var allMoved = true
|
||||||
|
val movedFiles = mutableListOf<File>()
|
||||||
selectedFiles.forEach { file ->
|
selectedFiles.forEach { file ->
|
||||||
try {
|
try {
|
||||||
val newFile = File(destinationFolder, file.name)
|
val newFile = File(destinationFolder, file.name)
|
||||||
@@ -913,22 +963,27 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
file.delete()
|
if (file.delete()) {
|
||||||
|
movedFiles.add(file)
|
||||||
|
} else {
|
||||||
|
allMoved = false
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
allMoved = false
|
allMoved = false
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
val message = if (allMoved) getString(R.string.files_moved_successfully) else getString(R.string.some_files_could_not_be_moved)
|
val message = if (allMoved) getString(R.string.files_moved_successfully) else getString(R.string.some_files_could_not_be_moved)
|
||||||
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
private fun showFolderSelectionDialog(onFolderSelected: (File) -> Unit) {
|
private fun showFolderSelectionDialog(onFolderSelected: (File) -> Unit) {
|
||||||
val folders = folderManager.getFoldersInDirectory(hiddenDir)
|
val folders = folderManager.getFoldersInDirectory(hiddenDir)
|
||||||
.filter { it != currentFolder }
|
.filter { it != currentFolder }
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ import android.util.Log
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
@@ -26,7 +23,9 @@ import devs.org.calculator.activities.PreviewActivity
|
|||||||
import devs.org.calculator.database.AppDatabase
|
import devs.org.calculator.database.AppDatabase
|
||||||
import devs.org.calculator.database.HiddenFileEntity
|
import devs.org.calculator.database.HiddenFileEntity
|
||||||
import devs.org.calculator.database.HiddenFileRepository
|
import devs.org.calculator.database.HiddenFileRepository
|
||||||
|
import devs.org.calculator.databinding.ListItemFileBinding
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
|
import devs.org.calculator.utils.FolderManager
|
||||||
import devs.org.calculator.utils.SecurityUtils
|
import devs.org.calculator.utils.SecurityUtils
|
||||||
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
|
||||||
@@ -40,22 +39,18 @@ class FileAdapter(
|
|||||||
private val lifecycleOwner: LifecycleOwner,
|
private val lifecycleOwner: LifecycleOwner,
|
||||||
private val currentFolder: File,
|
private val currentFolder: File,
|
||||||
private val showFileName: Boolean,
|
private val showFileName: Boolean,
|
||||||
private val onFolderLongClick: (Boolean) -> Unit
|
private val onFolderLongClick: (Boolean) -> Unit,
|
||||||
) : ListAdapter<File, FileAdapter.FileViewHolder>(FileDiffCallback()) {
|
) : ListAdapter<File, FileAdapter.FilesViewHolder>(FileDiffCallback()) {
|
||||||
|
|
||||||
|
|
||||||
|
private var filesOperationCallback: WeakReference<FilesOperationCallback>? = null
|
||||||
private val selectedItems = mutableSetOf<Int>()
|
private val selectedItems = mutableSetOf<Int>()
|
||||||
private var isSelectionMode = false
|
private var isSelectionMode = false
|
||||||
|
|
||||||
private var fileOperationCallback: WeakReference<FileOperationCallback>? = null
|
|
||||||
|
|
||||||
private val fileExecutor = Executors.newSingleThreadExecutor()
|
private val fileExecutor = Executors.newSingleThreadExecutor()
|
||||||
private val mainHandler = Handler(Looper.getMainLooper())
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
val hiddenFileRepository: HiddenFileRepository by lazy {
|
|
||||||
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FileOperationCallback {
|
interface FilesOperationCallback {
|
||||||
fun onFileDeleted(file: File)
|
fun onFileDeleted(file: File)
|
||||||
fun onFileRenamed(oldFile: File, newFile: File)
|
fun onFileRenamed(oldFile: File, newFile: File)
|
||||||
fun onRefreshNeeded()
|
fun onRefreshNeeded()
|
||||||
@@ -63,197 +58,77 @@ class FileAdapter(
|
|||||||
fun onSelectionCountChanged(selectedCount: Int)
|
fun onSelectionCountChanged(selectedCount: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFileOperationCallback(callback: FileOperationCallback?) {
|
fun setFilesOperationCallback(callback: FilesOperationCallback?) {
|
||||||
fileOperationCallback = callback?.let { WeakReference(it) }
|
filesOperationCallback = callback?.let { WeakReference(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class FileViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||||
val imageView: ImageView = view.findViewById(R.id.fileIconImageView)
|
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int,
|
||||||
|
): FilesViewHolder {
|
||||||
|
val binding =
|
||||||
|
ListItemFileBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return FilesViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: FilesViewHolder,
|
||||||
|
position: Int,
|
||||||
|
) {
|
||||||
|
holder.bind(getItem(position))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inner class FilesViewHolder(private val binding: ListItemFileBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
@SuppressLint("FileEndsWithExt")
|
||||||
fun bind(file: File) {
|
fun bind(file: File) {
|
||||||
|
val position = adapterPosition
|
||||||
lifecycleOwner.lifecycleScope.launch {
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
val fileType = if (hiddenFile?.fileType != null) hiddenFile.fileType
|
val currentFileData =
|
||||||
else {
|
hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
FileManager(context, lifecycleOwner).getFileType(file)
|
val currentFileType = currentFileData?.fileType ?: FileManager(
|
||||||
}
|
context,
|
||||||
|
lifecycleOwner
|
||||||
|
).getFileType(file)
|
||||||
|
|
||||||
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
|
val isCurrentFileEncrypted = currentFileData?.isEncrypted ?: file.endsWith(
|
||||||
|
SecurityUtils.ENCRYPTED_EXTENSION
|
||||||
|
)
|
||||||
|
|
||||||
|
setupClickListeners(file, currentFileType)
|
||||||
|
setupDisplay(
|
||||||
|
file,
|
||||||
|
currentFileType,
|
||||||
|
isCurrentFileEncrypted,
|
||||||
|
currentFileData
|
||||||
|
)
|
||||||
|
binding.fileNameTextView.text = if (isCurrentFileEncrypted) currentFileData?.fileName else file.name
|
||||||
|
binding.fileNameTextView.visibility =
|
||||||
|
if (showFileName) View.VISIBLE else View.GONE
|
||||||
|
binding.shade.visibility = if (showFileName) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
if (position != RecyclerView.NO_POSITION) {
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
val isSelected = selectedItems.contains(position)
|
val isSelected = selectedItems.contains(position)
|
||||||
updateSelectionUI(isSelected)
|
updateSelectionUI(isSelected)
|
||||||
}
|
}
|
||||||
encryptedIcon.visibility = if (hiddenFile?.isEncrypted == true) View.VISIBLE else View.GONE
|
binding.encrypted.visibility =
|
||||||
|
if (isCurrentFileEncrypted) View.VISIBLE else View.GONE
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("FileAdapter", "Error in bind: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun bind(file: File, payloads: List<Any>) {
|
|
||||||
if (payloads.isEmpty()) {
|
|
||||||
bind(file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val changes = payloads.firstOrNull() as? List<String>
|
|
||||||
changes?.forEach { change ->
|
|
||||||
when (change) {
|
|
||||||
"NAME_CHANGED" -> {
|
|
||||||
fileNameTextView.text = file.name
|
|
||||||
}
|
|
||||||
"SIZE_CHANGED", "MODIFIED_DATE_CHANGED" -> {
|
|
||||||
|
|
||||||
}
|
|
||||||
"SELECTION_CHANGED" -> {
|
|
||||||
val position = adapterPosition
|
|
||||||
if (position != RecyclerView.NO_POSITION) {
|
|
||||||
val isSelected = selectedItems.contains(position)
|
|
||||||
updateSelectionUI(isSelected)
|
|
||||||
notifySelectionModeChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateSelectionUI(isSelected: Boolean) {
|
|
||||||
selectedLayer.visibility = if (isSelected) View.VISIBLE else View.GONE
|
|
||||||
selected.visibility = if (isSelected) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupFileDisplay(file: File, fileType: FileManager.FileType, isEncrypted: Boolean, metadata: HiddenFileEntity?) {
|
|
||||||
fileNameTextView.text = metadata?.fileName ?: file.name
|
|
||||||
|
|
||||||
when (fileType) {
|
|
||||||
FileManager.FileType.IMAGE -> {
|
|
||||||
playIcon.visibility = View.GONE
|
|
||||||
if (isEncrypted) {
|
|
||||||
try {
|
|
||||||
val decryptedFile = getDecryptedPreviewFile(context, metadata!!)
|
|
||||||
if (decryptedFile != null && decryptedFile.exists() && decryptedFile.length() > 0) {
|
|
||||||
val uri = getUriForPreviewFile(context, decryptedFile)
|
|
||||||
if (uri != null) {
|
|
||||||
Glide.with(context)
|
|
||||||
.load(uri)
|
|
||||||
.centerCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.error(R.drawable.encrypted)
|
|
||||||
.into(imageView)
|
|
||||||
imageView.setPadding(0, 0, 0, 0)
|
|
||||||
} else {
|
|
||||||
showEncryptedIcon()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showEncryptedIcon()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
showEncryptedIcon()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Glide.with(context)
|
|
||||||
.load(file)
|
|
||||||
.centerCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.error(R.drawable.ic_image)
|
|
||||||
.into(imageView)
|
|
||||||
imageView.setPadding(0, 0, 0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FileManager.FileType.VIDEO -> {
|
|
||||||
playIcon.visibility = View.VISIBLE
|
|
||||||
if (isEncrypted) {
|
|
||||||
try {
|
|
||||||
val decryptedFile = getDecryptedPreviewFile(context, metadata!!)
|
|
||||||
if (decryptedFile != null && decryptedFile.exists() && decryptedFile.length() > 0) {
|
|
||||||
val uri = getUriForPreviewFile(context, decryptedFile)
|
|
||||||
if (uri != null) {
|
|
||||||
Glide.with(context)
|
|
||||||
.load(uri)
|
|
||||||
.centerCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.error(R.drawable.encrypted)
|
|
||||||
.into(imageView)
|
|
||||||
imageView.setPadding(0, 0, 0, 0)
|
|
||||||
} else {
|
|
||||||
showEncryptedIcon()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showEncryptedIcon()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
showEncryptedIcon()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Glide.with(context)
|
|
||||||
.load(file)
|
|
||||||
.centerCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.error(R.drawable.ic_video)
|
|
||||||
.into(imageView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FileManager.FileType.AUDIO -> {
|
|
||||||
playIcon.visibility = View.GONE
|
|
||||||
if (isEncrypted) {
|
|
||||||
imageView.setImageResource(R.drawable.encrypted)
|
|
||||||
} else {
|
|
||||||
imageView.setImageResource(R.drawable.ic_audio)
|
|
||||||
}
|
|
||||||
imageView.setPadding(50, 50, 50, 50)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
playIcon.visibility = View.GONE
|
|
||||||
if (isEncrypted) {
|
|
||||||
imageView.setImageResource(R.drawable.encrypted)
|
|
||||||
} else {
|
|
||||||
imageView.setImageResource(R.drawable.ic_document)
|
|
||||||
}
|
|
||||||
imageView.setPadding(50, 50, 50, 50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupClickListeners(file: File, fileType: FileManager.FileType) {
|
|
||||||
itemView.setOnClickListener {
|
|
||||||
val position = adapterPosition
|
|
||||||
if (position == RecyclerView.NO_POSITION) return@setOnClickListener
|
|
||||||
|
|
||||||
if (isSelectionMode) {
|
|
||||||
toggleSelection(position)
|
|
||||||
} else {
|
|
||||||
openFile(file, fileType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
itemView.setOnLongClickListener {
|
|
||||||
val position = adapterPosition
|
|
||||||
if (position == RecyclerView.NO_POSITION) return@setOnLongClickListener false
|
|
||||||
|
|
||||||
if (!isSelectionMode) {
|
|
||||||
enterSelectionMode()
|
|
||||||
toggleSelection(position)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
Toast.makeText(context,
|
||||||
@@ -305,7 +180,7 @@ class FileAdapter(
|
|||||||
} else {
|
} else {
|
||||||
openInPreview(fileType)
|
openInPreview(fileType)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
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()
|
||||||
}
|
}
|
||||||
@@ -317,6 +192,39 @@ class FileAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 openInPreview(fileType: FileManager.FileType) {
|
||||||
|
val fileTypeString = when (fileType) {
|
||||||
|
FileManager.FileType.IMAGE -> context.getString(R.string.image)
|
||||||
|
FileManager.FileType.VIDEO -> context.getString(R.string.video)
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(context, PreviewActivity::class.java).apply {
|
||||||
|
putExtra("type", fileTypeString)
|
||||||
|
putExtra("folder", currentFolder.toString())
|
||||||
|
putExtra("position", adapterPosition)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun openAudioFile(file: File) {
|
private fun openAudioFile(file: File) {
|
||||||
val fileType = FileManager(context,lifecycleOwner).getFileType(file)
|
val fileType = FileManager(context,lifecycleOwner).getFileType(file)
|
||||||
try {
|
try {
|
||||||
@@ -332,7 +240,7 @@ class FileAdapter(
|
|||||||
putExtra("position", adapterPosition)
|
putExtra("position", adapterPosition)
|
||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.no_audio_player_found),
|
context.getString(R.string.no_audio_player_found),
|
||||||
@@ -354,7 +262,7 @@ class FileAdapter(
|
|||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
@@ -364,84 +272,126 @@ class FileAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openInPreview(fileType: FileManager.FileType) {
|
private fun setupDisplay(
|
||||||
val fileTypeString = when (fileType) {
|
file: File,
|
||||||
FileManager.FileType.IMAGE -> context.getString(R.string.image)
|
type: FileManager.FileType,
|
||||||
FileManager.FileType.VIDEO -> context.getString(R.string.video)
|
isCurrentFileEncrypted: Boolean,
|
||||||
else -> "unknown"
|
metadata: HiddenFileEntity?,
|
||||||
|
) {
|
||||||
|
when (type) {
|
||||||
|
FileManager.FileType.IMAGE -> {
|
||||||
|
binding.videoPlay.visibility = View.GONE
|
||||||
|
binding.fileIconImageView.setPadding(0, 0, 0, 0)
|
||||||
|
if (isCurrentFileEncrypted) {
|
||||||
|
try {
|
||||||
|
val decryptedFile = getDecryptedPreviewFile(context, metadata!!)
|
||||||
|
if (decryptedFile != null && decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
val uri = getUriForPreviewFile(context, decryptedFile)
|
||||||
|
if (uri != null) {
|
||||||
|
Glide.with(context)
|
||||||
|
.load(uri)
|
||||||
|
.centerCrop()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
|
.skipMemoryCache(false)
|
||||||
|
.error(R.drawable.encrypted)
|
||||||
|
.into(binding.fileIconImageView)
|
||||||
|
} else {
|
||||||
|
showEncryptedIcon()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showEncryptedIcon()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("FileAdapter", "Error displaying encrypted image: ${e.message}")
|
||||||
|
showEncryptedIcon()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Glide.with(context)
|
||||||
|
.load(file)
|
||||||
|
.into(binding.fileIconImageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager.FileType.VIDEO -> {
|
||||||
|
binding.fileIconImageView.setPadding(0, 0, 0, 0)
|
||||||
|
binding.videoPlay.visibility = View.VISIBLE
|
||||||
|
if (isCurrentFileEncrypted) {
|
||||||
|
try {
|
||||||
|
val decryptedFile = getDecryptedPreviewFile(context, metadata!!)
|
||||||
|
if (decryptedFile != null && decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
val uri = getUriForPreviewFile(context, decryptedFile)
|
||||||
|
if (uri != null) {
|
||||||
|
Glide.with(context)
|
||||||
|
.load(uri)
|
||||||
|
.centerCrop()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.error(R.drawable.encrypted)
|
||||||
|
.into(binding.fileIconImageView)
|
||||||
|
binding.fileIconImageView.setPadding(0, 0, 0, 0)
|
||||||
|
} else {
|
||||||
|
showEncryptedIcon()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showEncryptedIcon()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("FileAdapter", "Error displaying encrypted video: ${e.message}")
|
||||||
|
showEncryptedIcon()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Glide.with(context)
|
||||||
|
.load(file)
|
||||||
|
.into(binding.fileIconImageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager.FileType.AUDIO -> {
|
||||||
|
binding.videoPlay.visibility = View.GONE
|
||||||
|
binding.fileIconImageView.setPadding(25, 25, 25, 25)
|
||||||
|
binding.fileIconImageView.setImageResource(R.drawable.ic_audio)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
binding.videoPlay.visibility = View.GONE
|
||||||
|
binding.fileIconImageView.setPadding(25, 25, 25, 25)
|
||||||
|
binding.fileIconImageView.setImageResource(R.drawable.ic_document)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEncryptedIcon() {
|
||||||
|
binding.fileIconImageView.setImageResource(R.drawable.encrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun updateSelectionUI(isSelected: Boolean) {
|
||||||
|
binding.selectedLayer.visibility = if (isSelected) View.VISIBLE else View.GONE
|
||||||
|
binding.selected.visibility = if (isSelected) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupClickListeners(file: File, fileType: FileManager.FileType) {
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
val position = adapterPosition
|
||||||
|
if (position == RecyclerView.NO_POSITION) return@setOnClickListener
|
||||||
|
|
||||||
|
if (isSelectionMode) {
|
||||||
|
toggleSelection(position)
|
||||||
|
} else {
|
||||||
|
openFile(file,fileType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val intent = Intent(context, PreviewActivity::class.java).apply {
|
itemView.setOnLongClickListener {
|
||||||
putExtra("type", fileTypeString)
|
val position = adapterPosition
|
||||||
putExtra("folder", currentFolder.toString())
|
if (position == RecyclerView.NO_POSITION) return@setOnLongClickListener false
|
||||||
putExtra("position", adapterPosition)
|
|
||||||
}
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("MissingInflatedId")
|
if (!isSelectionMode) {
|
||||||
private fun renameFile(file: File) {
|
enterSelectionMode()
|
||||||
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_input, null)
|
toggleSelection(position)
|
||||||
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
|
|
||||||
inputEditText.setText(file.name)
|
|
||||||
inputEditText.selectAll()
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(context.getString(R.string.rename_file))
|
|
||||||
.setView(dialogView)
|
|
||||||
.setPositiveButton(context.getString(R.string.rename)) { dialog, _ ->
|
|
||||||
val newName = inputEditText.text.toString().trim()
|
|
||||||
if (newName.isNotEmpty() && newName != file.name) {
|
|
||||||
if (isValidFileName(newName)) {
|
|
||||||
renameFileAsync(file, newName)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "Invalid file name", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
.setNegativeButton(context.getString(R.string.cancel)) { dialog, _ ->
|
|
||||||
dialog.cancel()
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isValidFileName(fileName: String): Boolean {
|
|
||||||
val forbiddenChars = charArrayOf('/', '\\', ':', '*', '?', '"', '<', '>', '|')
|
|
||||||
return fileName.isNotBlank() &&
|
|
||||||
fileName.none { it in forbiddenChars } &&
|
|
||||||
!fileName.startsWith(".") &&
|
|
||||||
fileName.length <= 255
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renameFileAsync(file: File, newName: String) {
|
|
||||||
fileExecutor.execute {
|
|
||||||
val parentDir = file.parentFile
|
|
||||||
if (parentDir != null) {
|
|
||||||
val newFile = File(parentDir, newName)
|
|
||||||
if (newFile.exists()) {
|
|
||||||
mainHandler.post {
|
|
||||||
Toast.makeText(context, "File with this name already exists", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
return@execute
|
|
||||||
}
|
|
||||||
|
|
||||||
val success = try {
|
|
||||||
file.renameTo(newFile)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
mainHandler.post {
|
|
||||||
if (success) {
|
|
||||||
fileOperationCallback?.get()?.onFileRenamed(file, newFile)
|
|
||||||
Toast.makeText(context, "File renamed", Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "Failed to rename file", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,29 +409,6 @@ class FileAdapter(
|
|||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showEncryptedIcon() {
|
|
||||||
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) {
|
private fun performDecryptionWithType(file: File, fileType: FileManager.FileType) {
|
||||||
lifecycleOwner.lifecycleScope.launch {
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
@@ -536,7 +463,7 @@ class FileAdapter(
|
|||||||
Toast.makeText(context, "Failed to decrypt file", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Failed to decrypt file", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
Toast.makeText(context, "Error decrypting file", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Error decrypting file", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
@@ -545,111 +472,8 @@ class FileAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.list_item_file, parent, false)
|
|
||||||
return FileViewHolder(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
|
|
||||||
if (position < itemCount) {
|
|
||||||
val file = getItem(position)
|
|
||||||
holder.bind(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: FileViewHolder, position: Int, payloads: MutableList<Any>) {
|
|
||||||
if (payloads.isEmpty()) {
|
|
||||||
onBindViewHolder(holder, position)
|
|
||||||
} else {
|
|
||||||
if (position < itemCount) {
|
|
||||||
val file = getItem(position)
|
|
||||||
holder.bind(file, payloads)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun submitList(list: List<File>?) {
|
|
||||||
val currentList = currentList.toMutableList()
|
|
||||||
if (list == null) {
|
|
||||||
currentList.clear()
|
|
||||||
super.submitList(null)
|
|
||||||
} else {
|
|
||||||
val newList = list.filter { it.name != ".nomedia" }.toMutableList()
|
|
||||||
super.submitList(newList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enterSelectionMode() {
|
|
||||||
if (!isSelectionMode) {
|
|
||||||
isSelectionMode = true
|
|
||||||
notifySelectionModeChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
fun exitSelectionMode() {
|
|
||||||
if (isSelectionMode) {
|
|
||||||
isSelectionMode = false
|
|
||||||
selectedItems.clear()
|
|
||||||
notifySelectionModeChange()
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearSelection() {
|
|
||||||
if (selectedItems.isNotEmpty()) {
|
|
||||||
val previouslySelected = selectedItems.toSet()
|
|
||||||
selectedItems.clear()
|
|
||||||
fileOperationCallback?.get()?.onSelectionCountChanged(0)
|
|
||||||
previouslySelected.forEach { position ->
|
|
||||||
if (position < itemCount) {
|
|
||||||
notifyItemChanged(position, listOf("SELECTION_CHANGED"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectAll() {
|
|
||||||
if (!isSelectionMode) {
|
|
||||||
enterSelectionMode()
|
|
||||||
}
|
|
||||||
val previouslySelected = selectedItems.toSet()
|
|
||||||
selectedItems.clear()
|
|
||||||
for (i in 0 until itemCount) {
|
|
||||||
selectedItems.add(i)
|
|
||||||
}
|
|
||||||
fileOperationCallback?.get()?.onSelectionCountChanged(selectedItems.size)
|
|
||||||
updateSelectionItems(selectedItems.toSet(), previouslySelected)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateSelectionItems(newSelections: Set<Int>, oldSelections: Set<Int>) {
|
|
||||||
val changedItems = (oldSelections - newSelections) + (newSelections - oldSelections)
|
|
||||||
changedItems.forEach { position ->
|
|
||||||
if (position < itemCount) {
|
|
||||||
notifyItemChanged(position, listOf("SELECTION_CHANGED"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun notifySelectionModeChange() {
|
|
||||||
fileOperationCallback?.get()?.onSelectionModeChanged(isSelectionMode, selectedItems.size)
|
|
||||||
onFolderLongClick(isSelectionMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSelectedItems(): List<File> {
|
|
||||||
return selectedItems.mapNotNull { position ->
|
|
||||||
if (position < itemCount) getItem(position) else null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun getSelectedCount(): Int = selectedItems.size
|
|
||||||
|
|
||||||
|
|
||||||
fun isInSelectionMode(): Boolean = isSelectionMode
|
fun isInSelectionMode(): Boolean = isSelectionMode
|
||||||
|
|
||||||
|
|
||||||
fun onBackPressed(): Boolean {
|
fun onBackPressed(): Boolean {
|
||||||
return if (isSelectionMode) {
|
return if (isSelectionMode) {
|
||||||
exitSelectionMode()
|
exitSelectionMode()
|
||||||
@@ -659,24 +483,76 @@ class FileAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun onSelectionCountChanged(count: Int) {
|
||||||
|
filesOperationCallback?.get()?.onSelectionCountChanged(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enterSelectionMode() {
|
||||||
|
if (!isSelectionMode) {
|
||||||
|
isSelectionMode = true
|
||||||
|
notifySelectionModeChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleSelectAll() {
|
||||||
|
if (!isSelectionMode) {
|
||||||
|
isSelectionMode = true
|
||||||
|
notifySelectionModeChange()
|
||||||
|
}
|
||||||
|
val filesInFolder = FolderManager().getFilesInFolder(currentFolder)
|
||||||
|
if (selectedItems.size == filesInFolder.size) {
|
||||||
|
selectedItems.clear()
|
||||||
|
onSelectionCountChanged(0)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
// for (i in 0 until filesInFolder.size) {
|
||||||
|
// notifyItemChanged(i)
|
||||||
|
// notifyChan
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
for (i in 0 until filesInFolder.size) {
|
||||||
|
if (!selectedItems.contains(i)) {
|
||||||
|
selectedItems.add(i)
|
||||||
|
notifyItemChanged(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onSelectionCountChanged(filesInFolder.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun exitSelectionMode() {
|
||||||
|
if (isSelectionMode) {
|
||||||
|
isSelectionMode = false
|
||||||
|
selectedItems.forEach { position ->
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
selectedItems.clear()
|
||||||
|
notifySelectionModeChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifySelectionModeChange() {
|
||||||
|
filesOperationCallback?.get()?.onSelectionModeChanged(isSelectionMode, selectedItems.size)
|
||||||
|
onFolderLongClick(isSelectionMode)
|
||||||
|
}
|
||||||
|
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
try {
|
try {
|
||||||
if (!fileExecutor.isShutdown) {
|
if (!fileExecutor.isShutdown) {
|
||||||
fileExecutor.shutdown()
|
fileExecutor.shutdown()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileOperationCallback?.clear()
|
filesOperationCallback?.clear()
|
||||||
fileOperationCallback = null
|
filesOperationCallback = null
|
||||||
}
|
|
||||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
|
||||||
super.onDetachedFromRecyclerView(recyclerView)
|
|
||||||
cleanup()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSelectionCountChanged(count: Int) {
|
fun getSelectedItems(): List<File> {
|
||||||
fileOperationCallback?.get()?.onSelectionCountChanged(count)
|
return selectedItems.mapNotNull { position ->
|
||||||
|
if (position < itemCount) getItem(position) else null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encryptSelectedFiles() {
|
fun encryptSelectedFiles() {
|
||||||
@@ -687,85 +563,16 @@ class FileAdapter(
|
|||||||
.setTitle(context.getString(R.string.encrypt_files))
|
.setTitle(context.getString(R.string.encrypt_files))
|
||||||
.setMessage(context.getString(R.string.encryption_disclaimer))
|
.setMessage(context.getString(R.string.encryption_disclaimer))
|
||||||
.setPositiveButton(context.getString(R.string.encrypt)) { _, _ ->
|
.setPositiveButton(context.getString(R.string.encrypt)) { _, _ ->
|
||||||
performEncryption(selectedFiles)
|
FileManager(context, lifecycleOwner).performEncryption(
|
||||||
|
selectedFiles
|
||||||
|
) {
|
||||||
|
updateItemsAfterEncryption(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(context.getString(R.string.cancel), null)
|
.setNegativeButton(context.getString(R.string.cancel), null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performEncryption(selectedFiles: List<File>) {
|
|
||||||
lifecycleOwner.lifecycleScope.launch {
|
|
||||||
var successCount = 0
|
|
||||||
var failCount = 0
|
|
||||||
val updatedFiles = mutableListOf<File>()
|
|
||||||
|
|
||||||
for (file in selectedFiles) {
|
|
||||||
try {
|
|
||||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
|
||||||
if (hiddenFile?.isEncrypted == true) continue
|
|
||||||
val originalExtension = ".${file.extension.lowercase()}"
|
|
||||||
val fileType = FileManager(context,lifecycleOwner).getFileType(file)
|
|
||||||
val encryptedFile = SecurityUtils.changeFileExtension(file, FileManager.ENCRYPTED_EXTENSION)
|
|
||||||
if (SecurityUtils.encryptFile(context, file, encryptedFile)) {
|
|
||||||
if (encryptedFile.exists()) {
|
|
||||||
if (hiddenFile == null){
|
|
||||||
hiddenFileRepository.insertHiddenFile(
|
|
||||||
HiddenFileEntity(
|
|
||||||
filePath = encryptedFile.absolutePath,
|
|
||||||
isEncrypted = true,
|
|
||||||
encryptedFileName = encryptedFile.name,
|
|
||||||
fileType = fileType,
|
|
||||||
fileName = file.name,
|
|
||||||
originalExtension = originalExtension
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}else{
|
|
||||||
hiddenFile.let {
|
|
||||||
hiddenFileRepository.updateEncryptionStatus(
|
|
||||||
filePath = hiddenFile.filePath,
|
|
||||||
newFilePath = encryptedFile.absolutePath,
|
|
||||||
encryptedFileName = encryptedFile.name,
|
|
||||||
isEncrypted = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (file.delete()) {
|
|
||||||
updatedFiles.add(encryptedFile)
|
|
||||||
successCount++
|
|
||||||
} else {
|
|
||||||
failCount++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
failCount++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
failCount++
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
failCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mainHandler.post {
|
|
||||||
exitSelectionMode()
|
|
||||||
when {
|
|
||||||
successCount > 0 && failCount == 0 -> {
|
|
||||||
Toast.makeText(context, "Encrypted $successCount file(s)", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
successCount > 0 && failCount > 0 -> {
|
|
||||||
Toast.makeText(context, "Encrypted $successCount file(s), failed to encrypt $failCount", Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
failCount > 0 -> {
|
|
||||||
Toast.makeText(context, "Failed to encrypt $failCount file(s)", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val currentFiles = currentFolder.listFiles()?.toList() ?: emptyList()
|
|
||||||
submitList(currentFiles)
|
|
||||||
fileOperationCallback?.get()?.onRefreshNeeded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun decryptSelectedFiles() {
|
fun decryptSelectedFiles() {
|
||||||
val selectedFiles = getSelectedItems()
|
val selectedFiles = getSelectedItems()
|
||||||
if (selectedFiles.isEmpty()) return
|
if (selectedFiles.isEmpty()) return
|
||||||
@@ -774,73 +581,40 @@ class FileAdapter(
|
|||||||
.setTitle(context.getString(R.string.decrypt_files))
|
.setTitle(context.getString(R.string.decrypt_files))
|
||||||
.setMessage(context.getString(R.string.decryption_disclaimer))
|
.setMessage(context.getString(R.string.decryption_disclaimer))
|
||||||
.setPositiveButton(context.getString(R.string.decrypt)) { _, _ ->
|
.setPositiveButton(context.getString(R.string.decrypt)) { _, _ ->
|
||||||
performDecryption(selectedFiles)
|
FileManager(context, lifecycleOwner).performDecryption(selectedFiles) {
|
||||||
|
updateItemsAfterDecryption(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(context.getString(R.string.cancel), null)
|
.setNegativeButton(context.getString(R.string.cancel), null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performDecryption(selectedFiles: List<File>) {
|
fun updateItemsAfterEncryption(encryptedFiles: Map<File, File>) {
|
||||||
lifecycleOwner.lifecycleScope.launch {
|
|
||||||
var successCount = 0
|
|
||||||
var failCount = 0
|
|
||||||
val updatedFiles = mutableListOf<File>()
|
|
||||||
|
|
||||||
for (file in selectedFiles) {
|
val currentList = FolderManager().getFilesInFolder(currentFolder)
|
||||||
try {
|
val updatedList = currentList.map { file ->
|
||||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
encryptedFiles[file] ?: file
|
||||||
if (hiddenFile?.isEncrypted != true) continue
|
}.toMutableList()
|
||||||
val originalExtension = hiddenFile.originalExtension
|
selectedItems.clear()
|
||||||
val decryptedFile = SecurityUtils.changeFileExtension(file, originalExtension)
|
exitSelectionMode()
|
||||||
if (SecurityUtils.decryptFile(context, file, decryptedFile)) {
|
submitList(updatedList)
|
||||||
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
mainHandler.postDelayed({
|
||||||
hiddenFile.let {
|
|
||||||
hiddenFileRepository.updateEncryptionStatus(
|
|
||||||
filePath = file.absolutePath,
|
|
||||||
newFilePath = decryptedFile.absolutePath,
|
|
||||||
encryptedFileName = decryptedFile.name,
|
|
||||||
isEncrypted = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (file.delete()) {
|
|
||||||
updatedFiles.add(decryptedFile)
|
|
||||||
successCount++
|
|
||||||
} else {
|
|
||||||
decryptedFile.delete()
|
|
||||||
failCount++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
decryptedFile.delete()
|
|
||||||
failCount++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (decryptedFile.exists()) {
|
|
||||||
decryptedFile.delete()
|
|
||||||
}
|
|
||||||
failCount++
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
failCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mainHandler.post {
|
filesOperationCallback?.get()?.onRefreshNeeded()
|
||||||
exitSelectionMode()
|
},20)
|
||||||
when {
|
}
|
||||||
successCount > 0 && failCount == 0 -> {
|
|
||||||
Toast.makeText(context, "Decrypted $successCount file(s)", Toast.LENGTH_SHORT).show()
|
fun updateItemsAfterDecryption(decryptedFiles: Map<File, File>) {
|
||||||
}
|
val currentList = FolderManager().getFilesInFolder(currentFolder)
|
||||||
successCount > 0 && failCount > 0 -> {
|
val updatedList = currentList.map { file ->
|
||||||
Toast.makeText(context, "Decrypted $successCount file(s), failed to decrypt $failCount", Toast.LENGTH_LONG).show()
|
decryptedFiles[file] ?: file
|
||||||
}
|
}.toMutableList()
|
||||||
failCount > 0 -> {
|
selectedItems.clear()
|
||||||
Toast.makeText(context, "Failed to decrypt $failCount file(s)", Toast.LENGTH_SHORT).show()
|
exitSelectionMode()
|
||||||
}
|
submitList(updatedList)
|
||||||
}
|
mainHandler.postDelayed({
|
||||||
val currentFiles = currentFolder.listFiles()?.toList() ?: emptyList()
|
|
||||||
submitList(currentFiles)
|
filesOperationCallback?.get()?.onRefreshNeeded()
|
||||||
fileOperationCallback?.get()?.onRefreshNeeded()
|
},20)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,6 +37,10 @@ class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
|||||||
changes.add("EXISTENCE_CHANGED")
|
changes.add("EXISTENCE_CHANGED")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldItem.absolutePath != newItem.absolutePath) {
|
||||||
|
changes.add("FILE_CHANGED")
|
||||||
|
}
|
||||||
|
|
||||||
return changes.ifEmpty { null }
|
return changes.ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import android.media.MediaPlayer
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -35,11 +36,14 @@ class ImagePreviewAdapter(
|
|||||||
private val hiddenFileRepository: HiddenFileRepository by lazy {
|
private val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||||
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||||
}
|
}
|
||||||
|
private var canZoomOnImages: Boolean = true
|
||||||
|
|
||||||
var images: List<File>
|
var images: List<File>
|
||||||
get() = differ.currentList
|
get() = differ.currentList
|
||||||
set(value) = differ.submitList(value)
|
set(value) = differ.submitList(value)
|
||||||
|
|
||||||
|
var onSlideshowShouldStop: () -> Unit = {}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
|
||||||
val binding = ViewpagerItemsBinding.inflate(LayoutInflater.from(context), parent, false)
|
val binding = ViewpagerItemsBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||||
return ImageViewHolder(binding)
|
return ImageViewHolder(binding)
|
||||||
@@ -55,6 +59,11 @@ class ImagePreviewAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int = images.size
|
override fun getItemCount(): Int = images.size
|
||||||
|
|
||||||
|
fun setImageZoomable(zoomable: Boolean) {
|
||||||
|
currentViewHolder?.setImageViewCanZoom(zoomable)
|
||||||
|
canZoomOnImages = zoomable
|
||||||
|
}
|
||||||
|
|
||||||
private fun stopAndResetCurrentAudio() {
|
private fun stopAndResetCurrentAudio() {
|
||||||
currentViewHolder?.stopAndResetAudio()
|
currentViewHolder?.stopAndResetAudio()
|
||||||
currentPlayingPosition = -1
|
currentPlayingPosition = -1
|
||||||
@@ -71,112 +80,142 @@ 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,decryptedFileType: FileManager.FileType) {
|
fun bind(file: File, position: Int, decryptedFileType: FileManager.FileType) {
|
||||||
currentPosition = position
|
currentPosition = position
|
||||||
|
|
||||||
releaseMediaPlayer()
|
releaseMediaPlayer()
|
||||||
resetAudioUI()
|
resetAudioUI()
|
||||||
cleanupTempFile()
|
cleanupTempFile()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
lifecycleOwner.lifecycleScope.launch {
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
if (hiddenFile != null){
|
if (hiddenFile != null) {
|
||||||
val isEncrypted = hiddenFile.isEncrypted
|
val isEncrypted = hiddenFile.isEncrypted
|
||||||
val fileType = hiddenFile.fileType
|
val fileType = hiddenFile.fileType
|
||||||
if (isEncrypted) {
|
if (isEncrypted) {
|
||||||
val tempDecryptedFile = getDecryptedPreviewFile(context, hiddenFile)
|
tempDecryptedFile = getDecryptedPreviewFile(context, hiddenFile)
|
||||||
if (tempDecryptedFile != null && tempDecryptedFile.exists() && tempDecryptedFile.length() > 0) {
|
if (tempDecryptedFile != null && tempDecryptedFile!!.exists() && tempDecryptedFile!!.length() > 0) {
|
||||||
displayFile(tempDecryptedFile, fileType,true)
|
displayFile(tempDecryptedFile!!, fileType, true)
|
||||||
} else {
|
} else {
|
||||||
|
Log.e("ImagePreviewAdapter", "Failed to get decrypted preview file for: ${file.absolutePath}")
|
||||||
showEncryptedError()
|
showEncryptedError()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
displayFile(file, decryptedFileType,false)
|
displayFile(file, decryptedFileType, false)
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
displayFile(file, decryptedFileType,false)
|
displayFile(file, decryptedFileType, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
} catch (_: Exception) {
|
Log.e("ImagePreviewAdapter", "Error in bind: ${e.message}")
|
||||||
|
displayFile(file, decryptedFileType, false)
|
||||||
displayFile(file, decryptedFileType,false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayFile(file: File, fileType: FileManager.FileType,isEncrypted: Boolean = false) {
|
private fun displayFile(file: File, fileType: FileManager.FileType, isEncrypted: Boolean = false) {
|
||||||
val uri = getUriForPreviewFile(context, file)
|
try {
|
||||||
when (fileType) {
|
val uri = if (isEncrypted) {
|
||||||
FileManager.FileType.VIDEO -> {
|
getUriForPreviewFile(context, file)
|
||||||
binding.imageView.visibility = View.GONE
|
} else {
|
||||||
binding.audioBg.visibility = View.GONE
|
Uri.fromFile(file)
|
||||||
binding.videoView.visibility = View.VISIBLE
|
}
|
||||||
|
|
||||||
val videoUri = if (isEncrypted){
|
if (uri == null) {
|
||||||
uri
|
Log.e("ImagePreviewAdapter", "Failed to get URI for file: ${file.absolutePath}")
|
||||||
}else{
|
showEncryptedError()
|
||||||
Uri.fromFile(file)
|
return
|
||||||
}
|
}
|
||||||
binding.videoView.setVideoURI(videoUri)
|
|
||||||
binding.videoView.start()
|
|
||||||
|
|
||||||
val mediaController = MediaController(context)
|
when (fileType) {
|
||||||
mediaController.setAnchorView(binding.videoView)
|
FileManager.FileType.VIDEO -> {
|
||||||
binding.videoView.setMediaController(mediaController)
|
binding.imageView.visibility = View.GONE
|
||||||
|
binding.audioBg.visibility = View.GONE
|
||||||
|
binding.videoView.visibility = View.VISIBLE
|
||||||
|
|
||||||
mediaController.setPrevNextListeners(
|
binding.videoView.setVideoURI(uri)
|
||||||
{
|
binding.videoView.start()
|
||||||
|
|
||||||
|
val mediaController = MediaController(context)
|
||||||
|
mediaController.setAnchorView(binding.videoView)
|
||||||
|
binding.videoView.setMediaController(mediaController)
|
||||||
|
|
||||||
|
mediaController.setPrevNextListeners(
|
||||||
|
{
|
||||||
|
val nextPosition = (adapterPosition + 1) % images.size
|
||||||
|
playVideoAtPosition(nextPosition)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val prevPosition = if (adapterPosition - 1 < 0) images.size - 1 else adapterPosition - 1
|
||||||
|
playVideoAtPosition(prevPosition)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.videoView.setOnCompletionListener {
|
||||||
val nextPosition = (adapterPosition + 1) % images.size
|
val nextPosition = (adapterPosition + 1) % images.size
|
||||||
playVideoAtPosition(nextPosition)
|
playVideoAtPosition(nextPosition)
|
||||||
},
|
|
||||||
{
|
|
||||||
val prevPosition = if (adapterPosition - 1 < 0) images.size - 1 else adapterPosition - 1
|
|
||||||
playVideoAtPosition(prevPosition)
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
FileManager.FileType.IMAGE -> {
|
||||||
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
binding.videoView.visibility = View.GONE
|
||||||
|
binding.audioBg.visibility = View.GONE
|
||||||
|
binding.imageView.isZoomable = canZoomOnImages
|
||||||
|
binding.imageView.doubleTapToZoom = canZoomOnImages
|
||||||
|
binding.imageView.setOnClickListener {
|
||||||
|
onSlideshowShouldStop()
|
||||||
|
}
|
||||||
|
Glide.with(context)
|
||||||
|
.load(uri)
|
||||||
|
.error(R.drawable.encrypted)
|
||||||
|
.into(binding.imageView)
|
||||||
|
}
|
||||||
|
FileManager.FileType.AUDIO -> {
|
||||||
|
val audioFile: File? = if (isEncrypted) {
|
||||||
|
getFileFromUri(context, uri)
|
||||||
|
} else {
|
||||||
|
file
|
||||||
|
}
|
||||||
|
if (audioFile == null) {
|
||||||
|
Log.e("ImagePreviewAdapter", "Failed to get audio file from URI")
|
||||||
|
showEncryptedError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.imageView.visibility = View.GONE
|
||||||
|
binding.audioBg.visibility = View.VISIBLE
|
||||||
|
binding.videoView.visibility = View.GONE
|
||||||
|
binding.audioTitle.text = file.name
|
||||||
|
|
||||||
binding.videoView.setOnCompletionListener {
|
setupAudioPlayer(audioFile)
|
||||||
val nextPosition = (adapterPosition + 1) % images.size
|
setupPlaybackControls()
|
||||||
playVideoAtPosition(nextPosition)
|
}
|
||||||
|
else -> {
|
||||||
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
binding.audioBg.visibility = View.GONE
|
||||||
|
binding.videoView.visibility = View.GONE
|
||||||
|
binding.imageView.isZoomable = canZoomOnImages
|
||||||
|
binding.imageView.doubleTapToZoom = canZoomOnImages
|
||||||
|
binding.imageView.setOnClickListener {
|
||||||
|
onSlideshowShouldStop()
|
||||||
|
}
|
||||||
|
Glide.with(context)
|
||||||
|
.load(uri)
|
||||||
|
.error(R.drawable.encrypted)
|
||||||
|
.into(binding.imageView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FileManager.FileType.IMAGE -> {
|
} catch (e: Exception) {
|
||||||
val imageUri = if (isEncrypted){
|
Log.e("ImagePreviewAdapter", "Error displaying file: ${e.message}")
|
||||||
uri
|
showEncryptedError()
|
||||||
}else{
|
|
||||||
Uri.fromFile(file)
|
|
||||||
}
|
|
||||||
binding.imageView.visibility = View.VISIBLE
|
|
||||||
binding.videoView.visibility = View.GONE
|
|
||||||
binding.audioBg.visibility = View.GONE
|
|
||||||
Glide.with(context)
|
|
||||||
.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(audioFile!!)
|
|
||||||
setupPlaybackControls()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
binding.imageView.visibility = View.VISIBLE
|
|
||||||
binding.audioBg.visibility = View.GONE
|
|
||||||
binding.videoView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setImageViewCanZoom(zoomEnabled: Boolean) {
|
||||||
|
binding.imageView.doubleTapToZoom = zoomEnabled
|
||||||
|
binding.imageView.isZoomable = zoomEnabled
|
||||||
|
}
|
||||||
|
|
||||||
fun getFileFromUri(context: Context, uri: Uri): File? {
|
fun getFileFromUri(context: Context, uri: Uri): File? {
|
||||||
return try {
|
return try {
|
||||||
val inputStream = context.contentResolver.openInputStream(uri)
|
val inputStream = context.contentResolver.openInputStream(uri)
|
||||||
@@ -199,11 +238,12 @@ class ImagePreviewAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun cleanupTempFile() {
|
private fun cleanupTempFile() {
|
||||||
tempDecryptedFile?.let { file ->
|
tempDecryptedFile?.let {
|
||||||
if (file.exists()) {
|
if (it.exists()) {
|
||||||
try {
|
try {
|
||||||
file.delete()
|
it.delete()
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("ImagePreviewAdapter", "Error cleaning up temp file: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tempDecryptedFile = null
|
tempDecryptedFile = null
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package devs.org.calculator.utils
|
package devs.org.calculator.utils
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.RecoverableSecurityException
|
import android.app.RecoverableSecurityException
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
@@ -8,6 +9,8 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
@@ -15,29 +18,23 @@ import android.widget.Toast
|
|||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.IntentSenderRequest
|
import androidx.activity.result.IntentSenderRequest
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import devs.org.calculator.callbacks.FileProcessCallback
|
import devs.org.calculator.callbacks.FileProcessCallback
|
||||||
|
import devs.org.calculator.database.AppDatabase
|
||||||
|
import devs.org.calculator.database.HiddenFileEntity
|
||||||
|
import devs.org.calculator.database.HiddenFileRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import android.Manifest
|
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import devs.org.calculator.R
|
|
||||||
import devs.org.calculator.database.AppDatabase
|
|
||||||
import devs.org.calculator.database.HiddenFileRepository
|
|
||||||
import devs.org.calculator.utils.PrefsUtil
|
|
||||||
import android.util.Log
|
|
||||||
import devs.org.calculator.database.HiddenFileEntity
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
class FileManager(private val context: Context, private val lifecycleOwner: LifecycleOwner) {
|
class FileManager(private val context: Context, private val lifecycleOwner: LifecycleOwner) {
|
||||||
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
private val prefs: PrefsUtil by lazy { PrefsUtil(context) }
|
|
||||||
val hiddenFileRepository: HiddenFileRepository by lazy {
|
val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||||
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||||
}
|
}
|
||||||
@@ -67,15 +64,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFilesInHiddenDir(type: FileType): List<File> {
|
|
||||||
val hiddenDir = getHiddenDirectory()
|
|
||||||
val typeDir = File(hiddenDir, type.dirName)
|
|
||||||
if (!typeDir.exists()) {
|
|
||||||
typeDir.mkdirs()
|
|
||||||
File(typeDir, ".nomedia").createNewFile()
|
|
||||||
}
|
|
||||||
return typeDir.listFiles()?.filterNotNull()?.filter { it.name != ".nomedia" } ?: emptyList()
|
|
||||||
}
|
|
||||||
fun getFilesInHiddenDirFromFolder(type: FileType, folder: String): List<File> {
|
fun getFilesInHiddenDirFromFolder(type: FileType, folder: String): List<File> {
|
||||||
val typeDir = File(folder)
|
val typeDir = File(folder)
|
||||||
if (!typeDir.exists()) {
|
if (!typeDir.exists()) {
|
||||||
@@ -117,8 +105,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
throw Exception("File copy failed")
|
throw Exception("File copy failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt file if encryption is enabled
|
|
||||||
|
|
||||||
// Media scan the new file to hide it
|
// Media scan the new file to hide it
|
||||||
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
|
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
|
||||||
mediaScanIntent.data = Uri.fromFile(targetDir)
|
mediaScanIntent.data = Uri.fromFile(targetDir)
|
||||||
@@ -137,7 +123,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
val contentResolver = context.contentResolver
|
val contentResolver = context.contentResolver
|
||||||
|
|
||||||
// Get the target directory
|
// Get the target directory
|
||||||
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES + "/Restore")
|
||||||
targetDir.mkdirs()
|
targetDir.mkdirs()
|
||||||
|
|
||||||
// Create target file
|
// Create target file
|
||||||
@@ -173,69 +159,13 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unHideFile(file: File, onSuccess: (() -> Unit)? = null, onError: ((String) -> Unit)? = null) {
|
|
||||||
lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
// Create target directory (Downloads)
|
|
||||||
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
targetDir.mkdirs()
|
|
||||||
|
|
||||||
// Create target file with same name or timestamp
|
|
||||||
val targetFile = File(targetDir, file.name)
|
|
||||||
|
|
||||||
// If file with same name exists, add timestamp
|
|
||||||
val finalTargetFile = if (targetFile.exists()) {
|
|
||||||
val nameWithoutExt = file.nameWithoutExtension
|
|
||||||
val extension = file.extension
|
|
||||||
File(targetDir, "${nameWithoutExt}_${System.currentTimeMillis()}.${extension}")
|
|
||||||
} else {
|
|
||||||
targetFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if file is encrypted
|
|
||||||
if (file.extension == ENCRYPTED_EXTENSION) {
|
|
||||||
// Decrypt file
|
|
||||||
val decryptedFile = SecurityUtils.changeFileExtension(file, SecurityUtils.getFileExtension(file))
|
|
||||||
if (SecurityUtils.decryptFile(context, file, decryptedFile)) {
|
|
||||||
decryptedFile.copyTo(finalTargetFile, overwrite = false)
|
|
||||||
decryptedFile.delete()
|
|
||||||
} else {
|
|
||||||
throw Exception("Failed to decrypt file")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Copy file content
|
|
||||||
file.copyTo(finalTargetFile, overwrite = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify copy success
|
|
||||||
if (!finalTargetFile.exists() || finalTargetFile.length() == 0L) {
|
|
||||||
throw Exception("File copy failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Media scan the new file
|
|
||||||
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
|
|
||||||
mediaScanIntent.data = Uri.fromFile(finalTargetFile)
|
|
||||||
context.sendBroadcast(mediaScanIntent)
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
onSuccess?.invoke()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
onError?.invoke(e.message ?: "Unknown error occurred")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
|
suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
// First try to delete using DocumentFile
|
// First try to delete using DocumentFile
|
||||||
val documentFile = DocumentFile.fromSingleUri(context, photoUri)
|
val documentFile = DocumentFile.fromSingleUri(context, photoUri)
|
||||||
if (documentFile?.exists() == true && documentFile.canWrite()) {
|
if (documentFile?.exists() == true && documentFile.canWrite()) {
|
||||||
val deleted = documentFile.delete()
|
documentFile.delete()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
|
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
@@ -400,5 +330,141 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
ALL("all")
|
ALL("all")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun performEncryption(selectedFiles: List<File>,onEncryptionEnded :(MutableMap<File, File>)-> Unit) {
|
||||||
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
|
var successCount = 0
|
||||||
|
var failCount = 0
|
||||||
|
val encryptedFiles = mutableMapOf<File, File>()
|
||||||
|
|
||||||
|
for (file in selectedFiles) {
|
||||||
|
try {
|
||||||
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
|
if (hiddenFile?.isEncrypted == true) continue
|
||||||
|
val originalExtension = ".${file.extension.lowercase()}"
|
||||||
|
val fileType = FileManager(context,lifecycleOwner).getFileType(file)
|
||||||
|
val encryptedFile = SecurityUtils.changeFileExtension(file, devs.org.calculator.utils.FileManager.ENCRYPTED_EXTENSION)
|
||||||
|
if (SecurityUtils.encryptFile(context, file, encryptedFile)) {
|
||||||
|
if (encryptedFile.exists()) {
|
||||||
|
if (hiddenFile == null){
|
||||||
|
hiddenFileRepository.insertHiddenFile(
|
||||||
|
HiddenFileEntity(
|
||||||
|
filePath = encryptedFile.absolutePath,
|
||||||
|
isEncrypted = true,
|
||||||
|
encryptedFileName = encryptedFile.name,
|
||||||
|
fileType = fileType,
|
||||||
|
fileName = file.name,
|
||||||
|
originalExtension = originalExtension
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
hiddenFile.let {
|
||||||
|
hiddenFileRepository.updateEncryptionStatus(
|
||||||
|
filePath = hiddenFile.filePath,
|
||||||
|
newFilePath = encryptedFile.absolutePath,
|
||||||
|
encryptedFileName = encryptedFile.name,
|
||||||
|
isEncrypted = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (file.delete()) {
|
||||||
|
encryptedFiles[file] = encryptedFile
|
||||||
|
successCount++
|
||||||
|
} else {
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Handler(Looper.getMainLooper()).post{
|
||||||
|
when {
|
||||||
|
successCount > 0 && failCount == 0 -> {
|
||||||
|
Toast.makeText(context, "Files encrypted successfully", Toast.LENGTH_SHORT).show()
|
||||||
|
onEncryptionEnded(encryptedFiles)
|
||||||
|
}
|
||||||
|
successCount > 0 && failCount > 0 -> {
|
||||||
|
Toast.makeText(context, "Some files could not be encrypted", Toast.LENGTH_SHORT).show()
|
||||||
|
onEncryptionEnded(encryptedFiles)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Toast.makeText(context, "Failed to encrypt files", Toast.LENGTH_SHORT).show()
|
||||||
|
onEncryptionEnded(encryptedFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun performDecryption(selectedFiles: List<File>,onDecryptionEnded :(MutableMap<File, File>) -> Unit) {
|
||||||
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
|
var successCount = 0
|
||||||
|
var failCount = 0
|
||||||
|
val decryptedFiles = mutableMapOf<File, File>()
|
||||||
|
|
||||||
|
for (file in selectedFiles) {
|
||||||
|
try {
|
||||||
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
|
if (hiddenFile?.isEncrypted == true) {
|
||||||
|
val originalExtension = hiddenFile.originalExtension
|
||||||
|
val decryptedFile = SecurityUtils.changeFileExtension(file, originalExtension)
|
||||||
|
if (SecurityUtils.decryptFile(context, file, decryptedFile)) {
|
||||||
|
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
hiddenFile.let {
|
||||||
|
hiddenFileRepository.updateEncryptionStatus(
|
||||||
|
filePath = file.absolutePath,
|
||||||
|
newFilePath = decryptedFile.absolutePath,
|
||||||
|
encryptedFileName = decryptedFile.name,
|
||||||
|
isEncrypted = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (file.delete()) {
|
||||||
|
decryptedFiles[file] = decryptedFile
|
||||||
|
successCount++
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (decryptedFile.exists()) {
|
||||||
|
decryptedFile.delete()
|
||||||
|
}
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Handler(Looper.getMainLooper()).post{
|
||||||
|
when {
|
||||||
|
successCount > 0 && failCount == 0 -> {
|
||||||
|
Toast.makeText(context, "Files decrypted successfully", Toast.LENGTH_SHORT).show()
|
||||||
|
onDecryptionEnded(decryptedFiles)
|
||||||
|
}
|
||||||
|
successCount > 0 && failCount > 0 -> {
|
||||||
|
Toast.makeText(context, "Some files could not be decrypted", Toast.LENGTH_SHORT).show()
|
||||||
|
onDecryptionEnded(decryptedFiles)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Toast.makeText(context, "Failed to decrypt files", Toast.LENGTH_SHORT).show()
|
||||||
|
onDecryptionEnded(decryptedFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ class FolderManager {
|
|||||||
directory.listFiles()?.filter { it.isDirectory && it.name != ".nomedia" } ?: emptyList()
|
directory.listFiles()?.filter { it.isDirectory && it.name != ".nomedia" } ?: emptyList()
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}.sortedBy { it.name.lowercase() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFilesInFolder(folder: File): List<File> {
|
fun getFilesInFolder(folder: File): List<File> {
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ 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
|
import androidx.core.content.edit
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
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
|
||||||
|
val ENCRYPTED_EXTENSION = ".enc"
|
||||||
|
|
||||||
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)
|
||||||
@@ -109,44 +111,59 @@ object SecurityUtils {
|
|||||||
try {
|
try {
|
||||||
val encryptedFile = File(meta.filePath)
|
val encryptedFile = File(meta.filePath)
|
||||||
if (!encryptedFile.exists()) {
|
if (!encryptedFile.exists()) {
|
||||||
|
Log.e("SecurityUtils", "Encrypted file does not exist: ${meta.filePath}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val tempDir = File(context.cacheDir, "preview_temp")
|
val tempDir = File(context.cacheDir, "preview_temp")
|
||||||
if (!tempDir.exists()) tempDir.mkdirs()
|
if (!tempDir.exists()) {
|
||||||
|
if (!tempDir.mkdirs()) {
|
||||||
|
Log.e("SecurityUtils", "Failed to create temp directory")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up old preview files
|
||||||
|
tempDir.listFiles()?.forEach {
|
||||||
|
if (it.lastModified() < System.currentTimeMillis() - 5 * 60 * 1000) { // 5 minutes
|
||||||
|
it.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val tempFile = File(tempDir, "preview_${System.currentTimeMillis()}_${meta.fileName}")
|
val tempFile = File(tempDir, "preview_${System.currentTimeMillis()}_${meta.fileName}")
|
||||||
|
|
||||||
tempDir.listFiles()?.forEach { it.delete() }
|
|
||||||
|
|
||||||
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) {
|
||||||
return tempFile
|
return tempFile
|
||||||
} else {
|
} else {
|
||||||
|
Log.e("SecurityUtils", "Failed to decrypt preview file: ${meta.filePath}")
|
||||||
if (tempFile.exists()) tempFile.delete()
|
if (tempFile.exists()) tempFile.delete()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("SecurityUtils", "Error in getDecryptedPreviewFile: ${e.message}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getUriForPreviewFile(context: Context, file: File): Uri? {
|
fun getUriForPreviewFile(context: Context, file: File): Uri? {
|
||||||
return try {
|
return try {
|
||||||
|
if (!file.exists() || file.length() == 0L) {
|
||||||
|
Log.e("SecurityUtils", "Preview file does not exist or is empty: ${file.absolutePath}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
FileProvider.getUriForFile(
|
FileProvider.getUriForFile(
|
||||||
context,
|
context,
|
||||||
"${context.packageName}.provider", // Must match AndroidManifest
|
"${context.packageName}.provider",
|
||||||
file
|
file
|
||||||
)
|
)
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("SecurityUtils", "Error getting URI for preview file: ${e.message}")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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()) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
@@ -16,17 +17,12 @@ import androidx.core.net.toUri
|
|||||||
|
|
||||||
class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
||||||
|
|
||||||
private val requestPermissionLauncher = activity.registerForActivityResult(
|
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
|
||||||
) { permissions ->
|
|
||||||
if (permissions.all { it.value }) {
|
|
||||||
onPermissionGranted?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var onPermissionGranted: (() -> Unit)? = null
|
private var onPermissionGranted: (() -> Unit)? = null
|
||||||
|
|
||||||
fun requestStoragePermission(onGranted: () -> Unit) {
|
fun requestStoragePermission(
|
||||||
|
launcher: ActivityResultLauncher<Array<String>>,
|
||||||
|
onGranted: () -> Unit
|
||||||
|
) {
|
||||||
onPermissionGranted = onGranted
|
onPermissionGranted = onGranted
|
||||||
|
|
||||||
when {
|
when {
|
||||||
@@ -45,7 +41,7 @@ class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
|||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
)
|
)
|
||||||
requestPermissionLauncher.launch(permissions)
|
launcher.launch(permissions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,4 +62,10 @@ class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
|||||||
writePermission == PermissionChecker.PERMISSION_GRANTED
|
writePermission == PermissionChecker.PERMISSION_GRANTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun handlePermissionResult(permissions: Map<String, Boolean>) {
|
||||||
|
if (permissions.all { it.value }) {
|
||||||
|
onPermissionGranted?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
5
app/src/main/res/drawable/ic_dice.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M340,680Q365,680 382.5,662.5Q400,645 400,620Q400,595 382.5,577.5Q365,560 340,560Q315,560 297.5,577.5Q280,595 280,620Q280,645 297.5,662.5Q315,680 340,680ZM340,400Q365,400 382.5,382.5Q400,365 400,340Q400,315 382.5,297.5Q365,280 340,280Q315,280 297.5,297.5Q280,315 280,340Q280,365 297.5,382.5Q315,400 340,400ZM620,680Q645,680 662.5,662.5Q680,645 680,620Q680,595 662.5,577.5Q645,560 620,560Q595,560 577.5,577.5Q560,595 560,620Q560,645 577.5,662.5Q595,680 620,680ZM620,400Q645,400 662.5,382.5Q680,365 680,340Q680,315 662.5,297.5Q645,280 620,280Q595,280 577.5,297.5Q560,315 560,340Q560,365 577.5,382.5Q595,400 620,400ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_select_all.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M268,720L42,494L99,438L269,608L269,608L325,664L268,720ZM494,720L268,494L324,437L494,607L862,239L918,296L494,720ZM494,494L437,438L635,240L692,296L494,494Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_slideshow.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:width="24dp">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/svgTintColor"
|
||||||
|
android:pathData="M10,8v8l5,-4 -5,-4zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14z"/>
|
||||||
|
</vector>
|
||||||
@@ -5,74 +5,86 @@
|
|||||||
android:id="@+id/main"
|
android:id="@+id/main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="16dp"
|
|
||||||
tools:context=".activities.SetupPasswordActivity">
|
tools:context=".activities.SetupPasswordActivity">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<LinearLayout
|
||||||
android:id="@+id/tvTitle"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:gravity="center_horizontal"
|
||||||
android:text="@string/change_password"
|
android:orientation="vertical"
|
||||||
android:textSize="24sp"
|
android:padding="18dp"
|
||||||
android:textStyle="bold"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/tilOldPassword"
|
android:id="@+id/tvTitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="32dp"
|
android:text="@string/change_password"
|
||||||
android:hint="@string/enter_old_password"
|
android:textSize="24sp"
|
||||||
app:endIconMode="password_toggle"
|
android:textStyle="bold"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/etOldPassword"
|
android:id="@+id/tilOldPassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:imeOptions="actionNext"
|
android:layout_marginTop="32dp"
|
||||||
android:singleLine="true"
|
android:hint="@string/enter_old_password"
|
||||||
android:inputType="number" />
|
app:endIconMode="password_toggle"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/tilNewPassword"
|
android:id="@+id/etOldPassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:imeOptions="actionNext"
|
||||||
android:hint="@string/enter_new_password"
|
android:inputType="number"
|
||||||
app:endIconMode="password_toggle"
|
android:singleLine="true" />
|
||||||
app:layout_constraintTop_toBottomOf="@id/tilOldPassword">
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/etNewPassword"
|
android:id="@+id/tilNewPassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:imeOptions="actionNext"
|
android:layout_marginTop="16dp"
|
||||||
android:singleLine="true"
|
android:hint="@string/enter_new_password"
|
||||||
android:inputType="number" />
|
app:endIconMode="password_toggle"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
app:layout_constraintTop_toBottomOf="@id/tilOldPassword">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/btnChangePassword"
|
android:id="@+id/etNewPassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="50dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="32dp"
|
android:imeOptions="actionNext"
|
||||||
android:padding="12dp"
|
android:inputType="number"
|
||||||
android:text="@string/change_password"
|
android:singleLine="true" />
|
||||||
app:layout_constraintTop_toBottomOf="@id/tilNewPassword" />
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnResetPassword"
|
android:id="@+id/btnChangePassword"
|
||||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="50dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_marginTop="32dp"
|
||||||
android:layout_marginTop="8dp"
|
android:padding="12dp"
|
||||||
android:text="@string/forgot_password"
|
android:text="@string/change_password"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintTop_toBottomOf="@id/tilNewPassword" />
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/btnChangePassword" />
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnResetPassword"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/forgot_password"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/btnChangePassword" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -60,6 +60,12 @@
|
|||||||
app:icon="@drawable/ic_more"
|
app:icon="@drawable/ic_more"
|
||||||
style="@style/Widget.Material3.Button.IconButton"/>
|
style="@style/Widget.Material3.Button.IconButton"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:icon="@drawable/ic_dice"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
|
android:id="@+id/random" />
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
android:gravity="end|bottom"
|
android:gravity="end|bottom"
|
||||||
android:autoSizeTextType="uniform"
|
android:autoSizeTextType="uniform"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:text=""
|
android:text="0"
|
||||||
android:textSize="70sp"
|
android:textSize="70sp"
|
||||||
tools:ignore="Suspicious0dp" />
|
tools:ignore="Suspicious0dp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:id="@+id/main"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:paddingLeft="15dp"
|
android:paddingLeft="15dp"
|
||||||
|
android:paddingRight="15dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" >
|
app:layout_constraintTop_toTopOf="parent" >
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
@@ -25,11 +27,20 @@
|
|||||||
android:id="@+id/back"/>
|
android:id="@+id/back"/>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:textSize="22sp"
|
android:textSize="22sp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:text="@string/preview_file"/>
|
android:text="@string/preview_file"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:src="@drawable/ic_slideshow"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:id="@+id/start_slideshow"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
@@ -43,6 +54,7 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/footer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:id="@+id/main"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".activities.SettingsActivity">
|
tools:context=".activities.SettingsActivity">
|
||||||
|
|
||||||
|
|||||||
@@ -5,104 +5,116 @@
|
|||||||
android:id="@+id/main"
|
android:id="@+id/main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="16dp"
|
|
||||||
tools:context=".activities.SetupPasswordActivity">
|
tools:context=".activities.SetupPasswordActivity">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<LinearLayout
|
||||||
android:id="@+id/tvTitle"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:orientation="vertical"
|
||||||
android:text="@string/setup_password"
|
android:padding="18dp"
|
||||||
android:textSize="24sp"
|
android:gravity="center_horizontal"
|
||||||
android:textStyle="bold"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/tilPassword"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
android:hint="@string/enter_password"
|
|
||||||
app:endIconMode="password_toggle"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/etPassword"
|
android:id="@+id/tvTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/setup_password"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilPassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:imeOptions="actionNext"
|
android:layout_marginTop="32dp"
|
||||||
android:singleLine="true"
|
android:hint="@string/enter_password"
|
||||||
android:inputType="number" />
|
app:endIconMode="password_toggle"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/tilConfirmPassword"
|
android:id="@+id/etPassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:imeOptions="actionNext"
|
||||||
android:hint="@string/confirm_password"
|
android:inputType="number"
|
||||||
app:endIconMode="password_toggle"
|
android:singleLine="true" />
|
||||||
app:layout_constraintTop_toBottomOf="@id/tilPassword">
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/etConfirmPassword"
|
android:id="@+id/tilConfirmPassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:imeOptions="actionNext"
|
android:layout_marginTop="16dp"
|
||||||
android:singleLine="true"
|
android:hint="@string/confirm_password"
|
||||||
android:inputType="number" />
|
app:endIconMode="password_toggle"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
app:layout_constraintTop_toBottomOf="@id/tilPassword">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/tilSecurityQuestion"
|
android:id="@+id/etConfirmPassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:imeOptions="actionNext"
|
||||||
android:hint="@string/security_question_for_password_reset"
|
android:inputType="number"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tilConfirmPassword">
|
android:singleLine="true" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/etSecurityQuestion"
|
android:id="@+id/tilSecurityQuestion"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="text" />
|
android:layout_marginTop="16dp"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
android:hint="@string/security_question_for_password_reset"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tilConfirmPassword">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/tilSecurityAnswer"
|
android:id="@+id/etSecurityQuestion"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:inputType="text" />
|
||||||
android:hint="@string/security_answer"
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
app:layout_constraintTop_toBottomOf="@id/tilSecurityQuestion">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/etSecurityAnswer"
|
android:id="@+id/tilSecurityAnswer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="text" />
|
android:layout_marginTop="16dp"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
android:hint="@string/security_answer"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tilSecurityQuestion">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/btnSavePassword"
|
android:id="@+id/etSecurityAnswer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="32dp"
|
android:inputType="text" />
|
||||||
android:padding="12dp"
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
android:text="@string/save_password"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/tilSecurityAnswer" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnResetPassword"
|
android:id="@+id/btnSavePassword"
|
||||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_marginTop="32dp"
|
||||||
android:layout_marginTop="8dp"
|
android:padding="12dp"
|
||||||
android:text="@string/forgot_password"
|
android:text="@string/save_password"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintTop_toBottomOf="@id/tilSecurityAnswer" />
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/btnSavePassword" />
|
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnResetPassword"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/forgot_password"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/btnSavePassword" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -39,26 +39,40 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:id="@+id/folderName"/>
|
android:id="@+id/folderName"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/selectAllButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:icon="@drawable/ic_select_all"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/menuButton"
|
android:id="@+id/menuButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:icon="@drawable/ic_more"
|
app:icon="@drawable/ic_more"
|
||||||
|
android:visibility="gone"
|
||||||
style="@style/Widget.Material3.Button.IconButton"/>
|
style="@style/Widget.Material3.Button.IconButton"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/recyclerView"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:id="@+id/swipeLayout"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/toolBar">
|
app:layout_constraintTop_toBottomOf="@+id/toolBar"
|
||||||
|
android:layout_height="0dp">
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp">
|
||||||
|
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/noItems"
|
android:id="@+id/noItems"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
|
|||||||
@@ -35,8 +35,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"/>
|
||||||
android:src="@drawable/add_image" />
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/shade"
|
android:id="@+id/shade"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Calculator</string>
|
<string name="app_name" translatable="false">Calculator</string>
|
||||||
|
<string name="version" translatable="false">Version 1.4.1</string>
|
||||||
<string name="invalid_message">Invalid Value Entered</string>
|
<string name="invalid_message">Invalid Value Entered</string>
|
||||||
<string name="add_image">Add Image</string>
|
<string name="add_image">Add Image</string>
|
||||||
<string name="add_audio">Add Audio</string>
|
<string name="add_audio">Add Audio</string>
|
||||||
@@ -121,7 +122,6 @@
|
|||||||
<string name="app_settings">App Settings</string>
|
<string name="app_settings">App Settings</string>
|
||||||
<string name="restrict_screenshots_in_hidden_section">Restrict Screenshots in Hidden Section</string>
|
<string name="restrict_screenshots_in_hidden_section">Restrict Screenshots in Hidden Section</string>
|
||||||
<string name="security_settings">Security Settings</string>
|
<string name="security_settings">Security Settings</string>
|
||||||
<string name="version">Version 1.4.0</string>
|
|
||||||
<string name="app_details">App Details</string>
|
<string name="app_details">App Details</string>
|
||||||
<string name="setup_password">Setup Password</string>
|
<string name="setup_password">Setup Password</string>
|
||||||
<string name="security_question_for_password_reset">Security Question (For Password Reset)</string>
|
<string name="security_question_for_password_reset">Security Question (For Password Reset)</string>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.9.3"
|
agp = "8.11.1"
|
||||||
documentfile = "1.1.0"
|
documentfile = "1.1.0"
|
||||||
exp4j = "0.4.8"
|
exp4j = "0.4.8"
|
||||||
glide = "4.16.0"
|
glide = "4.16.0"
|
||||||
@@ -8,7 +8,7 @@ coreKtx = "1.16.0"
|
|||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.1"
|
||||||
lottie = "6.2.0"
|
lottie = "6.2.0"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
activity = "1.10.1"
|
activity = "1.10.1"
|
||||||
@@ -17,8 +17,10 @@ materialColorUtilities = "1.3.0"
|
|||||||
gridlayout = "1.1.0"
|
gridlayout = "1.1.0"
|
||||||
photoview = "2.3.0"
|
photoview = "2.3.0"
|
||||||
roomRuntime = "2.7.1"
|
roomRuntime = "2.7.1"
|
||||||
|
swiperefreshlayout = "1.2.0-beta01"
|
||||||
viewpager = "1.1.0"
|
viewpager = "1.1.0"
|
||||||
zoomage = "1.3.1"
|
zoomage = "1.3.1"
|
||||||
|
lifecycleProcess = "2.9.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@@ -26,6 +28,7 @@ androidx-documentfile = { module = "androidx.documentfile:documentfile", version
|
|||||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
|
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
|
||||||
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" }
|
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" }
|
||||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
|
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
|
||||||
|
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
||||||
androidx-viewpager = { module = "androidx.viewpager:viewpager", version.ref = "viewpager" }
|
androidx-viewpager = { module = "androidx.viewpager:viewpager", version.ref = "viewpager" }
|
||||||
exp4j = { module = "net.objecthunter:exp4j", version.ref = "exp4j" }
|
exp4j = { module = "net.objecthunter:exp4j", version.ref = "exp4j" }
|
||||||
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
||||||
@@ -41,6 +44,7 @@ material-color-utilities = { module = "com.google.android.material:material-colo
|
|||||||
androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" }
|
androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" }
|
||||||
photoview = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoview" }
|
photoview = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoview" }
|
||||||
zoomage = { module = "com.jsibbold:zoomage", version.ref = "zoomage" }
|
zoomage = { module = "com.jsibbold:zoomage", version.ref = "zoomage" }
|
||||||
|
androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleProcess" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Sun Nov 03 19:53:13 IST 2024
|
#Sun Nov 03 19:53:13 IST 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 406 KiB After Width: | Height: | Size: 406 KiB |
BIN
media/Screenshot_6.jpg
Normal file
|
After Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 469 KiB |
|
Before Width: | Height: | Size: 557 KiB After Width: | Height: | Size: 557 KiB |
BIN
media/Screenshot_9.jpg
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
media/banner.jpg
Normal file
|
After Width: | Height: | Size: 665 KiB |
BIN
media/banner.png
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
media/payment_qr.png
Normal file
|
After Width: | Height: | Size: 234 KiB |