Changes For Folder Feature

This commit is contained in:
Binondi
2025-06-03 20:29:39 +05:30
parent 970cde737a
commit bfc339c101
31 changed files with 758 additions and 577 deletions

View File

@@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "devs.org.calculator",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 4,
"versionName": "1.3",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 26
}

View File

@@ -66,6 +66,10 @@
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>

View File

@@ -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)
}

View File

@@ -5,7 +5,6 @@ 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
@@ -14,6 +13,7 @@ import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.color.DynamicColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import devs.org.calculator.R
import devs.org.calculator.adapters.FolderAdapter
@@ -36,22 +36,20 @@ 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)
setupInitialUIState()
setupClickListeners()
setupBackPressedHandler()
@@ -100,20 +98,17 @@ 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)
}
}
}
@@ -141,11 +136,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}")
showEmptyState()
}
}
@@ -173,7 +167,6 @@ class HiddenActivity : AppCompatActivity() {
folderManager.createFolder(hiddenDir, newName)
refreshCurrentView()
} catch (e: Exception) {
Log.e(TAG, "Error creating folder: ${e.message}")
Toast.makeText(
this@HiddenActivity,
"Failed to create folder",
@@ -220,11 +213,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}")
showEmptyState()
}
}
@@ -232,8 +223,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 +236,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 +250,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 +263,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 +297,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()
}
}
@@ -344,11 +329,11 @@ class HiddenActivity : AppCompatActivity() {
}
override fun onNegativeButtonClicked() {
// Do nothing
}
override fun onNaturalButtonClicked() {
// Do nothing
}
}
)
@@ -360,7 +345,6 @@ class HiddenActivity : AppCompatActivity() {
selectedFolders.forEach { folder ->
if (!folderManager.deleteFolder(folder)) {
allDeleted = false
Log.e(TAG, "Failed to delete folder: ${folder.name}")
}
}
@@ -372,26 +356,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,25 +380,18 @@ 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.folderOrientation.visibility = View.VISIBLE
binding.settings.visibility = View.VISIBLE
@@ -429,7 +400,6 @@ 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
@@ -442,8 +412,6 @@ class HiddenActivity : AppCompatActivity() {
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 +424,6 @@ class HiddenActivity : AppCompatActivity() {
} else {
enterFolderSelectionMode()
}
// Always update edit button visibility when selection mode changes
updateEditButtonVisibility()
}
@@ -520,11 +487,8 @@ class HiddenActivity : AppCompatActivity() {
}
if (oldFolder.renameTo(newFolder)) {
// Clear selection from both adapters
folderAdapter?.clearSelection()
listFolderAdapter?.clearSelection()
// Exit selection mode
exitFolderSelectionMode()
refreshCurrentView()

View File

@@ -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,8 @@ 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 com.google.android.material.color.DynamicColors
class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.DialogCallback {
private lateinit var binding: ActivityMainBinding
@@ -34,13 +37,14 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
private val dialogUtil = DialogUtil(this)
private val fileManager = FileManager(this, this)
private val sp by lazy { getSharedPreferences("app", MODE_PRIVATE) }
private val prefs:PrefsUtil by lazy { PrefsUtil(this) }
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
enableEdgeToEdge()
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
handleActivityResult(result)
}
@@ -49,15 +53,14 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
binding.display.text = getString(R.string.enter_123456)
}
// Ask permission
if(!Environment.isExternalStorageManager()) {
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)
@@ -65,19 +68,20 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
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 +103,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 +112,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")
}
}
@@ -149,7 +152,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
}
private fun clearDisplay() {
currentExpression = "0"
currentExpression = ""
binding.total.text = ""
lastWasOperator = false
lastWasPercent = false
@@ -173,41 +176,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 +205,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 +220,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 +229,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 +255,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")
@@ -303,10 +281,11 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
}
}
@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()
@@ -363,8 +342,6 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback, DialogUtil.Dial
binding.total.text = ""
return
}
// Process the expression for preview calculation
var processedExpression = currentExpression.replace("×", "*")
if (processedExpression.contains("%")) {
@@ -411,27 +388,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()
}
}
}

View File

