Compare commits
59 Commits
1.3
...
92dcfbd0ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92dcfbd0ae | ||
|
|
db2326f3e7 | ||
|
|
5efeb6876b | ||
|
|
82982aa221 | ||
|
|
6f151554f0 | ||
|
|
5ecd3ec7d1 | ||
|
|
946953d4eb | ||
|
|
18172f5191 | ||
|
|
603d96b672 | ||
|
|
78ea4596ea | ||
|
|
a70a569b57 | ||
|
|
df0db1a479 | ||
|
|
9cdc6eb1a4 | ||
|
|
913af83b90 | ||
|
|
7ceb599d9f | ||
|
|
dd1767e55d | ||
|
|
28ccdda1bf | ||
|
|
27a538f7c6 | ||
|
|
22c2a64450 | ||
|
|
3af3f81f3c | ||
|
|
f2e206f208 | ||
|
|
2de1b28afe | ||
|
|
ccf291d2e2 | ||
|
|
0968d5c19b | ||
|
|
d64904fbe7 | ||
|
|
2da2c944a3 | ||
|
|
13e1fca28f | ||
|
|
5c5e0e4be8 | ||
|
|
aad939463c | ||
|
|
e4e2983acd | ||
|
|
8702491f85 | ||
|
|
5263f89cd3 | ||
|
|
0787d6dd5b | ||
|
|
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"?>
|
||||
<project version="4">
|
||||
<component name="AppInsightsSettings">
|
||||
<option name="selectedTabId" value="Firebase Crashlytics" />
|
||||
<option name="tabSettings">
|
||||
<map>
|
||||
<entry key="Firebase Crashlytics">
|
||||
|
||||
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.24" />
|
||||
<option name="version" value="2.0.0" />
|
||||
</component>
|
||||
</project>
|
||||
59
README.md
@@ -1,23 +1,31 @@
|
||||

