Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ceb599d9f | ||
|
|
dd1767e55d | ||
|
|
28ccdda1bf | ||
|
|
27a538f7c6 | ||
|
|
22c2a64450 | ||
|
|
3af3f81f3c | ||
|
|
f2e206f208 | ||
|
|
2de1b28afe | ||
|
|
ccf291d2e2 | ||
|
|
0968d5c19b | ||
|
|
d64904fbe7 | ||
|
|
2da2c944a3 | ||
|
|
13e1fca28f | ||
|
|
5c5e0e4be8 | ||
|
|
aad939463c | ||
|
|
e4e2983acd | ||
|
|
8702491f85 | ||
|
|
5263f89cd3 | ||
|
|
0787d6dd5b | ||
|
|
cf192b6ab9 | ||
|
|
191368bdf8 | ||
|
|
5aafc7e411 | ||
|
|
1f81cccd96 | ||
|
|
eea0f56379 | ||
|
|
e90e3c438f | ||
|
|
480a90b64c | ||
|
|
90a252f227 | ||
|
|
08cdd747ca | ||
|
|
2d10c2f941 | ||
|
|
d840732305 | ||
|
|
7da5885931 | ||
|
|
082dbf17aa | ||
|
|
093c419f34 | ||
|
|
8649ec9ac2 | ||
|
|
527f377ebd | ||
|
|
e1627c426f | ||
|
|
092219d487 | ||
|
|
7803ed2589 | ||
|
|
e40b59c460 | ||
|
|
c25ac613c2 | ||
|
|
bfc339c101 | ||
|
|
657b674ff3 | ||
|
|
ea7c6ea18a | ||
|
|
c97f38ae53 | ||
|
|
0ba60f2bae |
1
.idea/appInsightsSettings.xml
generated
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="AppInsightsSettings">
|
<component name="AppInsightsSettings">
|
||||||
|
<option name="selectedTabId" value="Firebase Crashlytics" />
|
||||||
<option name="tabSettings">
|
<option name="tabSettings">
|
||||||
<map>
|
<map>
|
||||||
<entry key="Firebase Crashlytics">
|
<entry key="Firebase Crashlytics">
|
||||||
|
|||||||
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.9.24" />
|
<option name="version" value="2.0.0" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
52
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.0-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/github/downloads/Binondi/Calculator-Hide-Files/total?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>
|
||||||
|
|
||||||
|
</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,26 +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="24%">
|
<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="24%">
|
<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 Hide File App - Passcode Protection" width="24%">
|
<img src="media/Screenshot_3.jpg" alt="Calculator Hide File App - Passcode Protection" width="32%">
|
||||||
<img src="app/src/main/assets/Screenshot_4.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="24%">
|
</div>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="media/Screenshot_4.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="media/Screenshot_6.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="media/Screenshot_7.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="media/Screenshot_9.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="32%">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -69,6 +88,8 @@ The **Calculator Hide File App** is an innovative **Android file-hiding app** th
|
|||||||
3. **Manage Hidden Files**
|
3. **Manage Hidden Files**
|
||||||
- Add, remove, and restore hidden files.
|
- Add, remove, and restore hidden files.
|
||||||
- Files stay protected even after closing the app.
|
- Files stay protected even after closing the app.
|
||||||
|
- Create Hidden Folders.
|
||||||
|
- Move & Copy Hidden Files Between Hidden Folders.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -79,6 +100,7 @@ The **Calculator Hide File App** is an innovative **Android file-hiding app** th
|
|||||||
### 🔹 Prerequisites
|
### 🔹 Prerequisites
|
||||||
- **Android 6.0 or higher**
|
- **Android 6.0 or higher**
|
||||||
- **Storage permissions enabled**
|
- **Storage permissions enabled**
|
||||||
|
- **Need Manage All Files Permissons For Android 11 and Higher Versions.**
|
||||||
|
|
||||||
### 🔹 Installation Steps
|
### 🔹 Installation Steps
|
||||||
```bash
|
```bash
|
||||||
@@ -92,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
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -111,7 +133,7 @@ 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)
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 Contributing
|
## 🔧 Contributing
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
id("kotlin-kapt")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -10,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 = 4
|
versionCode = 6
|
||||||
versionName = "1.3"
|
versionName = "1.4.1"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -80,4 +82,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
|
||||||
|
implementation(libs.androidx.room.runtime)
|
||||||
|
implementation(libs.androidx.room.ktx)
|
||||||
|
//noinspection KaptUsageInsteadOfKsp
|
||||||
|
kapt(libs.androidx.room.compiler)
|
||||||
}
|
}
|
||||||
37
app/release/output-metadata.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "APK",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "devs.org.calculator",
|
||||||
|
"variantName": "release",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "SINGLE",
|
||||||
|
"filters": [],
|
||||||
|
"attributes": [],
|
||||||
|
"versionCode": 4,
|
||||||
|
"versionName": "1.3",
|
||||||
|
"outputFile": "app-release.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"elementType": "File",
|
||||||
|
"baselineProfiles": [
|
||||||
|
{
|
||||||
|
"minApi": 28,
|
||||||
|
"maxApi": 30,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/1/app-release.dm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minApi": 31,
|
||||||
|
"maxApi": 2147483647,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/0/app-release.dm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minSdkVersionForDexing": 26
|
||||||
|
}
|
||||||
@@ -59,13 +59,17 @@
|
|||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="devs.org.calculator.fileprovider"
|
android:authorities="devs.org.calculator.provider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.icon"
|
||||||
|
android:resource="@mipmap/ic_launcher_monochrome" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
Before Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
BIN
app/src/main/assets/Screenshot_9.jpg
Normal file
|
After Width: | Height: | Size: 237 KiB |
@@ -3,19 +3,14 @@ package devs.org.calculator
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
|
import devs.org.calculator.utils.PrefsUtil
|
||||||
|
|
||||||
class CalculatorApp : Application() {
|
class CalculatorApp : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
val prefs = PrefsUtil(this)
|
||||||
// Initialize theme settings
|
|
||||||
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
|
||||||
|
|
||||||
// Apply saved theme mode
|
|
||||||
val themeMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
val themeMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
AppCompatDelegate.setDefaultNightMode(themeMode)
|
AppCompatDelegate.setDefaultNightMode(themeMode)
|
||||||
|
|
||||||
// Apply dynamic colors only if dynamic theme is enabled
|
|
||||||
if (prefs.getBoolean("dynamic_theme", true)) {
|
if (prefs.getBoolean("dynamic_theme", true)) {
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,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.WindowManager
|
import android.view.WindowManager
|
||||||
@@ -36,22 +35,20 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
private var folderAdapter: FolderAdapter? = null
|
private var folderAdapter: FolderAdapter? = null
|
||||||
private var listFolderAdapter: ListFolderAdapter? = null
|
private var listFolderAdapter: ListFolderAdapter? = null
|
||||||
private val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
private val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||||
|
private val prefs:PrefsUtil by lazy { PrefsUtil(this) }
|
||||||
|
|
||||||
private val mainHandler = Handler(Looper.getMainLooper())
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "HiddenActivity"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityHiddenBinding.inflate(layoutInflater)
|
binding = ActivityHiddenBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
fileManager = FileManager(this, this)
|
fileManager = FileManager(this, this)
|
||||||
folderManager = FolderManager(this)
|
folderManager = FolderManager()
|
||||||
dialogUtil = DialogUtil(this)
|
dialogUtil = DialogUtil(this)
|
||||||
|
|
||||||
|
|
||||||
setupInitialUIState()
|
setupInitialUIState()
|
||||||
setupClickListeners()
|
setupClickListeners()
|
||||||
setupBackPressedHandler()
|
setupBackPressedHandler()
|
||||||
@@ -100,20 +97,17 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.folderOrientation.setOnClickListener {
|
binding.folderOrientation.setOnClickListener {
|
||||||
// Switch between grid mode and list mode
|
|
||||||
val currentIsList = PrefsUtil(this).getBoolean("isList", false)
|
val currentIsList = PrefsUtil(this).getBoolean("isList", false)
|
||||||
val newIsList = !currentIsList
|
val newIsList = !currentIsList
|
||||||
|
|
||||||
if (newIsList) {
|
if (newIsList) {
|
||||||
// Switch to list view
|
|
||||||
showListUI()
|
showListUI()
|
||||||
PrefsUtil(this).setBoolean("isList", true)
|
PrefsUtil(this).setBoolean("isList", true)
|
||||||
binding.folderOrientation.setImageResource(R.drawable.ic_grid)
|
binding.folderOrientation.setIconResource(R.drawable.ic_grid)
|
||||||
} else {
|
} else {
|
||||||
// Switch to grid view
|
|
||||||
showGridUI()
|
showGridUI()
|
||||||
PrefsUtil(this).setBoolean("isList", false)
|
PrefsUtil(this).setBoolean("isList", false)
|
||||||
binding.folderOrientation.setImageResource(R.drawable.ic_list)
|
binding.folderOrientation.setIconResource(R.drawable.ic_list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,11 +135,10 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Hidden directory is not accessible: ${hiddenDir.absolutePath}")
|
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.e(TAG, "Error listing folders: ${e.message}")
|
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,26 +157,25 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
|
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle("Enter Folder Name To Create")
|
.setTitle(getString(R.string.enter_folder_name_to_create))
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setPositiveButton("Create") { dialog, _ ->
|
.setPositiveButton(getString(R.string.create)) { dialog, _ ->
|
||||||
val newName = inputEditText.text.toString().trim()
|
val newName = inputEditText.text.toString().trim()
|
||||||
if (newName.isNotEmpty()) {
|
if (newName.isNotEmpty()) {
|
||||||
try {
|
try {
|
||||||
folderManager.createFolder(hiddenDir, newName)
|
folderManager.createFolder(hiddenDir, newName)
|
||||||
refreshCurrentView()
|
refreshCurrentView()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.e(TAG, "Error creating folder: ${e.message}")
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HiddenActivity,
|
this@HiddenActivity,
|
||||||
"Failed to create folder",
|
getString(R.string.failed_to_create_folder),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
|
||||||
dialog.cancel()
|
dialog.cancel()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -195,7 +187,6 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupFlagSecure() {
|
private fun setupFlagSecure() {
|
||||||
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
|
||||||
if (prefs.getBoolean("screenshot_restriction", true)) {
|
if (prefs.getBoolean("screenshot_restriction", true)) {
|
||||||
window.setFlags(
|
window.setFlags(
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
@@ -220,11 +211,9 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Hidden directory is not accessible: ${hiddenDir.absolutePath}")
|
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.e(TAG, "Error listing folders: ${e.message}")
|
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,8 +221,6 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
private fun showFolderList(folders: List<File>) {
|
private fun showFolderList(folders: List<File>) {
|
||||||
binding.noItems.visibility = View.GONE
|
binding.noItems.visibility = View.GONE
|
||||||
binding.recyclerView.visibility = View.VISIBLE
|
binding.recyclerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
// Clear the existing adapter to avoid conflicts
|
|
||||||
listFolderAdapter = null
|
listFolderAdapter = null
|
||||||
|
|
||||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 2)
|
binding.recyclerView.layoutManager = GridLayoutManager(this, 2)
|
||||||
@@ -247,14 +234,13 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
onSelectionModeChanged = { isSelectionMode ->
|
onSelectionModeChanged = { isSelectionMode ->
|
||||||
handleFolderSelectionModeChange(isSelectionMode)
|
handleFolderSelectionModeChange(isSelectionMode)
|
||||||
},
|
},
|
||||||
onSelectionCountChanged = { selectedCount ->
|
onSelectionCountChanged = { _ ->
|
||||||
updateEditButtonVisibility()
|
updateEditButtonVisibility()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
binding.recyclerView.adapter = folderAdapter
|
binding.recyclerView.adapter = folderAdapter
|
||||||
folderAdapter?.submitList(folders)
|
folderAdapter?.submitList(folders)
|
||||||
|
|
||||||
// Ensure proper icon state for folder view
|
|
||||||
if (folderAdapter?.isInSelectionMode() != true) {
|
if (folderAdapter?.isInSelectionMode() != true) {
|
||||||
showFolderViewIcons()
|
showFolderViewIcons()
|
||||||
}
|
}
|
||||||
@@ -262,8 +248,6 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
private fun showFolderListStyle(folders: List<File>) {
|
private fun showFolderListStyle(folders: List<File>) {
|
||||||
binding.noItems.visibility = View.GONE
|
binding.noItems.visibility = View.GONE
|
||||||
binding.recyclerView.visibility = View.VISIBLE
|
binding.recyclerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
// Clear the existing adapter to avoid conflicts
|
|
||||||
folderAdapter = null
|
folderAdapter = null
|
||||||
|
|
||||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 1)
|
binding.recyclerView.layoutManager = GridLayoutManager(this, 1)
|
||||||
@@ -277,14 +261,13 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
onSelectionModeChanged = { isSelectionMode ->
|
onSelectionModeChanged = { isSelectionMode ->
|
||||||
handleFolderSelectionModeChange(isSelectionMode)
|
handleFolderSelectionModeChange(isSelectionMode)
|
||||||
},
|
},
|
||||||
onSelectionCountChanged = { selectedCount ->
|
onSelectionCountChanged = { _ ->
|
||||||
updateEditButtonVisibility()
|
updateEditButtonVisibility()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
binding.recyclerView.adapter = listFolderAdapter
|
binding.recyclerView.adapter = listFolderAdapter
|
||||||
listFolderAdapter?.submitList(folders)
|
listFolderAdapter?.submitList(folders)
|
||||||
|
|
||||||
// Ensure proper icon state for folder view
|
|
||||||
if (listFolderAdapter?.isInSelectionMode() != true) {
|
if (listFolderAdapter?.isInSelectionMode() != true) {
|
||||||
showFolderViewIcons()
|
showFolderViewIcons()
|
||||||
}
|
}
|
||||||
@@ -312,10 +295,10 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
private fun refreshCurrentView() {
|
private fun refreshCurrentView() {
|
||||||
val isList = PrefsUtil(this).getBoolean("isList", false)
|
val isList = PrefsUtil(this).getBoolean("isList", false)
|
||||||
if (isList) {
|
if (isList) {
|
||||||
binding.folderOrientation.setImageResource(R.drawable.ic_grid)
|
binding.folderOrientation.setIconResource(R.drawable.ic_grid)
|
||||||
listFoldersInHiddenDirectoryListStyle()
|
listFoldersInHiddenDirectoryListStyle()
|
||||||
} else {
|
} else {
|
||||||
binding.folderOrientation.setImageResource(R.drawable.ic_list)
|
binding.folderOrientation.setIconResource(R.drawable.ic_list)
|
||||||
listFoldersInHiddenDirectory()
|
listFoldersInHiddenDirectory()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,7 +318,7 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
if (selectedFolders.isNotEmpty()) {
|
if (selectedFolders.isNotEmpty()) {
|
||||||
dialogUtil.showMaterialDialog(
|
dialogUtil.showMaterialDialog(
|
||||||
getString(R.string.delete_items),
|
getString(R.string.delete_items),
|
||||||
getString(R.string.are_you_sure_you_want_to_delete_selected_items),
|
"${getString(R.string.are_you_sure_you_want_to_delete_selected_items)}\n ${getString(R.string.folder_will_be_deleted_permanently)}" ,
|
||||||
getString(R.string.delete),
|
getString(R.string.delete),
|
||||||
getString(R.string.cancel),
|
getString(R.string.cancel),
|
||||||
object : DialogUtil.DialogCallback {
|
object : DialogUtil.DialogCallback {
|
||||||
@@ -344,11 +327,11 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNegativeButtonClicked() {
|
override fun onNegativeButtonClicked() {
|
||||||
// Do nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNaturalButtonClicked() {
|
override fun onNaturalButtonClicked() {
|
||||||
// Do nothing
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -360,7 +343,6 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
selectedFolders.forEach { folder ->
|
selectedFolders.forEach { folder ->
|
||||||
if (!folderManager.deleteFolder(folder)) {
|
if (!folderManager.deleteFolder(folder)) {
|
||||||
allDeleted = false
|
allDeleted = false
|
||||||
Log.e(TAG, "Failed to delete folder: ${folder.name}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,26 +354,20 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
// Clear selection from both adapters
|
|
||||||
folderAdapter?.clearSelection()
|
folderAdapter?.clearSelection()
|
||||||
listFolderAdapter?.clearSelection()
|
listFolderAdapter?.clearSelection()
|
||||||
|
|
||||||
// This will trigger the selection mode change callback and show proper icons
|
|
||||||
exitFolderSelectionMode()
|
exitFolderSelectionMode()
|
||||||
|
|
||||||
// Refresh the current view based on orientation
|
|
||||||
refreshCurrentView()
|
refreshCurrentView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBackPress() {
|
private fun handleBackPress() {
|
||||||
|
|
||||||
|
|
||||||
// Check if folder adapters are in selection mode
|
|
||||||
if (folderAdapter?.onBackPressed() == true || listFolderAdapter?.onBackPressed() == true) {
|
if (folderAdapter?.onBackPressed() == true || listFolderAdapter?.onBackPressed() == true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle navigation back
|
|
||||||
if (currentFolder != null) {
|
if (currentFolder != null) {
|
||||||
navigateBackToFolders()
|
navigateBackToFolders()
|
||||||
} else {
|
} else {
|
||||||
@@ -402,25 +378,18 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
private fun navigateBackToFolders() {
|
private fun navigateBackToFolders() {
|
||||||
currentFolder = null
|
currentFolder = null
|
||||||
|
|
||||||
// Clean up file adapter
|
|
||||||
|
|
||||||
refreshCurrentView()
|
refreshCurrentView()
|
||||||
|
|
||||||
binding.folderName.text = getString(R.string.hidden_space)
|
binding.folderName.text = getString(R.string.hidden_space)
|
||||||
|
|
||||||
// Set proper icons for folder view
|
|
||||||
showFolderViewIcons()
|
showFolderViewIcons()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
|
|
||||||
// Remove any pending callbacks
|
|
||||||
mainHandler.removeCallbacksAndMessages(null)
|
mainHandler.removeCallbacksAndMessages(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
//visibility related code
|
|
||||||
private fun showFolderViewIcons() {
|
private fun showFolderViewIcons() {
|
||||||
binding.folderOrientation.visibility = View.VISIBLE
|
binding.folderOrientation.visibility = View.VISIBLE
|
||||||
binding.settings.visibility = View.VISIBLE
|
binding.settings.visibility = View.VISIBLE
|
||||||
@@ -429,7 +398,6 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
binding.menuButton.visibility = View.GONE
|
binding.menuButton.visibility = View.GONE
|
||||||
binding.addFolder.visibility = View.VISIBLE
|
binding.addFolder.visibility = View.VISIBLE
|
||||||
binding.edit.visibility = View.GONE
|
binding.edit.visibility = View.GONE
|
||||||
// Ensure FABs are properly managed
|
|
||||||
if (currentFolder == null) {
|
if (currentFolder == null) {
|
||||||
|
|
||||||
binding.addFolder.visibility = View.VISIBLE
|
binding.addFolder.visibility = View.VISIBLE
|
||||||
@@ -442,8 +410,6 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
binding.deleteSelected.visibility = View.VISIBLE
|
binding.deleteSelected.visibility = View.VISIBLE
|
||||||
binding.menuButton.visibility = View.GONE
|
binding.menuButton.visibility = View.GONE
|
||||||
binding.addFolder.visibility = View.GONE
|
binding.addFolder.visibility = View.GONE
|
||||||
|
|
||||||
// Update edit button visibility based on current selection count
|
|
||||||
updateEditButtonVisibility()
|
updateEditButtonVisibility()
|
||||||
}
|
}
|
||||||
private fun exitFolderSelectionMode() {
|
private fun exitFolderSelectionMode() {
|
||||||
@@ -456,7 +422,6 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
enterFolderSelectionMode()
|
enterFolderSelectionMode()
|
||||||
}
|
}
|
||||||
// Always update edit button visibility when selection mode changes
|
|
||||||
updateEditButtonVisibility()
|
updateEditButtonVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +433,8 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selectedFolders.size != 1) {
|
if (selectedFolders.size != 1) {
|
||||||
Toast.makeText(this, "Please select exactly one folder to edit", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this,
|
||||||
|
getString(R.string.please_select_exactly_one_folder_to_edit), Toast.LENGTH_SHORT).show()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,20 +449,21 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
inputEditText.selectAll()
|
inputEditText.selectAll()
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle("Rename Folder")
|
.setTitle(getString(R.string.rename_folder))
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setPositiveButton("Rename") { dialog, _ ->
|
.setPositiveButton(getString(R.string.rename)) { dialog, _ ->
|
||||||
val newName = inputEditText.text.toString().trim()
|
val newName = inputEditText.text.toString().trim()
|
||||||
if (newName.isNotEmpty() && newName != folder.name) {
|
if (newName.isNotEmpty() && newName != folder.name) {
|
||||||
if (isValidFolderName(newName)) {
|
if (isValidFolderName(newName)) {
|
||||||
renameFolder(folder, newName)
|
renameFolder(folder, newName)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "Invalid folder name", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this,
|
||||||
|
getString(R.string.invalid_folder_name), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
|
||||||
dialog.cancel()
|
dialog.cancel()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
@@ -515,21 +482,19 @@ class HiddenActivity : AppCompatActivity() {
|
|||||||
if (parentDir != null) {
|
if (parentDir != null) {
|
||||||
val newFolder = File(parentDir, newName)
|
val newFolder = File(parentDir, newName)
|
||||||
if (newFolder.exists()) {
|
if (newFolder.exists()) {
|
||||||
Toast.makeText(this, "Folder with this name already exists", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this,
|
||||||
|
getString(R.string.folder_with_this_name_already_exists), Toast.LENGTH_SHORT).show()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldFolder.renameTo(newFolder)) {
|
if (oldFolder.renameTo(newFolder)) {
|
||||||
// Clear selection from both adapters
|
|
||||||
folderAdapter?.clearSelection()
|
folderAdapter?.clearSelection()
|
||||||
listFolderAdapter?.clearSelection()
|
listFolderAdapter?.clearSelection()
|
||||||
|
|
||||||
// Exit selection mode
|
|
||||||
exitFolderSelectionMode()
|
exitFolderSelectionMode()
|
||||||
|
|
||||||
refreshCurrentView()
|
refreshCurrentView()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "Failed to rename folder", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, getString(R.string.failed_to_rename_folder), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.os.Bundle
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
@@ -22,6 +23,7 @@ import devs.org.calculator.utils.FileManager
|
|||||||
import devs.org.calculator.utils.PrefsUtil
|
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
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
|
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
@@ -40,7 +42,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
enableEdgeToEdge()
|
||||||
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
handleActivityResult(result)
|
handleActivityResult(result)
|
||||||
}
|
}
|
||||||
@@ -49,15 +51,14 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
binding.display.text = getString(R.string.enter_123456)
|
binding.display.text = getString(R.string.enter_123456)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask permission
|
|
||||||
if(!Environment.isExternalStorageManager()) {
|
if(!Environment.isExternalStorageManager()) {
|
||||||
dialogUtil.showMaterialDialog(
|
dialogUtil.showMaterialDialog(
|
||||||
"Storage Permission",
|
getString(R.string.storage_permission),
|
||||||
"To ensure the app works properly and allows you to easily hide or un-hide your private files, please grant storage access permission.\n" +
|
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) +
|
||||||
"\n" +
|
"\n" +
|
||||||
"For devices running Android 11 or higher, you'll need to grant the 'All Files Access' permission.",
|
getString(R.string.for_devices_running_android_11_or_higher_you_ll_need_to_grant_the_all_files_access_permission),
|
||||||
"Grant",
|
getString(R.string.grant_permission),
|
||||||
"Later",
|
getString(R.string.later),
|
||||||
object : DialogUtil.DialogCallback {
|
object : DialogUtil.DialogCallback {
|
||||||
override fun onPositiveButtonClicked() {
|
override fun onPositiveButtonClicked() {
|
||||||
fileManager.askPermission(this@MainActivity)
|
fileManager.askPermission(this@MainActivity)
|
||||||
@@ -65,19 +66,20 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
|
|
||||||
override fun onNegativeButtonClicked() {
|
override fun onNegativeButtonClicked() {
|
||||||
Toast.makeText(this@MainActivity,
|
Toast.makeText(this@MainActivity,
|
||||||
"Storage permission is required for the app to function properly",
|
getString(R.string.storage_permission_is_required_for_the_app_to_function_properly),
|
||||||
Toast.LENGTH_LONG).show()
|
Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNaturalButtonClicked() {
|
override fun onNaturalButtonClicked() {
|
||||||
Toast.makeText(this@MainActivity,
|
Toast.makeText(this@MainActivity,
|
||||||
"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.btn1, "1")
|
setupNumberButton(binding.btn1, "1")
|
||||||
setupNumberButton(binding.btn2, "2")
|
setupNumberButton(binding.btn2, "2")
|
||||||
setupNumberButton(binding.btn3, "3")
|
setupNumberButton(binding.btn3, "3")
|
||||||
@@ -99,6 +101,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
binding.cut.setOnClickListener { cutNumbers() }
|
binding.cut.setOnClickListener { cutNumbers() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun handleActivityResult(result: androidx.activity.result.ActivityResult) {
|
private fun handleActivityResult(result: androidx.activity.result.ActivityResult) {
|
||||||
if (result.resultCode == RESULT_OK) {
|
if (result.resultCode == RESULT_OK) {
|
||||||
result.data?.data?.let { uri ->
|
result.data?.data?.let { uri ->
|
||||||
@@ -107,10 +110,8 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
|
|
||||||
val preferences = getSharedPreferences("com.example.fileutility", MODE_PRIVATE)
|
val preferences = getSharedPreferences("com.example.fileutility", MODE_PRIVATE)
|
||||||
preferences.edit().putString("filestorageuri", uri.toString()).apply()
|
preferences.edit { putString("filestorageuri", uri.toString()) }
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Log.e("FileUtility", "Error occurred or operation cancelled: $result")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,41 +174,24 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculatePercentage() {
|
|
||||||
try {
|
|
||||||
val value = currentExpression.toDouble()
|
|
||||||
currentExpression = (value / 100).toString()
|
|
||||||
updateDisplay()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
binding.display.text = getString(R.string.invalid_message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun preprocessExpression(expression: String): String {
|
private fun preprocessExpression(expression: String): String {
|
||||||
val percentagePattern = Pattern.compile("(\\d+\\.?\\d*)%")
|
val percentagePattern = Pattern.compile("(\\d+\\.?\\d*)%")
|
||||||
val operatorPercentPattern = Pattern.compile("([+\\-*/])(\\d+\\.?\\d*)%")
|
val operatorPercentPattern = Pattern.compile("([+\\-*/])(\\d+\\.?\\d*)%")
|
||||||
|
|
||||||
var processedExpression = expression
|
var processedExpression = expression
|
||||||
|
|
||||||
// Replace standalone percentages (like "50%") with their decimal form (0.5)
|
|
||||||
val matcher = percentagePattern.matcher(processedExpression)
|
val matcher = percentagePattern.matcher(processedExpression)
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
val fullMatch = matcher.group(0)
|
val fullMatch = matcher.group(0)
|
||||||
val number = matcher.group(1)
|
val number = matcher.group(1)
|
||||||
|
|
||||||
// Check if it's a standalone percentage or part of an operation
|
|
||||||
val start = matcher.start()
|
val start = matcher.start()
|
||||||
if (start == 0 || !isOperator(processedExpression[start-1].toString())) {
|
if (start == 0 || !isOperator(processedExpression[start-1].toString())) {
|
||||||
val percentageValue = number.toDouble() / 100
|
val percentageValue = number!!.toDouble() / 100
|
||||||
processedExpression = processedExpression.replace(fullMatch, percentageValue.toString())
|
processedExpression = processedExpression.replace(fullMatch!!.toString(), percentageValue.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle operator-percentage combinations (like "100-20%")
|
|
||||||
val opMatcher = operatorPercentPattern.matcher(processedExpression)
|
val opMatcher = operatorPercentPattern.matcher(processedExpression)
|
||||||
val sb = StringBuilder(processedExpression)
|
val sb = StringBuilder(processedExpression)
|
||||||
|
|
||||||
// We need to process matches from right to left to maintain indices
|
|
||||||
val matches = mutableListOf<Triple<Int, Int, String>>()
|
val matches = mutableListOf<Triple<Int, Int, String>>()
|
||||||
|
|
||||||
while (opMatcher.find()) {
|
while (opMatcher.find()) {
|
||||||
@@ -219,15 +203,11 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
matches.add(Triple(start, end, "$operator$percentValue%"))
|
matches.add(Triple(start, end, "$operator$percentValue%"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process matches from right to left
|
|
||||||
for (match in matches.reversed()) {
|
for (match in matches.reversed()) {
|
||||||
val (start, end, fullMatch) = match
|
val (start, end, fullMatch) = match
|
||||||
|
|
||||||
// Find the number before this operator
|
|
||||||
var leftNumberEnd = start
|
|
||||||
var leftNumberStart = start - 1
|
var leftNumberStart = start - 1
|
||||||
|
|
||||||
// Skip parentheses and move to the actual number
|
|
||||||
if (leftNumberStart >= 0 && sb[leftNumberStart] == ')') {
|
if (leftNumberStart >= 0 && sb[leftNumberStart] == ')') {
|
||||||
var openParens = 1
|
var openParens = 1
|
||||||
leftNumberStart--
|
leftNumberStart--
|
||||||
@@ -238,7 +218,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
leftNumberStart--
|
leftNumberStart--
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we need to find the start of the expression
|
|
||||||
if (leftNumberStart >= 0) {
|
if (leftNumberStart >= 0) {
|
||||||
while (leftNumberStart >= 0 && (isDigit(sb[leftNumberStart].toString()) || sb[leftNumberStart] == '.' || sb[leftNumberStart] == '-')) {
|
while (leftNumberStart >= 0 && (isDigit(sb[leftNumberStart].toString()) || sb[leftNumberStart] == '.' || sb[leftNumberStart] == '-')) {
|
||||||
leftNumberStart--
|
leftNumberStart--
|
||||||
@@ -248,26 +227,24 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
leftNumberStart = 0
|
leftNumberStart = 0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For simple numbers, just find the start of the number
|
|
||||||
while (leftNumberStart >= 0 && (isDigit(sb[leftNumberStart].toString()) || sb[leftNumberStart] == '.')) {
|
while (leftNumberStart >= 0 && (isDigit(sb[leftNumberStart].toString()) || sb[leftNumberStart] == '.')) {
|
||||||
leftNumberStart--
|
leftNumberStart--
|
||||||
}
|
}
|
||||||
leftNumberStart++
|
leftNumberStart++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leftNumberStart < leftNumberEnd) {
|
if (leftNumberStart < start) {
|
||||||
val leftPart = sb.substring(leftNumberStart, leftNumberEnd)
|
val leftPart = sb.substring(leftNumberStart, start)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract the numerical values
|
|
||||||
val baseNumber = evaluateExpression(leftPart)
|
val baseNumber = evaluateExpression(leftPart)
|
||||||
val operator = fullMatch.substring(0, 1)
|
val operator = fullMatch.substring(0, 1)
|
||||||
val percentNumber = fullMatch.substring(1, fullMatch.length - 1).toDouble()
|
val percentNumber = fullMatch.substring(1, fullMatch.length - 1).toDouble()
|
||||||
|
|
||||||
// Calculate the percentage of the base number
|
|
||||||
val percentValue = baseNumber * (percentNumber / 100)
|
val percentValue = baseNumber * (percentNumber / 100)
|
||||||
|
|
||||||
// Calculate the new value based on the operator
|
|
||||||
val newValue = when (operator) {
|
val newValue = when (operator) {
|
||||||
"+" -> baseNumber + percentValue
|
"+" -> baseNumber + percentValue
|
||||||
"-" -> baseNumber - percentValue
|
"-" -> baseNumber - percentValue
|
||||||
@@ -276,7 +253,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
else -> baseNumber
|
else -> baseNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the entire expression "number operator percent%" with the result
|
|
||||||
sb.replace(leftNumberStart, end, newValue.toString())
|
sb.replace(leftNumberStart, end, newValue.toString())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Calculator", "Error processing percentage expression: $e")
|
Log.e("Calculator", "Error processing percentage expression: $e")
|
||||||
@@ -298,15 +274,16 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
private fun evaluateExpression(expression: String): Double {
|
private fun evaluateExpression(expression: String): Double {
|
||||||
return try {
|
return try {
|
||||||
ExpressionBuilder(expression).build().evaluate()
|
ExpressionBuilder(expression).build().evaluate()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
expression.toDouble()
|
expression.toDouble()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
private fun calculateResult() {
|
private fun calculateResult() {
|
||||||
if (currentExpression == "123456") {
|
if (currentExpression == "123456") {
|
||||||
val intent = Intent(this, SetupPasswordActivity::class.java)
|
val intent = Intent(this, SetupPasswordActivity::class.java)
|
||||||
sp.edit().putBoolean("isFirst", false).apply()
|
sp.edit { putBoolean("isFirst", false) }
|
||||||
intent.putExtra("password", currentExpression)
|
intent.putExtra("password", currentExpression)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
clearDisplay()
|
clearDisplay()
|
||||||
@@ -358,15 +335,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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the expression for preview calculation
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -382,7 +361,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
if (sp.getBoolean("isFirst", true) && (currentExpression == "123456" || binding.display.text.toString() == "123456")){
|
if (sp.getBoolean("isFirst", true) && (currentExpression == "123456" || binding.display.text.toString() == "123456")){
|
||||||
binding.total.text = getString(R.string.now_enter_button)
|
binding.total.text = getString(R.string.now_enter_button)
|
||||||
}else binding.total.text = formattedResult
|
}else binding.total.text = formattedResult
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
binding.total.text = ""
|
binding.total.text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,7 +374,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
val lastChar = currentExpression.last()
|
val lastChar = currentExpression.last()
|
||||||
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
|
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
|
||||||
|
|
||||||
// Update flags based on what was removed
|
|
||||||
if (lastChar == '%') {
|
if (lastChar == '%') {
|
||||||
lastWasPercent = false
|
lastWasPercent = false
|
||||||
} else if (isOperator(lastChar.toString())) {
|
} else if (isOperator(lastChar.toString())) {
|
||||||
@@ -411,27 +389,24 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPositiveButtonClicked() {
|
override fun onPositiveButtonClicked() {
|
||||||
// Handle positive button click for both DialogUtil and DialogActionsCallback
|
|
||||||
fileManager.askPermission(this)
|
fileManager.askPermission(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNegativeButtonClicked() {
|
override fun onNegativeButtonClicked() {
|
||||||
// Handle negative button click
|
Toast.makeText(this, getString(R.string.storage_permission_is_required_for_the_app_to_function_properly), Toast.LENGTH_LONG).show()
|
||||||
Toast.makeText(this, "Storage permission is required for the app to function properly", Toast.LENGTH_LONG).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNaturalButtonClicked() {
|
override fun onNaturalButtonClicked() {
|
||||||
// Handle neutral button click
|
Toast.makeText(this, getString(R.string.you_can_grant_permission_later_from_settings), Toast.LENGTH_LONG).show()
|
||||||
Toast.makeText(this, "You can grant permission later from Settings", Toast.LENGTH_LONG).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
if (requestCode == 6767) {
|
if (requestCode == 6767) {
|
||||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, getString(R.string.permission_granted), Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, getString(R.string.permission_denied), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,22 @@ package devs.org.calculator.activities
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import devs.org.calculator.R
|
import devs.org.calculator.R
|
||||||
import devs.org.calculator.adapters.ImagePreviewAdapter
|
import devs.org.calculator.adapters.ImagePreviewAdapter
|
||||||
|
import devs.org.calculator.database.AppDatabase
|
||||||
|
import devs.org.calculator.database.HiddenFileRepository
|
||||||
import devs.org.calculator.databinding.ActivityPreviewBinding
|
import devs.org.calculator.databinding.ActivityPreviewBinding
|
||||||
import devs.org.calculator.utils.DialogUtil
|
import devs.org.calculator.utils.DialogUtil
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
|
import devs.org.calculator.utils.PrefsUtil
|
||||||
|
import devs.org.calculator.utils.SecurityUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -18,43 +25,35 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private lateinit var binding: ActivityPreviewBinding
|
private lateinit var binding: ActivityPreviewBinding
|
||||||
private var currentPosition: Int = 0
|
private var currentPosition: Int = 0
|
||||||
private lateinit var files: List<File>
|
private var files: MutableList<File> = mutableListOf()
|
||||||
private lateinit var type: String
|
private lateinit var type: String
|
||||||
private lateinit var folder: String
|
private lateinit var folder: String
|
||||||
private lateinit var filetype: FileManager.FileType
|
private lateinit var filetype: FileManager.FileType
|
||||||
private lateinit var adapter: ImagePreviewAdapter
|
private lateinit var adapter: ImagePreviewAdapter
|
||||||
private lateinit var fileManager: FileManager
|
private lateinit var fileManager: FileManager
|
||||||
private val dialogUtil = DialogUtil(this)
|
private val dialogUtil = DialogUtil(this)
|
||||||
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
private val prefs: PrefsUtil by lazy { PrefsUtil(this) }
|
||||||
|
private val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||||
|
HiddenFileRepository(AppDatabase.getDatabase(this).hiddenFileDao())
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
fileManager = FileManager(this, this)
|
fileManager = FileManager(this, this)
|
||||||
|
|
||||||
currentPosition = intent.getIntExtra("position", 0)
|
currentPosition = intent.getIntExtra("position", 0)
|
||||||
type = intent.getStringExtra("type").toString()
|
type = intent.getStringExtra("type") ?: "IMAGE"
|
||||||
folder = intent.getStringExtra("folder").toString()
|
folder = intent.getStringExtra("folder") ?: ""
|
||||||
|
|
||||||
setupFileType()
|
setupFileType()
|
||||||
files = fileManager.getFilesInHiddenDirFromFolder(filetype, folder = folder)
|
loadFiles()
|
||||||
|
|
||||||
setupImagePreview()
|
setupImagePreview()
|
||||||
clickListeners()
|
setupClickListeners()
|
||||||
|
setupPageChangeCallback()
|
||||||
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
|
||||||
override fun onPageSelected(position: Int) {
|
|
||||||
super.onPageSelected(position)
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
binding.back.setOnClickListener {
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -62,8 +61,17 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
setupFlagSecure()
|
setupFlagSecure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
adapter.releaseAllResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
adapter.releaseAllResources()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupFlagSecure() {
|
private fun setupFlagSecure() {
|
||||||
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
|
||||||
if (prefs.getBoolean("screenshot_restriction", true)) {
|
if (prefs.getBoolean("screenshot_restriction", true)) {
|
||||||
window.setFlags(
|
window.setFlags(
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
@@ -78,14 +86,17 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
filetype = FileManager.FileType.IMAGE
|
filetype = FileManager.FileType.IMAGE
|
||||||
binding.title.text = getString(R.string.preview_images)
|
binding.title.text = getString(R.string.preview_images)
|
||||||
}
|
}
|
||||||
|
|
||||||
"VIDEO" -> {
|
"VIDEO" -> {
|
||||||
filetype = FileManager.FileType.VIDEO
|
filetype = FileManager.FileType.VIDEO
|
||||||
binding.title.text = getString(R.string.preview_videos)
|
binding.title.text = getString(R.string.preview_videos)
|
||||||
}
|
}
|
||||||
|
|
||||||
"AUDIO" -> {
|
"AUDIO" -> {
|
||||||
filetype = FileManager.FileType.AUDIO
|
filetype = FileManager.FileType.AUDIO
|
||||||
binding.title.text = getString(R.string.preview_audios)
|
binding.title.text = getString(R.string.preview_audios)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
filetype = FileManager.FileType.DOCUMENT
|
filetype = FileManager.FileType.DOCUMENT
|
||||||
binding.title.text = getString(R.string.preview_documents)
|
binding.title.text = getString(R.string.preview_documents)
|
||||||
@@ -93,108 +104,243 @@ class PreviewActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadFiles() {
|
||||||
|
try {
|
||||||
|
val filesList = fileManager.getFilesInHiddenDirFromFolder(filetype, folder = folder)
|
||||||
|
files = filesList.toMutableList()
|
||||||
|
|
||||||
|
if (currentPosition >= files.size) {
|
||||||
|
currentPosition = 0
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
files = mutableListOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupImagePreview() {
|
private fun setupImagePreview() {
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
adapter = ImagePreviewAdapter(this, this)
|
adapter = ImagePreviewAdapter(this, this)
|
||||||
adapter.images = files
|
adapter.images = files
|
||||||
binding.viewPager.adapter = adapter
|
binding.viewPager.adapter = adapter
|
||||||
|
if (currentPosition < files.size) {
|
||||||
binding.viewPager.setCurrentItem(currentPosition, false)
|
binding.viewPager.setCurrentItem(currentPosition, false)
|
||||||
|
|
||||||
val fileUri = Uri.fromFile(files[currentPosition])
|
|
||||||
val fileName = FileManager.FileName(this).getFileNameFromUri(fileUri).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
if (filetype == FileManager.FileType.AUDIO) {
|
|
||||||
(binding.viewPager.adapter as? ImagePreviewAdapter)?.currentMediaPlayer?.pause()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFileInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
private fun setupPageChangeCallback() {
|
||||||
super.onDestroy()
|
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
if (filetype == FileManager.FileType.AUDIO) {
|
override fun onPageSelected(position: Int) {
|
||||||
(binding.viewPager.adapter as? ImagePreviewAdapter)?.let { adapter ->
|
super.onPageSelected(position)
|
||||||
adapter.currentMediaPlayer?.release()
|
currentPosition = position
|
||||||
adapter.currentMediaPlayer = null
|
updateFileInfo()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateFileInfo() {
|
||||||
|
if (files.isNotEmpty() && currentPosition < files.size) {
|
||||||
|
val fileUri = Uri.fromFile(files[currentPosition])
|
||||||
|
val fileName = FileManager.FileName(this).getFileNameFromUri(fileUri) ?: "Unknown"
|
||||||
|
//For Now File Name not Needed, i am keeping it for later use
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupClickListeners() {
|
||||||
|
binding.back.setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
private fun clickListeners() {
|
|
||||||
binding.delete.setOnClickListener {
|
binding.delete.setOnClickListener {
|
||||||
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem])
|
handleDeleteFile()
|
||||||
if (fileUri != null) {
|
|
||||||
dialogUtil.showMaterialDialog(
|
|
||||||
getString(R.string.delete_file),
|
|
||||||
getString(R.string.are_you_sure_to_delete_this_file_permanently),
|
|
||||||
getString(R.string.delete_permanently),
|
|
||||||
getString(R.string.cancel),
|
|
||||||
object : DialogUtil.DialogCallback {
|
|
||||||
override fun onPositiveButtonClicked() {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
FileManager(this@PreviewActivity, this@PreviewActivity).deletePhotoFromExternalStorage(fileUri)
|
|
||||||
removeFileFromList(binding.viewPager.currentItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNegativeButtonClicked() {
|
|
||||||
// Handle negative button click
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNaturalButtonClicked() {
|
|
||||||
// Handle neutral button click
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.unHide.setOnClickListener {
|
binding.unHide.setOnClickListener {
|
||||||
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem])
|
performFileUnHiding()
|
||||||
if (fileUri != null) {
|
}
|
||||||
dialogUtil.showMaterialDialog(
|
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
getString(R.string.un_hide_file),
|
override fun onPageSelected(position: Int) {
|
||||||
getString(R.string.are_you_sure_you_want_to_un_hide_this_file),
|
super.onPageSelected(position)
|
||||||
getString(R.string.un_hide),
|
adapter.onItemScrolledAway(currentPosition)
|
||||||
getString(R.string.cancel),
|
currentPosition = position
|
||||||
object : DialogUtil.DialogCallback {
|
}
|
||||||
override fun onPositiveButtonClicked() {
|
})
|
||||||
lifecycleScope.launch {
|
}
|
||||||
FileManager(this@PreviewActivity, this@PreviewActivity).copyFileToNormalDir(fileUri)
|
|
||||||
removeFileFromList(binding.viewPager.currentItem)
|
private fun handleDeleteFile() {
|
||||||
|
if (files.isEmpty() || currentPosition >= files.size) return
|
||||||
|
|
||||||
|
val currentFile = files[currentPosition]
|
||||||
|
val fileUri = FileManager.FileManager().getContentUriImage(this, currentFile)
|
||||||
|
|
||||||
|
if (fileUri != null) {
|
||||||
|
dialogUtil.showMaterialDialog(
|
||||||
|
getString(R.string.delete_file),
|
||||||
|
getString(R.string.are_you_sure_to_delete_this_file_permanently),
|
||||||
|
getString(R.string.delete_permanently),
|
||||||
|
getString(R.string.cancel),
|
||||||
|
object : DialogUtil.DialogCallback {
|
||||||
|
override fun onPositiveButtonClicked() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
val hiddenFile =
|
||||||
|
hiddenFileRepository.getHiddenFileByPath(currentFile.absolutePath)
|
||||||
|
hiddenFile?.let {
|
||||||
|
hiddenFileRepository.deleteHiddenFile(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileManager.deletePhotoFromExternalStorage(fileUri)
|
||||||
|
removeFileFromList(currentPosition)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNegativeButtonClicked() {
|
|
||||||
// Handle negative button click
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNaturalButtonClicked() {
|
|
||||||
// Handle neutral button click
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
override fun onNegativeButtonClicked() {}
|
||||||
|
|
||||||
|
override fun onNaturalButtonClicked() {}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun performFileUnHiding() {
|
||||||
|
if (files.isEmpty() || currentPosition >= files.size) return
|
||||||
|
val file = files[currentPosition]
|
||||||
|
|
||||||
|
dialogUtil.showMaterialDialog(
|
||||||
|
getString(R.string.un_hide_file),
|
||||||
|
getString(R.string.are_you_sure_you_want_to_un_hide_this_file),
|
||||||
|
getString(R.string.un_hide),
|
||||||
|
getString(R.string.cancel),
|
||||||
|
object : DialogUtil.DialogCallback {
|
||||||
|
override fun onPositiveButtonClicked() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
var allUnhidden = true
|
||||||
|
try {
|
||||||
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
|
if (hiddenFile != null){
|
||||||
|
if (hiddenFile.isEncrypted) {
|
||||||
|
val originalExtension = hiddenFile.originalExtension
|
||||||
|
val decryptedFile = SecurityUtils.changeFileExtension(file, originalExtension)
|
||||||
|
|
||||||
|
if (SecurityUtils.decryptFile(this@PreviewActivity, file, decryptedFile)) {
|
||||||
|
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
val fileUri = FileManager.FileManager()
|
||||||
|
.getContentUriImage(this@PreviewActivity, decryptedFile)
|
||||||
|
if (fileUri != null) {
|
||||||
|
val result = fileManager.copyFileToNormalDir(fileUri)
|
||||||
|
if (result != null) {
|
||||||
|
hiddenFile.let {
|
||||||
|
hiddenFileRepository.deleteHiddenFile(it)
|
||||||
|
}
|
||||||
|
file.delete()
|
||||||
|
decryptedFile.delete()
|
||||||
|
removeFileFromList(currentPosition)
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (decryptedFile.exists()) {
|
||||||
|
decryptedFile.delete()
|
||||||
|
}
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val fileUri = FileManager.FileManager().getContentUriImage(this@PreviewActivity, file)
|
||||||
|
if (fileUri != null) {
|
||||||
|
val result = fileManager.copyFileToNormalDir(fileUri)
|
||||||
|
if (result != null) {
|
||||||
|
hiddenFile.let {
|
||||||
|
hiddenFileRepository.deleteHiddenFile(it)
|
||||||
|
}
|
||||||
|
file.delete()
|
||||||
|
removeFileFromList(currentPosition)
|
||||||
|
} else {
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
val fileUri = FileManager.FileManager().getContentUriImage(this@PreviewActivity, file)
|
||||||
|
if (fileUri != null) {
|
||||||
|
val result = fileManager.copyFileToNormalDir(fileUri)
|
||||||
|
if (result != null) {
|
||||||
|
file.delete()
|
||||||
|
removeFileFromList(currentPosition)
|
||||||
|
} else {
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (_: Exception) {
|
||||||
|
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
mainHandler.post {
|
||||||
|
val message = if (allUnhidden) {
|
||||||
|
getString(R.string.file_unhidden_successfully)
|
||||||
|
} else {
|
||||||
|
getString(R.string.something_went_wrong)
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(this@PreviewActivity, message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNegativeButtonClicked() {}
|
||||||
|
|
||||||
|
override fun onNaturalButtonClicked() {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private fun removeFileFromList(position: Int) {
|
private fun removeFileFromList(position: Int) {
|
||||||
val updatedFiles = files.toMutableList().apply { removeAt(position) }
|
if (position < 0 || position >= files.size) return
|
||||||
files = updatedFiles
|
adapter.releaseAllResources()
|
||||||
adapter.images = updatedFiles // Update adapter with the new list
|
files.removeAt(position)
|
||||||
|
adapter.images = files
|
||||||
// Update the ViewPager's position
|
if (files.isEmpty()) {
|
||||||
if (updatedFiles.isEmpty()) finish()
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentPosition = if (position >= files.size) {
|
||||||
|
files.size - 1
|
||||||
|
} else {
|
||||||
|
position
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.viewPager.setCurrentItem(currentPosition, false)
|
||||||
|
updateFileInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
onBackPressed()
|
finish()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,36 @@
|
|||||||
package devs.org.calculator.activities
|
package devs.org.calculator.activities
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.enableEdgeToEdge
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.net.toUri
|
||||||
import androidx.core.view.WindowCompat
|
import com.google.android.material.color.DynamicColors
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
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
|
||||||
import devs.org.calculator.R
|
import devs.org.calculator.R
|
||||||
import devs.org.calculator.databinding.ActivitySettingsBinding
|
import devs.org.calculator.databinding.ActivitySettingsBinding
|
||||||
|
import devs.org.calculator.utils.PrefsUtil
|
||||||
|
import devs.org.calculator.utils.SecurityUtils
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivitySettingsBinding
|
private lateinit var binding: ActivitySettingsBinding
|
||||||
private lateinit var prefs: SharedPreferences
|
private lateinit var prefs: PrefsUtil
|
||||||
private val DEV_GITHUB_URL = "https://github.com/binondi"
|
private var DEV_GITHUB_URL = ""
|
||||||
private val GITHUB_URL = "$DEV_GITHUB_URL/calculator-hide-files"
|
private var GITHUB_URL = ""
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
prefs = PrefsUtil(this)
|
||||||
prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
DEV_GITHUB_URL = getString(R.string.github_profile)
|
||||||
|
GITHUB_URL = getString(R.string.calculator_hide_files, DEV_GITHUB_URL)
|
||||||
setupUI()
|
setupUI()
|
||||||
loadSettings()
|
loadSettings()
|
||||||
setupListeners()
|
setupListeners()
|
||||||
@@ -42,6 +42,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun loadSettings() {
|
private fun loadSettings() {
|
||||||
|
|
||||||
binding.dynamicThemeSwitch.isChecked = prefs.getBoolean("dynamic_theme", true)
|
binding.dynamicThemeSwitch.isChecked = prefs.getBoolean("dynamic_theme", true)
|
||||||
@@ -55,8 +56,11 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
else -> binding.systemThemeRadio.isChecked = true
|
else -> binding.systemThemeRadio.isChecked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isUsingCustomKey = SecurityUtils.isUsingCustomKey(this)
|
||||||
|
binding.customKeyStatus.isChecked = isUsingCustomKey
|
||||||
binding.screenshotRestrictionSwitch.isChecked = prefs.getBoolean("screenshot_restriction", true)
|
binding.screenshotRestrictionSwitch.isChecked = prefs.getBoolean("screenshot_restriction", true)
|
||||||
binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true)
|
binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true)
|
||||||
|
binding.encryptionSwitch.isChecked = prefs.getBoolean("encryption", false)
|
||||||
|
|
||||||
updateThemeModeVisibility()
|
updateThemeModeVisibility()
|
||||||
}
|
}
|
||||||
@@ -72,11 +76,19 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.dynamicThemeSwitch.setOnCheckedChangeListener { _, isChecked ->
|
binding.dynamicThemeSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||||
prefs.edit().putBoolean("dynamic_theme", isChecked).apply()
|
prefs.setBoolean("dynamic_theme", isChecked)
|
||||||
if (!isChecked) {
|
if (!isChecked) {
|
||||||
showThemeModeDialog()
|
showThemeModeDialog()
|
||||||
|
}else{
|
||||||
|
showThemeModeDialog()
|
||||||
|
if (!prefs.getBoolean("isAppReopened",false)){
|
||||||
|
DynamicColors.applyToActivityIfAvailable(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.encryptionSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
prefs.setBoolean("encryption", isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
binding.themeModeSwitch.setOnCheckedChangeListener { _, isChecked ->
|
binding.themeModeSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||||
@@ -97,7 +109,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.screenshotRestrictionSwitch.setOnCheckedChangeListener { _, isChecked ->
|
binding.screenshotRestrictionSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||||
prefs.edit().putBoolean("screenshot_restriction", isChecked).apply()
|
prefs.setBoolean("screenshot_restriction", isChecked)
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
enableScreenshotRestriction()
|
enableScreenshotRestriction()
|
||||||
} else {
|
} else {
|
||||||
@@ -105,7 +117,11 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.showFileNames.setOnCheckedChangeListener { _, isChecked ->
|
binding.showFileNames.setOnCheckedChangeListener { _, isChecked ->
|
||||||
prefs.edit().putBoolean("showFileName", isChecked).apply()
|
prefs.setBoolean("showFileName", isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.customKeyStatus.setOnClickListener {
|
||||||
|
showCustomKeyDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,20 +131,16 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun showThemeModeDialog() {
|
private fun showThemeModeDialog() {
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle("Theme Mode")
|
.setTitle(getString(R.string.attention))
|
||||||
.setMessage("Would you like to set a specific theme mode?")
|
.setMessage(getString(R.string.if_you_turn_on_off_this_option_dynamic_theme_changes_will_be_visible_after_you_reopen_the_app))
|
||||||
.setPositiveButton("Yes") { _, _ ->
|
.setPositiveButton(getString(R.string.ok)) { _, _ ->
|
||||||
binding.themeModeSwitch.isChecked = true
|
|
||||||
}
|
|
||||||
.setNegativeButton("No") { _, _ ->
|
|
||||||
binding.systemThemeRadio.isChecked = true
|
|
||||||
applyThemeMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyThemeMode(themeMode: Int) {
|
private fun applyThemeMode(themeMode: Int) {
|
||||||
prefs.edit().putInt("theme_mode", themeMode).apply()
|
prefs.setInt("theme_mode", themeMode)
|
||||||
AppCompatDelegate.setDefaultNightMode(themeMode)
|
AppCompatDelegate.setDefaultNightMode(themeMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +157,64 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun openUrl(url: String) {
|
private fun openUrl(url: String) {
|
||||||
try {
|
try {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Snackbar.make(binding.root, "Could not open URL", Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(binding.root,
|
||||||
|
getString(R.string.could_not_open_url), Snackbar.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showCustomKeyDialog() {
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.dialog_custom_key, null)
|
||||||
|
val keyInput = dialogView.findViewById<EditText>(R.id.keyInput)
|
||||||
|
val confirmKeyInput = dialogView.findViewById<EditText>(R.id.confirmKeyInput)
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(getString(R.string.set_custom_encryption_key))
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton(getString(R.string.set)) { dialog, _ ->
|
||||||
|
val key = keyInput.text.toString()
|
||||||
|
val confirmKey = confirmKeyInput.text.toString()
|
||||||
|
|
||||||
|
if (key.isEmpty()) {
|
||||||
|
Toast.makeText(this, getString(R.string.key_cannot_be_empty), Toast.LENGTH_SHORT).show()
|
||||||
|
updateUI()
|
||||||
|
return@setPositiveButton
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key != confirmKey) {
|
||||||
|
Toast.makeText(this, getString(R.string.keys_do_not_match), Toast.LENGTH_SHORT).show()
|
||||||
|
updateUI()
|
||||||
|
return@setPositiveButton
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SecurityUtils.setCustomKey(this, key)) {
|
||||||
|
Toast.makeText(this,
|
||||||
|
getString(R.string.custom_key_set_successfully), Toast.LENGTH_SHORT).show()
|
||||||
|
updateUI()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this,
|
||||||
|
getString(R.string.failed_to_set_custom_key), Toast.LENGTH_SHORT).show()
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(getString(R.string.cancel)){ _, _ ->
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
.setNeutralButton(getString(R.string.delete_key)) { _, _ ->
|
||||||
|
SecurityUtils.clearCustomKey(this)
|
||||||
|
Toast.makeText(this,
|
||||||
|
getString(R.string.custom_encryption_key_cleared), Toast.LENGTH_SHORT).show()
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI() {
|
||||||
|
binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true)
|
||||||
|
binding.encryptionSwitch.isChecked = prefs.getBoolean("encryption", false)
|
||||||
|
val isUsingCustomKey = SecurityUtils.isUsingCustomKey(this)
|
||||||
|
binding.customKeyStatus.isChecked = isUsingCustomKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
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
|
||||||
import devs.org.calculator.databinding.ActivitySetupPasswordBinding
|
import devs.org.calculator.databinding.ActivitySetupPasswordBinding
|
||||||
@@ -18,6 +19,7 @@ class SetupPasswordActivity : AppCompatActivity() {
|
|||||||
private lateinit var binding2: ActivityChangePasswordBinding
|
private lateinit var binding2: ActivityChangePasswordBinding
|
||||||
private lateinit var prefsUtil: PrefsUtil
|
private lateinit var prefsUtil: PrefsUtil
|
||||||
private var hasPassword = false
|
private var hasPassword = false
|
||||||
|
private val prefs:PrefsUtil by lazy { PrefsUtil(this) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -72,8 +74,6 @@ class SetupPasswordActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.btnResetPassword.setOnClickListener {
|
binding.btnResetPassword.setOnClickListener {
|
||||||
// Implement password reset logic
|
|
||||||
// Could use security questions or email verification
|
|
||||||
if (prefsUtil.getSecurityQuestion() != null) showSecurityQuestionDialog(prefsUtil.getSecurityQuestion().toString())
|
if (prefsUtil.getSecurityQuestion() != null) showSecurityQuestionDialog(prefsUtil.getSecurityQuestion().toString())
|
||||||
else Toast.makeText(this,
|
else Toast.makeText(this,
|
||||||
getString(R.string.security_question_not_set_yet), Toast.LENGTH_SHORT).show()
|
getString(R.string.security_question_not_set_yet), Toast.LENGTH_SHORT).show()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package devs.org.calculator.activities
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
@@ -20,20 +19,28 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
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.databinding.ActivityViewFolderBinding
|
import devs.org.calculator.databinding.ActivityViewFolderBinding
|
||||||
import devs.org.calculator.databinding.ProccessingDialogBinding
|
import devs.org.calculator.databinding.ProccessingDialogBinding
|
||||||
import devs.org.calculator.utils.DialogUtil
|
import devs.org.calculator.utils.DialogUtil
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
|
import devs.org.calculator.utils.FileManager.Companion.ENCRYPTED_EXTENSION
|
||||||
import devs.org.calculator.utils.FileManager.Companion.HIDDEN_DIR
|
import devs.org.calculator.utils.FileManager.Companion.HIDDEN_DIR
|
||||||
import devs.org.calculator.utils.FolderManager
|
import devs.org.calculator.utils.FolderManager
|
||||||
|
import devs.org.calculator.utils.PrefsUtil
|
||||||
|
import devs.org.calculator.utils.SecurityUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.CompoundButton
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.view.WindowManager
|
||||||
|
import devs.org.calculator.adapters.FileAdapter
|
||||||
|
|
||||||
class ViewFolderActivity : AppCompatActivity() {
|
class ViewFolderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -51,12 +58,12 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
private var currentFolder: File? = null
|
private var currentFolder: File? = null
|
||||||
private val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
private val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||||
private lateinit var pickImageLauncher: ActivityResultLauncher<Intent>
|
private lateinit var pickImageLauncher: ActivityResultLauncher<Intent>
|
||||||
private lateinit var prefs: SharedPreferences
|
|
||||||
|
|
||||||
private var customDialog: androidx.appcompat.app.AlertDialog? = null
|
private var customDialog: androidx.appcompat.app.AlertDialog? = null
|
||||||
|
|
||||||
private var dialogShowTime: Long = 0
|
private var dialogShowTime: Long = 0
|
||||||
private val MINIMUM_DIALOG_DURATION = 1200L
|
private val MINIMUM_DIALOG_DURATION = 1200L
|
||||||
|
private val prefs:PrefsUtil by lazy { PrefsUtil(this) }
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -81,9 +88,9 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
fileManager = FileManager(this, this)
|
fileManager = FileManager(this, this)
|
||||||
folderManager = FolderManager(this)
|
folderManager = FolderManager()
|
||||||
dialogUtil = DialogUtil(this)
|
dialogUtil = DialogUtil(this)
|
||||||
prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupActivityResultLaunchers() {
|
private fun setupActivityResultLaunchers() {
|
||||||
@@ -135,16 +142,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -162,8 +172,46 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
object : FileProcessCallback {
|
object : FileProcessCallback {
|
||||||
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
|
copiedFiles.forEach { file ->
|
||||||
|
val fileType = fileManager.getFileType(file)
|
||||||
|
var finalFile = file
|
||||||
|
val extension = ".${file.extension}"
|
||||||
|
val isEncrypted = prefs.getBoolean("encryption",false)
|
||||||
|
|
||||||
|
if (isEncrypted) {
|
||||||
|
val encryptedFile = SecurityUtils.changeFileExtension(file, ENCRYPTED_EXTENSION)
|
||||||
|
if (SecurityUtils.encryptFile(this@ViewFolderActivity, file, encryptedFile)) {
|
||||||
|
finalFile = encryptedFile
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
fileAdapter?.hiddenFileRepository?.insertHiddenFile(
|
||||||
|
HiddenFileEntity(
|
||||||
|
filePath = finalFile.absolutePath,
|
||||||
|
fileName = file.name,
|
||||||
|
fileType = fileType,
|
||||||
|
originalExtension = extension,
|
||||||
|
isEncrypted = isEncrypted,
|
||||||
|
encryptedFileName = finalFile.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,23 +236,27 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
).show()
|
).show()
|
||||||
dismissCustomDialog()
|
dismissCustomDialog()
|
||||||
}
|
}
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshCurrentView() {
|
|
||||||
if (currentFolder != null) {
|
|
||||||
refreshCurrentFolder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
setupFlagSecure()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openFolder(folder: File) {
|
private fun setupFlagSecure() {
|
||||||
// Ensure folder exists and has .nomedia file
|
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()
|
||||||
@@ -218,34 +270,36 @@ 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) {
|
||||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
|
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
|
||||||
|
|
||||||
// Clean up previous adapter
|
|
||||||
fileAdapter?.cleanup()
|
fileAdapter?.cleanup()
|
||||||
|
|
||||||
fileAdapter = FileAdapter(this, this, folder, prefs.getBoolean("showFileName", true),
|
fileAdapter = FileAdapter(this, this, folder, prefs.getBoolean("showFileName", true),
|
||||||
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) {
|
||||||
@@ -261,7 +315,7 @@ 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.menuButton.setOnClickListener {
|
binding.menuButton.setOnClickListener {
|
||||||
@@ -306,24 +360,232 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
private fun showFileOptionsMenu(selectedFiles: List<File>) {
|
private fun showFileOptionsMenu(selectedFiles: List<File>) {
|
||||||
if (selectedFiles.isEmpty()) return
|
if (selectedFiles.isEmpty()) return
|
||||||
|
|
||||||
val options = arrayOf(
|
lifecycleScope.launch {
|
||||||
getString(R.string.un_hide),
|
var hasEncryptedFiles = false
|
||||||
getString(R.string.delete),
|
var hasDecryptedFiles = false
|
||||||
getString(R.string.copy_to_another_folder),
|
var hasEncFilesWithoutMetadata = false
|
||||||
getString(R.string.move_to_another_folder)
|
|
||||||
)
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
for (file in selectedFiles) {
|
||||||
.setTitle(getString(R.string.file_options))
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
.setItems(options) { _, which ->
|
|
||||||
when (which) {
|
if (file.name.endsWith(ENCRYPTED_EXTENSION)) {
|
||||||
0 -> unhideSelectedFiles(selectedFiles)
|
if (hiddenFile?.isEncrypted == true) {
|
||||||
1 -> deleteSelectedFiles(selectedFiles)
|
hasEncryptedFiles = true
|
||||||
2 -> copyToAnotherFolder(selectedFiles)
|
} else {
|
||||||
3 -> moveToAnotherFolder(selectedFiles)
|
hasEncFilesWithoutMetadata = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasDecryptedFiles = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.show()
|
|
||||||
|
val options = mutableListOf(
|
||||||
|
getString(R.string.un_hide),
|
||||||
|
getString(R.string.delete),
|
||||||
|
getString(R.string.copy_to_another_folder),
|
||||||
|
getString(R.string.move_to_another_folder)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasDecryptedFiles) {
|
||||||
|
options.add(getString(R.string.encrypt_file))
|
||||||
|
}
|
||||||
|
if (hasEncryptedFiles || hasEncFilesWithoutMetadata) {
|
||||||
|
options.add(getString(R.string.decrypt_file))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(this@ViewFolderActivity)
|
||||||
|
.setTitle(getString(R.string.file_options))
|
||||||
|
.setItems(options.toTypedArray()) { _, which ->
|
||||||
|
when (which) {
|
||||||
|
0 -> unhideSelectedFiles(selectedFiles)
|
||||||
|
1 -> deleteSelectedFiles(selectedFiles)
|
||||||
|
2 -> copyToAnotherFolder(selectedFiles)
|
||||||
|
3 -> moveToAnotherFolder(selectedFiles)
|
||||||
|
else -> {
|
||||||
|
val option = options[which]
|
||||||
|
when (option) {
|
||||||
|
getString(R.string.encrypt_file) -> fileAdapter?.encryptSelectedFiles()
|
||||||
|
getString(R.string.decrypt_file) -> {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val filesWithoutMetadata = selectedFiles.filter { file ->
|
||||||
|
file.name.endsWith(ENCRYPTED_EXTENSION) &&
|
||||||
|
fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)?.isEncrypted != true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filesWithoutMetadata.isNotEmpty()) {
|
||||||
|
showDecryptionTypeDialog(filesWithoutMetadata)
|
||||||
|
} else {
|
||||||
|
fileAdapter?.decryptSelectedFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDecryptionTypeDialog(selectedFiles: List<File>) {
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.dialog_file_type_selection, null)
|
||||||
|
val imageCheckbox = dialogView.findViewById<CheckBox>(R.id.checkboxImage)
|
||||||
|
val videoCheckbox = dialogView.findViewById<CheckBox>(R.id.checkboxVideo)
|
||||||
|
val audioCheckbox = dialogView.findViewById<CheckBox>(R.id.checkboxAudio)
|
||||||
|
val checkboxes = listOf(imageCheckbox, videoCheckbox, audioCheckbox)
|
||||||
|
checkboxes.forEach { checkbox ->
|
||||||
|
checkbox.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
checkboxes.filter { it != checkbox }.forEach { it.isChecked = false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialog = MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(getString(R.string.select_file_type))
|
||||||
|
.setMessage(getString(R.string.please_select_the_type_of_file_to_decrypt))
|
||||||
|
.setView(dialogView)
|
||||||
|
.setNegativeButton(getString(R.string.cancel), null)
|
||||||
|
.setPositiveButton(getString(R.string.decrypt), null)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.setOnShowListener {
|
||||||
|
val positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
|
positiveButton.isEnabled = false
|
||||||
|
val checkboxListener = CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||||
|
positiveButton.isEnabled = checkboxes.any { it.isChecked }
|
||||||
|
}
|
||||||
|
checkboxes.forEach { it.setOnCheckedChangeListener(checkboxListener) }
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.setOnDismissListener {
|
||||||
|
checkboxes.forEach { it.setOnCheckedChangeListener(null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
val selectedType = when {
|
||||||
|
imageCheckbox.isChecked -> FileManager.FileType.IMAGE
|
||||||
|
videoCheckbox.isChecked -> FileManager.FileType.VIDEO
|
||||||
|
audioCheckbox.isChecked -> FileManager.FileType.AUDIO
|
||||||
|
else -> return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
performDecryptionWithType(selectedFiles, selectedType)
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun performDecryptionWithType(selectedFiles: List<File>, fileType: FileManager.FileType) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
var successCount = 0
|
||||||
|
var failCount = 0
|
||||||
|
val decryptedFiles = mutableMapOf<File, File>()
|
||||||
|
|
||||||
|
for (file in selectedFiles) {
|
||||||
|
try {
|
||||||
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
|
|
||||||
|
if (hiddenFile?.isEncrypted == true) {
|
||||||
|
val originalExtension = hiddenFile.originalExtension
|
||||||
|
val decryptedFile = SecurityUtils.changeFileExtension(file, originalExtension)
|
||||||
|
|
||||||
|
if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) {
|
||||||
|
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
hiddenFile.let {
|
||||||
|
fileAdapter?.hiddenFileRepository?.updateEncryptionStatus(
|
||||||
|
filePath = file.absolutePath,
|
||||||
|
newFilePath = decryptedFile.absolutePath,
|
||||||
|
encryptedFileName = decryptedFile.name,
|
||||||
|
isEncrypted = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (file.delete()) {
|
||||||
|
decryptedFiles[file] = decryptedFile
|
||||||
|
successCount++
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (decryptedFile.exists()) {
|
||||||
|
decryptedFile.delete()
|
||||||
|
}
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else if (file.name.endsWith(ENCRYPTED_EXTENSION) && hiddenFile == null) {
|
||||||
|
val extension = when (fileType) {
|
||||||
|
FileManager.FileType.IMAGE -> ".jpg"
|
||||||
|
FileManager.FileType.VIDEO -> ".mp4"
|
||||||
|
FileManager.FileType.AUDIO -> ".mp3"
|
||||||
|
else -> ".txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
val decryptedFile = SecurityUtils.changeFileExtension(file, extension)
|
||||||
|
|
||||||
|
if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) {
|
||||||
|
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
fileAdapter?.hiddenFileRepository?.insertHiddenFile(
|
||||||
|
HiddenFileEntity(
|
||||||
|
filePath = decryptedFile.absolutePath,
|
||||||
|
fileName = decryptedFile.name,
|
||||||
|
encryptedFileName = file.name,
|
||||||
|
fileType = fileType,
|
||||||
|
originalExtension = extension,
|
||||||
|
isEncrypted = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (file.delete()) {
|
||||||
|
decryptedFiles[file] = decryptedFile
|
||||||
|
successCount++
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (decryptedFile.exists()) {
|
||||||
|
decryptedFile.delete()
|
||||||
|
}
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
failCount++
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainHandler.post {
|
||||||
|
when {
|
||||||
|
successCount > 0 && failCount == 0 -> {
|
||||||
|
Toast.makeText(this@ViewFolderActivity, "Decrypted $successCount file(s)", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
successCount > 0 && failCount > 0 -> {
|
||||||
|
Toast.makeText(this@ViewFolderActivity, "Decrypted $successCount file(s), failed to decrypt $failCount", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
failCount > 0 -> {
|
||||||
|
Toast.makeText(this@ViewFolderActivity, "Failed to decrypt $failCount file(s)", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (successCount > 0) {
|
||||||
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun moveToAnotherFolder(selectedFiles: List<File>) {
|
private fun moveToAnotherFolder(selectedFiles: List<File>) {
|
||||||
@@ -342,15 +604,12 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
object : DialogUtil.DialogCallback {
|
object : DialogUtil.DialogCallback {
|
||||||
override fun onPositiveButtonClicked() {
|
override fun onPositiveButtonClicked() {
|
||||||
performFileUnhiding(selectedFiles)
|
performFileUnhiding(selectedFiles)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNegativeButtonClicked() {
|
override fun onNegativeButtonClicked() {}
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNaturalButtonClicked() {
|
override fun onNaturalButtonClicked() {}
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -359,8 +618,8 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun deleteSelectedFiles(selectedFiles: List<File>) {
|
private fun deleteSelectedFiles(selectedFiles: List<File>) {
|
||||||
dialogUtil.showMaterialDialog(
|
dialogUtil.showMaterialDialog(
|
||||||
getString(R.string.delete_items),
|
getString(R.string.delete_file),
|
||||||
getString(R.string.are_you_sure_you_want_to_delete_selected_items),
|
getString(R.string.are_you_sure_to_delete_selected_files_permanently),
|
||||||
getString(R.string.delete),
|
getString(R.string.delete),
|
||||||
getString(R.string.cancel),
|
getString(R.string.cancel),
|
||||||
object : DialogUtil.DialogCallback {
|
object : DialogUtil.DialogCallback {
|
||||||
@@ -368,13 +627,9 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
performFileDeletion(selectedFiles)
|
performFileDeletion(selectedFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNegativeButtonClicked() {
|
override fun onNegativeButtonClicked() {}
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNaturalButtonClicked() {
|
override fun onNaturalButtonClicked() {}
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -383,20 +638,40 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun refreshCurrentFolder() {
|
private fun refreshCurrentFolder() {
|
||||||
currentFolder?.let { folder ->
|
currentFolder?.let { folder ->
|
||||||
val files = folderManager.getFilesInFolder(folder)
|
lifecycleScope.launch {
|
||||||
if (files.isNotEmpty()) {
|
try {
|
||||||
binding.recyclerView.visibility = View.VISIBLE
|
val files = folderManager.getFilesInFolder(folder)
|
||||||
binding.noItems.visibility = View.GONE
|
mainHandler.post {
|
||||||
fileAdapter?.submitList(files.toMutableList())
|
if (files.isNotEmpty()) {
|
||||||
fileAdapter?.let { adapter ->
|
binding.swipeLayout.visibility = View.VISIBLE
|
||||||
if (adapter.isInSelectionMode()) {
|
binding.noItems.visibility = View.GONE
|
||||||
showFileSelectionIcons()
|
|
||||||
} else {
|
val currentFiles = fileAdapter?.currentList ?: emptyList()
|
||||||
showFileViewIcons()
|
val hasChanges = files.size != currentFiles.size ||
|
||||||
|
files.any { newFile ->
|
||||||
|
currentFiles.none { it.absolutePath == newFile.absolutePath }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
fileAdapter?.submitList(files.toMutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
fileAdapter?.let { adapter ->
|
||||||
|
if (adapter.isInSelectionMode()) {
|
||||||
|
showFileSelectionIcons()
|
||||||
|
} else {
|
||||||
|
showFileViewIcons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showEmptyState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
mainHandler.post {
|
||||||
|
showEmptyState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
showEmptyState()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,6 +683,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/*") }
|
||||||
@@ -497,21 +775,65 @@ 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 fileUri = FileManager.FileManager().getContentUriImage(this@ViewFolderActivity, file)
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
|
|
||||||
if (fileUri != null) {
|
if (hiddenFile?.isEncrypted == true) {
|
||||||
val result = fileManager.copyFileToNormalDir(fileUri)
|
val originalExtension = hiddenFile.originalExtension
|
||||||
if (result == null) {
|
val decryptedFile = SecurityUtils.changeFileExtension(file, originalExtension)
|
||||||
|
|
||||||
|
if (SecurityUtils.decryptFile(this@ViewFolderActivity, file, decryptedFile)) {
|
||||||
|
if (decryptedFile.exists() && decryptedFile.length() > 0) {
|
||||||
|
val fileUri = FileManager.FileManager().getContentUriImage(this@ViewFolderActivity, decryptedFile)
|
||||||
|
if (fileUri != null) {
|
||||||
|
val result = fileManager.copyFileToNormalDir(fileUri)
|
||||||
|
if (result != null) {
|
||||||
|
hiddenFile.let {
|
||||||
|
fileAdapter?.hiddenFileRepository?.deleteHiddenFile(it)
|
||||||
|
}
|
||||||
|
file.delete()
|
||||||
|
decryptedFile.delete()
|
||||||
|
unhiddenFiles.add(file)
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decryptedFile.delete()
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (decryptedFile.exists()) {
|
||||||
|
decryptedFile.delete()
|
||||||
|
}
|
||||||
allUnhidden = false
|
allUnhidden = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
allUnhidden = false
|
val fileUri = FileManager.FileManager().getContentUriImage(this@ViewFolderActivity, file)
|
||||||
|
if (fileUri != null) {
|
||||||
|
val result = fileManager.copyFileToNormalDir(fileUri)
|
||||||
|
if (result != null) {
|
||||||
|
hiddenFile?.let {
|
||||||
|
fileAdapter?.hiddenFileRepository?.deleteHiddenFile(it)
|
||||||
|
}
|
||||||
|
file.delete()
|
||||||
|
unhiddenFiles.add(file)
|
||||||
|
} else {
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allUnhidden = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
allUnhidden = false
|
allUnhidden = false
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,33 +845,44 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
// Fixed: Ensure proper order of operations
|
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
refreshCurrentFolder()
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performFileDeletion(selectedFiles: List<File>) {
|
private fun performFileDeletion(selectedFiles: List<File>) {
|
||||||
var allDeleted = true
|
lifecycleScope.launch {
|
||||||
selectedFiles.forEach { file ->
|
var allDeleted = true
|
||||||
if (!file.delete()) {
|
val deletedFiles = mutableListOf<File>()
|
||||||
allDeleted = false
|
selectedFiles.forEach { file ->
|
||||||
|
try {
|
||||||
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
|
hiddenFile?.let {
|
||||||
|
fileAdapter?.hiddenFileRepository?.deleteHiddenFile(it)
|
||||||
|
}
|
||||||
|
if (file.delete()) {
|
||||||
|
deletedFiles.add(file)
|
||||||
|
} else {
|
||||||
|
allDeleted = false
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
allDeleted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainHandler.post {
|
||||||
|
val message = if (allDeleted) {
|
||||||
|
getString(R.string.files_deleted_successfully)
|
||||||
|
} else {
|
||||||
|
getString(R.string.some_items_could_not_be_deleted)
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
||||||
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val message = if (allDeleted) {
|
|
||||||
getString(R.string.files_deleted_successfully)
|
|
||||||
} else {
|
|
||||||
getString(R.string.some_items_could_not_be_deleted)
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
// Fixed: Ensure proper order of operations
|
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
refreshCurrentFolder()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyToAnotherFolder(selectedFiles: List<File>) {
|
private fun copyToAnotherFolder(selectedFiles: List<File>) {
|
||||||
@@ -559,47 +892,86 @@ class ViewFolderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
private fun copyFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
||||||
var allCopied = true
|
lifecycleScope.launch {
|
||||||
selectedFiles.forEach { file ->
|
var allCopied = true
|
||||||
try {
|
val copiedFiles = mutableListOf<File>()
|
||||||
val newFile = File(destinationFolder, file.name)
|
selectedFiles.forEach { file ->
|
||||||
file.copyTo(newFile, overwrite = true)
|
try {
|
||||||
} catch (e: Exception) {
|
val newFile = File(destinationFolder, file.name)
|
||||||
allCopied = false
|
file.copyTo(newFile, overwrite = true)
|
||||||
|
|
||||||
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
|
hiddenFile?.let {
|
||||||
|
fileAdapter?.hiddenFileRepository?.insertHiddenFile(
|
||||||
|
HiddenFileEntity(
|
||||||
|
filePath = newFile.absolutePath,
|
||||||
|
fileName = it.fileName,
|
||||||
|
fileType = it.fileType,
|
||||||
|
originalExtension = it.originalExtension,
|
||||||
|
isEncrypted = it.isEncrypted,
|
||||||
|
encryptedFileName = it.encryptedFileName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
copiedFiles.add(file)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
allCopied = false
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainHandler.post {
|
||||||
|
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()
|
||||||
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val message = if (allCopied) "Files copied successfully" else "Some files could not be copied"
|
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
// Fixed: Ensure proper order of operations
|
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
refreshCurrentFolder()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun moveFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
private fun moveFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
||||||
var allMoved = true
|
lifecycleScope.launch {
|
||||||
selectedFiles.forEach { file ->
|
var allMoved = true
|
||||||
try {
|
val movedFiles = mutableListOf<File>()
|
||||||
val newFile = File(destinationFolder, file.name)
|
selectedFiles.forEach { file ->
|
||||||
file.copyTo(newFile, overwrite = true)
|
try {
|
||||||
file.delete()
|
val newFile = File(destinationFolder, file.name)
|
||||||
} catch (e: Exception) {
|
file.copyTo(newFile, overwrite = true)
|
||||||
allMoved = false
|
|
||||||
|
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||||
|
hiddenFile?.let {
|
||||||
|
fileAdapter?.hiddenFileRepository?.updateEncryptionStatus(
|
||||||
|
filePath = file.absolutePath,
|
||||||
|
newFilePath = newFile.absolutePath,
|
||||||
|
encryptedFileName = it.encryptedFileName,
|
||||||
|
isEncrypted = it.isEncrypted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.delete()) {
|
||||||
|
movedFiles.add(file)
|
||||||
|
} else {
|
||||||
|
allMoved = false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
allMoved = false
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainHandler.post {
|
||||||
|
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()
|
||||||
|
refreshCurrentFolder()
|
||||||
|
fileAdapter?.exitSelectionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val message = if (allMoved) "Files moved successfully" else "Some files could not be moved"
|
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
// Fixed: Ensure proper order of operations
|
|
||||||
fileAdapter?.exitSelectionMode()
|
|
||||||
refreshCurrentFolder()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 } // Exclude current folder
|
.filter { it != currentFolder }
|
||||||
|
|
||||||
if (folders.isEmpty()) {
|
if (folders.isEmpty()) {
|
||||||
Toast.makeText(this, getString(R.string.no_folders_available), Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, getString(R.string.no_folders_available), Toast.LENGTH_SHORT).show()
|
||||||
|
|||||||
@@ -6,13 +6,10 @@ import java.io.File
|
|||||||
class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
||||||
|
|
||||||
override fun areItemsTheSame(oldItem: File, newItem: File): Boolean {
|
override fun areItemsTheSame(oldItem: File, newItem: File): Boolean {
|
||||||
// Compare by absolute path since File objects might be different instances
|
|
||||||
// but represent the same file
|
|
||||||
return oldItem.absolutePath == newItem.absolutePath
|
return oldItem.absolutePath == newItem.absolutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: File, newItem: File): Boolean {
|
override fun areContentsTheSame(oldItem: File, newItem: File): Boolean {
|
||||||
// Compare all relevant properties that might change and affect the UI
|
|
||||||
return oldItem.name == newItem.name &&
|
return oldItem.name == newItem.name &&
|
||||||
oldItem.length() == newItem.length() &&
|
oldItem.length() == newItem.length() &&
|
||||||
oldItem.lastModified() == newItem.lastModified() &&
|
oldItem.lastModified() == newItem.lastModified() &&
|
||||||
@@ -22,8 +19,6 @@ class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getChangePayload(oldItem: File, newItem: File): Any? {
|
override fun getChangePayload(oldItem: File, newItem: File): Any? {
|
||||||
// Return a payload if only specific properties changed
|
|
||||||
// This allows for partial updates instead of full rebinding
|
|
||||||
val changes = mutableListOf<String>()
|
val changes = mutableListOf<String>()
|
||||||
|
|
||||||
if (oldItem.name != newItem.name) {
|
if (oldItem.name != newItem.name) {
|
||||||
@@ -42,6 +37,10 @@ class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
|||||||
changes.add("EXISTENCE_CHANGED")
|
changes.add("EXISTENCE_CHANGED")
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (changes.isNotEmpty()) changes else null
|
if (oldItem.absolutePath != newItem.absolutePath) {
|
||||||
|
changes.add("FILE_CHANGED")
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes.ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package devs.org.calculator.adapters
|
package devs.org.calculator.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -80,6 +81,7 @@ class FolderAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun clearSelection() {
|
fun clearSelection() {
|
||||||
val wasInSelectionMode = isSelectionMode
|
val wasInSelectionMode = isSelectionMode
|
||||||
selectedItems.clear()
|
selectedItems.clear()
|
||||||
|
|||||||
@@ -5,19 +5,25 @@ 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
|
||||||
import android.widget.MediaController
|
import android.widget.MediaController
|
||||||
import android.widget.SeekBar
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.AsyncListDiffer
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import devs.org.calculator.R
|
||||||
|
import devs.org.calculator.database.AppDatabase
|
||||||
|
import devs.org.calculator.database.HiddenFileRepository
|
||||||
import devs.org.calculator.databinding.ViewpagerItemsBinding
|
import devs.org.calculator.databinding.ViewpagerItemsBinding
|
||||||
import devs.org.calculator.utils.FileManager
|
import devs.org.calculator.utils.FileManager
|
||||||
|
import devs.org.calculator.utils.SecurityUtils.getDecryptedPreviewFile
|
||||||
|
import devs.org.calculator.utils.SecurityUtils.getUriForPreviewFile
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import devs.org.calculator.R
|
|
||||||
|
|
||||||
class ImagePreviewAdapter(
|
class ImagePreviewAdapter(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -25,11 +31,11 @@ class ImagePreviewAdapter(
|
|||||||
) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() {
|
) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() {
|
||||||
|
|
||||||
private val differ = AsyncListDiffer(this, FileDiffCallback())
|
private val differ = AsyncListDiffer(this, FileDiffCallback())
|
||||||
var currentMediaPlayer: MediaPlayer? = null
|
|
||||||
var isMediaPlayerPrepared = false
|
|
||||||
var currentViewHolder: ImageViewHolder? = null
|
|
||||||
private var currentPlayingPosition = -1
|
private var currentPlayingPosition = -1
|
||||||
private var isPlaying = false
|
private var currentViewHolder: ImageViewHolder? = null
|
||||||
|
private val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||||
|
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||||
|
}
|
||||||
|
|
||||||
var images: List<File>
|
var images: List<File>
|
||||||
get() = differ.currentList
|
get() = differ.currentList
|
||||||
@@ -42,110 +48,219 @@ class ImagePreviewAdapter(
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
||||||
val imageUrl = images[position]
|
val imageUrl = images[position]
|
||||||
val fileType = FileManager(context, lifecycleOwner).getFileType(images[position])
|
val fileType = FileManager(context,lifecycleOwner).getFileType(imageUrl)
|
||||||
holder.bind(imageUrl,fileType)
|
stopAndResetCurrentAudio()
|
||||||
currentViewHolder = holder
|
|
||||||
|
|
||||||
currentMediaPlayer?.let {
|
holder.bind(imageUrl, position,fileType)
|
||||||
if (it.isPlaying) it.pause()
|
|
||||||
it.seekTo(0)
|
|
||||||
}
|
|
||||||
currentMediaPlayer = null
|
|
||||||
isMediaPlayerPrepared = false
|
|
||||||
|
|
||||||
if (currentMediaPlayer?.isPlaying == true) {
|
|
||||||
currentMediaPlayer?.stop()
|
|
||||||
currentMediaPlayer?.release()
|
|
||||||
}
|
|
||||||
currentMediaPlayer = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = images.size
|
override fun getItemCount(): Int = images.size
|
||||||
|
|
||||||
|
private fun stopAndResetCurrentAudio() {
|
||||||
|
currentViewHolder?.stopAndResetAudio()
|
||||||
|
currentPlayingPosition = -1
|
||||||
|
currentViewHolder = null
|
||||||
|
}
|
||||||
|
|
||||||
inner class ImageViewHolder(private val binding: ViewpagerItemsBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class ImageViewHolder(private val binding: ViewpagerItemsBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
private var mediaPlayer: MediaPlayer? = null
|
|
||||||
private var seekHandler = Handler(Looper.getMainLooper())
|
private var seekHandler = Handler(Looper.getMainLooper())
|
||||||
private var seekRunnable: Runnable? = null
|
private var seekRunnable: Runnable? = null
|
||||||
|
private var mediaPlayer: MediaPlayer? = null
|
||||||
|
private var isMediaPlayerPrepared = false
|
||||||
|
private var isPlaying = false
|
||||||
|
private var currentPosition = 0
|
||||||
|
private var tempDecryptedFile: File? = null
|
||||||
|
|
||||||
fun bind(file: File, fileType: FileManager.FileType) {
|
fun bind(file: File, position: Int, decryptedFileType: FileManager.FileType) {
|
||||||
when (fileType) {
|
currentPosition = position
|
||||||
FileManager.FileType.VIDEO -> {
|
|
||||||
binding.imageView.visibility = View.GONE
|
|
||||||
binding.audioBg.visibility = View.GONE
|
|
||||||
binding.videoView.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
val videoUri = Uri.fromFile(file)
|
releaseMediaPlayer()
|
||||||
binding.videoView.setVideoURI(videoUri)
|
resetAudioUI()
|
||||||
binding.videoView.start()
|
cleanupTempFile()
|
||||||
|
|
||||||
val mediaController = MediaController(context)
|
try {
|
||||||
mediaController.setAnchorView(binding.videoView)
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
binding.videoView.setMediaController(mediaController)
|
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||||
|
if (hiddenFile != null) {
|
||||||
mediaController.setPrevNextListeners(
|
val isEncrypted = hiddenFile.isEncrypted
|
||||||
{
|
val fileType = hiddenFile.fileType
|
||||||
val nextPosition = (adapterPosition + 1) % images.size
|
if (isEncrypted) {
|
||||||
playVideoAtPosition(nextPosition)
|
tempDecryptedFile = getDecryptedPreviewFile(context, hiddenFile)
|
||||||
},
|
if (tempDecryptedFile != null && tempDecryptedFile!!.exists() && tempDecryptedFile!!.length() > 0) {
|
||||||
{
|
displayFile(tempDecryptedFile!!, fileType, true)
|
||||||
val prevPosition = if (adapterPosition - 1 < 0) images.size - 1 else adapterPosition - 1
|
} else {
|
||||||
playVideoAtPosition(prevPosition)
|
Log.e("ImagePreviewAdapter", "Failed to get decrypted preview file for: ${file.absolutePath}")
|
||||||
|
showEncryptedError()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
displayFile(file, decryptedFileType, false)
|
||||||
}
|
}
|
||||||
)
|
} else {
|
||||||
|
displayFile(file, decryptedFileType, false)
|
||||||
binding.videoView.setOnCompletionListener {
|
|
||||||
val nextPosition = (adapterPosition + 1) % images.size
|
|
||||||
playVideoAtPosition(nextPosition)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FileManager.FileType.IMAGE -> {
|
} catch (e: Exception) {
|
||||||
binding.imageView.visibility = View.VISIBLE
|
Log.e("ImagePreviewAdapter", "Error in bind: ${e.message}")
|
||||||
binding.videoView.visibility = View.GONE
|
displayFile(file, decryptedFileType, false)
|
||||||
binding.audioBg.visibility = View.GONE
|
|
||||||
Glide.with(context)
|
|
||||||
.load(file)
|
|
||||||
.into(binding.imageView)
|
|
||||||
}
|
|
||||||
FileManager.FileType.AUDIO -> {
|
|
||||||
binding.imageView.visibility = View.GONE
|
|
||||||
binding.audioBg.visibility = View.VISIBLE
|
|
||||||
binding.videoView.visibility = View.GONE
|
|
||||||
binding.audioTitle.text = file.name
|
|
||||||
|
|
||||||
setupAudioPlayer(file)
|
|
||||||
setupSeekBar()
|
|
||||||
setupPlaybackControls()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
binding.imageView.visibility = View.VISIBLE
|
|
||||||
binding.audioBg.visibility = View.GONE
|
|
||||||
binding.videoView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun displayFile(file: File, fileType: FileManager.FileType, isEncrypted: Boolean = false) {
|
||||||
|
try {
|
||||||
|
val uri = if (isEncrypted) {
|
||||||
|
getUriForPreviewFile(context, file)
|
||||||
|
} else {
|
||||||
|
Uri.fromFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
Log.e("ImagePreviewAdapter", "Failed to get URI for file: ${file.absolutePath}")
|
||||||
|
showEncryptedError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
when (fileType) {
|
||||||
|
FileManager.FileType.VIDEO -> {
|
||||||
|
binding.imageView.visibility = View.GONE
|
||||||
|
binding.audioBg.visibility = View.GONE
|
||||||
|
binding.videoView.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
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
|
||||||
|
playVideoAtPosition(nextPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileManager.FileType.IMAGE -> {
|
||||||
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
binding.videoView.visibility = View.GONE
|
||||||
|
binding.audioBg.visibility = View.GONE
|
||||||
|
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
|
||||||
|
|
||||||
|
setupAudioPlayer(audioFile)
|
||||||
|
setupPlaybackControls()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
binding.audioBg.visibility = View.GONE
|
||||||
|
binding.videoView.visibility = View.GONE
|
||||||
|
Glide.with(context)
|
||||||
|
.load(uri)
|
||||||
|
.error(R.drawable.encrypted)
|
||||||
|
.into(binding.imageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("ImagePreviewAdapter", "Error displaying file: ${e.message}")
|
||||||
|
showEncryptedError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFileFromUri(context: Context, uri: Uri): File? {
|
||||||
|
return try {
|
||||||
|
val inputStream = context.contentResolver.openInputStream(uri)
|
||||||
|
val tempFile = File.createTempFile("temp_audio", null, context.cacheDir)
|
||||||
|
tempFile.outputStream().use { output ->
|
||||||
|
inputStream?.copyTo(output)
|
||||||
|
}
|
||||||
|
tempFile
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEncryptedError() {
|
||||||
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
binding.videoView.visibility = View.GONE
|
||||||
|
binding.audioBg.visibility = View.GONE
|
||||||
|
binding.imageView.setImageResource(R.drawable.encrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanupTempFile() {
|
||||||
|
tempDecryptedFile?.let {
|
||||||
|
if (it.exists()) {
|
||||||
|
try {
|
||||||
|
it.delete()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("ImagePreviewAdapter", "Error cleaning up temp file: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempDecryptedFile = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetAudioUI() {
|
||||||
|
binding.playPause.setImageResource(R.drawable.play)
|
||||||
|
binding.audioSeekBar.value = 0f
|
||||||
|
binding.audioSeekBar.valueTo = 100f
|
||||||
|
seekRunnable?.let { seekHandler.removeCallbacks(it) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupAudioPlayer(file: File) {
|
private fun setupAudioPlayer(file: File) {
|
||||||
mediaPlayer = MediaPlayer().apply {
|
try {
|
||||||
setDataSource(file.absolutePath)
|
mediaPlayer = MediaPlayer().apply {
|
||||||
setOnPreparedListener { mp ->
|
setDataSource(file.absolutePath)
|
||||||
binding.audioSeekBar.valueTo = mp.duration.toFloat()
|
setOnPreparedListener { mp ->
|
||||||
|
binding.audioSeekBar.valueTo = mp.duration.toFloat()
|
||||||
isMediaPlayerPrepared = true
|
binding.audioSeekBar.value = 0f
|
||||||
|
setupSeekBar()
|
||||||
|
isMediaPlayerPrepared = true
|
||||||
|
}
|
||||||
|
setOnCompletionListener {
|
||||||
|
stopAndResetAudio()
|
||||||
|
}
|
||||||
|
setOnErrorListener { _, _, _ ->
|
||||||
|
releaseMediaPlayer()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
prepareAsync()
|
||||||
}
|
}
|
||||||
setOnCompletionListener {
|
} catch (e: Exception) {
|
||||||
// isPlaying = false
|
e.printStackTrace()
|
||||||
binding.playPause.setImageResource(R.drawable.play)
|
releaseMediaPlayer()
|
||||||
binding.audioSeekBar.value = 0f
|
|
||||||
|
|
||||||
seekHandler.removeCallbacks(seekRunnable!!)
|
|
||||||
}
|
|
||||||
prepareAsync()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSeekBar() {
|
private fun setupSeekBar() {
|
||||||
binding.audioSeekBar.addOnChangeListener { slider, value, fromUser ->
|
binding.audioSeekBar.addOnChangeListener { _, value, fromUser ->
|
||||||
if (fromUser && mediaPlayer != null && isMediaPlayerPrepared) {
|
if (fromUser && mediaPlayer != null && isMediaPlayerPrepared) {
|
||||||
mediaPlayer?.seekTo(value.toInt())
|
mediaPlayer?.seekTo(value.toInt())
|
||||||
}
|
}
|
||||||
@@ -153,15 +268,18 @@ class ImagePreviewAdapter(
|
|||||||
|
|
||||||
seekRunnable = Runnable {
|
seekRunnable = Runnable {
|
||||||
mediaPlayer?.let { mp ->
|
mediaPlayer?.let { mp ->
|
||||||
if (mp.isPlaying) {
|
if (mp.isPlaying && isMediaPlayerPrepared) {
|
||||||
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
try {
|
||||||
seekHandler.postDelayed(seekRunnable!!, 100)
|
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
||||||
|
seekHandler.postDelayed(seekRunnable!!, 100)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun setupPlaybackControls() {
|
private fun setupPlaybackControls() {
|
||||||
binding.playPause.setOnClickListener {
|
binding.playPause.setOnClickListener {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
@@ -173,65 +291,127 @@ class ImagePreviewAdapter(
|
|||||||
|
|
||||||
binding.preview.setOnClickListener {
|
binding.preview.setOnClickListener {
|
||||||
mediaPlayer?.let { mp ->
|
mediaPlayer?.let { mp ->
|
||||||
val newPosition = mp.currentPosition - 10000
|
if (isMediaPlayerPrepared) {
|
||||||
mp.seekTo(maxOf(0, newPosition))
|
try {
|
||||||
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
val newPosition = mp.currentPosition - 10000
|
||||||
|
mp.seekTo(maxOf(0, newPosition))
|
||||||
|
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.next.setOnClickListener {
|
binding.next.setOnClickListener {
|
||||||
mediaPlayer?.let { mp ->
|
mediaPlayer?.let { mp ->
|
||||||
val newPosition = mp.currentPosition + 10000
|
if (isMediaPlayerPrepared) {
|
||||||
mp.seekTo(minOf(mp.duration, newPosition))
|
try {
|
||||||
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
val newPosition = mp.currentPosition + 10000
|
||||||
|
mp.seekTo(minOf(mp.duration, newPosition))
|
||||||
|
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playAudio() {
|
private fun playAudio() {
|
||||||
mediaPlayer?.let { mp ->
|
mediaPlayer?.let { mp ->
|
||||||
if (currentPlayingPosition != -1 && currentPlayingPosition != adapterPosition) {
|
if (isMediaPlayerPrepared) {
|
||||||
currentViewHolder?.pauseAudio()
|
try {
|
||||||
|
if (currentPlayingPosition != currentPosition) {
|
||||||
|
stopAndResetCurrentAudio()
|
||||||
|
}
|
||||||
|
|
||||||
|
mp.start()
|
||||||
|
isPlaying = true
|
||||||
|
binding.playPause.setImageResource(R.drawable.pause)
|
||||||
|
seekRunnable?.let { seekHandler.post(it) }
|
||||||
|
|
||||||
|
currentPlayingPosition = currentPosition
|
||||||
|
currentViewHolder = this@ImageViewHolder
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
releaseMediaPlayer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mp.start()
|
|
||||||
isPlaying = true
|
|
||||||
binding.playPause.setImageResource(R.drawable.pause)
|
|
||||||
seekHandler.post(seekRunnable!!)
|
|
||||||
currentPlayingPosition = adapterPosition
|
|
||||||
currentViewHolder = this
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pauseAudio() {
|
private fun pauseAudio() {
|
||||||
mediaPlayer?.let { mp ->
|
mediaPlayer?.let { mp ->
|
||||||
if (mp.isPlaying) {
|
try {
|
||||||
mp.pause()
|
if (mp.isPlaying) {
|
||||||
isPlaying = false
|
mp.pause()
|
||||||
binding.playPause.setImageResource(R.drawable.play)
|
isPlaying = false
|
||||||
seekHandler.removeCallbacks(seekRunnable!!)
|
binding.playPause.setImageResource(R.drawable.play)
|
||||||
|
seekRunnable?.let { seekHandler.removeCallbacks(it) }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
releaseMediaPlayer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun stopAndResetAudio() {
|
||||||
|
try {
|
||||||
|
mediaPlayer?.let { mp ->
|
||||||
|
if (mp.isPlaying) {
|
||||||
|
mp.stop()
|
||||||
|
mp.prepare()
|
||||||
|
} else if (isMediaPlayerPrepared) {
|
||||||
|
mp.seekTo(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isPlaying = false
|
||||||
|
resetAudioUI()
|
||||||
|
|
||||||
|
if (currentPlayingPosition == currentPosition) {
|
||||||
|
currentPlayingPosition = -1
|
||||||
|
currentViewHolder = null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
releaseMediaPlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun releaseMediaPlayer() {
|
fun releaseMediaPlayer() {
|
||||||
mediaPlayer?.let { mp ->
|
try {
|
||||||
if (mp.isPlaying) {
|
mediaPlayer?.let { mp ->
|
||||||
mp.stop()
|
if (mp.isPlaying) {
|
||||||
|
mp.stop()
|
||||||
|
}
|
||||||
|
mp.release()
|
||||||
}
|
}
|
||||||
mp.release()
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
mediaPlayer = null
|
mediaPlayer = null
|
||||||
isPlaying = false
|
isPlaying = false
|
||||||
seekHandler.removeCallbacks(seekRunnable!!)
|
isMediaPlayerPrepared = false
|
||||||
|
seekRunnable?.let { seekHandler.removeCallbacks(it) }
|
||||||
|
|
||||||
|
if (currentPlayingPosition == currentPosition) {
|
||||||
|
currentPlayingPosition = -1
|
||||||
|
currentViewHolder = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playVideoAtPosition(position: Int) {
|
private fun playVideoAtPosition(position: Int) {
|
||||||
val nextFile = images[position]
|
if (position < images.size) {
|
||||||
val fileType = FileManager(context, lifecycleOwner).getFileType(images[position])
|
val nextFile = images[position]
|
||||||
if (fileType == FileManager.FileType.VIDEO) {
|
val fileType = FileManager(context, lifecycleOwner).getFileType(images[position])
|
||||||
val videoUri = Uri.fromFile(nextFile)
|
if (fileType == FileManager.FileType.VIDEO) {
|
||||||
binding.videoView.setVideoURI(videoUri)
|
val videoUri = Uri.fromFile(nextFile)
|
||||||
binding.videoView.start()
|
binding.videoView.setVideoURI(videoUri)
|
||||||
|
binding.videoView.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,5 +420,14 @@ class ImagePreviewAdapter(
|
|||||||
super.onViewRecycled(holder)
|
super.onViewRecycled(holder)
|
||||||
holder.releaseMediaPlayer()
|
holder.releaseMediaPlayer()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
fun onItemScrolledAway(position: Int) {
|
||||||
|
if (currentPlayingPosition == position) {
|
||||||
|
stopAndResetCurrentAudio()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun releaseAllResources() {
|
||||||
|
stopAndResetCurrentAudio()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package devs.org.calculator.adapters
|
package devs.org.calculator.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
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.ImageView
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
@@ -22,9 +22,9 @@ class ListFolderAdapter(
|
|||||||
private var isSelectionMode = false
|
private var isSelectionMode = false
|
||||||
|
|
||||||
inner class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
inner class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
val folderNameTextView: TextView = itemView.findViewById(R.id.folderName)
|
private val folderNameTextView: TextView = itemView.findViewById(R.id.folderName)
|
||||||
|
|
||||||
val selectedLayer: View = itemView.findViewById(R.id.selectedLayer)
|
private val selectedLayer: View = itemView.findViewById(R.id.selectedLayer)
|
||||||
|
|
||||||
fun bind(folder: File, onFolderClick: (File) -> Unit, onFolderLongClick: (File) -> Unit, isSelected: Boolean) {
|
fun bind(folder: File, onFolderClick: (File) -> Unit, onFolderLongClick: (File) -> Unit, isSelected: Boolean) {
|
||||||
folderNameTextView.text = folder.name
|
folderNameTextView.text = folder.name
|
||||||
@@ -89,6 +89,7 @@ class ListFolderAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun clearSelection() {
|
fun clearSelection() {
|
||||||
val wasInSelectionMode = isSelectionMode
|
val wasInSelectionMode = isSelectionMode
|
||||||
selectedItems.clear()
|
selectedItems.clear()
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package devs.org.calculator.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
|
||||||
|
@Database(entities = [HiddenFileEntity::class], version = 1)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun hiddenFileDao(): HiddenFileDao
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: AppDatabase? = null
|
||||||
|
|
||||||
|
fun getDatabase(context: Context): AppDatabase {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
val instance = Room.databaseBuilder(
|
||||||
|
context.applicationContext,
|
||||||
|
AppDatabase::class.java,
|
||||||
|
"calculator_database"
|
||||||
|
).build()
|
||||||
|
INSTANCE = instance
|
||||||
|
instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package devs.org.calculator.database
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface HiddenFileDao {
|
||||||
|
@Query("SELECT * FROM hidden_files")
|
||||||
|
fun getAllHiddenFiles(): Flow<List<HiddenFileEntity>>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertHiddenFile(hiddenFile: HiddenFileEntity)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteHiddenFile(hiddenFile: HiddenFileEntity)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM hidden_files WHERE filePath = :filePath")
|
||||||
|
suspend fun getHiddenFileByPath(filePath: String): HiddenFileEntity?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM hidden_files WHERE fileName = :fileName")
|
||||||
|
suspend fun getHiddenFileByOriginalName(fileName: String): HiddenFileEntity?
|
||||||
|
|
||||||
|
@Query("UPDATE hidden_files SET isEncrypted = :isEncrypted, filePath = :newFilePath, encryptedFileName = :encryptedFileName WHERE filePath = :filePath")
|
||||||
|
suspend fun updateEncryptionStatus(
|
||||||
|
filePath: String,
|
||||||
|
newFilePath: String,
|
||||||
|
encryptedFileName: String?,
|
||||||
|
isEncrypted: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateHiddenFile(hiddenFile: HiddenFileEntity)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package devs.org.calculator.database
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import devs.org.calculator.utils.FileManager
|
||||||
|
|
||||||
|
@Entity(tableName = "hidden_files")
|
||||||
|
data class HiddenFileEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
val filePath: String, //absolute path of the file
|
||||||
|
val fileName: String, // Original filename with extension
|
||||||
|
val encryptedFileName: String, // Encrypted filename
|
||||||
|
val fileType: FileManager.FileType, //type of the file
|
||||||
|
val originalExtension: String, // original file extension
|
||||||
|
val isEncrypted: Boolean, // is the file encrypted or not
|
||||||
|
var dateAdded: Long = System.currentTimeMillis()
|
||||||
|
)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package devs.org.calculator.database
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class HiddenFileRepository(private val hiddenFileDao: HiddenFileDao) {
|
||||||
|
|
||||||
|
fun getAllHiddenFiles(): Flow<List<HiddenFileEntity>> {
|
||||||
|
return hiddenFileDao.getAllHiddenFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun insertHiddenFile(hiddenFile: HiddenFileEntity) {
|
||||||
|
hiddenFileDao.insertHiddenFile(hiddenFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteHiddenFile(hiddenFile: HiddenFileEntity) {
|
||||||
|
hiddenFileDao.deleteHiddenFile(hiddenFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getHiddenFileByPath(filePath: String): HiddenFileEntity? {
|
||||||
|
return hiddenFileDao.getHiddenFileByPath(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateEncryptionStatus(filePath: String, newFilePath: String,encryptedFileName: String, isEncrypted: Boolean) {
|
||||||
|
hiddenFileDao.updateEncryptionStatus(filePath,newFilePath, encryptedFileName = encryptedFileName, isEncrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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,22 +18,26 @@ 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
|
|
||||||
|
|
||||||
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()
|
||||||
|
val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||||
|
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val HIDDEN_DIR = ".CalculatorHide"
|
const val HIDDEN_DIR = ".CalculatorHide"
|
||||||
@@ -38,8 +45,10 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
const val VIDEOS_DIR = "videos"
|
const val VIDEOS_DIR = "videos"
|
||||||
const val AUDIO_DIR = "audio"
|
const val AUDIO_DIR = "audio"
|
||||||
const val DOCS_DIR = "documents"
|
const val DOCS_DIR = "documents"
|
||||||
|
const val ENCRYPTED_EXTENSION = ".enc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getHiddenDirectory(): File {
|
fun getHiddenDirectory(): File {
|
||||||
val dir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
val dir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
@@ -47,7 +56,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
if (!created) {
|
if (!created) {
|
||||||
throw RuntimeException("Failed to create hidden directory: ${dir.absolutePath}")
|
throw RuntimeException("Failed to create hidden directory: ${dir.absolutePath}")
|
||||||
}
|
}
|
||||||
// Create .nomedia file to hide from media scanners
|
|
||||||
val nomediaFile = File(dir, ".nomedia")
|
val nomediaFile = File(dir, ".nomedia")
|
||||||
if (!nomediaFile.exists()) {
|
if (!nomediaFile.exists()) {
|
||||||
nomediaFile.createNewFile()
|
nomediaFile.createNewFile()
|
||||||
@@ -56,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()) {
|
||||||
@@ -92,7 +91,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
val extension = MimeTypeMap.getSingleton()
|
val extension = MimeTypeMap.getSingleton()
|
||||||
.getExtensionFromMimeType(mimeType) ?: ""
|
.getExtensionFromMimeType(mimeType) ?: ""
|
||||||
val fileName = "${System.currentTimeMillis()}.${extension}"
|
val fileName = "${System.currentTimeMillis()}.${extension}"
|
||||||
val targetFile = File(targetDir, fileName)
|
var targetFile = File(targetDir, fileName)
|
||||||
|
|
||||||
// Copy file using DocumentFile
|
// Copy file using DocumentFile
|
||||||
contentResolver.openInputStream(uri)?.use { input ->
|
contentResolver.openInputStream(uri)?.use { input ->
|
||||||
@@ -160,71 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy file content
|
|
||||||
file.copyTo(finalTargetFile, overwrite = false)
|
|
||||||
|
|
||||||
// Verify copy success
|
|
||||||
if (finalTargetFile.exists() && finalTargetFile.length() > 0) {
|
|
||||||
// Delete original hidden file
|
|
||||||
if (file.delete()) {
|
|
||||||
// Trigger media scan for the new file
|
|
||||||
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
|
|
||||||
mediaScanIntent.data = Uri.fromFile(finalTargetFile)
|
|
||||||
context.sendBroadcast(mediaScanIntent)
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Toast.makeText(context, context.getString(R.string.file_unhidden_successfully), Toast.LENGTH_SHORT).show()
|
|
||||||
onSuccess?.invoke() // Call success callback
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Toast.makeText(context, "File copied but failed to remove from hidden folder", Toast.LENGTH_SHORT).show()
|
|
||||||
onError?.invoke("Failed to remove from hidden folder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Toast.makeText(context, "Failed to copy file", Toast.LENGTH_SHORT).show()
|
|
||||||
onError?.invoke("Failed to copy file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Toast.makeText(context, "Error unhiding file: ${e.message}", Toast.LENGTH_LONG).show()
|
|
||||||
onError?.invoke(e.message ?: "Unknown error")
|
|
||||||
}
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
|
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()
|
||||||
}
|
}
|
||||||
@@ -388,4 +329,142 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
|||||||
DOCUMENT(DOCS_DIR),
|
DOCUMENT(DOCS_DIR),
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,14 @@
|
|||||||
package devs.org.calculator.utils
|
package devs.org.calculator.utils
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Environment
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class FolderManager(private val context: Context) {
|
class FolderManager {
|
||||||
companion object {
|
|
||||||
const val HIDDEN_DIR = ".CalculatorHide"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createFolder(parentDir: File, folderName: String): Boolean {
|
fun createFolder(parentDir: File, folderName: String): Boolean {
|
||||||
val newFolder = File(parentDir, folderName)
|
val newFolder = File(parentDir, folderName)
|
||||||
return if (!newFolder.exists()) {
|
return if (!newFolder.exists()) {
|
||||||
newFolder.mkdirs()
|
newFolder.mkdirs()
|
||||||
// Create .nomedia file to hide from media scanners
|
|
||||||
File(newFolder, ".nomedia").createNewFile()
|
File(newFolder, ".nomedia").createNewFile()
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -54,20 +49,4 @@ class FolderManager(private val context: Context) {
|
|||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveFileToFolder(file: File, targetFolder: File): Boolean {
|
|
||||||
return try {
|
|
||||||
if (!targetFolder.exists()) {
|
|
||||||
targetFolder.mkdirs()
|
|
||||||
File(targetFolder, ".nomedia").createNewFile()
|
|
||||||
}
|
|
||||||
val newFile = File(targetFolder, file.name)
|
|
||||||
file.copyTo(newFile, overwrite = true)
|
|
||||||
file.delete()
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package devs.org.calculator.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
class PrefsUtil(context: Context) {
|
class PrefsUtil(context: Context) {
|
||||||
private val prefs: SharedPreferences = context.getSharedPreferences("Calculator", Context.MODE_PRIVATE)
|
private val prefs: SharedPreferences = context.getSharedPreferences("Calculator", Context.MODE_PRIVATE)
|
||||||
@@ -13,26 +14,33 @@ class PrefsUtil(context: Context) {
|
|||||||
|
|
||||||
fun savePassword(password: String) {
|
fun savePassword(password: String) {
|
||||||
val hashedPassword = hashPassword(password)
|
val hashedPassword = hashPassword(password)
|
||||||
prefs.edit()
|
prefs.edit {
|
||||||
.putString("password", hashedPassword)
|
putString("password", hashedPassword)
|
||||||
.apply()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBoolean(key:String, value: Boolean){
|
fun setBoolean(key:String, value: Boolean){
|
||||||
return prefs.edit().putBoolean(key,value).apply()
|
return prefs.edit { putBoolean(key, value) }
|
||||||
|
|
||||||
|
}
|
||||||
|
fun setInt(key:String, value: Int){
|
||||||
|
return prefs.edit { putInt(key, value) }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBoolean(key: String, defValue: Boolean = false): Boolean{
|
fun getBoolean(key: String, defValue: Boolean = false): Boolean{
|
||||||
return prefs.getBoolean(key,defValue)
|
return prefs.getBoolean(key,defValue)
|
||||||
}
|
}
|
||||||
|
fun getInt(key: String, defValue: Int): Int{
|
||||||
|
return prefs.getInt(key,defValue)
|
||||||
|
}
|
||||||
|
|
||||||
fun resetPassword(){
|
fun resetPassword(){
|
||||||
prefs.edit()
|
prefs.edit {
|
||||||
.remove("password")
|
remove("password")
|
||||||
.remove("security_question")
|
.remove("security_question")
|
||||||
.remove("security_answer")
|
.remove("security_answer")
|
||||||
.apply()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validatePassword(input: String): Boolean {
|
fun validatePassword(input: String): Boolean {
|
||||||
@@ -40,11 +48,15 @@ class PrefsUtil(context: Context) {
|
|||||||
return stored == hashPassword(input)
|
return stored == hashPassword(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPassword(): String{
|
||||||
|
return prefs.getString("password", "") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
fun saveSecurityQA(question: String, answer: String) {
|
fun saveSecurityQA(question: String, answer: String) {
|
||||||
prefs.edit()
|
prefs.edit {
|
||||||
.putString("security_question", question)
|
putString("security_question", question)
|
||||||
.putString("security_answer", hashPassword(answer))
|
.putString("security_answer", hashPassword(answer))
|
||||||
.apply()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateSecurityAnswer(answer: String): Boolean {
|
fun validateSecurityAnswer(answer: String): Boolean {
|
||||||
|
|||||||
@@ -5,60 +5,254 @@ import android.net.Uri
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import java.security.SecureRandom
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.CipherInputStream
|
||||||
|
import javax.crypto.CipherOutputStream
|
||||||
|
import javax.crypto.KeyGenerator
|
||||||
import javax.crypto.SecretKey
|
import javax.crypto.SecretKey
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import devs.org.calculator.database.HiddenFileEntity
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
class SecurityUtils {
|
object SecurityUtils {
|
||||||
companion object {
|
private const val ALGORITHM = "AES"
|
||||||
private const val ALGORITHM = "AES"
|
private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
|
||||||
private const val HIDDEN_FOLDER = "Calculator_Data"
|
private const val KEY_SIZE = 256
|
||||||
|
val ENCRYPTED_EXTENSION = ".enc"
|
||||||
|
|
||||||
fun validatePassword(input: String, storedHash: String): Boolean {
|
private fun getSecretKey(context: Context): SecretKey {
|
||||||
return input.hashCode().toString() == storedHash
|
val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE)
|
||||||
}
|
val useCustomKey = keyStore.getBoolean("use_custom_key", false)
|
||||||
|
|
||||||
fun encryptFile(context: Context, sourceUri: Uri, password: String): File {
|
if (useCustomKey) {
|
||||||
val inputStream = context.contentResolver.openInputStream(sourceUri)
|
val customKey = keyStore.getString("custom_key", null)
|
||||||
val secretKey = generateKey(password)
|
if (customKey != null) {
|
||||||
val cipher = Cipher.getInstance(ALGORITHM)
|
try {
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
val messageDigest = java.security.MessageDigest.getInstance("SHA-256")
|
||||||
|
val keyBytes = messageDigest.digest(customKey.toByteArray())
|
||||||
val hiddenDir = File(context.getExternalFilesDir(null), HIDDEN_FOLDER)
|
return SecretKeySpec(keyBytes, ALGORITHM)
|
||||||
if (!hiddenDir.exists()) hiddenDir.mkdirs()
|
} catch (_: Exception) {
|
||||||
|
|
||||||
val encryptedFile = File(hiddenDir, "${System.currentTimeMillis()}_encrypted")
|
|
||||||
val outputStream = FileOutputStream(encryptedFile)
|
|
||||||
|
|
||||||
inputStream?.use { input ->
|
|
||||||
val buffer = ByteArray(1024)
|
|
||||||
var read: Int
|
|
||||||
while (input.read(buffer).also { read = it } != -1) {
|
|
||||||
val encrypted = cipher.update(buffer, 0, read)
|
|
||||||
outputStream.write(encrypted)
|
|
||||||
}
|
}
|
||||||
val finalBlock = cipher.doFinal()
|
|
||||||
outputStream.write(finalBlock)
|
|
||||||
}
|
}
|
||||||
outputStream.close()
|
|
||||||
return encryptedFile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decryptFile(file: File, password: String): ByteArray {
|
val encodedKey = keyStore.getString("secret_key", null)
|
||||||
val secretKey = generateKey(password)
|
return if (encodedKey != null) {
|
||||||
val cipher = Cipher.getInstance(ALGORITHM)
|
try {
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey)
|
val decodedKey = android.util.Base64.decode(encodedKey, android.util.Base64.DEFAULT)
|
||||||
|
SecretKeySpec(decodedKey, ALGORITHM)
|
||||||
val inputStream = FileInputStream(file)
|
} catch (_: Exception) {
|
||||||
val bytes = inputStream.readBytes()
|
generateAndStoreNewKey(keyStore)
|
||||||
inputStream.close()
|
}
|
||||||
|
} else {
|
||||||
return cipher.doFinal(bytes)
|
generateAndStoreNewKey(keyStore)
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateKey(password: String): SecretKey {
|
|
||||||
val keyBytes = password.toByteArray().copyOf(16)
|
|
||||||
return SecretKeySpec(keyBytes, ALGORITHM)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateAndStoreNewKey(keyStore: SharedPreferences): SecretKey {
|
||||||
|
val keyGenerator = KeyGenerator.getInstance(ALGORITHM)
|
||||||
|
keyGenerator.init(KEY_SIZE, SecureRandom())
|
||||||
|
val key = keyGenerator.generateKey()
|
||||||
|
val encodedKey = android.util.Base64.encodeToString(key.encoded, android.util.Base64.DEFAULT)
|
||||||
|
keyStore.edit { putString("secret_key", encodedKey) }
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encryptFile(context: Context, inputFile: File, outputFile: File): Boolean {
|
||||||
|
return try {
|
||||||
|
if (!inputFile.exists()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val secretKey = getSecretKey(context)
|
||||||
|
val cipher = Cipher.getInstance(TRANSFORMATION)
|
||||||
|
val iv = ByteArray(16)
|
||||||
|
SecureRandom().nextBytes(iv)
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
|
||||||
|
|
||||||
|
FileInputStream(inputFile).use { input ->
|
||||||
|
FileOutputStream(outputFile).use { output ->
|
||||||
|
output.write(iv)
|
||||||
|
CipherOutputStream(output, cipher).use { cipherOutput ->
|
||||||
|
input.copyTo(cipherOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outputFile.exists() || outputFile.length() == 0L) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream(outputFile).use { input ->
|
||||||
|
val iv = ByteArray(16)
|
||||||
|
val bytesRead = input.read(iv)
|
||||||
|
if (bytesRead != 16) {
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} catch (_: Exception) {
|
||||||
|
|
||||||
|
if (outputFile.exists()) {
|
||||||
|
outputFile.delete()
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDecryptedPreviewFile(context: Context, meta: HiddenFileEntity): File? {
|
||||||
|
try {
|
||||||
|
val encryptedFile = File(meta.filePath)
|
||||||
|
if (!encryptedFile.exists()) {
|
||||||
|
Log.e("SecurityUtils", "Encrypted file does not exist: ${meta.filePath}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val tempDir = File(context.cacheDir, "preview_temp")
|
||||||
|
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 success = decryptFile(context, encryptedFile, tempFile)
|
||||||
|
|
||||||
|
if (success && tempFile.exists() && tempFile.length() > 0) {
|
||||||
|
return tempFile
|
||||||
|
} else {
|
||||||
|
Log.e("SecurityUtils", "Failed to decrypt preview file: ${meta.filePath}")
|
||||||
|
if (tempFile.exists()) tempFile.delete()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("SecurityUtils", "Error in getDecryptedPreviewFile: ${e.message}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUriForPreviewFile(context: Context, file: File): Uri? {
|
||||||
|
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(
|
||||||
|
context,
|
||||||
|
"${context.packageName}.provider",
|
||||||
|
file
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("SecurityUtils", "Error getting URI for preview file: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decryptFile(context: Context, inputFile: File, outputFile: File): Boolean {
|
||||||
|
return try {
|
||||||
|
if (!inputFile.exists()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputFile.length() == 0L) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val secretKey = getSecretKey(context)
|
||||||
|
val cipher = Cipher.getInstance(TRANSFORMATION)
|
||||||
|
FileInputStream(inputFile).use { input ->
|
||||||
|
val iv = ByteArray(16)
|
||||||
|
val bytesRead = input.read(iv)
|
||||||
|
if (bytesRead != 16) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv))
|
||||||
|
|
||||||
|
FileInputStream(inputFile).use { decInput ->
|
||||||
|
decInput.skip(16)
|
||||||
|
|
||||||
|
FileOutputStream(outputFile).use { output ->
|
||||||
|
CipherInputStream(decInput, cipher).use { cipherInput ->
|
||||||
|
cipherInput.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outputFile.exists() || outputFile.length() == 0L) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} catch (_: Exception) {
|
||||||
|
if (outputFile.exists()) {
|
||||||
|
outputFile.delete()
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFileExtension(file: File): String {
|
||||||
|
val name = file.name
|
||||||
|
val lastDotIndex = name.lastIndexOf('.')
|
||||||
|
return if (lastDotIndex > 0) {
|
||||||
|
name.substring(lastDotIndex)
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeFileExtension(file: File, newExtension: String): File {
|
||||||
|
val name = file.name
|
||||||
|
val lastDotIndex = name.lastIndexOf('.')
|
||||||
|
val newName = if (lastDotIndex > 0) {
|
||||||
|
name.substring(0, lastDotIndex) + newExtension
|
||||||
|
} else {
|
||||||
|
name + newExtension
|
||||||
|
}
|
||||||
|
return File(file.parent, newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCustomKey(context: Context, key: String): Boolean {
|
||||||
|
return try {
|
||||||
|
val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE)
|
||||||
|
keyStore.edit {
|
||||||
|
putString("custom_key", key)
|
||||||
|
putBoolean("use_custom_key", true)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCustomKey(context: Context) {
|
||||||
|
val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE)
|
||||||
|
keyStore.edit {
|
||||||
|
remove("custom_key")
|
||||||
|
putBoolean("use_custom_key", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isUsingCustomKey(context: Context): Boolean {
|
||||||
|
val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE)
|
||||||
|
return keyStore.getBoolean("use_custom_key", false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.PermissionChecker
|
import androidx.core.content.PermissionChecker
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
|||||||
onGranted()
|
onGranted()
|
||||||
} else {
|
} else {
|
||||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
|
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
|
||||||
data = Uri.parse("package:${activity.packageName}")
|
data = "package:${activity.packageName}".toUri()
|
||||||
}
|
}
|
||||||
activity.startActivity(intent)
|
activity.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="800dp"
|
android:width="50dp"
|
||||||
android:height="800dp"
|
android:height="50dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<corners android:bottomLeftRadius="15dp" android:bottomRightRadius="15dp"/>
|
<corners android:bottomLeftRadius="15dp" android:bottomRightRadius="15dp"/>
|
||||||
<solid android:color="?attr/cardForegroundColor"/>
|
<solid android:color="?attr/colorSecondaryContainer"/>
|
||||||
</shape>
|
</shape>
|
||||||
5
app/src/main/res/drawable/bottom_shade.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<gradient android:startColor="#CC000000" android:centerColor="#00ffffff" android:endColor="#00ffffff" android:angle="90"/>
|
||||||
|
</shape>
|
||||||
17
app/src/main/res/drawable/encrypted.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="50dp" android:viewportHeight="73" android:viewportWidth="73" android:width="50dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#00FFFFFF" android:fillType="nonZero" android:pathData="M15,1L58,1A14,14 0,0 1,72 15L72,58A14,14 0,0 1,58 72L15,72A14,14 0,0 1,1 58L1,15A14,14 0,0 1,15 1z" android:strokeColor="#0004673E" android:strokeWidth="2"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#00DD80" android:fillType="nonZero" android:pathData="M54.191,43.754C52.953,47.108 51.081,50.025 48.626,52.423C45.832,55.151 42.173,57.319 37.751,58.866C37.605,58.917 37.454,58.958 37.302,58.989C37.101,59.028 36.896,59.05 36.694,59.053L36.654,59.053C36.438,59.053 36.221,59.031 36.005,58.989C35.853,58.958 35.704,58.917 35.56,58.867C31.132,57.323 27.469,55.156 24.671,52.427C22.215,50.03 20.344,47.115 19.108,43.76C16.86,37.66 16.988,30.941 17.091,25.542L17.093,25.459C17.113,25.013 17.127,24.544 17.134,24.027C17.172,21.488 19.191,19.387 21.73,19.246C27.025,18.95 31.121,17.223 34.621,13.812L34.652,13.784C35.233,13.251 35.965,12.989 36.694,13C37.396,13.009 38.096,13.271 38.657,13.784L38.687,13.812C42.187,17.223 46.283,18.95 51.578,19.246C54.118,19.387 56.137,21.488 56.174,24.027C56.182,24.548 56.195,25.016 56.216,25.459L56.217,25.494C56.319,30.904 56.446,37.636 54.191,43.754Z" android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#03D078" android:fillType="nonZero" android:pathData="M54.191,43.754C52.953,47.108 51.081,50.025 48.626,52.423C45.832,55.151 42.173,57.319 37.751,58.866C37.605,58.917 37.454,58.958 37.302,58.989C37.101,59.028 36.896,59.05 36.694,59.053L36.694,13C37.396,13.009 38.096,13.271 38.657,13.784L38.687,13.812C42.187,17.223 46.283,18.95 51.578,19.246C54.118,19.387 56.137,21.488 56.174,24.027C56.182,24.548 56.195,25.016 56.216,25.459L56.217,25.494C56.319,30.904 56.446,37.636 54.191,43.754Z" android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#00FFFFFF" android:fillType="nonZero" android:pathData="M48.131,36.026C48.131,42.341 43.003,47.482 36.694,47.504L36.653,47.504C30.325,47.504 25.176,42.355 25.176,36.026C25.176,29.698 30.325,24.549 36.653,24.549L36.694,24.549C43.003,24.571 48.131,29.712 48.131,36.026Z" android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#00E1EBF0" android:fillType="nonZero" android:pathData="M48.131,36.026C48.131,42.341 43.003,47.482 36.694,47.504L36.694,24.549C43.003,24.571 48.131,29.712 48.131,36.026Z" android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#FFFFFF" android:fillType="nonZero" android:pathData="M41.863,34.374L36.694,39.543L35.577,40.66C35.313,40.924 34.967,41.056 34.621,41.056C34.275,41.056 33.929,40.924 33.665,40.66L31.264,38.258C30.736,37.73 30.736,36.875 31.264,36.347C31.791,35.819 32.646,35.819 33.174,36.347L34.621,37.794L39.952,32.463C40.48,31.935 41.336,31.935 41.863,32.463C42.391,32.991 42.391,33.847 41.863,34.374Z" android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#F8F6F6" android:fillType="nonZero" android:pathData="M41.863,34.374L36.694,39.543L36.694,35.721L39.952,32.463C40.48,31.935 41.336,31.935 41.863,32.463C42.391,32.991 42.391,33.847 41.863,34.374Z" android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="50dp"
|
||||||
android:height="24dp"
|
android:height="50dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/textColor"
|
android:fillColor="#FF000000"
|
||||||
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2H18c1.1,0 2,-0.9 2,-2V8l-6,-6H6zM13,9V3.5L18.5,9H13z"/>
|
android:pathData="M13.172,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8.828c0,-0.53 -0.211,-1.039 -0.586,-1.414l-4.828,-4.828C14.211,2.211 13.702,2 13.172,2zM15,18H9c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0C16,17.552 15.552,18 15,18zM15,14H9c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0C16,13.552 15.552,14 15,14zM13,9V3.5L18.5,9H13z"/>
|
||||||
</vector>
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_file_no_item.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="50dp"
|
||||||
|
android:height="50dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#757575"
|
||||||
|
android:pathData="M13.172,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8.828c0,-0.53 -0.211,-1.039 -0.586,-1.414l-4.828,-4.828C14.211,2.211 13.702,2 13.172,2zM15,18H9c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0C16,17.552 15.552,18 15,18zM15,14H9c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0C16,13.552 15.552,14 15,14zM13,9V3.5L18.5,9H13z"/>
|
||||||
|
</vector>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnChangePassword"
|
android:id="@+id/btnChangePassword"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="50dp"
|
||||||
android:layout_marginTop="32dp"
|
android:layout_marginTop="32dp"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:text="@string/change_password"
|
android:text="@string/change_password"
|
||||||
|
|||||||
@@ -39,57 +39,38 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:id="@+id/folderName"/>
|
android:id="@+id/folderName"/>
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_width="40dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="40dp"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_edit"
|
app:icon="@drawable/ic_edit"
|
||||||
app:tint="?attr/colorPrimary"
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:padding="9dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:background="#00000000"
|
|
||||||
android:id="@+id/edit"/>
|
android:id="@+id/edit"/>
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/delete"
|
android:id="@+id/delete"
|
||||||
android:layout_width="40dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="40dp"
|
android:layout_height="wrap_content"
|
||||||
android:background="#00000000"
|
app:icon="@drawable/ic_delete"
|
||||||
android:padding="9dp"
|
style="@style/Widget.Material3.Button.IconButton" />
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/ic_delete"
|
|
||||||
app:tint="?attr/colorPrimary"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/menuButton"
|
android:id="@+id/menuButton"
|
||||||
android:layout_width="40dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="40dp"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_more"
|
app:icon="@drawable/ic_more"
|
||||||
app:tint="?attr/colorPrimary"
|
style="@style/Widget.Material3.Button.IconButton"/>
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:padding="9dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:background="#00000000"/>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_width="40dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="40dp"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_list"
|
app:icon="@drawable/ic_list"
|
||||||
android:scaleType="fitCenter"
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
app:tint="?attr/colorPrimary"
|
|
||||||
android:background="#00000000"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:id="@+id/folderOrientation"/>
|
android:id="@+id/folderOrientation"/>
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_width="40dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="40dp"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_settings"
|
app:icon="@drawable/ic_settings"
|
||||||
android:scaleType="fitCenter"
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
app:tint="?attr/colorPrimary"
|
|
||||||
|
|
||||||
android:background="#00000000"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:id="@+id/settings"/>
|
android:id="@+id/settings"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -128,7 +109,7 @@
|
|||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:text="@string/no_items_available_add_one_by_clicking_on_the_plus_button"
|
android:text="@string/there_is_no_folders_available_create_one_by_clicking_on_the_add_folder_button_showing_in_the_bottom"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
android:layout_margin="0dp"
|
android:layout_margin="0dp"
|
||||||
android:background="@drawable/bottom_corner"
|
android:background="@drawable/bottom_corner"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHeight_percent="0.3"
|
app:layout_constraintHeight_percent="0.4"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
android:paddingTop="27dp"
|
||||||
android:gravity="end|bottom"
|
android:gravity="end|bottom"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/total"
|
app:layout_constraintBottom_toTopOf="@+id/total"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -47,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>
|
||||||
@@ -59,7 +60,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:autoSizeMaxTextSize="40sp"
|
android:autoSizeMaxTextSize="40sp"
|
||||||
android:autoSizeMinTextSize="24sp"
|
android:autoSizeMinTextSize="12sp"
|
||||||
android:autoSizeStepGranularity="2sp"
|
android:autoSizeStepGranularity="2sp"
|
||||||
android:autoSizeTextType="uniform"
|
android:autoSizeTextType="uniform"
|
||||||
android:gravity="end|bottom"
|
android:gravity="end|bottom"
|
||||||
@@ -85,7 +86,7 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/displayContainer">
|
app:layout_constraintTop_toBottomOf="@id/displayContainer">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@@ -95,49 +96,68 @@
|
|||||||
android:id="@+id/btnClear"
|
android:id="@+id/btnClear"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:layout_weight="1"
|
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||||
android:text="C"
|
android:text="C"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnPercent"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnPercent"
|
android:id="@+id/btnPercent"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="%"
|
android:text="%"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
android:layout_marginHorizontal="3dp"
|
||||||
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnDivide"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btnClear"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnDivide"
|
android:id="@+id/btnDivide"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="÷"
|
android:text="÷"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
android:layout_marginHorizontal="3dp"
|
||||||
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/cut"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btnPercent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/cut"
|
android:id="@+id/cut"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_marginHorizontal="4dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
android:background="@drawable/gradient_bg"
|
android:background="@drawable/gradient_bg"
|
||||||
|
android:scaleType="centerInside"
|
||||||
android:src="@drawable/backspace"
|
android:src="@drawable/backspace"
|
||||||
android:scaleType="centerInside"/>
|
android:layout_marginHorizontal="3dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btnDivide"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@@ -147,10 +167,16 @@
|
|||||||
android:id="@+id/btn7"
|
android:id="@+id/btn7"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="7"
|
android:text="7"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn8"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp" />
|
||||||
|
|
||||||
@@ -158,21 +184,32 @@
|
|||||||
android:id="@+id/btn8"
|
android:id="@+id/btn8"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="8"
|
android:text="8"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn9"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn7"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn9"
|
android:id="@+id/btn9"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="9"
|
android:text="9"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnMultiply"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn8"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp" />
|
||||||
|
|
||||||
@@ -180,18 +217,22 @@
|
|||||||
android:id="@+id/btnMultiply"
|
android:id="@+id/btnMultiply"
|
||||||
style="@style/CustomMaterialButton"
|
style="@style/CustomMaterialButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_marginHorizontal="4dp"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textColor="@color/white"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="×"
|
android:text="×"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn9"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@@ -201,51 +242,71 @@
|
|||||||
android:id="@+id/btn4"
|
android:id="@+id/btn4"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="4"
|
android:text="4"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn5"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn5"
|
android:id="@+id/btn5"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="5"
|
android:text="5"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn6"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn4"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn6"
|
android:id="@+id/btn6"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="6"
|
android:text="6"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnMinus"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn5"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnMinus"
|
android:id="@+id/btnMinus"
|
||||||
style="@style/CustomMaterialButton"
|
style="@style/CustomMaterialButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_marginHorizontal="4dp"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:text="-"
|
android:text="-"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn6"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@@ -255,92 +316,144 @@
|
|||||||
android:id="@+id/btn1"
|
android:id="@+id/btn1"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="1"
|
android:text="1"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn2"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn2"
|
android:id="@+id/btn2"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="2"
|
android:text="2"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn3"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn1"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn3"
|
android:id="@+id/btn3"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="3"
|
android:text="3"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnPlus"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn2"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnPlus"
|
android:id="@+id/btnPlus"
|
||||||
style="@style/CustomMaterialButton"
|
style="@style/CustomMaterialButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_marginHorizontal="4dp"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="+"
|
android:text="+"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn3"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
/>
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btn00"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
|
android:text="00"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="30sp"
|
||||||
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn0"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btn0"
|
android:id="@+id/btn0"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
android:layout_weight="1"
|
||||||
android:layout_weight="2"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="0"
|
android:text="0"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnDot"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn00"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnDot"
|
android:id="@+id/btnDot"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="4dp"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="."
|
android:text="."
|
||||||
|
android:layout_weight="1"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1.15"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnEquals"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btn0"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnEquals"
|
android:id="@+id/btnEquals"
|
||||||
style="@style/CustomMaterialButton"
|
style="@style/CustomMaterialButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_marginHorizontal="4dp"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textColor="@color/white"
|
android:layout_marginHorizontal="3dp"
|
||||||
android:text="="
|
android:text="="
|
||||||
|
android:textColor="@color/white"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
app:cornerRadius="15dp" />
|
app:cornerRadius="15dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btnDot"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,9 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:cardCornerRadius="10dp"
|
app:cardCornerRadius="10dp"
|
||||||
app:cardElevation="5dp">
|
android:background="#00000000"
|
||||||
|
android:backgroundTint="#00000000"
|
||||||
|
app:cardElevation="0dp">
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/appLogo"
|
android:id="@+id/appLogo"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
@@ -254,6 +256,23 @@
|
|||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/restrict_screenshots_in_hidden_section"
|
android:text="@string/restrict_screenshots_in_hidden_section"
|
||||||
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/encryptionSwitch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/encrypt_file_when_hiding"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/customKeyStatus"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/set_custom_encryption_key"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
|||||||
@@ -40,29 +40,32 @@
|
|||||||
android:id="@+id/folderName"/>
|
android:id="@+id/folderName"/>
|
||||||
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/menuButton"
|
android:id="@+id/menuButton"
|
||||||
android:layout_width="40dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="40dp"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_more"
|
app:icon="@drawable/ic_more"
|
||||||
app:tint="?attr/colorPrimary"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:padding="7dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:background="#00000000"/>
|
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"
|
||||||
@@ -79,7 +82,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:src="@drawable/ic_no_items" />
|
android:src="@drawable/ic_file_no_item" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/noItemsTxt"
|
android:id="@+id/noItemsTxt"
|
||||||
|
|||||||
47
app/src/main/res/layout/dialog_custom_key.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="Enter encryption key"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/keyInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Confirm encryption key"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/confirmKeyInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Note: Make sure to remember your key. If you lose it, you won't be able to decrypt your files."
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="?android:textColorSecondary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
29
app/src/main/res/layout/dialog_file_type_selection.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkboxImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Image"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkboxVideo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Video"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkboxAudio"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Audio"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -10,8 +10,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="4dp"
|
android:layout_margin="4dp"
|
||||||
app:cardCornerRadius="8dp"
|
app:cardCornerRadius="8dp">
|
||||||
app:cardElevation="2dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.card.MaterialCardView 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:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="12dp"
|
android:layout_margin="3dp"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<ImageView
|
<LinearLayout
|
||||||
android:id="@+id/folderIcon"
|
|
||||||
android:layout_width="34dp"
|
|
||||||
android:layout_height="34dp"
|
|
||||||
android:src="@drawable/ic_folder"
|
|
||||||
app:tint="?attr/colorPrimary" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/folderName"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:padding="12dp"
|
||||||
android:textSize="18sp"/>
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/folderIcon"
|
||||||
|
android:layout_width="34dp"
|
||||||
|
android:layout_height="34dp"
|
||||||
|
android:src="@drawable/ic_folder"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
</LinearLayout>
|
<TextView
|
||||||
|
android:id="@+id/folderName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:textSize="18sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
@@ -15,13 +15,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/cardView"
|
android:id="@+id/cardView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:cardCornerRadius="10dp"
|
app:cardCornerRadius="10dp"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
app:cardElevation="3dp"
|
android:backgroundTint="#404040"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -35,8 +35,12 @@
|
|||||||
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
|
||||||
|
android:id="@+id/shade"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/bottom_shade"/>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/selectedLayer"
|
android:id="@+id/selectedLayer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -49,7 +53,21 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fileNameTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="13dp"
|
||||||
|
android:paddingVertical="6dp"
|
||||||
|
android:textColor="#fff"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/cardView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
<ImageView
|
<ImageView
|
||||||
@@ -60,6 +78,13 @@
|
|||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:src="@drawable/ic_play_circle"
|
android:src="@drawable/ic_play_circle"
|
||||||
android:layout_gravity="center"/>
|
android:layout_gravity="center"/>
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:src="@drawable/encrypted"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:id="@+id/encrypted"/>
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/selected"
|
android:id="@+id/selected"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
@@ -70,16 +95,7 @@
|
|||||||
android:src="@drawable/selected"
|
android:src="@drawable/selected"
|
||||||
android:background="@drawable/gradient_bg"
|
android:background="@drawable/gradient_bg"
|
||||||
android:layout_gravity="end"/>
|
android:layout_gravity="end"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/fileNameTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:padding="5dp"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
app:cardCornerRadius="15dp"
|
app:cardCornerRadius="15dp"
|
||||||
app:cardElevation="10dp"
|
|
||||||
android:layout_margin="20dp">
|
android:layout_margin="20dp">
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@android:color/transparent" />
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
<item name="android:windowLightStatusBar">false</item>
|
<item name="android:windowLightStatusBar">false</item>
|
||||||
|
<item name="colorSecondaryContainer">#481A4324</item>
|
||||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
|
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
<string name="preview_videos">Preview Videos</string>
|
<string name="preview_videos">Preview Videos</string>
|
||||||
<string name="preview_audios">Preview Audios</string>
|
<string name="preview_audios">Preview Audios</string>
|
||||||
<string name="preview_documents">Preview Documents</string>
|
<string name="preview_documents">Preview Documents</string>
|
||||||
<string name="delete_file">Delete File</string>
|
<string name="delete_file">Delete File!</string>
|
||||||
<string name="are_you_sure_to_delete_this_file_permanently">Are you sure to Delete this file permanently?</string>
|
<string name="are_you_sure_to_delete_this_file_permanently">Are you sure to Delete this file permanently?</string>
|
||||||
<string name="delete_permanently">Delete Permanently</string>
|
<string name="delete_permanently">Delete Permanently</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
<string name="unknown_file">Unknown File</string>
|
<string name="unknown_file">Unknown File</string>
|
||||||
<string name="details"> DETAILS</string>
|
<string name="details"> DETAILS</string>
|
||||||
<string name="audio_hidded_successfully">Audios hidden successfully</string>
|
<string name="audio_hidded_successfully">Audios hidden successfully</string>
|
||||||
<string name="no_items_available_add_one_by_clicking_on_the_plus_button">No Items Available, Add one by clicking on the</string>
|
<string name="no_items_available_add_one_by_clicking_on_the_plus_button">No Files Available, Add one by clicking on the \'+\' button</string>
|
||||||
<string name="now_enter_button">Now Enter \'=\' button</string>
|
<string name="now_enter_button">Now Enter \'=\' button</string>
|
||||||
<string name="enter_123456">Enter 123456</string>
|
<string name="enter_123456">Enter 123456</string>
|
||||||
<string name="create_folder">Create Folder</string>
|
<string name="create_folder">Create Folder</string>
|
||||||
@@ -79,6 +80,7 @@
|
|||||||
<string name="error_loading_files">Error loading files</string>
|
<string name="error_loading_files">Error loading files</string>
|
||||||
<string name="delete_items">Delete Folder</string>
|
<string name="delete_items">Delete Folder</string>
|
||||||
<string name="are_you_sure_you_want_to_delete_selected_items">Are you sure you want to delete selected Folders?</string>
|
<string name="are_you_sure_you_want_to_delete_selected_items">Are you sure you want to delete selected Folders?</string>
|
||||||
|
<string name="folder_will_be_deleted_permanently">Note: If you delete the folder(S) all the files available inside the folder will be deleted !</string>
|
||||||
<string name="items_deleted_successfully">Items deleted successfully</string>
|
<string name="items_deleted_successfully">Items deleted successfully</string>
|
||||||
<string name="folder_deleted_successfully">Folder deleted successfully</string>
|
<string name="folder_deleted_successfully">Folder deleted successfully</string>
|
||||||
<string name="some_items_could_not_be_deleted">Some items could not be deleted</string>
|
<string name="some_items_could_not_be_deleted">Some items could not be deleted</string>
|
||||||
@@ -107,8 +109,8 @@
|
|||||||
<string name="are_you_sure_you_want_to_un_hide_selected_files">Are you sure you want to unhide the selected files?</string>
|
<string name="are_you_sure_you_want_to_un_hide_selected_files">Are you sure you want to unhide the selected files?</string>
|
||||||
<string name="files_unhidden_successfully">Files unhidden successfully</string>
|
<string name="files_unhidden_successfully">Files unhidden successfully</string>
|
||||||
<string name="some_files_could_not_be_unhidden">Some files could not be unhidden</string>
|
<string name="some_files_could_not_be_unhidden">Some files could not be unhidden</string>
|
||||||
<string name="copy_to_another_folder">Copy to Another Folder</string>
|
<string name="copy_to_another_folder">Copy</string>
|
||||||
<string name="move_to_another_folder">Move to Another Folder</string>
|
<string name="move_to_another_folder">Move</string>
|
||||||
<string name="select_destination_folder">Select Destination Folder</string>
|
<string name="select_destination_folder">Select Destination Folder</string>
|
||||||
<string name="no_folders_available">No other folders available</string>
|
<string name="no_folders_available">No other folders available</string>
|
||||||
<string name="change_password">Change Password</string>
|
<string name="change_password">Change Password</string>
|
||||||
@@ -120,10 +122,58 @@
|
|||||||
<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.3</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>
|
||||||
<string name="security_answer">Security Answer</string>
|
<string name="security_answer">Security Answer</string>
|
||||||
<string name="save_password">Save Password</string>
|
<string name="save_password">Save Password</string>
|
||||||
|
<string name="there_is_no_folders_available_create_one_by_clicking_on_the_add_folder_button_showing_in_the_bottom">There is no Folders Available, create one by clicking on the \'Add Folder\' Button showing in the bottom.</string>
|
||||||
|
<string name="storage_permission">Storage Permission</string>
|
||||||
|
<string name="to_ensure_the_app_works_properly_and_allows_you_to_easily_hide_or_un_hide_your_private_files_please_grant_storage_access_permission">To ensure the app works properly and allows you to easily hide or un-hide your private files, please grant storage access permission.\n</string>
|
||||||
|
<string name="for_devices_running_android_11_or_higher_you_ll_need_to_grant_the_all_files_access_permission">For devices running Android 11 or higher, you\'ll need to grant the \'All Files Access\' permission.</string>
|
||||||
|
<string name="grant_permission">Grant Permission</string>
|
||||||
|
<string name="later">Later</string>
|
||||||
|
<string name="storage_permission_is_required_for_the_app_to_function_properly">Storage permission is required for the app to function properly</string>
|
||||||
|
<string name="you_can_grant_permission_later_from_settings">You can grant permission later from Settings</string>
|
||||||
|
<string name="permission_granted">Permission granted</string>
|
||||||
|
<string name="permission_denied">Permission denied</string>
|
||||||
|
<string name="github_profile">https://github.com/binondi</string>
|
||||||
|
<string name="calculator_hide_files">%1$s/calculator-hide-files</string>
|
||||||
|
<string name="would_you_like_to_set_a_specific_theme_mode">Would you like to set a specific theme mode?</string>
|
||||||
|
<string name="no">No</string>
|
||||||
|
<string name="could_not_open_url">Could not open URL</string>
|
||||||
|
<string name="files_copied_successfully">Files copied successfully</string>
|
||||||
|
<string name="some_files_could_not_be_copied">Some files could not be copied</string>
|
||||||
|
<string name="files_moved_successfully">Files moved successfully</string>
|
||||||
|
<string name="some_files_could_not_be_moved">Some files could not be moved</string>
|
||||||
|
<string name="ok">OK</string>
|
||||||
|
<string name="if_you_turn_on_off_this_option_dynamic_theme_changes_will_be_visible_after_you_reopen_the_app">If you turn on/off this option, dynamic theme changes will be visible after you reopen the app.</string>
|
||||||
|
<string name="attention">Attention!</string>
|
||||||
|
<string name="are_you_sure_to_delete_selected_files_permanently">Are you sure to delete selected files permanently ?</string>
|
||||||
|
<string name="encrypt_file_when_hiding">Encrypt File When Hiding</string>
|
||||||
|
<string name="encrypt_file">Encrypt File</string>
|
||||||
|
<string name="decrypt_file">Decrypt File</string>
|
||||||
|
<string name="encrypt_files">Encrypt Files</string>
|
||||||
|
<string name="decrypt_files">Decrypt Files</string>
|
||||||
|
<string name="encrypt">Encrypt</string>
|
||||||
|
<string name="decrypt">Decrypt</string>
|
||||||
|
<string name="encryption_disclaimer">Warning: This will encrypt the selected files. Please use a custom encryption key from Settings, you can only decrypt the encrypted files with the same key so if you use default key and uninstall the app without decrypting the file you cannot decrypt the file later!. Do you want to continue?</string>
|
||||||
|
<string name="decryption_disclaimer">Warning: This will decrypt the selected files. The encrypted files will be deleted after successful decryption. Make sure to do this. Do you want to continue?</string>
|
||||||
|
<string name="custom_encryption_key_cleared">Custom encryption key cleared</string>
|
||||||
|
<string name="failed_to_set_custom_key">Failed to set custom key</string>
|
||||||
|
<string name="custom_key_set_successfully">Custom key set successfully</string>
|
||||||
|
<string name="keys_do_not_match">Keys do not match</string>
|
||||||
|
<string name="key_cannot_be_empty">Key cannot be empty</string>
|
||||||
|
<string name="set_custom_encryption_key">Set Custom Encryption Key</string>
|
||||||
|
<string name="set">Set</string>
|
||||||
|
<string name="delete_key">Delete Key</string>
|
||||||
|
<string name="enter_folder_name_to_create">Enter Folder Name To Create</string>
|
||||||
|
<string name="please_select_exactly_one_folder_to_edit">Please select exactly one folder to edit</string>
|
||||||
|
<string name="invalid_folder_name">Invalid folder name</string>
|
||||||
|
<string name="folder_with_this_name_already_exists">Folder with this name already exists</string>
|
||||||
|
<string name="failed_to_rename_folder">Failed to rename folder</string>
|
||||||
|
<string name="select_file_type">Select File Type!</string>
|
||||||
|
<string name="please_select_the_type_of_file_to_decrypt">Please select the type of file to decrypt</string>
|
||||||
|
<string name="file_no_longer_exists">File no longer exists</string>
|
||||||
|
<string name="something_went_wrong">Something went wrong !</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
<item name="colorControlNormal">#483CFF61</item>
|
<item name="colorControlNormal">#483CFF61</item>
|
||||||
|
<item name="colorSecondaryContainer">#48BDFFCB</item>
|
||||||
|
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
|
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<!-- Allow access to external files -->
|
|
||||||
<external-path name="external_files" path="." />
|
<external-path name="external_files" path="." />
|
||||||
<!-- Allow access to hidden directories -->
|
|
||||||
<external-files-path name="hidden_files" path=".CalculatorHide/" />
|
<external-files-path name="hidden_files" path=".CalculatorHide/" />
|
||||||
|
<cache-path name="cache_files" path="." />
|
||||||
</paths>
|
</paths>
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.9.1"
|
agp = "8.10.1"
|
||||||
documentfile = "1.0.1"
|
documentfile = "1.1.0"
|
||||||
exp4j = "0.4.8"
|
exp4j = "0.4.8"
|
||||||
glide = "4.16.0"
|
glide = "4.16.0"
|
||||||
kotlin = "1.9.24"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.15.0"
|
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.9.3"
|
activity = "1.10.1"
|
||||||
constraintlayout = "2.2.0"
|
constraintlayout = "2.2.1"
|
||||||
materialColorUtilities = "1.3.0"
|
materialColorUtilities = "1.3.0"
|
||||||
gridlayout = "1.0.0"
|
gridlayout = "1.1.0"
|
||||||
photoview = "2.3.0"
|
photoview = "2.3.0"
|
||||||
|
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"
|
||||||
|
|
||||||
[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" }
|
||||||
androidx-documentfile = { module = "androidx.documentfile:documentfile", version.ref = "documentfile" }
|
androidx-documentfile = { module = "androidx.documentfile:documentfile", version.ref = "documentfile" }
|
||||||
|
androidx-room-compiler = { module = "androidx.room:room-compiler", 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-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" }
|
||||||
|
|||||||
BIN
media/Screenshot_1.jpg
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
media/Screenshot_2.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
media/Screenshot_3.jpg
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
media/Screenshot_4.jpg
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
media/Screenshot_5.jpg
Normal file
|
After Width: | Height: | Size: 406 KiB |
BIN
media/Screenshot_6.jpg
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
media/Screenshot_7.jpg
Normal file
|
After Width: | Height: | Size: 469 KiB |
BIN
media/Screenshot_8.jpg
Normal file
|
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 |