@@ -6,11 +6,13 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.color.DynamicColors
import devs.org.calculator.R
import devs.org.calculator.adapters.ImagePreviewAdapter
import devs.org.calculator.databinding.ActivityPreviewBinding
import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.PrefsUtil
import kotlinx.coroutines.launch
import java.io.File
@@ -18,43 +20,31 @@ 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 prefs:PrefsUtil by lazy { PrefsUtil(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPreviewBinding.inflate(layoutInflater)
setContentView(binding.root)
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,6 +52,16 @@ 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)) {
@@ -93,108 +93,162 @@ 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
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 clickListeners() {
private fun setupClickListeners() {
binding.back.setOnClickListener {
finish()
}
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)
handleUnhideFile()
}
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
adapter.onItemScrolledAway(currentPosition)
currentPosition = position
}
})
}
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 {
fileManager.deletePhotoFromExternalStorage(fileUri)
removeFileFromList(currentPosition)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
override fun onNegativeButtonClicked() {
// Handle negative button click
}
override fun onNegativeButtonClicked() {}
override fun onNaturalButtonClicked() {
// Handle neutral button click
override fun onNaturalButtonClicked() {}
}
)
}
}
private fun handleUnhideFile() {
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.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 {
try {
fileManager.copyFileToNormalDir(fileUri)
removeFileFromList(currentPosition)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
)
}
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
}
}
}

View File

@@ -1,36 +1,32 @@
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 androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.net.toUri
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
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 val prefs:PrefsUtil by lazy { PrefsUtil(this) }
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)
DEV_GITHUB_URL = getString(R.string.github_profile)
GITHUB_URL = getString(R.string.calculator_hide_files, DEV_GITHUB_URL)
setupUI()
loadSettings()
setupListeners()
@@ -42,6 +38,7 @@ class SettingsActivity : AppCompatActivity() {
}
}
private fun loadSettings() {
binding.dynamicThemeSwitch.isChecked = prefs.getBoolean("dynamic_theme", true)
@@ -72,9 +69,14 @@ 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)
}
}
}
@@ -97,7 +99,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 +107,7 @@ class SettingsActivity : AppCompatActivity() {
}
}
binding.showFileNames.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean("showFileName", isChecked).apply()
prefs.setBoolean("showFileName", isChecked)
}
}
@@ -115,20 +117,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 +143,11 @@ 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()
}
}
}

View File

@@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
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,6 +19,7 @@ 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)
@@ -72,8 +74,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()

View File

@@ -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,8 +19,9 @@ 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.color.DynamicColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import devs.org.calculator.R
import devs.org.calculator.adapters.FileAdapter
import devs.org.calculator.adapters.FolderSelectionAdapter
@@ -32,6 +32,7 @@ import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.FileManager.Companion.HIDDEN_DIR
import devs.org.calculator.utils.FolderManager
import devs.org.calculator.utils.PrefsUtil
import kotlinx.coroutines.launch
import java.io.File
@@ -51,12 +52,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?) {
@@ -81,11 +82,11 @@ 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() {
pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
@@ -192,19 +193,12 @@ class ViewFolderActivity : AppCompatActivity() {
}
}
private fun refreshCurrentView() {
if (currentFolder != null) {
refreshCurrentFolder()
}
}
override fun onResume() {
super.onResume()
refreshCurrentFolder()
}
private fun openFolder(folder: File) {
// Ensure folder exists and has .nomedia file
if (!folder.exists()) {
folder.mkdirs()
File(folder, ".nomedia").createNewFile()
@@ -227,8 +221,6 @@ class ViewFolderActivity : AppCompatActivity() {
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),
@@ -344,13 +336,9 @@ class ViewFolderActivity : AppCompatActivity() {
performFileUnhiding(selectedFiles)
}
override fun onNegativeButtonClicked() {
// Do nothing
}
override fun onNegativeButtonClicked() {}
override fun onNaturalButtonClicked() {
// Do nothing
}
override fun onNaturalButtonClicked() {}
}
)
}
@@ -368,13 +356,9 @@ class ViewFolderActivity : AppCompatActivity() {
performFileDeletion(selectedFiles)
}
override fun onNegativeButtonClicked() {
// Do nothing
}
override fun onNegativeButtonClicked() {}
override fun onNaturalButtonClicked() {
// Do nothing
}
override fun onNaturalButtonClicked() {}
}
)
}
@@ -523,8 +507,6 @@ class ViewFolderActivity : AppCompatActivity() {
}
Toast.makeText(this@ViewFolderActivity, message, Toast.LENGTH_SHORT).show()
// Fixed: Ensure proper order of operations
fileAdapter?.exitSelectionMode()
refreshCurrentFolder()
}
@@ -546,8 +528,6 @@ class ViewFolderActivity : AppCompatActivity() {
}
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// Fixed: Ensure proper order of operations
fileAdapter?.exitSelectionMode()
refreshCurrentFolder()
}
@@ -569,10 +549,8 @@ class ViewFolderActivity : AppCompatActivity() {
}
}
val message = if (allCopied) "Files copied successfully" else "Some files could not be copied"
val message = if (allCopied) getString(R.string.files_copied_successfully) else getString(R.string.some_files_could_not_be_copied)
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// Fixed: Ensure proper order of operations
fileAdapter?.exitSelectionMode()
refreshCurrentFolder()
}
@@ -589,17 +567,15 @@ class ViewFolderActivity : AppCompatActivity() {
}
}
val message = if (allMoved) "Files moved successfully" else "Some files could not be moved"
val message = if (allMoved) getString(R.string.files_moved_successfully) else getString(R.string.some_files_could_not_be_moved)
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// Fixed: Ensure proper order of operations
fileAdapter?.exitSelectionMode()
refreshCurrentFolder()
}
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()

