From bfc339c101d7c85d5066b9a609ccb42473ec65fe Mon Sep 17 00:00:00 2001 From: Binondi Date: Tue, 3 Jun 2025 20:29:39 +0530 Subject: [PATCH] Changes For Folder Feature --- app/release/output-metadata.json | 37 +++ app/src/main/AndroidManifest.xml | 4 + .../java/devs/org/calculator/CalculatorApp.kt | 9 +- .../calculator/activities/HiddenActivity.kt | 62 +--- .../org/calculator/activities/MainActivity.kt | 82 ++---- .../calculator/activities/PreviewActivity.kt | 238 +++++++++------ .../calculator/activities/SettingsActivity.kt | 51 ++-- .../activities/SetupPasswordActivity.kt | 4 +- .../activities/ViewFolderActivity.kt | 50 +--- .../org/calculator/adapters/FileAdapter.kt | 41 +-- .../calculator/adapters/FileDiffCallback.kt | 7 +- .../org/calculator/adapters/FolderAdapter.kt | 2 + .../adapters/ImagePreviewAdapter.kt | 227 ++++++++++----- .../calculator/adapters/ListFolderAdapter.kt | 7 +- .../org/calculator/utils/FolderManager.kt | 25 +- .../devs/org/calculator/utils/PrefsUtil.kt | 34 ++- .../calculator/utils/StoragePermissionUtil.kt | 3 +- app/src/main/res/drawable/add_image.xml | 4 +- app/src/main/res/drawable/bottom_corner.xml | 2 +- app/src/main/res/drawable/ic_file.xml | 12 +- app/src/main/res/layout/activity_hidden.xml | 71 ++--- app/src/main/res/layout/activity_main.xml | 271 +++++++++++++----- app/src/main/res/layout/activity_settings.xml | 4 +- .../main/res/layout/activity_view_folder.xml | 16 +- .../res/layout/item_folder_list_style.xml | 3 +- .../main/res/layout/item_folder_selection.xml | 37 ++- app/src/main/res/layout/list_item_file.xml | 5 +- app/src/main/res/layout/viewpager_items.xml | 1 - app/src/main/res/values-night/themes.xml | 1 + app/src/main/res/values/strings.xml | 24 +- app/src/main/res/values/themes.xml | 1 + 31 files changed, 758 insertions(+), 577 deletions(-) create mode 100644 app/release/output-metadata.json diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..881f6a9 --- /dev/null +++ b/app/release/output-metadata.json @@ -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 +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b6cd3d8..e6d63f1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -66,6 +66,10 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> + + \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/CalculatorApp.kt b/app/src/main/java/devs/org/calculator/CalculatorApp.kt index 55b74ab..509aec4 100644 --- a/app/src/main/java/devs/org/calculator/CalculatorApp.kt +++ b/app/src/main/java/devs/org/calculator/CalculatorApp.kt @@ -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) } diff --git a/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt b/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt index 7059a66..a2ecce8 100644 --- a/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/HiddenActivity.kt @@ -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) { 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) { 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() diff --git a/app/src/main/java/devs/org/calculator/activities/MainActivity.kt b/app/src/main/java/devs/org/calculator/activities/MainActivity.kt index 001a02b..b26a4e0 100644 --- a/app/src/main/java/devs/org/calculator/activities/MainActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/MainActivity.kt @@ -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>() 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, 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() } } } diff --git a/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt b/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt index b386ee5..dc0a7e2 100644 --- a/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt @@ -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 + private var files: MutableList = 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 } - - -} +} \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/activities/SettingsActivity.kt b/app/src/main/java/devs/org/calculator/activities/SettingsActivity.kt index 0dfb400..146ebb5 100644 --- a/app/src/main/java/devs/org/calculator/activities/SettingsActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/SettingsActivity.kt @@ -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() } } } \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/activities/SetupPasswordActivity.kt b/app/src/main/java/devs/org/calculator/activities/SetupPasswordActivity.kt index 431068e..86bd32f 100644 --- a/app/src/main/java/devs/org/calculator/activities/SetupPasswordActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/SetupPasswordActivity.kt @@ -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() diff --git a/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt b/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt index 845a8f2..03e2902 100644 --- a/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/ViewFolderActivity.kt @@ -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 - 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, 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() diff --git a/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt b/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt index 3405d88..38c853b 100644 --- a/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt +++ b/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt @@ -38,10 +38,8 @@ class FileAdapter( private val selectedItems = mutableSetOf() private var isSelectionMode = false - // Use WeakReference to prevent memory leaks private var fileOperationCallback: WeakReference? = 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 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() } } diff --git a/app/src/main/java/devs/org/calculator/adapters/FileDiffCallback.kt b/app/src/main/java/devs/org/calculator/adapters/FileDiffCallback.kt index 7446983..f999020 100644 --- a/app/src/main/java/devs/org/calculator/adapters/FileDiffCallback.kt +++ b/app/src/main/java/devs/org/calculator/adapters/FileDiffCallback.kt @@ -6,13 +6,10 @@ import java.io.File class FileDiffCallback : DiffUtil.ItemCallback() { 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() { } 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() if (oldItem.name != newItem.name) { @@ -42,6 +37,6 @@ class FileDiffCallback : DiffUtil.ItemCallback() { changes.add("EXISTENCE_CHANGED") } - return if (changes.isNotEmpty()) changes else null + return changes.ifEmpty { null } } } \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/adapters/FolderAdapter.kt b/app/src/main/java/devs/org/calculator/adapters/FolderAdapter.kt index 424a8c9..ae7ae4f 100644 --- a/app/src/main/java/devs/org/calculator/adapters/FolderAdapter.kt +++ b/app/src/main/java/devs/org/calculator/adapters/FolderAdapter.kt @@ -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() diff --git a/app/src/main/java/devs/org/calculator/adapters/ImagePreviewAdapter.kt b/app/src/main/java/devs/org/calculator/adapters/ImagePreviewAdapter.kt index d138701..f2e7348 100644 --- a/app/src/main/java/devs/org/calculator/adapters/ImagePreviewAdapter.kt +++ b/app/src/main/java/devs/org/calculator/adapters/ImagePreviewAdapter.kt @@ -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() { 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 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/adapters/ListFolderAdapter.kt b/app/src/main/java/devs/org/calculator/adapters/ListFolderAdapter.kt index c670ebd..13503ab 100644 --- a/app/src/main/java/devs/org/calculator/adapters/ListFolderAdapter.kt +++ b/app/src/main/java/devs/org/calculator/adapters/ListFolderAdapter.kt @@ -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() diff --git a/app/src/main/java/devs/org/calculator/utils/FolderManager.kt b/app/src/main/java/devs/org/calculator/utils/FolderManager.kt index b58cb15..a81beeb 100644 --- a/app/src/main/java/devs/org/calculator/utils/FolderManager.kt +++ b/app/src/main/java/devs/org/calculator/utils/FolderManager.kt @@ -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 - } - } } \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/utils/PrefsUtil.kt b/app/src/main/java/devs/org/calculator/utils/PrefsUtil.kt index 6c52a81..7e07603 100644 --- a/app/src/main/java/devs/org/calculator/utils/PrefsUtil.kt +++ b/app/src/main/java/devs/org/calculator/utils/PrefsUtil.kt @@ -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 { diff --git a/app/src/main/java/devs/org/calculator/utils/StoragePermissionUtil.kt b/app/src/main/java/devs/org/calculator/utils/StoragePermissionUtil.kt index 8c97d2a..2bf198e 100644 --- a/app/src/main/java/devs/org/calculator/utils/StoragePermissionUtil.kt +++ b/app/src/main/java/devs/org/calculator/utils/StoragePermissionUtil.kt @@ -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) } diff --git a/app/src/main/res/drawable/add_image.xml b/app/src/main/res/drawable/add_image.xml index 120d2c4..5cc7428 100644 --- a/app/src/main/res/drawable/add_image.xml +++ b/app/src/main/res/drawable/add_image.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_file.xml b/app/src/main/res/drawable/ic_file.xml index 53c0205..68e0809 100644 --- a/app/src/main/res/drawable/ic_file.xml +++ b/app/src/main/res/drawable/ic_file.xml @@ -1,9 +1,9 @@ - - \ No newline at end of file + + diff --git a/app/src/main/res/layout/activity_hidden.xml b/app/src/main/res/layout/activity_hidden.xml index cddf2a1..3be996b 100644 --- a/app/src/main/res/layout/activity_hidden.xml +++ b/app/src/main/res/layout/activity_hidden.xml @@ -39,57 +39,38 @@ android:layout_weight="1" android:id="@+id/folderName"/> - - + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:icon="@drawable/ic_delete" + style="@style/Widget.Material3.Button.IconButton" /> - + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:icon="@drawable/ic_more" + style="@style/Widget.Material3.Button.IconButton"/> - - @@ -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" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d5c72ac..a9f27ad 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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"> - + 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" /> + 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" /> + 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" /> + 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" /> - + - @@ -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" + /> @@ -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" /> - + - + 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"/> + 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"/> + 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"/> + 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"/> - + - + 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"/> + 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"/> + 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"/> + 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" + /> - + - + + + 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"/> + 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" /> + 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"/> - + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index d0299a1..e7a99f6 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -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"> - + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:icon="@drawable/ic_more" + style="@style/Widget.Material3.Button.IconButton"/> @@ -79,7 +75,7 @@ + android:src="@drawable/ic_file" /> + app:cardCornerRadius="8dp"> - - - - + android:padding="12dp" + android:orientation="horizontal" + android:gravity="center_vertical"> + - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_file.xml b/app/src/main/res/layout/list_item_file.xml index 99c4a6c..3fff396 100644 --- a/app/src/main/res/layout/list_item_file.xml +++ b/app/src/main/res/layout/list_item_file.xml @@ -15,13 +15,12 @@ - + @android:color/transparent @android:color/transparent false + #481A4324 false diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec28001..966208a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -64,7 +64,7 @@ Unknown File DETAILS Audios hidden successfully - No Items Available, Add one by clicking on the + No Files Available, Add one by clicking on the \'+\' button Now Enter \'=\' button Enter 123456 Create Folder @@ -126,4 +126,26 @@ Security Question (For Password Reset) Security Answer Save Password + There is no Folders Available, create one by clicking on the \'Add Folder\' Button showing in the bottom. + 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 + For devices running Android 11 or higher, you\'ll need to grant the \'All Files Access\' permission. + Grant Permission + Later + Storage permission is required for the app to function properly + You can grant permission later from Settings + Permission granted + Permission denied + https://github.com/binondi + %1$s/calculator-hide-files + Would you like to set a specific theme mode? + No + Could not open URL + Files copied successfully + Some files could not be copied + Files moved successfully + Some files could not be moved + OK + If you turn on/off this option, dynamic theme changes will be visible after you reopen the app. + Attention! \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 341c1c6..c4eae63 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -12,6 +12,7 @@ @android:color/transparent @android:color/transparent #483CFF61 + #48BDFFCB true true