|
||||
|
||||
<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 📂
|
||||
|
||||
<a href="https://github.com/Binondi/Calculator-Hide-Files/releases/latest">
|
||||
<img alt="Latest release" src="https://img.shields.io/badge/Releases-v1.0-blue?logo=github&style=for-the-badge">
|
||||
<img alt="Latest release" src="https://img.shields.io/badge/Releases-v1.4.2-blue?logo=github&style=for-the-badge">
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/Binondi/Calculator-Hide-Files/releases/latest">
|
||||
<img alt="Downloads" src="https://img.shields.io/badge/Downloads-1.3k-blue?logo=github&style=for-the-badge">
|
||||
<img alt="Downloads" src="https://img.shields.io/badge/downloads-1.1k-blue?logo=github&style=for-the-badge">
|
||||
</a>
|
||||
|
||||
<a 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>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
<br>
|
||||
</div>
|
||||
<div align="center">
|
||||
|
||||
<h4>Download</h4>
|
||||
|
||||
<a>[<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png" alt="Get it on GitHub" height="80">](https://github.com/binondi/Calculator-Hide-Files/releases) </a><a href="https://apt.izzysoft.de/fdroid/index/apk/devs.org.calculator"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="80"></a> <a href="https://www.androidfreeware.net/download-apk-devs-org-calculator.html"><img src="https://www.androidfreeware.net/images/androidfreeware-badge.png" height="80"></a>
|
||||
|
||||
</div>
|
||||
|
||||
## 😍 Why Choose This App?
|
||||
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.
|
||||
> - Works like a **real calculator** with hidden storage mode.
|
||||
> - No one will suspect it’s a file vault!
|
||||
|
||||
> - **Encrypt** files with your **custom key** to secure your hidden files.
|
||||
---
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
✅ **Dual Functionality** – A working **calculator** & a **file vault** in one app.
|
||||
✅ **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.
|
||||
✅ **No Root Required** – Works without rooting your phone.
|
||||
|
||||
✅ **Security** – Encrypt & Decrypt files.
|
||||
---
|
||||
|
||||
## 🖼️ Screenshots
|
||||
|
||||
<div align="center">
|
||||
<img src="app/src/main/assets/Screenshot_1.jpg" alt="Calculator Hide File App - Home Screen" width="24%">
|
||||
<img src="app/src/main/assets/Screenshot_2.jpg" alt="Calculator Hide File App - Secure File Storage" width="24%">
|
||||
<img src="app/src/main/assets/Screenshot_3.jpg" alt="Calculator Hide File App - Passcode Protection" width="24%">
|
||||
<img src="app/src/main/assets/Screenshot_4.jpg" alt="Calculator Hide File App - Hidden Files Manager" width="24%">
|
||||
<img src="media/Screenshot_1.jpg" alt="Calculator Hide File App - Home Screen" width="32%">
|
||||
<img src="media/Screenshot_2.jpg" alt="Calculator Hide File App - Secure File Storage" width="32%">
|
||||
<img src="media/Screenshot_3.jpg" alt="Calculator Hide File App - Passcode Protection" width="32%">
|
||||
</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>
|
||||
|
||||
---
|
||||
@@ -68,7 +87,9 @@ The **Calculator Hide File App** is an innovative **Android file-hiding app** th
|
||||
|
||||
3. **Manage 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -78,7 +99,8 @@ The **Calculator Hide File App** is an innovative **Android file-hiding app** th
|
||||
|
||||
### 🔹 Prerequisites
|
||||
- **Android 6.0 or higher**
|
||||
- **Storage permissions enabled**
|
||||
- **Storage permissions enabled**
|
||||
- **Need Manage All Files Permissons For Android 11 and Higher Versions.**
|
||||
|
||||
### 🔹 Installation Steps
|
||||
```bash
|
||||
@@ -92,7 +114,7 @@ git clone https://github.com/Binondi/Calculator-Hide-Files.git
|
||||
## 🛠️ Technologies Used
|
||||
|
||||
- **Programming Language**: Kotlin
|
||||
- **UI Framework**: XML (For UI)
|
||||
- **UI Framework**: XML
|
||||
- **File Storage**: Secure internal storage & MediaStore API
|
||||
|
||||
---
|
||||
@@ -111,7 +133,12 @@ If you find this app useful, please consider supporting the development. 🙏
|
||||
|
||||
[](https://github.com/sponsors/Binondi)
|
||||
[](https://paypal.me/BinondiBorthakur56)
|
||||
[](https://buymeacoffee.com/binondi)
|
||||
|
||||
- **UPI ID** 📱
|
||||
``
|
||||
binondi@naviaxis
|
||||
``
|
||||
---
|
||||
|
||||
## 🔧 Contributing
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
id("kotlin-kapt")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -10,9 +11,10 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "devs.org.calculator"
|
||||
minSdk = 26
|
||||
//noinspection OldTargetApi
|
||||
targetSdk = 34
|
||||
versionCode = 4
|
||||
versionName = "1.3"
|
||||
versionCode = 7
|
||||
versionName = "1.4.2"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -68,6 +70,7 @@ dependencies {
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.gridlayout)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
@@ -80,4 +83,11 @@ dependencies {
|
||||
implementation(libs.androidx.viewpager)
|
||||
implementation(libs.zoomage)
|
||||
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": 6,
|
||||
"versionName": "1.4.1",
|
||||
"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
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="devs.org.calculator.fileprovider"
|
||||
android:authorities="devs.org.calculator.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.icon"
|
||||
android:resource="@mipmap/ic_launcher_monochrome" />
|
||||
</application>
|
||||
|
||||
</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 androidx.appcompat.app.AppCompatDelegate
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import devs.org.calculator.utils.PrefsUtil
|
||||
|
||||
class CalculatorApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// Initialize theme settings
|
||||
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
||||
|
||||
// Apply saved theme mode
|
||||
val prefs = PrefsUtil(this)
|
||||
val themeMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
AppCompatDelegate.setDefaultNightMode(themeMode)
|
||||
|
||||
// Apply dynamic colors only if dynamic theme is enabled
|
||||
if (prefs.getBoolean("dynamic_theme", true)) {
|
||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
}
|
||||
|
||||
@@ -5,14 +5,16 @@ import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import devs.org.calculator.R
|
||||
@@ -36,21 +38,24 @@ class HiddenActivity : AppCompatActivity() {
|
||||
private var folderAdapter: FolderAdapter? = null
|
||||
private var listFolderAdapter: ListFolderAdapter? = null
|
||||
private val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||
private val prefs:PrefsUtil by lazy { PrefsUtil(this) }
|
||||
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
companion object {
|
||||
private const val TAG = "HiddenActivity"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityHiddenBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
fileManager = FileManager(this, this)
|
||||
folderManager = FolderManager(this)
|
||||
folderManager = FolderManager()
|
||||
dialogUtil = DialogUtil(this)
|
||||
enableEdgeToEdge()
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
|
||||
setupInitialUIState()
|
||||
setupClickListeners()
|
||||
@@ -100,22 +105,26 @@ class HiddenActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.folderOrientation.setOnClickListener {
|
||||
// Switch between grid mode and list mode
|
||||
val currentIsList = PrefsUtil(this).getBoolean("isList", false)
|
||||
val newIsList = !currentIsList
|
||||
|
||||
if (newIsList) {
|
||||
// Switch to list view
|
||||
showListUI()
|
||||
PrefsUtil(this).setBoolean("isList", true)
|
||||
binding.folderOrientation.setImageResource(R.drawable.ic_grid)
|
||||
binding.folderOrientation.setIconResource(R.drawable.ic_grid)
|
||||
} else {
|
||||
// Switch to grid view
|
||||
showGridUI()
|
||||
PrefsUtil(this).setBoolean("isList", false)
|
||||
binding.folderOrientation.setImageResource(R.drawable.ic_list)
|
||||
binding.folderOrientation.setIconResource(R.drawable.ic_list)
|
||||
}
|
||||
}
|
||||
|
||||
binding.random.setOnClickListener {
|
||||
val folders = folderManager.getFoldersInDirectory(hiddenDir)
|
||||
val randomIndex = (Math.random() * folders.size).toInt()
|
||||
val folder = folders[randomIndex]
|
||||
startActivity(Intent(this,ViewFolderActivity::class.java).putExtra("folder",folder.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showGridUI() {
|
||||
@@ -141,11 +150,10 @@ class HiddenActivity : AppCompatActivity() {
|
||||
showEmptyState()
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Hidden directory is not accessible: ${hiddenDir.absolutePath}")
|
||||
showEmptyState()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error listing folders: ${e.message}")
|
||||
} catch (_: Exception) {
|
||||
|
||||
showEmptyState()
|
||||
}
|
||||
}
|
||||
@@ -164,26 +172,25 @@ class HiddenActivity : AppCompatActivity() {
|
||||
val inputEditText = dialogView.findViewById<EditText>(R.id.editText)
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Enter Folder Name To Create")
|
||||
.setTitle(getString(R.string.enter_folder_name_to_create))
|
||||
.setView(dialogView)
|
||||
.setPositiveButton("Create") { dialog, _ ->
|
||||
.setPositiveButton(getString(R.string.create)) { dialog, _ ->
|
||||
val newName = inputEditText.text.toString().trim()
|
||||
if (newName.isNotEmpty()) {
|
||||
try {
|
||||
folderManager.createFolder(hiddenDir, newName)
|
||||
refreshCurrentView()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error creating folder: ${e.message}")
|
||||
} catch (_: Exception) {
|
||||
Toast.makeText(
|
||||
this@HiddenActivity,
|
||||
"Failed to create folder",
|
||||
getString(R.string.failed_to_create_folder),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton("Cancel") { dialog, _ ->
|
||||
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.show()
|
||||
@@ -195,7 +202,6 @@ class HiddenActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun setupFlagSecure() {
|
||||
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
||||
if (prefs.getBoolean("screenshot_restriction", true)) {
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
@@ -220,11 +226,9 @@ class HiddenActivity : AppCompatActivity() {
|
||||
showEmptyState()
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Hidden directory is not accessible: ${hiddenDir.absolutePath}")
|
||||
showEmptyState()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error listing folders: ${e.message}")
|
||||
} catch (_: Exception) {
|
||||
showEmptyState()
|
||||
}
|
||||
}
|
||||
@@ -232,8 +236,6 @@ class HiddenActivity : AppCompatActivity() {
|
||||
private fun showFolderList(folders: List<File>) {
|
||||
binding.noItems.visibility = View.GONE
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
|
||||
// Clear the existing adapter to avoid conflicts
|
||||
listFolderAdapter = null
|
||||
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 2)
|
||||
@@ -247,14 +249,13 @@ class HiddenActivity : AppCompatActivity() {
|
||||
onSelectionModeChanged = { isSelectionMode ->
|
||||
handleFolderSelectionModeChange(isSelectionMode)
|
||||
},
|
||||
onSelectionCountChanged = { selectedCount ->
|
||||
onSelectionCountChanged = { _ ->
|
||||
updateEditButtonVisibility()
|
||||
}
|
||||
)
|
||||
binding.recyclerView.adapter = folderAdapter
|
||||
folderAdapter?.submitList(folders)
|
||||
|
||||
// Ensure proper icon state for folder view
|
||||
if (folderAdapter?.isInSelectionMode() != true) {
|
||||
showFolderViewIcons()
|
||||
}
|
||||
@@ -262,8 +263,6 @@ class HiddenActivity : AppCompatActivity() {
|
||||
private fun showFolderListStyle(folders: List<File>) {
|
||||
binding.noItems.visibility = View.GONE
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
|
||||
// Clear the existing adapter to avoid conflicts
|
||||
folderAdapter = null
|
||||
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 1)
|
||||
@@ -277,14 +276,13 @@ class HiddenActivity : AppCompatActivity() {
|
||||
onSelectionModeChanged = { isSelectionMode ->
|
||||
handleFolderSelectionModeChange(isSelectionMode)
|
||||
},
|
||||
onSelectionCountChanged = { selectedCount ->
|
||||
onSelectionCountChanged = { _ ->
|
||||
updateEditButtonVisibility()
|
||||
}
|
||||
)
|
||||
binding.recyclerView.adapter = listFolderAdapter
|
||||
listFolderAdapter?.submitList(folders)
|
||||
|
||||
// Ensure proper icon state for folder view
|
||||
if (listFolderAdapter?.isInSelectionMode() != true) {
|
||||
showFolderViewIcons()
|
||||
}
|
||||
@@ -312,10 +310,10 @@ class HiddenActivity : AppCompatActivity() {
|
||||
private fun refreshCurrentView() {
|
||||
val isList = PrefsUtil(this).getBoolean("isList", false)
|
||||
if (isList) {
|
||||
binding.folderOrientation.setImageResource(R.drawable.ic_grid)
|
||||
binding.folderOrientation.setIconResource(R.drawable.ic_grid)
|
||||
listFoldersInHiddenDirectoryListStyle()
|
||||
} else {
|
||||
binding.folderOrientation.setImageResource(R.drawable.ic_list)
|
||||
binding.folderOrientation.setIconResource(R.drawable.ic_list)
|
||||
listFoldersInHiddenDirectory()
|
||||
}
|
||||
}
|
||||
@@ -335,7 +333,7 @@ class HiddenActivity : AppCompatActivity() {
|
||||
if (selectedFolders.isNotEmpty()) {
|
||||
dialogUtil.showMaterialDialog(
|
||||
getString(R.string.delete_items),
|
||||
getString(R.string.are_you_sure_you_want_to_delete_selected_items),
|
||||
"${getString(R.string.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.cancel),
|
||||
object : DialogUtil.DialogCallback {
|
||||
@@ -344,11 +342,11 @@ class HiddenActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
// Do nothing
|
||||
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
// Do nothing
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -360,7 +358,6 @@ class HiddenActivity : AppCompatActivity() {
|
||||
selectedFolders.forEach { folder ->
|
||||
if (!folderManager.deleteFolder(folder)) {
|
||||
allDeleted = false
|
||||
Log.e(TAG, "Failed to delete folder: ${folder.name}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,26 +369,20 @@ class HiddenActivity : AppCompatActivity() {
|
||||
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
|
||||
// Clear selection from both adapters
|
||||
folderAdapter?.clearSelection()
|
||||
listFolderAdapter?.clearSelection()
|
||||
|
||||
// This will trigger the selection mode change callback and show proper icons
|
||||
exitFolderSelectionMode()
|
||||
|
||||
// Refresh the current view based on orientation
|
||||
refreshCurrentView()
|
||||
}
|
||||
|
||||
private fun handleBackPress() {
|
||||
|
||||
|
||||
// Check if folder adapters are in selection mode
|
||||
if (folderAdapter?.onBackPressed() == true || listFolderAdapter?.onBackPressed() == true) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle navigation back
|
||||
if (currentFolder != null) {
|
||||
navigateBackToFolders()
|
||||
} else {
|
||||
@@ -402,26 +393,20 @@ class HiddenActivity : AppCompatActivity() {
|
||||
private fun navigateBackToFolders() {
|
||||
currentFolder = null
|
||||
|
||||
// Clean up file adapter
|
||||
|
||||
refreshCurrentView()
|
||||
|
||||
binding.folderName.text = getString(R.string.hidden_space)
|
||||
|
||||
// Set proper icons for folder view
|
||||
showFolderViewIcons()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
|
||||
// Remove any pending callbacks
|
||||
mainHandler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
//visibility related code
|
||||
private fun showFolderViewIcons() {
|
||||
binding.random.visibility = View.VISIBLE
|
||||
binding.folderOrientation.visibility = View.VISIBLE
|
||||
binding.settings.visibility = View.VISIBLE
|
||||
binding.delete.visibility = View.GONE
|
||||
@@ -429,21 +414,19 @@ class HiddenActivity : AppCompatActivity() {
|
||||
binding.menuButton.visibility = View.GONE
|
||||
binding.addFolder.visibility = View.VISIBLE
|
||||
binding.edit.visibility = View.GONE
|
||||
// Ensure FABs are properly managed
|
||||
if (currentFolder == null) {
|
||||
|
||||
binding.addFolder.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
private fun showFolderSelectionIcons() {
|
||||
binding.random.visibility = View.GONE
|
||||
binding.folderOrientation.visibility = View.GONE
|
||||
binding.settings.visibility = View.GONE
|
||||
binding.delete.visibility = View.VISIBLE
|
||||
binding.deleteSelected.visibility = View.VISIBLE
|
||||
binding.menuButton.visibility = View.GONE
|
||||
binding.addFolder.visibility = View.GONE
|
||||
|
||||
// Update edit button visibility based on current selection count
|
||||
updateEditButtonVisibility()
|
||||
}
|
||||
private fun exitFolderSelectionMode() {
|
||||
@@ -456,7 +439,6 @@ class HiddenActivity : AppCompatActivity() {
|
||||
} else {
|
||||
enterFolderSelectionMode()
|
||||
}
|
||||
// Always update edit button visibility when selection mode changes
|
||||
updateEditButtonVisibility()
|
||||
}
|
||||
|
||||
@@ -468,7 +450,8 @@ class HiddenActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
if (selectedFolders.size != 1) {
|
||||
Toast.makeText(this, "Please select exactly one folder to edit", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this,
|
||||
getString(R.string.please_select_exactly_one_folder_to_edit), Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -483,20 +466,21 @@ class HiddenActivity : AppCompatActivity() {
|
||||
inputEditText.selectAll()
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Rename Folder")
|
||||
.setTitle(getString(R.string.rename_folder))
|
||||
.setView(dialogView)
|
||||
.setPositiveButton("Rename") { dialog, _ ->
|
||||
.setPositiveButton(getString(R.string.rename)) { dialog, _ ->
|
||||
val newName = inputEditText.text.toString().trim()
|
||||
if (newName.isNotEmpty() && newName != folder.name) {
|
||||
if (isValidFolderName(newName)) {
|
||||
renameFolder(folder, newName)
|
||||
} else {
|
||||
Toast.makeText(this, "Invalid folder name", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this,
|
||||
getString(R.string.invalid_folder_name), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton("Cancel") { dialog, _ ->
|
||||
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.show()
|
||||
@@ -515,21 +499,19 @@ class HiddenActivity : AppCompatActivity() {
|
||||
if (parentDir != null) {
|
||||
val newFolder = File(parentDir, newName)
|
||||
if (newFolder.exists()) {
|
||||
Toast.makeText(this, "Folder with this name already exists", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this,
|
||||
getString(R.string.folder_with_this_name_already_exists), Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
if (oldFolder.renameTo(newFolder)) {
|
||||
// Clear selection from both adapters
|
||||
folderAdapter?.clearSelection()
|
||||
listFolderAdapter?.clearSelection()
|
||||
|
||||
// Exit selection mode
|
||||
exitFolderSelectionMode()
|
||||
|
||||
refreshCurrentView()
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to rename folder", Toast.LENGTH_SHORT).show()
|
||||
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.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -22,6 +23,11 @@ import devs.org.calculator.utils.FileManager
|
||||
import devs.org.calculator.utils.PrefsUtil
|
||||
import net.objecthunter.exp4j.ExpressionBuilder
|
||||
import java.util.regex.Pattern
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import devs.org.calculator.utils.StoragePermissionUtil
|
||||
|
||||
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
@@ -34,50 +40,72 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
private val dialogUtil = DialogUtil(this)
|
||||
private val fileManager = FileManager(this, this)
|
||||
private val sp by lazy { getSharedPreferences("app", MODE_PRIVATE) }
|
||||
private lateinit var storagePermissionUtil: StoragePermissionUtil
|
||||
private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
enableEdgeToEdge()
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, 0, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
|
||||
permissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
storagePermissionUtil.handlePermissionResult(permissions)
|
||||
}
|
||||
|
||||
// Initialize StoragePermissionUtil
|
||||
storagePermissionUtil = StoragePermissionUtil(this)
|
||||
|
||||
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
handleActivityResult(result)
|
||||
}
|
||||
|
||||
if (sp.getBoolean("isFirst", true)){
|
||||
binding.display.text = getString(R.string.enter_123456)
|
||||
}
|
||||
|
||||
// Ask permission
|
||||
if(!Environment.isExternalStorageManager()) {
|
||||
|
||||
if (!storagePermissionUtil.hasStoragePermission()) {
|
||||
dialogUtil.showMaterialDialog(
|
||||
"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.storage_permission),
|
||||
getString(R.string.to_ensure_the_app_works_properly_and_allows_you_to_easily_hide_or_un_hide_your_private_files_please_grant_storage_access_permission) +
|
||||
"\n" +
|
||||
"For devices running Android 11 or higher, you'll need to grant the 'All Files Access' permission.",
|
||||
"Grant",
|
||||
"Later",
|
||||
getString(R.string.for_devices_running_android_11_or_higher_you_ll_need_to_grant_the_all_files_access_permission),
|
||||
getString(R.string.grant_permission),
|
||||
getString(R.string.later),
|
||||
object : DialogUtil.DialogCallback {
|
||||
override fun onPositiveButtonClicked() {
|
||||
fileManager.askPermission(this@MainActivity)
|
||||
storagePermissionUtil.requestStoragePermission(permissionLauncher) {
|
||||
Toast.makeText(this@MainActivity, getString(R.string.permission_granted), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
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()
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
setupNumberButton(binding.btn0, "0")
|
||||
setupNumberButton(binding.btn00, "00")
|
||||
setupNumberButton(binding.btn1, "1")
|
||||
setupNumberButton(binding.btn2, "2")
|
||||
setupNumberButton(binding.btn3, "3")
|
||||
@@ -99,6 +127,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
binding.cut.setOnClickListener { cutNumbers() }
|
||||
}
|
||||
|
||||
|
||||
private fun handleActivityResult(result: androidx.activity.result.ActivityResult) {
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
result.data?.data?.let { uri ->
|
||||
@@ -107,10 +136,8 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
|
||||
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 +200,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 {
|
||||
val percentagePattern = Pattern.compile("(\\d+\\.?\\d*)%")
|
||||
val operatorPercentPattern = Pattern.compile("([+\\-*/])(\\d+\\.?\\d*)%")
|
||||
|
||||
var processedExpression = expression
|
||||
|
||||
// Replace standalone percentages (like "50%") with their decimal form (0.5)
|
||||
val matcher = percentagePattern.matcher(processedExpression)
|
||||
while (matcher.find()) {
|
||||
val fullMatch = matcher.group(0)
|
||||
val number = matcher.group(1)
|
||||
|
||||
// Check if it's a standalone percentage or part of an operation
|
||||
val start = matcher.start()
|
||||
if (start == 0 || !isOperator(processedExpression[start-1].toString())) {
|
||||
val percentageValue = number.toDouble() / 100
|
||||
processedExpression = processedExpression.replace(fullMatch, percentageValue.toString())
|
||||
val percentageValue = number!!.toDouble() / 100
|
||||
processedExpression = processedExpression.replace(fullMatch!!.toString(), percentageValue.toString())
|
||||
}
|
||||
}
|
||||
|
||||
// Handle operator-percentage combinations (like "100-20%")
|
||||
val opMatcher = operatorPercentPattern.matcher(processedExpression)
|
||||
val sb = StringBuilder(processedExpression)
|
||||
|
||||
// We need to process matches from right to left to maintain indices
|
||||
val matches = mutableListOf<Triple<Int, Int, String>>()
|
||||
|
||||
while (opMatcher.find()) {
|
||||
@@ -219,15 +229,11 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
matches.add(Triple(start, end, "$operator$percentValue%"))
|
||||
}
|
||||
|
||||
// Process matches from right to left
|
||||
for (match in matches.reversed()) {
|
||||
val (start, end, fullMatch) = match
|
||||
|
||||
// Find the number before this operator
|
||||
var leftNumberEnd = start
|
||||
var leftNumberStart = start - 1
|
||||
|
||||
// Skip parentheses and move to the actual number
|
||||
if (leftNumberStart >= 0 && sb[leftNumberStart] == ')') {
|
||||
var openParens = 1
|
||||
leftNumberStart--
|
||||
@@ -238,7 +244,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
leftNumberStart--
|
||||
}
|
||||
|
||||
// Now we need to find the start of the expression
|
||||
if (leftNumberStart >= 0) {
|
||||
while (leftNumberStart >= 0 && (isDigit(sb[leftNumberStart].toString()) || sb[leftNumberStart] == '.' || sb[leftNumberStart] == '-')) {
|
||||
leftNumberStart--
|
||||
@@ -248,26 +253,24 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
leftNumberStart = 0
|
||||
}
|
||||
} else {
|
||||
// For simple numbers, just find the start of the number
|
||||
|
||||
while (leftNumberStart >= 0 && (isDigit(sb[leftNumberStart].toString()) || sb[leftNumberStart] == '.')) {
|
||||
leftNumberStart--
|
||||
}
|
||||
leftNumberStart++
|
||||
}
|
||||
|
||||
if (leftNumberStart < leftNumberEnd) {
|
||||
val leftPart = sb.substring(leftNumberStart, leftNumberEnd)
|
||||
if (leftNumberStart < start) {
|
||||
val leftPart = sb.substring(leftNumberStart, start)
|
||||
|
||||
try {
|
||||
// Extract the numerical values
|
||||
|
||||
val baseNumber = evaluateExpression(leftPart)
|
||||
val operator = fullMatch.substring(0, 1)
|
||||
val percentNumber = fullMatch.substring(1, fullMatch.length - 1).toDouble()
|
||||
|
||||
// Calculate the percentage of the base number
|
||||
val percentValue = baseNumber * (percentNumber / 100)
|
||||
|
||||
// Calculate the new value based on the operator
|
||||
val newValue = when (operator) {
|
||||
"+" -> baseNumber + percentValue
|
||||
"-" -> baseNumber - percentValue
|
||||
@@ -276,7 +279,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
else -> baseNumber
|
||||
}
|
||||
|
||||
// Replace the entire expression "number operator percent%" with the result
|
||||
sb.replace(leftNumberStart, end, newValue.toString())
|
||||
} catch (e: Exception) {
|
||||
Log.e("Calculator", "Error processing percentage expression: $e")
|
||||
@@ -298,15 +300,16 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
private fun evaluateExpression(expression: String): Double {
|
||||
return try {
|
||||
ExpressionBuilder(expression).build().evaluate()
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
expression.toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private fun calculateResult() {
|
||||
if (currentExpression == "123456") {
|
||||
val intent = Intent(this, SetupPasswordActivity::class.java)
|
||||
sp.edit().putBoolean("isFirst", false).apply()
|
||||
sp.edit { putBoolean("isFirst", false) }
|
||||
intent.putExtra("password", currentExpression)
|
||||
startActivity(intent)
|
||||
clearDisplay()
|
||||
@@ -358,15 +361,17 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
}
|
||||
|
||||
try {
|
||||
if (currentExpression.isEmpty() ||
|
||||
(isOperator(currentExpression.last().toString()) && currentExpression.last() != '%')) {
|
||||
if (currentExpression.isEmpty()) {
|
||||
binding.total.text = ""
|
||||
return
|
||||
}
|
||||
|
||||
// Process the expression for preview calculation
|
||||
var processedExpression = currentExpression.replace("×", "*")
|
||||
|
||||
if (isOperator(processedExpression.last().toString())) {
|
||||
processedExpression = processedExpression.substring(0, processedExpression.length - 1)
|
||||
}
|
||||
|
||||
if (processedExpression.contains("%")) {
|
||||
processedExpression = preprocessExpression(processedExpression)
|
||||
}
|
||||
@@ -382,7 +387,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
if (sp.getBoolean("isFirst", true) && (currentExpression == "123456" || binding.display.text.toString() == "123456")){
|
||||
binding.total.text = getString(R.string.now_enter_button)
|
||||
}else binding.total.text = formattedResult
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
binding.total.text = ""
|
||||
}
|
||||
}
|
||||
@@ -395,7 +400,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
val lastChar = currentExpression.last()
|
||||
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
|
||||
|
||||
// Update flags based on what was removed
|
||||
if (lastChar == '%') {
|
||||
lastWasPercent = false
|
||||
} else if (isOperator(lastChar.toString())) {
|
||||
@@ -411,27 +415,24 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
|
||||
}
|
||||
|
||||
override fun onPositiveButtonClicked() {
|
||||
// Handle positive button click for both DialogUtil and DialogActionsCallback
|
||||
fileManager.askPermission(this)
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
// Handle negative button click
|
||||
Toast.makeText(this, "Storage permission is required for the app to function properly", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(this, getString(R.string.storage_permission_is_required_for_the_app_to_function_properly), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
// Handle neutral button click
|
||||
Toast.makeText(this, "You can grant permission later from Settings", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(this, getString(R.string.you_can_grant_permission_later_from_settings), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == 6767) {
|
||||
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 {
|
||||
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this, getString(R.string.permission_denied), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,27 @@ package devs.org.calculator.activities
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.adapters.ImagePreviewAdapter
|
||||
import devs.org.calculator.database.AppDatabase
|
||||
import devs.org.calculator.database.HiddenFileRepository
|
||||
import devs.org.calculator.databinding.ActivityPreviewBinding
|
||||
import devs.org.calculator.utils.DialogUtil
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import devs.org.calculator.utils.PrefsUtil
|
||||
import devs.org.calculator.utils.SecurityUtils
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
@@ -18,43 +30,56 @@ class PreviewActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityPreviewBinding
|
||||
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 folder: String
|
||||
private lateinit var filetype: FileManager.FileType
|
||||
private lateinit var adapter: ImagePreviewAdapter
|
||||
private lateinit var fileManager: FileManager
|
||||
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())
|
||||
}
|
||||
private var slideshowRunning = false
|
||||
private val slideshowDelay = 2000L
|
||||
|
||||
private val slideshowRunner = object : Runnable {
|
||||
override fun run() {
|
||||
if (slideshowRunning) {
|
||||
if (currentPosition + 1 >= files.size) {
|
||||
currentPosition = 0
|
||||
} else {
|
||||
currentPosition += 1
|
||||
}
|
||||
binding.viewPager.setCurrentItem(currentPosition, true)
|
||||
mainHandler.postDelayed(this, slideshowDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityPreviewBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
|
||||
enableEdgeToEdge()
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
fileManager = FileManager(this, this)
|
||||
|
||||
currentPosition = intent.getIntExtra("position", 0)
|
||||
type = intent.getStringExtra("type").toString()
|
||||
folder = intent.getStringExtra("folder").toString()
|
||||
type = intent.getStringExtra("type") ?: "IMAGE"
|
||||
folder = intent.getStringExtra("folder") ?: ""
|
||||
|
||||
setupFileType()
|
||||
files = fileManager.getFilesInHiddenDirFromFolder(filetype, folder = folder)
|
||||
|
||||
loadFiles()
|
||||
setupImagePreview()
|
||||
clickListeners()
|
||||
|
||||
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
binding.back.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
setupClickListeners()
|
||||
setupPageChangeCallback()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -62,8 +87,17 @@ class PreviewActivity : AppCompatActivity() {
|
||||
setupFlagSecure()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
adapter.releaseAllResources()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
adapter.releaseAllResources()
|
||||
}
|
||||
|
||||
private fun setupFlagSecure() {
|
||||
val prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
||||
if (prefs.getBoolean("screenshot_restriction", true)) {
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
@@ -78,14 +112,17 @@ class PreviewActivity : AppCompatActivity() {
|
||||
filetype = FileManager.FileType.IMAGE
|
||||
binding.title.text = getString(R.string.preview_images)
|
||||
}
|
||||
|
||||
"VIDEO" -> {
|
||||
filetype = FileManager.FileType.VIDEO
|
||||
binding.title.text = getString(R.string.preview_videos)
|
||||
}
|
||||
|
||||
"AUDIO" -> {
|
||||
filetype = FileManager.FileType.AUDIO
|
||||
binding.title.text = getString(R.string.preview_audios)
|
||||
}
|
||||
|
||||
else -> {
|
||||
filetype = FileManager.FileType.DOCUMENT
|
||||
binding.title.text = getString(R.string.preview_documents)
|
||||
@@ -93,108 +130,276 @@ 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() {
|
||||
if (files.isEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
adapter = ImagePreviewAdapter(this, this)
|
||||
adapter.images = files
|
||||
adapter.onSlideshowShouldStop = {
|
||||
if (slideshowRunning) {
|
||||
stopSlideshow()
|
||||
}
|
||||
}
|
||||
binding.viewPager.adapter = adapter
|
||||
|
||||
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()
|
||||
if (currentPosition < files.size) {
|
||||
binding.viewPager.setCurrentItem(currentPosition, false)
|
||||
}
|
||||
|
||||
updateFileInfo()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (filetype == FileManager.FileType.AUDIO) {
|
||||
(binding.viewPager.adapter as? ImagePreviewAdapter)?.let { adapter ->
|
||||
adapter.currentMediaPlayer?.release()
|
||||
adapter.currentMediaPlayer = null
|
||||
private fun setupPageChangeCallback() {
|
||||
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
currentPosition = position
|
||||
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 {
|
||||
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem])
|
||||
if (fileUri != null) {
|
||||
dialogUtil.showMaterialDialog(
|
||||
getString(R.string.delete_file),
|
||||
getString(R.string.are_you_sure_to_delete_this_file_permanently),
|
||||
getString(R.string.delete_permanently),
|
||||
getString(R.string.cancel),
|
||||
object : 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
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
handleDeleteFile()
|
||||
}
|
||||
|
||||
binding.unHide.setOnClickListener {
|
||||
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem])
|
||||
if (fileUri != null) {
|
||||
dialogUtil.showMaterialDialog(
|
||||
getString(R.string.un_hide_file),
|
||||
getString(R.string.are_you_sure_you_want_to_un_hide_this_file),
|
||||
getString(R.string.un_hide),
|
||||
getString(R.string.cancel),
|
||||
object : DialogUtil.DialogCallback {
|
||||
override fun onPositiveButtonClicked() {
|
||||
lifecycleScope.launch {
|
||||
FileManager(this@PreviewActivity, this@PreviewActivity).copyFileToNormalDir(fileUri)
|
||||
removeFileFromList(binding.viewPager.currentItem)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
// Handle negative button click
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
// Handle neutral button click
|
||||
}
|
||||
}
|
||||
)
|
||||
performFileUnHiding()
|
||||
}
|
||||
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
adapter.onItemScrolledAway(currentPosition)
|
||||
currentPosition = position
|
||||
}
|
||||
})
|
||||
if (filetype != FileManager.FileType.IMAGE) {
|
||||
binding.startSlideshow.visibility = View.GONE
|
||||
}
|
||||
binding.startSlideshow.setOnClickListener {
|
||||
startSlideshow()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startSlideshow() {
|
||||
if (!slideshowRunning) {
|
||||
slideshowRunning = true
|
||||
toggleToolbars()
|
||||
mainHandler.postDelayed(slideshowRunner, slideshowDelay)
|
||||
adapter.setImageZoomable(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopSlideshow() {
|
||||
slideshowRunning = false
|
||||
toggleToolbars()
|
||||
mainHandler.removeCallbacks(slideshowRunner)
|
||||
adapter.setImageZoomable(true)
|
||||
}
|
||||
|
||||
private fun toggleToolbars() {
|
||||
val visibility = if (slideshowRunning) View.GONE else View.VISIBLE
|
||||
binding.toolbar.visibility = visibility
|
||||
binding.footer.visibility = visibility
|
||||
}
|
||||
|
||||
private fun handleDeleteFile() {
|
||||
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() {}
|
||||
|
||||
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) {
|
||||
val updatedFiles = files.toMutableList().apply { removeAt(position) }
|
||||
files = updatedFiles
|
||||
adapter.images = updatedFiles // Update adapter with the new list
|
||||
|
||||
// Update the ViewPager's position
|
||||
if (updatedFiles.isEmpty()) finish()
|
||||
if (position < 0 || position >= files.size) return
|
||||
adapter.releaseAllResources()
|
||||
files.removeAt(position)
|
||||
adapter.images = files
|
||||
if (files.isEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
currentPosition = if (position >= files.size) {
|
||||
files.size - 1
|
||||
} else {
|
||||
position
|
||||
}
|
||||
|
||||
binding.viewPager.setCurrentItem(currentPosition, false)
|
||||
updateFileInfo()
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
onBackPressed()
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package devs.org.calculator.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
|
||||
abstract class SecureBaseActivity : AppCompatActivity() {
|
||||
|
||||
class AppLifecycleObserver : DefaultLifecycleObserver {
|
||||
companion object {
|
||||
var wasInBackground = false
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
wasInBackground = true
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
wasInBackground = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (AppLifecycleObserver.wasInBackground) {
|
||||
AppLifecycleObserver.wasInBackground = false
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +1,45 @@
|
||||
package devs.org.calculator.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.databinding.ActivitySettingsBinding
|
||||
import devs.org.calculator.utils.PrefsUtil
|
||||
import devs.org.calculator.utils.SecurityUtils
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private val DEV_GITHUB_URL = "https://github.com/binondi"
|
||||
private val GITHUB_URL = "$DEV_GITHUB_URL/calculator-hide-files"
|
||||
private lateinit var prefs: PrefsUtil
|
||||
private var DEV_GITHUB_URL = ""
|
||||
private var GITHUB_URL = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
||||
prefs = PrefsUtil(this)
|
||||
enableEdgeToEdge()
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
DEV_GITHUB_URL = getString(R.string.github_profile)
|
||||
GITHUB_URL = getString(R.string.calculator_hide_files, DEV_GITHUB_URL)
|
||||
setupUI()
|
||||
loadSettings()
|
||||
setupListeners()
|
||||
@@ -42,6 +51,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun loadSettings() {
|
||||
|
||||
binding.dynamicThemeSwitch.isChecked = prefs.getBoolean("dynamic_theme", true)
|
||||
@@ -55,8 +65,11 @@ class SettingsActivity : AppCompatActivity() {
|
||||
else -> binding.systemThemeRadio.isChecked = true
|
||||
}
|
||||
|
||||
val isUsingCustomKey = SecurityUtils.isUsingCustomKey(this)
|
||||
binding.customKeyStatus.isChecked = isUsingCustomKey
|
||||
binding.screenshotRestrictionSwitch.isChecked = prefs.getBoolean("screenshot_restriction", true)
|
||||
binding.showFileNames.isChecked = prefs.getBoolean("showFileName", true)
|
||||
binding.encryptionSwitch.isChecked = prefs.getBoolean("encryption", false)
|
||||
|
||||
updateThemeModeVisibility()
|
||||
}
|
||||
@@ -72,11 +85,19 @@ class SettingsActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.dynamicThemeSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.edit().putBoolean("dynamic_theme", isChecked).apply()
|
||||
prefs.setBoolean("dynamic_theme", isChecked)
|
||||
if (!isChecked) {
|
||||
showThemeModeDialog()
|
||||
}else{
|
||||
showThemeModeDialog()
|
||||
if (!prefs.getBoolean("isAppReopened",false)){
|
||||
DynamicColors.applyToActivityIfAvailable(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.encryptionSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.setBoolean("encryption", isChecked)
|
||||
}
|
||||
|
||||
|
||||
binding.themeModeSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
@@ -97,7 +118,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.screenshotRestrictionSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.edit().putBoolean("screenshot_restriction", isChecked).apply()
|
||||
prefs.setBoolean("screenshot_restriction", isChecked)
|
||||
if (isChecked) {
|
||||
enableScreenshotRestriction()
|
||||
} else {
|
||||
@@ -105,7 +126,11 @@ class SettingsActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
binding.showFileNames.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.edit().putBoolean("showFileName", isChecked).apply()
|
||||
prefs.setBoolean("showFileName", isChecked)
|
||||
}
|
||||
|
||||
binding.customKeyStatus.setOnClickListener {
|
||||
showCustomKeyDialog()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,20 +140,16 @@ class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
private fun showThemeModeDialog() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Theme Mode")
|
||||
.setMessage("Would you like to set a specific theme mode?")
|
||||
.setPositiveButton("Yes") { _, _ ->
|
||||
binding.themeModeSwitch.isChecked = true
|
||||
}
|
||||
.setNegativeButton("No") { _, _ ->
|
||||
binding.systemThemeRadio.isChecked = true
|
||||
applyThemeMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
.setTitle(getString(R.string.attention))
|
||||
.setMessage(getString(R.string.if_you_turn_on_off_this_option_dynamic_theme_changes_will_be_visible_after_you_reopen_the_app))
|
||||
.setPositiveButton(getString(R.string.ok)) { _, _ ->
|
||||
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun applyThemeMode(themeMode: Int) {
|
||||
prefs.edit().putInt("theme_mode", themeMode).apply()
|
||||
prefs.setInt("theme_mode", themeMode)
|
||||
AppCompatDelegate.setDefaultNightMode(themeMode)
|
||||
}
|
||||
|
||||
@@ -145,10 +166,64 @@ class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
private fun openUrl(url: String) {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
startActivity(intent)
|
||||
} 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
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,11 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import devs.org.calculator.databinding.ActivitySetupPasswordBinding
|
||||
@@ -18,13 +22,19 @@ class SetupPasswordActivity : AppCompatActivity() {
|
||||
private lateinit var binding2: ActivityChangePasswordBinding
|
||||
private lateinit var prefsUtil: PrefsUtil
|
||||
private var hasPassword = false
|
||||
private val prefs:PrefsUtil by lazy { PrefsUtil(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivitySetupPasswordBinding.inflate(layoutInflater)
|
||||
binding2 = ActivityChangePasswordBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
enableEdgeToEdge()
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
prefsUtil = PrefsUtil(this)
|
||||
hasPassword = prefsUtil.hasPassword()
|
||||
|
||||
@@ -72,8 +82,6 @@ class SetupPasswordActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.btnResetPassword.setOnClickListener {
|
||||
// Implement password reset logic
|
||||
// Could use security questions or email verification
|
||||
if (prefsUtil.getSecurityQuestion() != null) showSecurityQuestionDialog(prefsUtil.getSecurityQuestion().toString())
|
||||
else Toast.makeText(this,
|
||||
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.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
@@ -20,20 +19,31 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.adapters.FileAdapter
|
||||
import devs.org.calculator.adapters.FolderSelectionAdapter
|
||||
import devs.org.calculator.callbacks.FileProcessCallback
|
||||
import devs.org.calculator.database.HiddenFileEntity
|
||||
import devs.org.calculator.databinding.ActivityViewFolderBinding
|
||||
import devs.org.calculator.databinding.ProccessingDialogBinding
|
||||
import devs.org.calculator.utils.DialogUtil
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import devs.org.calculator.utils.FileManager.Companion.ENCRYPTED_EXTENSION
|
||||
import devs.org.calculator.utils.FileManager.Companion.HIDDEN_DIR
|
||||
import devs.org.calculator.utils.FolderManager
|
||||
import devs.org.calculator.utils.PrefsUtil
|
||||
import devs.org.calculator.utils.SecurityUtils
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.app.AlertDialog
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import devs.org.calculator.adapters.FileAdapter
|
||||
|
||||
class ViewFolderActivity : AppCompatActivity() {
|
||||
|
||||
@@ -51,12 +61,12 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
private var currentFolder: File? = null
|
||||
private val hiddenDir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||
private lateinit var pickImageLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var prefs: SharedPreferences
|
||||
|
||||
private var customDialog: androidx.appcompat.app.AlertDialog? = null
|
||||
|
||||
private var dialogShowTime: Long = 0
|
||||
private val MINIMUM_DIALOG_DURATION = 1200L
|
||||
private val prefs:PrefsUtil by lazy { PrefsUtil(this) }
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -64,6 +74,12 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
|
||||
binding = ActivityViewFolderBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
enableEdgeToEdge()
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
setupAnimations()
|
||||
initialize()
|
||||
setupClickListeners()
|
||||
@@ -81,9 +97,9 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
|
||||
private fun initialize() {
|
||||
fileManager = FileManager(this, this)
|
||||
folderManager = FolderManager(this)
|
||||
folderManager = FolderManager()
|
||||
dialogUtil = DialogUtil(this)
|
||||
prefs = getSharedPreferences("app_settings", MODE_PRIVATE)
|
||||
|
||||
}
|
||||
|
||||
private fun setupActivityResultLaunchers() {
|
||||
@@ -135,16 +151,19 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
customDialog?.dismiss()
|
||||
customDialog = null
|
||||
updateFilesToAdapter()
|
||||
refreshCurrentFolder()
|
||||
}, remainingTime)
|
||||
} else {
|
||||
customDialog?.dismiss()
|
||||
customDialog = null
|
||||
refreshCurrentFolder()
|
||||
updateFilesToAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFilesToAdapter() {
|
||||
openFolder(currentFolder!!)
|
||||
val files = folderManager.getFilesInFolder(currentFolder!!)
|
||||
fileAdapter?.submitList(files)
|
||||
}
|
||||
|
||||
|
||||
@@ -162,8 +181,46 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
object : FileProcessCallback {
|
||||
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
|
||||
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({
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -188,23 +245,27 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
).show()
|
||||
dismissCustomDialog()
|
||||
}
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshCurrentView() {
|
||||
if (currentFolder != null) {
|
||||
refreshCurrentFolder()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refreshCurrentFolder()
|
||||
setupFlagSecure()
|
||||
}
|
||||
|
||||
private fun openFolder(folder: File) {
|
||||
// Ensure folder exists and has .nomedia file
|
||||
private fun setupFlagSecure() {
|
||||
if (prefs.getBoolean("screenshot_restriction", true)) {
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun openFolder(folder: File) {
|
||||
if (!folder.exists()) {
|
||||
folder.mkdirs()
|
||||
File(folder, ".nomedia").createNewFile()
|
||||
@@ -218,34 +279,36 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
} else {
|
||||
showEmptyState()
|
||||
}
|
||||
binding.swipeLayout.isRefreshing = false
|
||||
}
|
||||
|
||||
private fun showEmptyState() {
|
||||
binding.noItems.visibility = View.VISIBLE
|
||||
binding.recyclerView.visibility = View.GONE
|
||||
binding.swipeLayout.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun showFileList(files: List<File>, folder: File) {
|
||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
|
||||
|
||||
// Clean up previous adapter
|
||||
fileAdapter?.cleanup()
|
||||
|
||||
fileAdapter = FileAdapter(this, this, folder, prefs.getBoolean("showFileName", true),
|
||||
onFolderLongClick = { isSelected ->
|
||||
handleFileSelectionModeChange(isSelected)
|
||||
}).apply {
|
||||
setFileOperationCallback(object : FileAdapter.FileOperationCallback {
|
||||
setFilesOperationCallback(object : FileAdapter.FilesOperationCallback {
|
||||
override fun onFileDeleted(file: File) {
|
||||
refreshCurrentFolder()
|
||||
updateFilesToAdapter()
|
||||
}
|
||||
|
||||
override fun onFileRenamed(oldFile: File, newFile: File) {
|
||||
refreshCurrentFolder()
|
||||
updateFilesToAdapter()
|
||||
}
|
||||
|
||||
override fun onRefreshNeeded() {
|
||||
refreshCurrentFolder()
|
||||
updateFilesToAdapter()
|
||||
}
|
||||
|
||||
override fun onSelectionModeChanged(isSelectionMode: Boolean, selectedCount: Int) {
|
||||
@@ -261,9 +324,13 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.recyclerView.adapter = fileAdapter
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
binding.swipeLayout.visibility = View.VISIBLE
|
||||
binding.noItems.visibility = View.GONE
|
||||
|
||||
binding.selectAllButton.setOnClickListener {
|
||||
fileAdapter?.toggleSelectAll()
|
||||
}
|
||||
|
||||
binding.menuButton.setOnClickListener {
|
||||
fileAdapter?.let { adapter ->
|
||||
showFileOptionsMenu(adapter.getSelectedItems())
|
||||
@@ -306,24 +373,232 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
private fun showFileOptionsMenu(selectedFiles: List<File>) {
|
||||
if (selectedFiles.isEmpty()) return
|
||||
|
||||
val options = arrayOf(
|
||||
getString(R.string.un_hide),
|
||||
getString(R.string.delete),
|
||||
getString(R.string.copy_to_another_folder),
|
||||
getString(R.string.move_to_another_folder)
|
||||
)
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(getString(R.string.file_options))
|
||||
.setItems(options) { _, which ->
|
||||
when (which) {
|
||||
0 -> unhideSelectedFiles(selectedFiles)
|
||||
1 -> deleteSelectedFiles(selectedFiles)
|
||||
2 -> copyToAnotherFolder(selectedFiles)
|
||||
3 -> moveToAnotherFolder(selectedFiles)
|
||||
lifecycleScope.launch {
|
||||
var hasEncryptedFiles = false
|
||||
var hasDecryptedFiles = false
|
||||
var hasEncFilesWithoutMetadata = false
|
||||
|
||||
for (file in selectedFiles) {
|
||||
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||
|
||||
if (file.name.endsWith(ENCRYPTED_EXTENSION)) {
|
||||
if (hiddenFile?.isEncrypted == true) {
|
||||
hasEncryptedFiles = true
|
||||
} else {
|
||||
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>) {
|
||||
@@ -342,15 +617,12 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
object : DialogUtil.DialogCallback {
|
||||
override fun onPositiveButtonClicked() {
|
||||
performFileUnhiding(selectedFiles)
|
||||
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
// Do nothing
|
||||
}
|
||||
override fun onNegativeButtonClicked() {}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
// Do nothing
|
||||
}
|
||||
override fun onNaturalButtonClicked() {}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -359,8 +631,8 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
|
||||
private fun deleteSelectedFiles(selectedFiles: List<File>) {
|
||||
dialogUtil.showMaterialDialog(
|
||||
getString(R.string.delete_items),
|
||||
getString(R.string.are_you_sure_you_want_to_delete_selected_items),
|
||||
getString(R.string.delete_file),
|
||||
getString(R.string.are_you_sure_to_delete_selected_files_permanently),
|
||||
getString(R.string.delete),
|
||||
getString(R.string.cancel),
|
||||
object : DialogUtil.DialogCallback {
|
||||
@@ -368,13 +640,9 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
performFileDeletion(selectedFiles)
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
// Do nothing
|
||||
}
|
||||
override fun onNegativeButtonClicked() {}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
// Do nothing
|
||||
}
|
||||
override fun onNaturalButtonClicked() {}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -383,20 +651,40 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
|
||||
private fun refreshCurrentFolder() {
|
||||
currentFolder?.let { folder ->
|
||||
val files = folderManager.getFilesInFolder(folder)
|
||||
if (files.isNotEmpty()) {
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
binding.noItems.visibility = View.GONE
|
||||
fileAdapter?.submitList(files.toMutableList())
|
||||
fileAdapter?.let { adapter ->
|
||||
if (adapter.isInSelectionMode()) {
|
||||
showFileSelectionIcons()
|
||||
} else {
|
||||
showFileViewIcons()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val files = folderManager.getFilesInFolder(folder)
|
||||
mainHandler.post {
|
||||
if (files.isNotEmpty()) {
|
||||
binding.swipeLayout.visibility = View.VISIBLE
|
||||
binding.noItems.visibility = View.GONE
|
||||
|
||||
val currentFiles = fileAdapter?.currentList ?: emptyList()
|
||||
val hasChanges = files.size != currentFiles.size ||
|
||||
files.any { newFile ->
|
||||
currentFiles.none { it.absolutePath == newFile.absolutePath }
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
fileAdapter?.submitList(files.toMutableList())
|
||||
}
|
||||
|
||||
fileAdapter?.let { adapter ->
|
||||
if (adapter.isInSelectionMode()) {
|
||||
showFileSelectionIcons()
|
||||
} else {
|
||||
showFileViewIcons()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showEmptyState()
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
mainHandler.post {
|
||||
showEmptyState()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showEmptyState()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,6 +696,9 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
binding.back.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
binding.swipeLayout.setOnRefreshListener {
|
||||
openFolder(currentFolder!!)
|
||||
}
|
||||
|
||||
binding.addImage.setOnClickListener { openFilePicker("image/*") }
|
||||
binding.addVideo.setOnClickListener { openFilePicker("video/*") }
|
||||
@@ -475,6 +766,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
|
||||
private fun showFileViewIcons() {
|
||||
binding.menuButton.visibility = View.GONE
|
||||
binding.selectAllButton.visibility = View.GONE
|
||||
binding.fabExpend.visibility = View.VISIBLE
|
||||
binding.addImage.visibility = View.INVISIBLE
|
||||
binding.addVideo.visibility = View.INVISIBLE
|
||||
@@ -486,6 +778,7 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
|
||||
private fun showFileSelectionIcons() {
|
||||
binding.menuButton.visibility = View.VISIBLE
|
||||
binding.selectAllButton.visibility = View.VISIBLE
|
||||
binding.fabExpend.visibility = View.GONE
|
||||
binding.addImage.visibility = View.INVISIBLE
|
||||
binding.addVideo.visibility = View.INVISIBLE
|
||||
@@ -497,21 +790,65 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
private fun performFileUnhiding(selectedFiles: List<File>) {
|
||||
lifecycleScope.launch {
|
||||
var allUnhidden = true
|
||||
val unhiddenFiles = mutableListOf<File>()
|
||||
selectedFiles.forEach { file ->
|
||||
try {
|
||||
val fileUri = FileManager.FileManager().getContentUriImage(this@ViewFolderActivity, file)
|
||||
val hiddenFile = fileAdapter?.hiddenFileRepository?.getHiddenFileByPath(file.absolutePath)
|
||||
|
||||
if (fileUri != null) {
|
||||
val result = fileManager.copyFileToNormalDir(fileUri)
|
||||
if (result == null) {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
} 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) {
|
||||
allUnhidden = false
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,33 +860,44 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
|
||||
|
||||
// Fixed: Ensure proper order of operations
|
||||
fileAdapter?.exitSelectionMode()
|
||||
refreshCurrentFolder()
|
||||
fileAdapter?.exitSelectionMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun performFileDeletion(selectedFiles: List<File>) {
|
||||
var allDeleted = true
|
||||
selectedFiles.forEach { file ->
|
||||
if (!file.delete()) {
|
||||
allDeleted = false
|
||||
lifecycleScope.launch {
|
||||
var allDeleted = true
|
||||
val deletedFiles = mutableListOf<File>()
|
||||
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>) {
|
||||
@@ -559,47 +907,86 @@ class ViewFolderActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun copyFilesToFolder(selectedFiles: List<File>, destinationFolder: File) {
|
||||
var allCopied = true
|
||||
selectedFiles.forEach { file ->
|
||||
try {
|
||||
val newFile = File(destinationFolder, file.name)
|
||||
file.copyTo(newFile, overwrite = true)
|
||||
} catch (e: Exception) {
|
||||
allCopied = false
|
||||
lifecycleScope.launch {
|
||||
var allCopied = true
|
||||
val copiedFiles = mutableListOf<File>()
|
||||
selectedFiles.forEach { file ->
|
||||
try {
|
||||
val newFile = File(destinationFolder, file.name)
|
||||
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) {
|
||||
var allMoved = true
|
||||
selectedFiles.forEach { file ->
|
||||
try {
|
||||
val newFile = File(destinationFolder, file.name)
|
||||
file.copyTo(newFile, overwrite = true)
|
||||
file.delete()
|
||||
} catch (e: Exception) {
|
||||
allMoved = false
|
||||
lifecycleScope.launch {
|
||||
var allMoved = true
|
||||
val movedFiles = mutableListOf<File>()
|
||||
selectedFiles.forEach { file ->
|
||||
try {
|
||||
val newFile = File(destinationFolder, file.name)
|
||||
file.copyTo(newFile, overwrite = true)
|
||||
|
||||
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) {
|
||||
val folders = folderManager.getFoldersInDirectory(hiddenDir)
|
||||
.filter { it != currentFolder } // Exclude current folder
|
||||
.filter { it != currentFolder }
|
||||
|
||||
if (folders.isEmpty()) {
|
||||
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>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: File, newItem: File): Boolean {
|
||||
// Compare by absolute path since File objects might be different instances
|
||||
// but represent the same file
|
||||
return oldItem.absolutePath == newItem.absolutePath
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: File, newItem: File): Boolean {
|
||||
// Compare all relevant properties that might change and affect the UI
|
||||
return oldItem.name == newItem.name &&
|
||||
oldItem.length() == newItem.length() &&
|
||||
oldItem.lastModified() == newItem.lastModified() &&
|
||||
@@ -22,8 +19,6 @@ class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: File, newItem: File): Any? {
|
||||
// Return a payload if only specific properties changed
|
||||
// This allows for partial updates instead of full rebinding
|
||||
val changes = mutableListOf<String>()
|
||||
|
||||
if (oldItem.name != newItem.name) {
|
||||
@@ -42,6 +37,10 @@ class FileDiffCallback : DiffUtil.ItemCallback<File>() {
|
||||
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
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -80,6 +81,7 @@ class FolderAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun clearSelection() {
|
||||
val wasInSelectionMode = isSelectionMode
|
||||
selectedItems.clear()
|
||||
|
||||
@@ -5,19 +5,25 @@ import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.MediaController
|
||||
import android.widget.SeekBar
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.database.AppDatabase
|
||||
import devs.org.calculator.database.HiddenFileRepository
|
||||
import devs.org.calculator.databinding.ViewpagerItemsBinding
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import devs.org.calculator.utils.SecurityUtils.getDecryptedPreviewFile
|
||||
import devs.org.calculator.utils.SecurityUtils.getUriForPreviewFile
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import devs.org.calculator.R
|
||||
|
||||
class ImagePreviewAdapter(
|
||||
private val context: Context,
|
||||
@@ -25,16 +31,19 @@ class ImagePreviewAdapter(
|
||||
) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() {
|
||||
|
||||
private val differ = AsyncListDiffer(this, FileDiffCallback())
|
||||
var currentMediaPlayer: MediaPlayer? = null
|
||||
var isMediaPlayerPrepared = false
|
||||
var currentViewHolder: ImageViewHolder? = null
|
||||
private var currentPlayingPosition = -1
|
||||
private var isPlaying = false
|
||||
private var currentViewHolder: ImageViewHolder? = null
|
||||
private val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||
}
|
||||
private var canZoomOnImages: Boolean = true
|
||||
|
||||
var images: List<File>
|
||||
get() = differ.currentList
|
||||
set(value) = differ.submitList(value)
|
||||
|
||||
var onSlideshowShouldStop: () -> Unit = {}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
|
||||
val binding = ViewpagerItemsBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
return ImageViewHolder(binding)
|
||||
@@ -42,110 +51,239 @@ class ImagePreviewAdapter(
|
||||
|
||||
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
||||
val imageUrl = images[position]
|
||||
val fileType = FileManager(context, lifecycleOwner).getFileType(images[position])
|
||||
holder.bind(imageUrl,fileType)
|
||||
currentViewHolder = holder
|
||||
val fileType = FileManager(context,lifecycleOwner).getFileType(imageUrl)
|
||||
stopAndResetCurrentAudio()
|
||||
|
||||
currentMediaPlayer?.let {
|
||||
if (it.isPlaying) it.pause()
|
||||
it.seekTo(0)
|
||||
}
|
||||
currentMediaPlayer = null
|
||||
isMediaPlayerPrepared = false
|
||||
|
||||
if (currentMediaPlayer?.isPlaying == true) {
|
||||
currentMediaPlayer?.stop()
|
||||
currentMediaPlayer?.release()
|
||||
}
|
||||
currentMediaPlayer = null
|
||||
holder.bind(imageUrl, position,fileType)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = images.size
|
||||
|
||||
fun setImageZoomable(zoomable: Boolean) {
|
||||
currentViewHolder?.setImageViewCanZoom(zoomable)
|
||||
canZoomOnImages = zoomable
|
||||
}
|
||||
|
||||
private fun stopAndResetCurrentAudio() {
|
||||
currentViewHolder?.stopAndResetAudio()
|
||||
currentPlayingPosition = -1
|
||||
currentViewHolder = null
|
||||
}
|
||||
|
||||
inner class ImageViewHolder(private val binding: ViewpagerItemsBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var seekHandler = Handler(Looper.getMainLooper())
|
||||
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) {
|
||||
when (fileType) {
|
||||
FileManager.FileType.VIDEO -> {
|
||||
binding.imageView.visibility = View.GONE
|
||||
binding.audioBg.visibility = View.GONE
|
||||
binding.videoView.visibility = View.VISIBLE
|
||||
fun bind(file: File, position: Int, decryptedFileType: FileManager.FileType) {
|
||||
currentPosition = position
|
||||
|
||||
val videoUri = Uri.fromFile(file)
|
||||
binding.videoView.setVideoURI(videoUri)
|
||||
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)
|
||||
releaseMediaPlayer()
|
||||
resetAudioUI()
|
||||
cleanupTempFile()
|
||||
|
||||
try {
|
||||
lifecycleOwner.lifecycleScope.launch {
|
||||
val hiddenFile = hiddenFileRepository.getHiddenFileByPath(file.absolutePath)
|
||||
if (hiddenFile != null) {
|
||||
val isEncrypted = hiddenFile.isEncrypted
|
||||
val fileType = hiddenFile.fileType
|
||||
if (isEncrypted) {
|
||||
tempDecryptedFile = getDecryptedPreviewFile(context, hiddenFile)
|
||||
if (tempDecryptedFile != null && tempDecryptedFile!!.exists() && tempDecryptedFile!!.length() > 0) {
|
||||
displayFile(tempDecryptedFile!!, fileType, true)
|
||||
} else {
|
||||
Log.e("ImagePreviewAdapter", "Failed to get decrypted preview file for: ${file.absolutePath}")
|
||||
showEncryptedError()
|
||||
}
|
||||
} else {
|
||||
displayFile(file, decryptedFileType, false)
|
||||
}
|
||||
)
|
||||
|
||||
binding.videoView.setOnCompletionListener {
|
||||
val nextPosition = (adapterPosition + 1) % images.size
|
||||
playVideoAtPosition(nextPosition)
|
||||
} else {
|
||||
displayFile(file, decryptedFileType, false)
|
||||
}
|
||||
}
|
||||
FileManager.FileType.IMAGE -> {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
binding.videoView.visibility = View.GONE
|
||||
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
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ImagePreviewAdapter", "Error in bind: ${e.message}")
|
||||
displayFile(file, decryptedFileType, false)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
binding.imageView.isZoomable = canZoomOnImages
|
||||
binding.imageView.doubleTapToZoom = canZoomOnImages
|
||||
binding.imageView.setOnClickListener {
|
||||
onSlideshowShouldStop()
|
||||
}
|
||||
Glide.with(context)
|
||||
.load(uri)
|
||||
.error(R.drawable.encrypted)
|
||||
.into(binding.imageView)
|
||||
}
|
||||
FileManager.FileType.AUDIO -> {
|
||||
val audioFile: File? = if (isEncrypted) {
|
||||
getFileFromUri(context, uri)
|
||||
} else {
|
||||
file
|
||||
}
|
||||
if (audioFile == null) {
|
||||
Log.e("ImagePreviewAdapter", "Failed to get audio file from URI")
|
||||
showEncryptedError()
|
||||
return
|
||||
}
|
||||
binding.imageView.visibility = View.GONE
|
||||
binding.audioBg.visibility = View.VISIBLE
|
||||
binding.videoView.visibility = View.GONE
|
||||
binding.audioTitle.text = file.name
|
||||
|
||||
setupAudioPlayer(audioFile)
|
||||
setupPlaybackControls()
|
||||
}
|
||||
else -> {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
binding.audioBg.visibility = View.GONE
|
||||
binding.videoView.visibility = View.GONE
|
||||
binding.imageView.isZoomable = canZoomOnImages
|
||||
binding.imageView.doubleTapToZoom = canZoomOnImages
|
||||
binding.imageView.setOnClickListener {
|
||||
onSlideshowShouldStop()
|
||||
}
|
||||
Glide.with(context)
|
||||
.load(uri)
|
||||
.error(R.drawable.encrypted)
|
||||
.into(binding.imageView)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ImagePreviewAdapter", "Error displaying file: ${e.message}")
|
||||
showEncryptedError()
|
||||
}
|
||||
}
|
||||
|
||||
fun setImageViewCanZoom(zoomEnabled: Boolean) {
|
||||
binding.imageView.doubleTapToZoom = zoomEnabled
|
||||
binding.imageView.isZoomable = zoomEnabled
|
||||
}
|
||||
|
||||
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) {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setDataSource(file.absolutePath)
|
||||
setOnPreparedListener { mp ->
|
||||
binding.audioSeekBar.valueTo = mp.duration.toFloat()
|
||||
|
||||
isMediaPlayerPrepared = true
|
||||
try {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setDataSource(file.absolutePath)
|
||||
setOnPreparedListener { mp ->
|
||||
binding.audioSeekBar.valueTo = mp.duration.toFloat()
|
||||
binding.audioSeekBar.value = 0f
|
||||
setupSeekBar()
|
||||
isMediaPlayerPrepared = true
|
||||
}
|
||||
setOnCompletionListener {
|
||||
stopAndResetAudio()
|
||||
}
|
||||
setOnErrorListener { _, _, _ ->
|
||||
releaseMediaPlayer()
|
||||
true
|
||||
}
|
||||
prepareAsync()
|
||||
}
|
||||
setOnCompletionListener {
|
||||
// isPlaying = false
|
||||
binding.playPause.setImageResource(R.drawable.play)
|
||||
binding.audioSeekBar.value = 0f
|
||||
|
||||
seekHandler.removeCallbacks(seekRunnable!!)
|
||||
}
|
||||
prepareAsync()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
releaseMediaPlayer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSeekBar() {
|
||||
binding.audioSeekBar.addOnChangeListener { slider, value, fromUser ->
|
||||
binding.audioSeekBar.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser && mediaPlayer != null && isMediaPlayerPrepared) {
|
||||
mediaPlayer?.seekTo(value.toInt())
|
||||
}
|
||||
@@ -153,15 +291,18 @@ class ImagePreviewAdapter(
|
||||
|
||||
seekRunnable = Runnable {
|
||||
mediaPlayer?.let { mp ->
|
||||
if (mp.isPlaying) {
|
||||
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
||||
seekHandler.postDelayed(seekRunnable!!, 100)
|
||||
if (mp.isPlaying && isMediaPlayerPrepared) {
|
||||
try {
|
||||
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
||||
seekHandler.postDelayed(seekRunnable!!, 100)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setupPlaybackControls() {
|
||||
binding.playPause.setOnClickListener {
|
||||
if (isPlaying) {
|
||||
@@ -173,65 +314,127 @@ class ImagePreviewAdapter(
|
||||
|
||||
binding.preview.setOnClickListener {
|
||||
mediaPlayer?.let { mp ->
|
||||
val newPosition = mp.currentPosition - 10000
|
||||
mp.seekTo(maxOf(0, newPosition))
|
||||
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
||||
if (isMediaPlayerPrepared) {
|
||||
try {
|
||||
val newPosition = mp.currentPosition - 10000
|
||||
mp.seekTo(maxOf(0, newPosition))
|
||||
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.next.setOnClickListener {
|
||||
mediaPlayer?.let { mp ->
|
||||
val newPosition = mp.currentPosition + 10000
|
||||
mp.seekTo(minOf(mp.duration, newPosition))
|
||||
binding.audioSeekBar.value = mp.currentPosition.toFloat()
|
||||
if (isMediaPlayerPrepared) {
|
||||
try {
|
||||
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() {
|
||||
mediaPlayer?.let { mp ->
|
||||
if (currentPlayingPosition != -1 && currentPlayingPosition != adapterPosition) {
|
||||
currentViewHolder?.pauseAudio()
|
||||
if (isMediaPlayerPrepared) {
|
||||
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() {
|
||||
mediaPlayer?.let { mp ->
|
||||
if (mp.isPlaying) {
|
||||
mp.pause()
|
||||
isPlaying = false
|
||||
binding.playPause.setImageResource(R.drawable.play)
|
||||
seekHandler.removeCallbacks(seekRunnable!!)
|
||||
try {
|
||||
if (mp.isPlaying) {
|
||||
mp.pause()
|
||||
isPlaying = false
|
||||
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() {
|
||||
mediaPlayer?.let { mp ->
|
||||
if (mp.isPlaying) {
|
||||
mp.stop()
|
||||
try {
|
||||
mediaPlayer?.let { mp ->
|
||||
if (mp.isPlaying) {
|
||||
mp.stop()
|
||||
}
|
||||
mp.release()
|
||||
}
|
||||
mp.release()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
mediaPlayer = null
|
||||
isPlaying = false
|
||||
seekHandler.removeCallbacks(seekRunnable!!)
|
||||
isMediaPlayerPrepared = false
|
||||
seekRunnable?.let { seekHandler.removeCallbacks(it) }
|
||||
|
||||
if (currentPlayingPosition == currentPosition) {
|
||||
currentPlayingPosition = -1
|
||||
currentViewHolder = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun playVideoAtPosition(position: Int) {
|
||||
val nextFile = images[position]
|
||||
val fileType = FileManager(context, lifecycleOwner).getFileType(images[position])
|
||||
if (fileType == FileManager.FileType.VIDEO) {
|
||||
val videoUri = Uri.fromFile(nextFile)
|
||||
binding.videoView.setVideoURI(videoUri)
|
||||
binding.videoView.start()
|
||||
if (position < images.size) {
|
||||
val nextFile = images[position]
|
||||
val fileType = FileManager(context, lifecycleOwner).getFileType(images[position])
|
||||
if (fileType == FileManager.FileType.VIDEO) {
|
||||
val videoUri = Uri.fromFile(nextFile)
|
||||
binding.videoView.setVideoURI(videoUri)
|
||||
binding.videoView.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,5 +443,14 @@ class ImagePreviewAdapter(
|
||||
super.onViewRecycled(holder)
|
||||
holder.releaseMediaPlayer()
|
||||
}
|
||||
}
|
||||
|
||||
fun onItemScrolledAway(position: Int) {
|
||||
if (currentPlayingPosition == position) {
|
||||
stopAndResetCurrentAudio()
|
||||
}
|
||||
}
|
||||
|
||||
fun releaseAllResources() {
|
||||
stopAndResetCurrentAudio()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package devs.org.calculator.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
@@ -22,9 +22,9 @@ class ListFolderAdapter(
|
||||
private var isSelectionMode = false
|
||||
|
||||
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) {
|
||||
folderNameTextView.text = folder.name
|
||||
@@ -89,6 +89,7 @@ class ListFolderAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun clearSelection() {
|
||||
val wasInSelectionMode = isSelectionMode
|
||||
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
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.RecoverableSecurityException
|
||||
import android.content.ActivityNotFoundException
|
||||
@@ -8,6 +9,8 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.MediaStore
|
||||
import android.provider.Settings
|
||||
import android.webkit.MimeTypeMap
|
||||
@@ -15,22 +18,26 @@ import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import android.Manifest
|
||||
import androidx.core.content.FileProvider
|
||||
import devs.org.calculator.R
|
||||
|
||||
class FileManager(private val context: Context, private val lifecycleOwner: LifecycleOwner) {
|
||||
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||
val intent = Intent()
|
||||
val hiddenFileRepository: HiddenFileRepository by lazy {
|
||||
HiddenFileRepository(AppDatabase.getDatabase(context).hiddenFileDao())
|
||||
}
|
||||
|
||||
companion object {
|
||||
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 AUDIO_DIR = "audio"
|
||||
const val DOCS_DIR = "documents"
|
||||
const val ENCRYPTED_EXTENSION = ".enc"
|
||||
}
|
||||
|
||||
|
||||
fun getHiddenDirectory(): File {
|
||||
val dir = File(Environment.getExternalStorageDirectory(), HIDDEN_DIR)
|
||||
if (!dir.exists()) {
|
||||
@@ -47,7 +56,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
if (!created) {
|
||||
throw RuntimeException("Failed to create hidden directory: ${dir.absolutePath}")
|
||||
}
|
||||
// Create .nomedia file to hide from media scanners
|
||||
val nomediaFile = File(dir, ".nomedia")
|
||||
if (!nomediaFile.exists()) {
|
||||
nomediaFile.createNewFile()
|
||||
@@ -56,15 +64,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
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> {
|
||||
val typeDir = File(folder)
|
||||
if (!typeDir.exists()) {
|
||||
@@ -92,7 +91,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
val extension = MimeTypeMap.getSingleton()
|
||||
.getExtensionFromMimeType(mimeType) ?: ""
|
||||
val fileName = "${System.currentTimeMillis()}.${extension}"
|
||||
val targetFile = File(targetDir, fileName)
|
||||
var targetFile = File(targetDir, fileName)
|
||||
|
||||
// Copy file using DocumentFile
|
||||
contentResolver.openInputStream(uri)?.use { input ->
|
||||
@@ -124,7 +123,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
val contentResolver = context.contentResolver
|
||||
|
||||
// Get the target directory
|
||||
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES + "/Restore")
|
||||
targetDir.mkdirs()
|
||||
|
||||
// Create target file
|
||||
@@ -159,64 +158,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun unHideFile(file: File, onSuccess: (() -> Unit)? = null, onError: ((String) -> Unit)? = null) {
|
||||
lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
// Create target directory (Downloads)
|
||||
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
targetDir.mkdirs()
|
||||
|
||||
// Create target file with same name or timestamp
|
||||
val targetFile = File(targetDir, file.name)
|
||||
|
||||
// If file with same name exists, add timestamp
|
||||
val finalTargetFile = if (targetFile.exists()) {
|
||||
val nameWithoutExt = file.nameWithoutExtension
|
||||
val extension = file.extension
|
||||
File(targetDir, "${nameWithoutExt}_${System.currentTimeMillis()}.${extension}")
|
||||
} else {
|
||||
targetFile
|
||||
}
|
||||
|
||||
// Copy file content
|
||||
file.copyTo(finalTargetFile, overwrite = false)
|
||||
|
||||
// Verify copy success
|
||||
if (finalTargetFile.exists() && finalTargetFile.length() > 0) {
|
||||
// Delete original hidden file
|
||||
if (file.delete()) {
|
||||
// Trigger media scan for the new file
|
||||
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
|
||||
mediaScanIntent.data = Uri.fromFile(finalTargetFile)
|
||||
context.sendBroadcast(mediaScanIntent)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, context.getString(R.string.file_unhidden_successfully), Toast.LENGTH_SHORT).show()
|
||||
onSuccess?.invoke() // Call success callback
|
||||
}
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "File copied but failed to remove from hidden folder", Toast.LENGTH_SHORT).show()
|
||||
onError?.invoke("Failed to remove from hidden folder")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Failed to copy file", Toast.LENGTH_SHORT).show()
|
||||
onError?.invoke("Failed to copy file")
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Error unhiding file: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
onError?.invoke(e.message ?: "Unknown error")
|
||||
}
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -224,7 +165,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
// First try to delete using DocumentFile
|
||||
val documentFile = DocumentFile.fromSingleUri(context, photoUri)
|
||||
if (documentFile?.exists() == true && documentFile.canWrite()) {
|
||||
val deleted = documentFile.delete()
|
||||
documentFile.delete()
|
||||
withContext(Dispatchers.Main) {
|
||||
// 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),
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import java.io.File
|
||||
|
||||
class FolderManager(private val context: Context) {
|
||||
companion object {
|
||||
const val HIDDEN_DIR = ".CalculatorHide"
|
||||
}
|
||||
class FolderManager {
|
||||
|
||||
|
||||
fun createFolder(parentDir: File, folderName: String): Boolean {
|
||||
val newFolder = File(parentDir, folderName)
|
||||
return if (!newFolder.exists()) {
|
||||
newFolder.mkdirs()
|
||||
// Create .nomedia file to hide from media scanners
|
||||
File(newFolder, ".nomedia").createNewFile()
|
||||
true
|
||||
} else {
|
||||
@@ -44,7 +39,7 @@ class FolderManager(private val context: Context) {
|
||||
directory.listFiles()?.filter { it.isDirectory && it.name != ".nomedia" } ?: emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}.sortedBy { it.name.lowercase() }
|
||||
}
|
||||
|
||||
fun getFilesInFolder(folder: File): List<File> {
|
||||
@@ -54,20 +49,4 @@ class FolderManager(private val context: Context) {
|
||||
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.SharedPreferences
|
||||
import java.security.MessageDigest
|
||||
import androidx.core.content.edit
|
||||
|
||||
class PrefsUtil(context: Context) {
|
||||
private val prefs: SharedPreferences = context.getSharedPreferences("Calculator", Context.MODE_PRIVATE)
|
||||
@@ -13,26 +14,33 @@ class PrefsUtil(context: Context) {
|
||||
|
||||
fun savePassword(password: String) {
|
||||
val hashedPassword = hashPassword(password)
|
||||
prefs.edit()
|
||||
.putString("password", hashedPassword)
|
||||
.apply()
|
||||
prefs.edit {
|
||||
putString("password", hashedPassword)
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
return prefs.getBoolean(key,defValue)
|
||||
}
|
||||
fun getInt(key: String, defValue: Int): Int{
|
||||
return prefs.getInt(key,defValue)
|
||||
}
|
||||
|
||||
fun resetPassword(){
|
||||
prefs.edit()
|
||||
.remove("password")
|
||||
.remove("security_question")
|
||||
.remove("security_answer")
|
||||
.apply()
|
||||
prefs.edit {
|
||||
remove("password")
|
||||
.remove("security_question")
|
||||
.remove("security_answer")
|
||||
}
|
||||
}
|
||||
|
||||
fun validatePassword(input: String): Boolean {
|
||||
@@ -40,11 +48,15 @@ class PrefsUtil(context: Context) {
|
||||
return stored == hashPassword(input)
|
||||
}
|
||||
|
||||
fun getPassword(): String{
|
||||
return prefs.getString("password", "") ?: ""
|
||||
}
|
||||
|
||||
fun saveSecurityQA(question: String, answer: String) {
|
||||
prefs.edit()
|
||||
.putString("security_question", question)
|
||||
.putString("security_answer", hashPassword(answer))
|
||||
.apply()
|
||||
prefs.edit {
|
||||
putString("security_question", question)
|
||||
.putString("security_answer", hashPassword(answer))
|
||||
}
|
||||
}
|
||||
|
||||
fun validateSecurityAnswer(answer: String): Boolean {
|
||||
|
||||
@@ -5,60 +5,254 @@ import android.net.Uri
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import javax.crypto.CipherOutputStream
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
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 {
|
||||
companion object {
|
||||
private const val ALGORITHM = "AES"
|
||||
private const val HIDDEN_FOLDER = "Calculator_Data"
|
||||
object SecurityUtils {
|
||||
private const val ALGORITHM = "AES"
|
||||
private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
|
||||
private const val KEY_SIZE = 256
|
||||
val ENCRYPTED_EXTENSION = ".enc"
|
||||
|
||||
private fun getSecretKey(context: Context): SecretKey {
|
||||
val keyStore = context.getSharedPreferences("keystore", Context.MODE_PRIVATE)
|
||||
val useCustomKey = keyStore.getBoolean("use_custom_key", false)
|
||||
|
||||
fun validatePassword(input: String, storedHash: String): Boolean {
|
||||
return input.hashCode().toString() == storedHash
|
||||
}
|
||||
|
||||
fun encryptFile(context: Context, sourceUri: Uri, password: String): File {
|
||||
val inputStream = context.contentResolver.openInputStream(sourceUri)
|
||||
val secretKey = generateKey(password)
|
||||
val cipher = Cipher.getInstance(ALGORITHM)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||
|
||||
val hiddenDir = File(context.getExternalFilesDir(null), HIDDEN_FOLDER)
|
||||
if (!hiddenDir.exists()) hiddenDir.mkdirs()
|
||||
|
||||
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)
|
||||
if (useCustomKey) {
|
||||
val customKey = keyStore.getString("custom_key", null)
|
||||
if (customKey != null) {
|
||||
try {
|
||||
val messageDigest = java.security.MessageDigest.getInstance("SHA-256")
|
||||
val keyBytes = messageDigest.digest(customKey.toByteArray())
|
||||
return SecretKeySpec(keyBytes, ALGORITHM)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
val finalBlock = cipher.doFinal()
|
||||
outputStream.write(finalBlock)
|
||||
}
|
||||
outputStream.close()
|
||||
return encryptedFile
|
||||
}
|
||||
|
||||
fun decryptFile(file: File, password: String): ByteArray {
|
||||
val secretKey = generateKey(password)
|
||||
val cipher = Cipher.getInstance(ALGORITHM)
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey)
|
||||
|
||||
val inputStream = FileInputStream(file)
|
||||
val bytes = inputStream.readBytes()
|
||||
inputStream.close()
|
||||
|
||||
return cipher.doFinal(bytes)
|
||||
}
|
||||
|
||||
private fun generateKey(password: String): SecretKey {
|
||||
val keyBytes = password.toByteArray().copyOf(16)
|
||||
return SecretKeySpec(keyBytes, ALGORITHM)
|
||||
val encodedKey = keyStore.getString("secret_key", null)
|
||||
return if (encodedKey != null) {
|
||||
try {
|
||||
val decodedKey = android.util.Base64.decode(encodedKey, android.util.Base64.DEFAULT)
|
||||
SecretKeySpec(decodedKey, ALGORITHM)
|
||||
} catch (_: Exception) {
|
||||
generateAndStoreNewKey(keyStore)
|
||||
}
|
||||
} else {
|
||||
generateAndStoreNewKey(keyStore)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -7,34 +7,31 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.PermissionChecker
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
||||
|
||||
private val requestPermissionLauncher = activity.registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
if (permissions.all { it.value }) {
|
||||
onPermissionGranted?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
private var onPermissionGranted: (() -> Unit)? = null
|
||||
|
||||
fun requestStoragePermission(onGranted: () -> Unit) {
|
||||
fun requestStoragePermission(
|
||||
launcher: ActivityResultLauncher<Array<String>>,
|
||||
onGranted: () -> Unit
|
||||
) {
|
||||
onPermissionGranted = onGranted
|
||||
|
||||
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
onGranted()
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
@@ -44,7 +41,7 @@ class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
requestPermissionLauncher.launch(permissions)
|
||||
launcher.launch(permissions)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,4 +62,10 @@ class StoragePermissionUtil(private val activity: AppCompatActivity) {
|
||||
writePermission == PermissionChecker.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handlePermissionResult(permissions: Map<String, Boolean>) {
|
||||
if (permissions.all { it.value }) {
|
||||
onPermissionGranted?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800dp"
|
||||
android:height="800dp"
|
||||
android:width="50dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<corners android:bottomLeftRadius="15dp" android:bottomRightRadius="15dp"/>
|
||||
<solid android:color="?attr/cardForegroundColor"/>
|
||||
<solid android:color="?attr/colorSecondaryContainer"/>
|
||||
</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>
|
||||
5
app/src/main/res/drawable/ic_dice.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M340,680Q365,680 382.5,662.5Q400,645 400,620Q400,595 382.5,577.5Q365,560 340,560Q315,560 297.5,577.5Q280,595 280,620Q280,645 297.5,662.5Q315,680 340,680ZM340,400Q365,400 382.5,382.5Q400,365 400,340Q400,315 382.5,297.5Q365,280 340,280Q315,280 297.5,297.5Q280,315 280,340Q280,365 297.5,382.5Q315,400 340,400ZM620,680Q645,680 662.5,662.5Q680,645 680,620Q680,595 662.5,577.5Q645,560 620,560Q595,560 577.5,577.5Q560,595 560,620Q560,645 577.5,662.5Q595,680 620,680ZM620,400Q645,400 662.5,382.5Q680,365 680,340Q680,315 662.5,297.5Q645,280 620,280Q595,280 577.5,297.5Q560,315 560,340Q560,365 577.5,382.5Q595,400 620,400ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z"/>
|
||||
|
||||
</vector>
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="50dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/textColor"
|
||||
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"/>
|
||||
</vector>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
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>
|
||||
|
||||
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>
|
||||
5
app/src/main/res/drawable/ic_select_all.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M268,720L42,494L99,438L269,608L269,608L325,664L268,720ZM494,720L268,494L324,437L494,607L862,239L918,296L494,720ZM494,494L437,438L635,240L692,296L494,494Z"/>
|
||||
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_slideshow.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp">
|
||||
<path
|
||||
android:fillColor="@color/svgTintColor"
|
||||
android:pathData="M10,8v8l5,-4 -5,-4zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,5h14v14z"/>
|
||||
</vector>
|
||||
@@ -5,74 +5,86 @@
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
tools:context=".activities.SetupPasswordActivity">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/change_password"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="18dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilOldPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:hint="@string/enter_old_password"
|
||||
app:endIconMode="password_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/change_password"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etOldPassword"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilOldPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:singleLine="true"
|
||||
android:inputType="number" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
android:layout_marginTop="32dp"
|
||||
android:hint="@string/enter_old_password"
|
||||
app:endIconMode="password_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilNewPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/enter_new_password"
|
||||
app:endIconMode="password_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilOldPassword">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etOldPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="number"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etNewPassword"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilNewPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:singleLine="true"
|
||||
android:inputType="number" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/enter_new_password"
|
||||
app:endIconMode="password_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilOldPassword">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnChangePassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:padding="12dp"
|
||||
android:text="@string/change_password"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilNewPassword" />
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etNewPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="number"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnResetPassword"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/forgot_password"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnChangePassword" />
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnChangePassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:padding="12dp"
|
||||
android:text="@string/change_password"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilNewPassword" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnResetPassword"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/forgot_password"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnChangePassword" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -39,57 +39,44 @@
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/folderName"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_edit"
|
||||
app:tint="?attr/colorPrimary"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="9dp"
|
||||
android:visibility="gone"
|
||||
android:background="#00000000"
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_edit"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:id="@+id/edit"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/delete"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="#00000000"
|
||||
android:padding="9dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_delete"
|
||||
app:tint="?attr/colorPrimary"
|
||||
android:visibility="gone" />
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_delete"
|
||||
style="@style/Widget.Material3.Button.IconButton" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/menuButton"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_more"
|
||||
app:tint="?attr/colorPrimary"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="9dp"
|
||||
android:visibility="gone"
|
||||
android:background="#00000000"/>
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_more"
|
||||
style="@style/Widget.Material3.Button.IconButton"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_list"
|
||||
android:scaleType="fitCenter"
|
||||
app:tint="?attr/colorPrimary"
|
||||
android:background="#00000000"
|
||||
android:padding="8dp"
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_dice"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:id="@+id/random" />
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_list"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:id="@+id/folderOrientation"/>
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_settings"
|
||||
android:scaleType="fitCenter"
|
||||
app:tint="?attr/colorPrimary"
|
||||
|
||||
android:background="#00000000"
|
||||
android:padding="8dp"
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_settings"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:id="@+id/settings"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -128,7 +115,7 @@
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:text="@string/no_items_available_add_one_by_clicking_on_the_plus_button"
|
||||
android:text="@string/there_is_no_folders_available_create_one_by_clicking_on_the_add_folder_button_showing_in_the_bottom"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
android:layout_margin="0dp"
|
||||
android:background="@drawable/bottom_corner"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHeight_percent="0.3"
|
||||
app:layout_constraintHeight_percent="0.4"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:scrollbars="none"
|
||||
android:paddingTop="27dp"
|
||||
android:gravity="end|bottom"
|
||||
app:layout_constraintBottom_toTopOf="@+id/total"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -47,7 +48,7 @@
|
||||
android:gravity="end|bottom"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:padding="10dp"
|
||||
android:text=""
|
||||
android:text="0"
|
||||
android:textSize="70sp"
|
||||
tools:ignore="Suspicious0dp" />
|
||||
</LinearLayout>
|
||||
@@ -59,7 +60,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoSizeMaxTextSize="40sp"
|
||||
android:autoSizeMinTextSize="24sp"
|
||||
android:autoSizeMinTextSize="12sp"
|
||||
android:autoSizeStepGranularity="2sp"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:gravity="end|bottom"
|
||||
@@ -85,7 +86,7 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/displayContainer">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
@@ -95,49 +96,68 @@
|
||||
android:id="@+id/btnClear"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||
android:text="C"
|
||||
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
|
||||
android:id="@+id/btnPercent"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="0dp"
|
||||
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||
android:text="%"
|
||||
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
|
||||
android:id="@+id/btnDivide"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="0dp"
|
||||
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||
android:text="÷"
|
||||
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
|
||||
android:id="@+id/cut"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/gradient_bg"
|
||||
android:scaleType="centerInside"
|
||||
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_height="0dp"
|
||||
android:layout_weight="1"
|
||||
@@ -147,10 +167,16 @@
|
||||
android:id="@+id/btn7"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
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"
|
||||
app:cornerRadius="15dp" />
|
||||
|
||||
@@ -158,21 +184,32 @@
|
||||
android:id="@+id/btn8"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:text="8"
|
||||
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
|
||||
android:id="@+id/btn9"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
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"
|
||||
app:cornerRadius="15dp" />
|
||||
|
||||
@@ -180,18 +217,22 @@
|
||||
android:id="@+id/btnMultiply"
|
||||
style="@style/CustomMaterialButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
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"
|
||||
app:cornerRadius="15dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
@@ -201,51 +242,71 @@
|
||||
android:id="@+id/btn4"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:layout_weight="1"
|
||||
android:text="4"
|
||||
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
|
||||
android:id="@+id/btn5"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:layout_weight="1"
|
||||
android:text="5"
|
||||
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
|
||||
android:id="@+id/btn6"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:layout_weight="1"
|
||||
android:text="6"
|
||||
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
|
||||
android:id="@+id/btnMinus"
|
||||
style="@style/CustomMaterialButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/white"
|
||||
android:text="-"
|
||||
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_height="0dp"
|
||||
android:layout_weight="1"
|
||||
@@ -255,92 +316,144 @@
|
||||
android:id="@+id/btn1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:text="1"
|
||||
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
|
||||
android:id="@+id/btn2"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:text="2"
|
||||
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
|
||||
android:id="@+id/btn3"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:text="3"
|
||||
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
|
||||
android:id="@+id/btnPlus"
|
||||
style="@style/CustomMaterialButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:text="+"
|
||||
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_height="0dp"
|
||||
android:layout_weight="1"
|
||||
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
|
||||
android:id="@+id/btn0"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_weight="2"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:text="0"
|
||||
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
|
||||
android:id="@+id/btnDot"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:text="."
|
||||
android:layout_weight="1"
|
||||
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
|
||||
android:id="@+id/btnEquals"
|
||||
style="@style/CustomMaterialButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:text="="
|
||||
android:textColor="@color/white"
|
||||
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>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/main"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
@@ -13,6 +14,7 @@
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" >
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
@@ -25,11 +27,20 @@
|
||||
android:id="@+id/back"/>
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:textSize="22sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/preview_file"/>
|
||||
android:text="@string/preview_file"
|
||||
android:layout_weight="1"/>
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_slideshow"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="#00000000"
|
||||
android:padding="8dp"
|
||||
android:id="@+id/start_slideshow"/>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
@@ -43,6 +54,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/footer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/main"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.SettingsActivity">
|
||||
|
||||
@@ -81,7 +82,9 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="5dp">
|
||||
android:background="#00000000"
|
||||
android:backgroundTint="#00000000"
|
||||
app:cardElevation="0dp">
|
||||
<ImageView
|
||||
android:id="@+id/appLogo"
|
||||
android:layout_width="48dp"
|
||||
@@ -254,6 +257,23 @@
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/restrict_screenshots_in_hidden_section"
|
||||
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>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
@@ -5,104 +5,116 @@
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
tools:context=".activities.SetupPasswordActivity">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setup_password"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
android:padding="18dp"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:hint="@string/enter_password"
|
||||
app:endIconMode="password_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etPassword"
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setup_password"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:singleLine="true"
|
||||
android:inputType="number" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
android:layout_marginTop="32dp"
|
||||
android:hint="@string/enter_password"
|
||||
app:endIconMode="password_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTitle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilConfirmPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/confirm_password"
|
||||
app:endIconMode="password_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilPassword">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="number"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etConfirmPassword"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilConfirmPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:singleLine="true"
|
||||
android:inputType="number" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/confirm_password"
|
||||
app:endIconMode="password_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilPassword">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilSecurityQuestion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/security_question_for_password_reset"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilConfirmPassword">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etConfirmPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="number"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etSecurityQuestion"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilSecurityQuestion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/security_question_for_password_reset"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilConfirmPassword">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilSecurityAnswer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/security_answer"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilSecurityQuestion">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etSecurityQuestion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etSecurityAnswer"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilSecurityAnswer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/security_answer"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilSecurityQuestion">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSavePassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:padding="12dp"
|
||||
android:text="@string/save_password"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilSecurityAnswer" />
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etSecurityAnswer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnResetPassword"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/forgot_password"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnSavePassword" />
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSavePassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:padding="12dp"
|
||||
android:text="@string/save_password"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilSecurityAnswer" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnResetPassword"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/forgot_password"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnSavePassword" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -39,30 +39,40 @@
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/folderName"/>
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/menuButton"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_more"
|
||||
app:tint="?attr/colorPrimary"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="7dp"
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/selectAllButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_select_all"
|
||||
android:visibility="gone"
|
||||
android:background="#00000000"/>
|
||||
style="@style/Widget.Material3.Button.IconButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/menuButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_more"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Button.IconButton"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/swipeLayout"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="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
|
||||
android:id="@+id/noItems"
|
||||
@@ -79,7 +89,7 @@
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_no_items" />
|
||||
android:src="@drawable/ic_file_no_item" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noItemsTxt"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
|
||||
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_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
app:cardCornerRadius="8dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:layout_margin="3dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/folderIcon"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:src="@drawable/ic_folder"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folderName"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textSize="18sp"/>
|
||||
android:padding="12dp"
|
||||
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:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:cardCornerRadius="10dp"
|
||||
android:layout_margin="5dp"
|
||||
app:cardElevation="3dp"
|
||||
android:backgroundTint="#404040"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -35,8 +35,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/add_image" />
|
||||
android:scaleType="centerCrop"/>
|
||||
<View
|
||||
android:id="@+id/shade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bottom_shade"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/selectedLayer"
|
||||
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>
|
||||
<ImageView
|
||||
@@ -60,6 +78,13 @@
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_play_circle"
|
||||
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
|
||||
android:id="@+id/selected"
|
||||
android:layout_width="20dp"
|
||||
@@ -70,16 +95,7 @@
|
||||
android:src="@drawable/selected"
|
||||
android:background="@drawable/gradient_bg"
|
||||
android:layout_gravity="end"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fileNameTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="5dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -42,7 +42,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:cardCornerRadius="15dp"
|
||||
app:cardElevation="10dp"
|
||||
android:layout_margin="20dp">
|
||||
<LinearLayout
|
||||
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:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
<item name="colorSecondaryContainer">#481A4324</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<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="add_image">Add Image</string>
|
||||
<string name="add_audio">Add Audio</string>
|
||||
@@ -24,7 +25,7 @@
|
||||
<string name="preview_videos">Preview Videos</string>
|
||||
<string name="preview_audios">Preview Audios</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="delete_permanently">Delete Permanently</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
@@ -64,7 +65,7 @@
|
||||
<string name="unknown_file">Unknown File</string>
|
||||
<string name="details"> DETAILS</string>
|
||||
<string name="audio_hidded_successfully">Audios hidden successfully</string>
|
||||
<string name="no_items_available_add_one_by_clicking_on_the_plus_button">No Items Available, Add one by clicking on the</string>
|
||||
<string name="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="enter_123456">Enter 123456</string>
|
||||
<string name="create_folder">Create Folder</string>
|
||||
@@ -79,6 +80,7 @@
|
||||
<string name="error_loading_files">Error loading files</string>
|
||||
<string name="delete_items">Delete Folder</string>
|
||||
<string name="are_you_sure_you_want_to_delete_selected_items">Are you sure you want to delete selected Folders?</string>
|
||||
<string name="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="folder_deleted_successfully">Folder deleted successfully</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="files_unhidden_successfully">Files unhidden successfully</string>
|
||||
<string name="some_files_could_not_be_unhidden">Some files could not be unhidden</string>
|
||||
<string name="copy_to_another_folder">Copy to Another Folder</string>
|
||||
<string name="move_to_another_folder">Move to Another Folder</string>
|
||||
<string name="copy_to_another_folder">Copy</string>
|
||||
<string name="move_to_another_folder">Move</string>
|
||||
<string name="select_destination_folder">Select Destination Folder</string>
|
||||
<string name="no_folders_available">No other folders available</string>
|
||||
<string name="change_password">Change Password</string>
|
||||
@@ -120,10 +122,58 @@
|
||||
<string name="app_settings">App Settings</string>
|
||||
<string name="restrict_screenshots_in_hidden_section">Restrict Screenshots in Hidden Section</string>
|
||||
<string name="security_settings">Security Settings</string>
|
||||
<string name="version">Version 1.3</string>
|
||||
<string name="app_details">App Details</string>
|
||||
<string name="setup_password">Setup Password</string>
|
||||
<string name="security_question_for_password_reset">Security Question (For Password Reset)</string>
|
||||
<string name="security_answer">Security Answer</string>
|
||||
<string name="save_password">Save Password</string>
|
||||
<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>
|
||||
@@ -12,6 +12,7 @@
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="colorControlNormal">#483CFF61</item>
|
||||
<item name="colorSecondaryContainer">#48BDFFCB</item>
|
||||
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Allow access to external files -->
|
||||
<external-path name="external_files" path="." />
|
||||
<!-- Allow access to hidden directories -->
|
||||
<external-files-path name="hidden_files" path=".CalculatorHide/" />
|
||||
<cache-path name="cache_files" path="." />
|
||||
</paths>
|
||||
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
[versions]
|
||||
agp = "8.9.1"
|
||||
documentfile = "1.0.1"
|
||||
agp = "8.11.1"
|
||||
documentfile = "1.1.0"
|
||||
exp4j = "0.4.8"
|
||||
glide = "4.16.0"
|
||||
kotlin = "1.9.24"
|
||||
coreKtx = "1.15.0"
|
||||
kotlin = "2.0.0"
|
||||
coreKtx = "1.16.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
appcompat = "1.7.0"
|
||||
appcompat = "1.7.1"
|
||||
lottie = "6.2.0"
|
||||
material = "1.12.0"
|
||||
activity = "1.9.3"
|
||||
constraintlayout = "2.2.0"
|
||||
activity = "1.10.1"
|
||||
constraintlayout = "2.2.1"
|
||||
materialColorUtilities = "1.3.0"
|
||||
gridlayout = "1.0.0"
|
||||
gridlayout = "1.1.0"
|
||||
photoview = "2.3.0"
|
||||
roomRuntime = "2.7.1"
|
||||
swiperefreshlayout = "1.2.0-beta01"
|
||||
viewpager = "1.1.0"
|
||||
zoomage = "1.3.1"
|
||||
lifecycleProcess = "2.9.2"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
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" }
|
||||
exp4j = { module = "net.objecthunter:exp4j", version.ref = "exp4j" }
|
||||
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
||||
@@ -37,6 +44,7 @@ material-color-utilities = { module = "com.google.android.material:material-colo
|
||||
androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" }
|
||||
photoview = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoview" }
|
||||
zoomage = { module = "com.jsibbold:zoomage", version.ref = "zoomage" }
|
||||
androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleProcess" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Sun Nov 03 19:53:13 IST 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
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 |
BIN
media/payment_qr.png
Normal file
|
After Width: | Height: | Size: 234 KiB |