View File

@@ -38,10 +38,8 @@ class FileAdapter(
private val selectedItems = mutableSetOf<Int>()
private var isSelectionMode = false
// Use WeakReference to prevent memory leaks
private var fileOperationCallback: WeakReference<FileOperationCallback>? = null
// Background executor for file operations
private val fileExecutor = Executors.newSingleThreadExecutor()
private val mainHandler = Handler(Looper.getMainLooper())
@@ -49,7 +47,6 @@ class FileAdapter(
private const val TAG = "FileAdapter"
}
// Callback interface for handling file operations and selection changes
interface FileOperationCallback {
fun onFileDeleted(file: File)
fun onFileRenamed(oldFile: File, newFile: File)
@@ -75,7 +72,6 @@ class FileAdapter(
setupClickListeners(file, fileType)
fileNameTextView.visibility = if (showFileName) View.VISIBLE else View.GONE
// Update selection state
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
val isSelected = selectedItems.contains(position)
@@ -89,7 +85,6 @@ class FileAdapter(
return
}
// Handle partial updates based on payload
val changes = payloads.firstOrNull() as? List<String>
changes?.forEach { change ->
when (change) {
@@ -97,14 +92,13 @@ class FileAdapter(
fileNameTextView.text = file.name
}
"SIZE_CHANGED", "MODIFIED_DATE_CHANGED" -> {
// Could update file info if displayed
}
"SELECTION_CHANGED" -> {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
val isSelected = selectedItems.contains(position)
updateSelectionUI(isSelected)
// Notify activity about selection change
notifySelectionModeChange()
}
}
@@ -128,7 +122,6 @@ class FileAdapter(
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.error(R.drawable.ic_document)
.placeholder(R.drawable.ic_document)
.into(imageView)
}
FileManager.FileType.VIDEO -> {
@@ -138,12 +131,12 @@ class FileAdapter(
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.error(R.drawable.ic_document)
.placeholder(R.drawable.ic_document)
.into(imageView)
}
FileManager.FileType.AUDIO -> {
playIcon.visibility = View.GONE
imageView.setImageResource(R.drawable.ic_audio)
imageView.setPadding(50,50,50,50)
}
else -> {
playIcon.visibility = View.GONE
@@ -191,16 +184,18 @@ class FileAdapter(
}
private fun openAudioFile(file: File) {
val fileType = FileManager(context,lifecycleOwner).getFileType(file)
try {
val uri = FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "audio/*")
val fileTypeString = when (fileType) {
FileManager.FileType.IMAGE -> context.getString(R.string.image)
FileManager.FileType.VIDEO -> context.getString(R.string.video)
else -> "unknown"
}
val intent = Intent(context, PreviewActivity::class.java).apply {
putExtra("type", fileTypeString)
putExtra("folder", currentFolder.toString())
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
putExtra("position", adapterPosition)
}
context.startActivity(intent)
} catch (e: Exception) {
@@ -316,7 +311,6 @@ class FileAdapter(
}
private fun deleteFile(file: File) {
// Show confirmation dialog first
MaterialAlertDialogBuilder(context)
.setTitle("Delete File")
.setMessage("Are you sure you want to delete ${file.name}?")
@@ -436,12 +430,10 @@ class FileAdapter(
}
private fun copyToAnotherFolder(file: File) {
// This will be handled by the activity
fileOperationCallback?.get()?.onRefreshNeeded()
}
private fun moveToAnotherFolder(file: File) {
// This will be handled by the activity
fileOperationCallback?.get()?.onRefreshNeeded()
}
@@ -489,7 +481,6 @@ class FileAdapter(
currentList.clear()
super.submitList(null)
} else {
// Create a new list to force update
val newList = list.toMutableList()
super.submitList(newList)
}
@@ -502,6 +493,7 @@ class FileAdapter(
}
}
@SuppressLint("NotifyDataSetChanged")
fun exitSelectionMode() {
if (isSelectionMode) {
isSelectionMode = false
@@ -574,8 +566,6 @@ class FileAdapter(
fun deleteSelectedFiles() {
val selectedFiles = getSelectedItems()
if (selectedFiles.isEmpty()) return
// Show confirmation dialog
MaterialAlertDialogBuilder(context)
.setTitle("Delete Files")
.setMessage("Are you sure you want to delete ${selectedFiles.size} file(s)?")
@@ -611,10 +601,8 @@ class FileAdapter(
}
mainHandler.post {
// Exit selection mode first
exitSelectionMode()
// Show detailed result message
when {
deletedCount > 0 && failedCount == 0 -> {
Toast.makeText(context, "Deleted $deletedCount file(s)", Toast.LENGTH_SHORT).show()
@@ -641,7 +629,6 @@ class FileAdapter(
try {
if (selectedFiles.size == 1) {
// Share single file
val file = selectedFiles.first()
val uri = FileProvider.getUriForFile(
context,
@@ -657,7 +644,6 @@ class FileAdapter(
Intent.createChooser(shareIntent, context.getString(R.string.share_file))
)
} else {
// Share multiple files
val uris = selectedFiles.mapNotNull { file ->
try {
FileProvider.getUriForFile(
@@ -709,7 +695,6 @@ class FileAdapter(
notifyItemChanged(position, listOf("SELECTION_CHANGED"))
}
}
// Ensure callback is notified
notifySelectionModeChange()
}
}

View File

@@ -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,6 @@ class FileDiffCallback : DiffUtil.ItemCallback<File>() {
changes.add("EXISTENCE_CHANGED")
}
return if (changes.isNotEmpty()) changes else null
return changes.ifEmpty { null }
}
}

View File

@@ -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()

View File

@@ -9,7 +9,6 @@ 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.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.RecyclerView
@@ -25,11 +24,8 @@ 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
var images: List<File>
get() = differ.currentList
@@ -43,32 +39,35 @@ 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
currentMediaPlayer?.let {
if (it.isPlaying) it.pause()
it.seekTo(0)
}
currentMediaPlayer = null
isMediaPlayerPrepared = false
stopAndResetCurrentAudio()
if (currentMediaPlayer?.isPlaying == true) {
currentMediaPlayer?.stop()
currentMediaPlayer?.release()
}
currentMediaPlayer = null
holder.bind(imageUrl, fileType, position)
}
override fun getItemCount(): Int = images.size
private fun stopAndResetCurrentAudio() {
currentViewHolder?.stopAndResetAudio()
currentPlayingPosition = -1
currentViewHolder = null
}
inner class ImageViewHolder(private val binding: ViewpagerItemsBinding) : RecyclerView.ViewHolder(binding.root) {
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
fun bind(file: File, fileType: FileManager.FileType, position: Int) {
currentPosition = position
releaseMediaPlayer()
resetAudioUI()
fun bind(file: File, fileType: FileManager.FileType) {
when (fileType) {
FileManager.FileType.VIDEO -> {
binding.imageView.visibility = View.GONE
@@ -114,7 +113,6 @@ class ImagePreviewAdapter(
binding.audioTitle.text = file.name
setupAudioPlayer(file)
setupSeekBar()
setupPlaybackControls()
}
else -> {
@@ -125,27 +123,40 @@ class ImagePreviewAdapter(
}
}
private fun resetAudioUI() {
binding.playPause.setImageResource(R.drawable.play)
binding.audioSeekBar.value = 0f
binding.audioSeekBar.valueTo = 100f // Default value
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 +164,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 +187,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 +316,14 @@ class ImagePreviewAdapter(
super.onViewRecycled(holder)
holder.releaseMediaPlayer()
}
}
fun onItemScrolledAway(position: Int) {
if (currentPlayingPosition == position) {
stopAndResetCurrentAudio()
}
}
fun releaseAllResources() {
stopAndResetCurrentAudio()
}
}

View File

@@ -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()

View File

@@ -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 {
@@ -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
}
}
}

View File

@@ -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 {
@@ -41,10 +49,10 @@ class PrefsUtil(context: Context) {
}
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 {

View File

@@ -12,6 +12,7 @@ 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) {
@@ -34,7 +35,7 @@ class StoragePermissionUtil(private val activity: AppCompatActivity) {
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)
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -39,57 +39,38 @@
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_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 +109,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>

View File

@@ -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"
@@ -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>

View File

@@ -81,7 +81,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"

View File

@@ -40,16 +40,12 @@
android:id="@+id/folderName"/>
<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="7dp"
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"/>
</LinearLayout>
@@ -79,7 +75,7 @@
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_no_items" />
android:src="@drawable/ic_file" />
<TextView
android:id="@+id/noItemsTxt"

View File

@@ -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"

View File

@@ -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>

View File

@@ -15,13 +15,12 @@
<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"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
@@ -49,7 +48,7 @@
</androidx.cardview.widget.CardView>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView

View File

@@ -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"

View File

@@ -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>

View File

@@ -64,7 +64,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>
@@ -126,4 +126,26 @@
<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>
</resources>

View File

@@ -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>