From 4c1b15855f8531adc8183791d78eb679580d2ec5 Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Thu, 23 Jul 2026 23:36:47 -0300 Subject: [PATCH 1/4] Improve localization support (+add brazilian portuguese localization) --- .../dev/pranav/applock/AppLockApplication.kt | 3 +- .../java/dev/pranav/applock/MainActivity.kt | 6 + .../applock/core/navigation/AppNavigator.kt | 16 +- .../pranav/applock/core/utils/AppLockUtils.kt | 7 +- .../applock/core/utils/LocaleManager.kt | 25 + .../data/repository/AppLockRepository.kt | 3 + .../data/repository/PreferencesRepository.kt | 13 + .../features/admin/AdminDisableActivity.kt | 6 + .../antiuninstall/ui/AntiUninstallScreen.kt | 54 +- .../features/appintro/ui/AppIntroScreen.kt | 1 + .../applock/features/applist/ui/MainScreen.kt | 22 +- .../ui/AlphanumericPasswordOverlayScreen.kt | 4 +- .../lockscreen/ui/LockScreenOverlayManager.kt | 6 +- .../lockscreen/ui/PasswordOverlayScreen.kt | 28 +- .../lockscreen/ui/PatternLockScreen.kt | 6 +- .../ui/TransparentBiometricActivity.kt | 6 + .../ui/PatternSetPasswordScreen.kt | 16 +- .../features/settings/ui/SettingsScreen.kt | 126 ++++- .../ui/TriggerExclusionsScreen.kt | 30 +- .../services/AppLockAccessibilityService.kt | 8 +- .../pranav/applock/services/AppLockManager.kt | 21 +- .../applock/services/ShizukuAppLockService.kt | 12 +- .../applock/services/UsageLockService.kt | 12 +- .../ui/components/UsageStatsPermission.kt | 12 +- app/src/main/res/values-pt-rBR/strings.xml | 477 ++++++++++++++++++ app/src/main/res/values/strings.xml | 123 ++++- 26 files changed, 908 insertions(+), 135 deletions(-) create mode 100644 app/src/main/java/dev/pranav/applock/core/utils/LocaleManager.kt create mode 100644 app/src/main/res/values-pt-rBR/strings.xml diff --git a/app/src/main/java/dev/pranav/applock/AppLockApplication.kt b/app/src/main/java/dev/pranav/applock/AppLockApplication.kt index 07fb0ed..360c06a 100644 --- a/app/src/main/java/dev/pranav/applock/AppLockApplication.kt +++ b/app/src/main/java/dev/pranav/applock/AppLockApplication.kt @@ -4,6 +4,7 @@ import android.app.Application import android.content.Context import android.os.Build import android.util.Log +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.core.utils.LogUtils import dev.pranav.applock.data.repository.AppLockRepository import org.lsposed.hiddenapibypass.HiddenApiBypass @@ -16,7 +17,7 @@ class AppLockApplication : Application() { private set override fun attachBaseContext(base: Context?) { - super.attachBaseContext(base) + super.attachBaseContext(LocaleManager.wrap(base ?: return)) initializeHiddenApiBypass() } diff --git a/app/src/main/java/dev/pranav/applock/MainActivity.kt b/app/src/main/java/dev/pranav/applock/MainActivity.kt index 22d8d95..f9d0a66 100644 --- a/app/src/main/java/dev/pranav/applock/MainActivity.kt +++ b/app/src/main/java/dev/pranav/applock/MainActivity.kt @@ -1,5 +1,6 @@ package dev.pranav.applock +import android.content.Context import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -10,12 +11,17 @@ import androidx.navigation.compose.rememberNavController import dev.pranav.applock.core.navigation.AppNavHost import dev.pranav.applock.core.navigation.NavigationManager import dev.pranav.applock.core.navigation.Screen +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.ui.theme.AppLockTheme class MainActivity : FragmentActivity() { private lateinit var navigationManager: NavigationManager + override fun attachBaseContext(newBase: Context?) { + super.attachBaseContext(LocaleManager.wrap(newBase ?: return)) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() diff --git a/app/src/main/java/dev/pranav/applock/core/navigation/AppNavigator.kt b/app/src/main/java/dev/pranav/applock/core/navigation/AppNavigator.kt index 2b73fb3..27f8a94 100644 --- a/app/src/main/java/dev/pranav/applock/core/navigation/AppNavigator.kt +++ b/app/src/main/java/dev/pranav/applock/core/navigation/AppNavigator.kt @@ -1,5 +1,6 @@ package dev.pranav.applock.core.navigation +import android.content.Context import android.util.Log import androidx.activity.compose.LocalActivity import androidx.biometric.BiometricManager @@ -9,6 +10,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.scaleIn import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity import androidx.navigation.NavController @@ -16,6 +18,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import dev.pranav.applock.AppLockApplication +import dev.pranav.applock.R import dev.pranav.applock.core.utils.LogUtils import dev.pranav.applock.data.repository.PreferencesRepository import dev.pranav.applock.features.antiuninstall.ui.AntiUninstallScreen @@ -189,18 +192,18 @@ private fun handleBiometricAuthentication( } ) - val promptInfo = createBiometricPromptInfo() + val promptInfo = createBiometricPromptInfo(context) biometricPrompt.authenticate(promptInfo) } catch (e: Exception) { Log.e(TAG, "Error during biometric authentication", e) } } -private fun createBiometricPromptInfo(): BiometricPrompt.PromptInfo { +private fun createBiometricPromptInfo(context: Context): BiometricPrompt.PromptInfo { return BiometricPrompt.PromptInfo.Builder() - .setTitle(BIOMETRIC_TITLE) - .setSubtitle(BIOMETRIC_SUBTITLE) - .setNegativeButtonText(BIOMETRIC_NEGATIVE_BUTTON) + .setTitle(context.getString(R.string.biometric_prompt_title)) + .setSubtitle(context.getString(R.string.biometric_prompt_subtitle)) + .setNegativeButtonText(context.getString(R.string.biometric_prompt_negative_button)) .setAllowedAuthenticators( BiometricManager.Authenticators.BIOMETRIC_WEAK or BiometricManager.Authenticators.BIOMETRIC_STRONG @@ -226,6 +229,3 @@ private fun navigateToMain(navController: NavHostController) { private const val TAG = "AppNavHost" private const val ANIMATION_DURATION = 400 private const val SCALE_INITIAL = 0.9f -private const val BIOMETRIC_TITLE = "Confirm password" -private const val BIOMETRIC_SUBTITLE = "Confirm biometric to continue" -private const val BIOMETRIC_NEGATIVE_BUTTON = "Use PIN" diff --git a/app/src/main/java/dev/pranav/applock/core/utils/AppLockUtils.kt b/app/src/main/java/dev/pranav/applock/core/utils/AppLockUtils.kt index 4f73ff6..29d47b2 100644 --- a/app/src/main/java/dev/pranav/applock/core/utils/AppLockUtils.kt +++ b/app/src/main/java/dev/pranav/applock/core/utils/AppLockUtils.kt @@ -11,6 +11,7 @@ import android.provider.Settings import android.util.Log import android.widget.Toast import androidx.core.net.toUri +import dev.pranav.applock.R import dev.pranav.applock.AppLockApplication import dev.pranav.applock.data.repository.AppLockRepository @@ -53,7 +54,7 @@ fun launchBatterySettings(context: Context) { if (requestIgnoreIntent.resolveActivity(pm) != null) { context.startActivity(requestIgnoreIntent) - showBatteryOptimizationToast(context, "Battery optimization request sent") + showBatteryOptimizationToast(context, context.getString(R.string.utils_battery_optimization_sent_toast)) } else { Log.w( TAG, @@ -63,7 +64,7 @@ fun launchBatterySettings(context: Context) { } } catch (e: Exception) { Log.e(TAG, "Failed to launch battery settings", e) - showBatteryOptimizationToast(context, "Failed to open battery settings") + showBatteryOptimizationToast(context, context.getString(R.string.utils_battery_open_failed_toast)) } } @@ -92,7 +93,7 @@ private fun launchGeneralBatterySettings(context: Context) { context.startActivity(generalBatteryIntent) showBatteryOptimizationToast( context, - "Please find and configure this app in battery settings" + context.getString(R.string.utils_battery_configure_toast) ) } catch (e: Exception) { Log.e(TAG, "Failed to launch general battery settings", e) diff --git a/app/src/main/java/dev/pranav/applock/core/utils/LocaleManager.kt b/app/src/main/java/dev/pranav/applock/core/utils/LocaleManager.kt new file mode 100644 index 0000000..cfe88f5 --- /dev/null +++ b/app/src/main/java/dev/pranav/applock/core/utils/LocaleManager.kt @@ -0,0 +1,25 @@ +package dev.pranav.applock.core.utils + +import android.content.Context +import android.content.res.Configuration +import dev.pranav.applock.data.repository.PreferencesRepository +import java.util.Locale + +object LocaleManager { + + fun wrap(context: Context): Context { + val prefs = context.getSharedPreferences("app_lock_settings", Context.MODE_PRIVATE) + val language = prefs.getString(PreferencesRepository.KEY_LANGUAGE, PreferencesRepository.LANGUAGE_SYSTEM) + ?: PreferencesRepository.LANGUAGE_SYSTEM + if (language == PreferencesRepository.LANGUAGE_SYSTEM) { + return context + } + + val locale = Locale.forLanguageTag(language) + Locale.setDefault(locale) + + val config = Configuration(context.resources.configuration) + config.setLocale(locale) + return context.createConfigurationContext(config) + } +} diff --git a/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt b/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt index 3e92870..dce1387 100644 --- a/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt +++ b/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt @@ -98,6 +98,9 @@ class AppLockRepository(private val context: Context) { fun isLoggingEnabled(): Boolean = preferencesRepository.isLoggingEnabled() fun setLoggingEnabled(enabled: Boolean) = preferencesRepository.setLoggingEnabled(enabled) + fun setLanguage(code: String) = preferencesRepository.setLanguage(code) + fun getLanguage(): String = preferencesRepository.getLanguage() + fun setActiveBackend(backend: BackendImplementation) = backendServiceManager.setActiveBackend(backend) diff --git a/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt b/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt index 522d712..893b56b 100644 --- a/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt +++ b/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt @@ -170,6 +170,14 @@ class PreferencesRepository(context: Context) { settingsPrefs.edit { putBoolean(KEY_LOGGING_ENABLED, enabled) } } + fun setLanguage(code: String) { + settingsPrefs.edit { putString(KEY_LANGUAGE, code) } + } + + fun getLanguage(): String { + return settingsPrefs.getString(KEY_LANGUAGE, LANGUAGE_SYSTEM) ?: LANGUAGE_SYSTEM + } + companion object { private const val PREFS_NAME_APP_LOCK = "app_lock_prefs" private const val PREFS_NAME_SETTINGS = "app_lock_settings" @@ -197,5 +205,10 @@ class PreferencesRepository(context: Context) { const val LOCK_TYPE_PIN = "pin" const val LOCK_TYPE_PATTERN = "pattern" const val LOCK_TYPE_PASSWORD = "password" + + const val KEY_LANGUAGE = "app_language" + const val LANGUAGE_SYSTEM = "system" + const val LANGUAGE_ENGLISH = "en" + const val LANGUAGE_PORTUGUESE_BR = "pt-BR" } } diff --git a/app/src/main/java/dev/pranav/applock/features/admin/AdminDisableActivity.kt b/app/src/main/java/dev/pranav/applock/features/admin/AdminDisableActivity.kt index 16e9cc7..c5a4463 100644 --- a/app/src/main/java/dev/pranav/applock/features/admin/AdminDisableActivity.kt +++ b/app/src/main/java/dev/pranav/applock/features/admin/AdminDisableActivity.kt @@ -2,6 +2,7 @@ package dev.pranav.applock.features.admin import android.app.admin.DevicePolicyManager import android.content.ComponentName +import android.content.Context import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity @@ -27,6 +28,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import dev.pranav.applock.R import dev.pranav.applock.core.broadcast.DeviceAdmin +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.core.utils.SecurityUtils import dev.pranav.applock.core.utils.appLockRepository import dev.pranav.applock.data.repository.AppLockRepository @@ -41,6 +43,10 @@ class AdminDisableActivity : ComponentActivity() { private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var deviceAdminComponentName: ComponentName + override fun attachBaseContext(newBase: Context?) { + super.attachBaseContext(LocaleManager.wrap(newBase ?: return)) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) devicePolicyManager = getSystemService(DEVICE_POLICY_SERVICE) as DevicePolicyManager diff --git a/app/src/main/java/dev/pranav/applock/features/antiuninstall/ui/AntiUninstallScreen.kt b/app/src/main/java/dev/pranav/applock/features/antiuninstall/ui/AntiUninstallScreen.kt index 37304f0..2816175 100644 --- a/app/src/main/java/dev/pranav/applock/features/antiuninstall/ui/AntiUninstallScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/antiuninstall/ui/AntiUninstallScreen.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -63,6 +64,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import dev.pranav.applock.R import dev.pranav.applock.core.utils.appLockRepository import dev.pranav.applock.core.utils.blockUninstallForUser import dev.pranav.applock.core.utils.unblockUninstallForUser @@ -263,7 +265,7 @@ fun AntiUninstallScreen( modifier = Modifier.fillMaxSize(), topBar = { SearchTopBar( - title = "Anti-Uninstall Protection", + title = stringResource(R.string.anti_uninstall_screen_title), searchQuery = searchQuery, onSearchQueryChange = viewModel::updateSearchQuery, onBack = { navController.navigateUp() }, @@ -283,7 +285,7 @@ fun AntiUninstallScreen( ) { item { Text( - text = "Select apps that will be protected from uninstallation.", + text = stringResource(R.string.anti_uninstall_screen_desc), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -310,7 +312,7 @@ fun AntiUninstallScreen( if (protectedNotInList.isNotEmpty()) { item { Text( - text = "Manually Added Packages", + text = stringResource(R.string.anti_uninstall_manual_packages_title), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(bottom = 4.dp) @@ -324,7 +326,7 @@ fun AntiUninstallScreen( } item { Text( - text = "Installed Apps", + text = stringResource(R.string.anti_uninstall_installed_apps_title), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) @@ -347,19 +349,19 @@ fun AntiUninstallScreen( AlertDialog( onDismissRequest = { showManualAddDialog.value = false }, properties = DialogProperties(usePlatformDefaultWidth = false), - title = { Text("Add Package Manually") }, + title = { Text(stringResource(R.string.anti_uninstall_manual_add_dialog_title)) }, text = { Column(Modifier.fillMaxWidth(0.8f)) { Text( - text = "Enter the package name of the app you want to protect:", + text = stringResource(R.string.anti_uninstall_manual_add_dialog_text), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 16.dp) ) OutlinedTextField( value = manualPackageName, onValueChange = viewModel::updateManualPackageName, - label = { Text("Package Name") }, - placeholder = { Text("com.example.app") }, + label = { Text(stringResource(R.string.anti_uninstall_package_name_label)) }, + placeholder = { Text(stringResource(R.string.anti_uninstall_package_name_placeholder)) }, modifier = Modifier.fillMaxWidth(), singleLine = true ) @@ -372,10 +374,10 @@ fun AntiUninstallScreen( showManualAddDialog.value = false }, enabled = manualPackageName.isNotBlank() - ) { Text("Add") } + ) { Text(stringResource(R.string.add_button)) } }, dismissButton = { - TextButton(onClick = { showManualAddDialog.value = false }) { Text("Cancel") } + TextButton(onClick = { showManualAddDialog.value = false }) { Text(stringResource(R.string.cancel_button)) } } ) } @@ -391,10 +393,10 @@ fun AntiUninstallScreen( title = { Text( text = when (shizukuState) { - ShizukuState.NOT_INSTALLED -> "Shizuku Not Installed" - ShizukuState.NOT_RUNNING -> "Shizuku Not Running" - ShizukuState.PERMISSION_DENIED -> "Permission Required" - else -> "Shizuku Setup" + ShizukuState.NOT_INSTALLED -> stringResource(R.string.anti_uninstall_shizuku_not_installed_title) + ShizukuState.NOT_RUNNING -> stringResource(R.string.anti_uninstall_shizuku_not_running_title) + ShizukuState.PERMISSION_DENIED -> stringResource(R.string.anti_uninstall_shizuku_permission_required_title) + else -> stringResource(R.string.anti_uninstall_shizuku_setup_title) } ) }, @@ -402,9 +404,9 @@ fun AntiUninstallScreen( Column(Modifier.fillMaxWidth(0.8f)) { Text( text = when (shizukuState) { - ShizukuState.NOT_INSTALLED -> "Shizuku is required to configure Anti-Uninstall protection without root.\n\nPlease install Shizuku from the Play Store or GitHub to continue." - ShizukuState.NOT_RUNNING -> "Shizuku is installed, but the background service has not been started yet (Binder hasn't been received).\n\nPlease open the Shizuku application and start the service via Wireless Debugging or Root." - ShizukuState.PERMISSION_DENIED -> "Shizuku is active, but this application needs your permission authorization to configure package restrictions.\n\nThankfully, Shizuku is only used while blocking/unblocking uninstalls, so it doesn't need to stay running forever!" + ShizukuState.NOT_INSTALLED -> stringResource(R.string.anti_uninstall_shizuku_not_installed_text) + ShizukuState.NOT_RUNNING -> stringResource(R.string.anti_uninstall_shizuku_not_running_text) + ShizukuState.PERMISSION_DENIED -> stringResource(R.string.anti_uninstall_shizuku_permission_required_text) else -> "" }, style = MaterialTheme.typography.bodyMedium @@ -441,10 +443,10 @@ fun AntiUninstallScreen( ) { Text( text = when (shizukuState) { - ShizukuState.NOT_INSTALLED -> "Install Shizuku" - ShizukuState.NOT_RUNNING -> "Open Shizuku" - ShizukuState.PERMISSION_DENIED -> "Grant Permission" - else -> "Confirm" + ShizukuState.NOT_INSTALLED -> stringResource(R.string.anti_uninstall_install_shizuku_button) + ShizukuState.NOT_RUNNING -> stringResource(R.string.anti_uninstall_open_shizuku_button) + ShizukuState.PERMISSION_DENIED -> stringResource(R.string.grant_permission_button) + else -> stringResource(R.string.confirm_button) } ) } @@ -452,7 +454,7 @@ fun AntiUninstallScreen( dismissButton = { TextButton(onClick = { navController.popBackStack() - }) { Text("Go Back") } + }) { Text(stringResource(R.string.go_back_button)) } } ) } @@ -484,7 +486,7 @@ private fun SearchTopBar( verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = onBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_cd)) } Text( text = title, @@ -492,7 +494,7 @@ private fun SearchTopBar( modifier = Modifier.weight(1f) ) IconButton(onClick = onAdd) { - Icon(Icons.Default.Add, contentDescription = "Add package manually") + Icon(Icons.Default.Add, contentDescription = stringResource(R.string.anti_uninstall_add_manual_cd)) } } @@ -505,7 +507,7 @@ private fun SearchTopBar( OutlinedTextField( value = searchQuery, onValueChange = onSearchQueryChange, - placeholder = { Text("Search apps or package names...") }, + placeholder = { Text(stringResource(R.string.anti_uninstall_search_placeholder)) }, leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, modifier = Modifier.fillMaxWidth(), singleLine = true, @@ -593,7 +595,7 @@ private fun ManualPackageItem(packageName: String, onToggle: () -> Unit) { overflow = TextOverflow.Ellipsis ) Text( - text = "Manually added package", + text = stringResource(R.string.anti_uninstall_manually_added_label), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) diff --git a/app/src/main/java/dev/pranav/applock/features/appintro/ui/AppIntroScreen.kt b/app/src/main/java/dev/pranav/applock/features/appintro/ui/AppIntroScreen.kt index 7713a14..75adfdf 100644 --- a/app/src/main/java/dev/pranav/applock/features/appintro/ui/AppIntroScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/appintro/ui/AppIntroScreen.kt @@ -503,6 +503,7 @@ fun AppIntroScreen(navController: NavController) { showSkipButton = false, useAnimatedPager = true, nextButtonText = stringResource(R.string.next_button), + backButtonText = stringResource(R.string.back_button), finishButtonText = stringResource(R.string.get_started_button) ) } diff --git a/app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt b/app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt index 9e9cf81..f290601 100644 --- a/app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt @@ -183,7 +183,7 @@ fun MainScreen( ) Spacer(Modifier.width(6.dp)) Text( - text = if (applockEnabled) "ON" else "OFF", + text = if (applockEnabled) stringResource(R.string.main_screen_protection_on) else stringResource(R.string.main_screen_protection_off), style = MaterialTheme.typography.labelLarge, color = if (applockEnabled) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant ) @@ -211,7 +211,7 @@ fun MainScreen( ) { Icon( imageVector = Icons.Default.Block, - contentDescription = "Trigger exclusions", + contentDescription = stringResource(R.string.main_screen_trigger_exclusions_cd), tint = MaterialTheme.colorScheme.onSurface ) } @@ -224,7 +224,7 @@ fun MainScreen( ) { Icon( imageVector = Icons.Outlined.Lock, - contentDescription = "Anti-Uninstall", + contentDescription = stringResource(R.string.main_screen_anti_uninstall_cd), tint = MaterialTheme.colorScheme.onSurface ) } @@ -244,7 +244,7 @@ fun MainScreen( ) { Icon( imageVector = Icons.Default.Add, - contentDescription = stringResource(R.string.main_screen_search_cd) // Update ideally + contentDescription = stringResource(R.string.main_screen_add_button_cd) ) } } @@ -432,13 +432,13 @@ private fun EmptyDashboardState(modifier: Modifier = Modifier) { ) Spacer(modifier = Modifier.height(24.dp)) Text( - text = "No Protected Apps", + text = stringResource(R.string.main_screen_empty_title), style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Tap the + button to selectively secure your apps.", + text = stringResource(R.string.main_screen_empty_desc), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = androidx.compose.ui.text.style.TextAlign.Center, @@ -496,7 +496,7 @@ private fun AddProtectedAppsSheetContent( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Select Apps", + text = stringResource(R.string.main_screen_select_apps_title), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold ) @@ -504,7 +504,7 @@ private fun AddProtectedAppsSheetContent( onClick = onSave, enabled = selectedPackages.isNotEmpty() ) { - Text("Protect (${selectedPackages.size})") + Text(stringResource(R.string.main_screen_protect_count_button, selectedPackages.size)) } } @@ -593,7 +593,7 @@ private fun ProtectedAppItem( }, supportingContent = { Text( - text = "Protected", + text = stringResource(R.string.main_screen_protected_label), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.primary, maxLines = 1, @@ -624,7 +624,7 @@ private fun ProtectedAppItem( IconButton(onClick = onUnlock) { Icon( imageVector = Icons.Outlined.LockOpen, - contentDescription = "Unlock ${appName ?: "app"}", + contentDescription = stringResource(R.string.main_screen_unlock_app_cd, appName ?: "app"), tint = MaterialTheme.colorScheme.onSurfaceVariant ) } @@ -811,7 +811,7 @@ private fun PermissionWarningBanner( ) { Icon( imageVector = Icons.Rounded.Warning, - contentDescription = "Warning", + contentDescription = stringResource(R.string.main_screen_warning_cd), tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(28.dp) ) diff --git a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/AlphanumericPasswordOverlayScreen.kt b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/AlphanumericPasswordOverlayScreen.kt index d125de1..32876a8 100644 --- a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/AlphanumericPasswordOverlayScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/AlphanumericPasswordOverlayScreen.kt @@ -94,7 +94,7 @@ fun AlphanumericPasswordOverlayScreen( ) { Icon( imageVector = Icons.Default.Close, - contentDescription = "Close", + contentDescription = stringResource(R.string.alphanumeric_password_overlay_close_cd), tint = MaterialTheme.colorScheme.onSurface ) } @@ -111,7 +111,7 @@ fun AlphanumericPasswordOverlayScreen( ) { Text( text = if (!fromMainActivity && !lockedAppName.isNullOrEmpty()) - "Continue to $lockedAppName" + stringResource(R.string.alphanumeric_overlay_continue_to_app, lockedAppName ?: "") else stringResource(R.string.enter_password_to_continue), style = MaterialTheme.typography.headlineMediumEmphasized, diff --git a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/LockScreenOverlayManager.kt b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/LockScreenOverlayManager.kt index ee2c228..fe9c7d1 100644 --- a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/LockScreenOverlayManager.kt +++ b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/LockScreenOverlayManager.kt @@ -26,6 +26,8 @@ import androidx.savedstate.SavedStateRegistry import androidx.savedstate.SavedStateRegistryController import androidx.savedstate.SavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner +import dev.pranav.applock.R +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.core.utils.appLockRepository import dev.pranav.applock.data.repository.PreferencesRepository import dev.pranav.applock.services.AppLockManager @@ -66,7 +68,7 @@ class LockScreenOverlayManager(private val context: Context): isStateRestored = true } - composeView = ComposeView(context).apply { + composeView = ComposeView(LocaleManager.wrap(context)).apply { setViewTreeLifecycleOwner(this@LockScreenOverlayManager) setViewTreeSavedStateRegistryOwner(this@LockScreenOverlayManager) setViewTreeViewModelStoreOwner(this@LockScreenOverlayManager) @@ -86,7 +88,7 @@ class LockScreenOverlayManager(private val context: Context): pm.getApplicationLabel(pm.getApplicationInfo(lockedPackageName, 0)) .toString() } catch (_: Exception) { - "App" + context.getString(R.string.lock_screen_overlay_default_app_name) } val onPinAttemptCallback = { pin: String -> diff --git a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt index c485bd3..2314ab2 100644 --- a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt @@ -1,6 +1,5 @@ package dev.pranav.applock.features.lockscreen.ui -import dev.pranav.applock.features.lockscreen.ui.AlphanumericPasswordOverlayScreen import android.content.Context import android.content.res.Configuration import android.os.Build @@ -47,6 +46,7 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope import dev.pranav.applock.R import dev.pranav.applock.core.ui.shapes +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.core.utils.appLockRepository import dev.pranav.applock.core.utils.vibrate import dev.pranav.applock.data.repository.AppLockRepository @@ -72,6 +72,10 @@ class PasswordOverlayActivity: FragmentActivity() { private val TAG = "PasswordOverlayActivity" + override fun attachBaseContext(newBase: Context?) { + super.attachBaseContext(LocaleManager.wrap(newBase ?: return)) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -388,7 +392,7 @@ fun PinPasswordOverlayScreen( ) { Icon( imageVector = Icons.Default.Close, - contentDescription = "Close", + contentDescription = stringResource(R.string.password_overlay_close_cd), tint = MaterialTheme.colorScheme.onSurface ) } @@ -412,15 +416,15 @@ fun PinPasswordOverlayScreen( .padding(end = 32.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center - ) { - Text( - text = if (!fromMainActivity && !lockedAppName.isNullOrEmpty()) - "Continue to $lockedAppName" - else - stringResource(R.string.enter_password_to_continue), - style = MaterialTheme.typography.titleLarge, - textAlign = TextAlign.Center - ) + ) { + Text( + text = if (!fromMainActivity && !lockedAppName.isNullOrEmpty()) + stringResource(R.string.password_overlay_continue_to_app, lockedAppName) + else + stringResource(R.string.enter_password_to_continue), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center + ) // if (!fromMainActivity && !triggeringPackageName.isNullOrEmpty()) { // Spacer(modifier = Modifier.height(8.dp)) @@ -485,7 +489,7 @@ fun PinPasswordOverlayScreen( Text( text = if (!fromMainActivity && !lockedAppName.isNullOrEmpty()) - "Continue to $lockedAppName" + stringResource(R.string.password_overlay_continue_to_app, lockedAppName) else stringResource(R.string.enter_password_to_continue), style = if (!fromMainActivity && !lockedAppName.isNullOrEmpty()) diff --git a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PatternLockScreen.kt b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PatternLockScreen.kt index aae4483..0f1e6da 100644 --- a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PatternLockScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PatternLockScreen.kt @@ -111,7 +111,7 @@ fun PatternLockScreen( ) { Text( text = if (!fromMainActivity && !lockedAppName.isNullOrEmpty()) - "Continue to $lockedAppName" + stringResource(R.string.pattern_lock_continue_to_app, lockedAppName) else stringResource(R.string.enter_pattern_to_continue), style = MaterialTheme.typography.titleMedium, @@ -183,7 +183,7 @@ fun PatternLockScreen( ) { Text( text = if (!fromMainActivity && !lockedAppName.isNullOrEmpty()) - "Continue to $lockedAppName" + stringResource(R.string.pattern_lock_continue_to_app, lockedAppName) else stringResource(R.string.enter_pattern_to_continue), style = MaterialTheme.typography.headlineMedium, @@ -252,7 +252,7 @@ fun PatternLockScreen( ) { Icon( imageVector = Icons.Default.Close, - contentDescription = "Close", + contentDescription = stringResource(R.string.pattern_lock_close_cd), tint = MaterialTheme.colorScheme.onSurface ) } diff --git a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/TransparentBiometricActivity.kt b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/TransparentBiometricActivity.kt index 9231384..1698df3 100644 --- a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/TransparentBiometricActivity.kt +++ b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/TransparentBiometricActivity.kt @@ -1,5 +1,6 @@ package dev.pranav.applock.features.lockscreen.ui +import android.content.Context import android.os.Bundle import android.util.Log import androidx.activity.compose.setContent @@ -12,6 +13,7 @@ import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity import dev.pranav.applock.R +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.services.AppLockManager import dev.pranav.applock.ui.theme.AppLockTheme @@ -19,6 +21,10 @@ class TransparentBiometricActivity: FragmentActivity() { private val TAG = "TransparentBiometric" private var lockedPackageName: String? = null + override fun attachBaseContext(newBase: Context?) { + super.attachBaseContext(LocaleManager.wrap(newBase ?: return)) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lockedPackageName = intent.getStringExtra("locked_package") diff --git a/app/src/main/java/dev/pranav/applock/features/setpassword/ui/PatternSetPasswordScreen.kt b/app/src/main/java/dev/pranav/applock/features/setpassword/ui/PatternSetPasswordScreen.kt index 450291d..8b65c24 100644 --- a/app/src/main/java/dev/pranav/applock/features/setpassword/ui/PatternSetPasswordScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/setpassword/ui/PatternSetPasswordScreen.kt @@ -213,9 +213,9 @@ fun PatternSetPasswordScreen( ) { Text( text = when { - isVerifyOldPasswordMode -> "Enter current Pattern" - isConfirmationMode -> "Confirm pattern" - else -> "Create a pattern" + isVerifyOldPasswordMode -> stringResource(R.string.pattern_set_password_enter_current_label) + isConfirmationMode -> stringResource(R.string.pattern_set_password_confirm_label) + else -> stringResource(R.string.pattern_set_password_create_label) }, style = MaterialTheme.typography.titleMedium, textAlign = TextAlign.Center @@ -225,7 +225,7 @@ fun PatternSetPasswordScreen( if (showMismatchError) { Text( - text = "Incorrect Pattern", + text = stringResource(R.string.pattern_set_password_incorrect_error), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center @@ -233,7 +233,7 @@ fun PatternSetPasswordScreen( } if (showMinLengthError) { Text( - text = "Patter length should be at least 4", + text = stringResource(R.string.pattern_set_password_min_length_error), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center @@ -241,7 +241,7 @@ fun PatternSetPasswordScreen( } if (showInvalidOldPasswordError) { Text( - text = "Incorrect pattern", + text = stringResource(R.string.pattern_set_password_incorrect_short_error), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center @@ -379,7 +379,7 @@ fun PatternSetPasswordScreen( if (showMinLengthError) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Patter length should be at least 4", + text = stringResource(R.string.pattern_set_password_min_length_error), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.labelLarge, textAlign = TextAlign.Center @@ -389,7 +389,7 @@ fun PatternSetPasswordScreen( if (showInvalidOldPasswordError) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Incorrect pattern", + text = stringResource(R.string.pattern_set_password_incorrect_short_error), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.labelLarge, textAlign = TextAlign.Center diff --git a/app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt b/app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt index 7438a6a..e899a0f 100644 --- a/app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.widget.Toast +import androidx.activity.compose.LocalActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.biometric.BiometricManager @@ -37,6 +38,7 @@ import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.navigation.NavController import dev.pranav.applock.R +import dev.pranav.applock.core.broadcast.AppLockServiceStarter import dev.pranav.applock.core.broadcast.DeviceAdmin import dev.pranav.applock.core.navigation.Screen import dev.pranav.applock.core.utils.LogUtils @@ -45,15 +47,21 @@ import dev.pranav.applock.core.utils.isAccessibilityServiceEnabled import dev.pranav.applock.core.utils.openAccessibilitySettings import dev.pranav.applock.data.repository.AppLockRepository import dev.pranav.applock.data.repository.BackendImplementation +import dev.pranav.applock.data.repository.PreferencesRepository import dev.pranav.applock.features.admin.AdminDisableActivity -import dev.pranav.applock.services.ShizukuAppLockService -import dev.pranav.applock.services.UsageLockService +import dev.pranav.applock.services.AppLockManager import dev.pranav.applock.ui.components.DonateButton import dev.pranav.applock.ui.icons.* import rikka.shizuku.Shizuku import rikka.shizuku.ShizukuProvider import kotlin.math.abs +private val languageOptions = listOf( + PreferencesRepository.LANGUAGE_SYSTEM to R.string.settings_screen_language_system, + PreferencesRepository.LANGUAGE_ENGLISH to R.string.settings_screen_language_english, + PreferencesRepository.LANGUAGE_PORTUGUESE_BR to R.string.settings_screen_language_portuguese_brazil +) + @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( @@ -61,6 +69,7 @@ fun SettingsScreen( ) { val context = LocalContext.current val appLockRepository = remember { AppLockRepository(context) } + val activity = LocalActivity.current var showDialog by remember { mutableStateOf(false) } var showUnlockTimeDialog by remember { mutableStateOf(false) } @@ -94,6 +103,8 @@ fun SettingsScreen( var showPermissionDialog by remember { mutableStateOf(false) } var showDeviceAdminDialog by remember { mutableStateOf(false) } var showAccessibilityDialog by remember { mutableStateOf(false) } + var showLanguageDialog by remember { mutableStateOf(false) } + var selectedLanguage by remember { mutableStateOf(appLockRepository.getLanguage()) } val biometricManager = remember { BiometricManager.from(context) } val isBiometricAvailable = remember { @@ -188,6 +199,52 @@ fun SettingsScreen( ) } + if (showLanguageDialog) { + val languages = languageOptions + AlertDialog( + onDismissRequest = { showLanguageDialog = false }, + title = { Text(stringResource(R.string.settings_screen_language_title)) }, + text = { + Column { + languages.forEach { (code, labelRes) -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { selectedLanguage = code } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selectedLanguage == code, + onClick = { selectedLanguage = code } + ) + Text( + text = stringResource(labelRes), + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } + }, + confirmButton = { + TextButton(onClick = { + appLockRepository.setLanguage(selectedLanguage) + AppLockManager.stopAllServices(context) + AppLockServiceStarter.startAppropriateServices(context, appLockRepository) + showLanguageDialog = false + activity?.recreate() + }) { + Text(stringResource(R.string.confirm_button)) + } + }, + dismissButton = { + TextButton(onClick = { showLanguageDialog = false }) { + Text(stringResource(R.string.cancel_button)) + } + } + ) + } + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( @@ -232,7 +289,7 @@ fun SettingsScreen( null } } - val versionName = packageInfo?.versionName ?: "Unknown" + val versionName = packageInfo?.versionName ?: stringResource(R.string.settings_screen_version_unknown) Text( text = stringResource(R.string.settings_screen_version_template, versionName), style = MaterialTheme.typography.bodyMedium, @@ -245,6 +302,26 @@ fun SettingsScreen( DonateButton() } + item { + SectionTitle(text = stringResource(R.string.settings_screen_general_title)) + } + + item { + SettingsGroup( + items = listOf( + ActionSettingItem( + icon = Icons.Default.Language, + title = stringResource(R.string.settings_screen_language_title), + subtitle = stringResource( + languageOptions.firstOrNull { it.first == selectedLanguage }?.second + ?: R.string.settings_screen_language_system + ), + onClick = { showLanguageDialog = true } + ) + ) + ) + } + item { SectionTitle(text = stringResource(R.string.settings_screen_lock_screen_customization_title)) } @@ -320,7 +397,7 @@ fun SettingsScreen( icon = Timer, title = stringResource(R.string.settings_screen_unlock_duration_title), subtitle = if (unlockTimeDuration > 0) { - if (unlockTimeDuration > 10_000) "Until screen off" + if (unlockTimeDuration > 10_000) stringResource(R.string.settings_screen_unlock_duration_until_screen_off) else stringResource( R.string.settings_screen_unlock_duration_summary_minutes, unlockTimeDuration @@ -388,7 +465,7 @@ fun SettingsScreen( addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } context.startActivity( - Intent.createChooser(shareIntent, "Share audit logs") + Intent.createChooser(shareIntent, context.getString(R.string.settings_screen_share_audit_logs_title)) ) } else { Toast.makeText( @@ -412,7 +489,7 @@ fun SettingsScreen( addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } context.startActivity( - Intent.createChooser(shareIntent, "Share logs") + Intent.createChooser(shareIntent, context.getString(R.string.settings_screen_share_logs_title)) ) } else { Toast.makeText( @@ -425,8 +502,8 @@ fun SettingsScreen( ), ToggleSettingItem( icon = Icons.Default.Troubleshoot, - title = "Logging", - subtitle = "Enable debug logging for troubleshooting", + title = stringResource(R.string.settings_screen_logging_title), + subtitle = stringResource(R.string.settings_screen_logging_desc), checked = loggingEnabled, enabled = true, onCheckedChange = { isChecked -> @@ -725,7 +802,7 @@ fun UnlockTimeDurationDialog( duration ) 60 -> stringResource(R.string.settings_screen_unlock_duration_dialog_option_hour) - Integer.MAX_VALUE -> "Until Screen Off" + Integer.MAX_VALUE -> stringResource(R.string.settings_screen_unlock_duration_dialog_option_until_screen_off) else -> stringResource( R.string.settings_screen_unlock_duration_summary_minutes, duration @@ -771,6 +848,7 @@ fun BackendSelectionCard( backend = backend, isSelected = selectedBackend == backend, onClick = { + if (selectedBackend == backend) return@BackendSelectionItem when (backend) { BackendImplementation.SHIZUKU -> { if (!Shizuku.pingBinder() || Shizuku.checkSelfPermission() == PackageManager.PERMISSION_DENIED) { @@ -790,9 +868,6 @@ fun BackendSelectionCard( appLockRepository.setBackendImplementation( BackendImplementation.SHIZUKU ) - context.startService( - Intent(context, ShizukuAppLockService::class.java) - ) } } BackendImplementation.USAGE_STATS -> { @@ -810,9 +885,6 @@ fun BackendSelectionCard( } selectedBackend = backend appLockRepository.setBackendImplementation(BackendImplementation.USAGE_STATS) - context.startService( - Intent(context, UsageLockService::class.java) - ) } BackendImplementation.ACCESSIBILITY -> { if (!context.isAccessibilityServiceEnabled()) { @@ -823,6 +895,8 @@ fun BackendSelectionCard( appLockRepository.setBackendImplementation(BackendImplementation.ACCESSIBILITY) } } + AppLockManager.stopAllServices(context) + AppLockServiceStarter.startAppropriateServices(context, appLockRepository) } ) } @@ -904,19 +978,21 @@ fun BackendSelectionItem( ) } +@Composable private fun getBackendDisplayName(backend: BackendImplementation): String { return when (backend) { - BackendImplementation.ACCESSIBILITY -> "Accessibility Service" - BackendImplementation.USAGE_STATS -> "Usage Statistics" - BackendImplementation.SHIZUKU -> "Shizuku Service" + BackendImplementation.ACCESSIBILITY -> stringResource(R.string.settings_screen_backend_accessibility_name) + BackendImplementation.USAGE_STATS -> stringResource(R.string.settings_screen_backend_usage_stats_name) + BackendImplementation.SHIZUKU -> stringResource(R.string.settings_screen_backend_shizuku_name) } } +@Composable private fun getBackendDescription(backend: BackendImplementation): String { return when (backend) { - BackendImplementation.ACCESSIBILITY -> "Standard method that works on most devices" - BackendImplementation.USAGE_STATS -> "Experimental method using app usage statistics" - BackendImplementation.SHIZUKU -> "Advanced method using Shizuku and internal APIs" + BackendImplementation.ACCESSIBILITY -> stringResource(R.string.settings_screen_backend_accessibility_desc_short) + BackendImplementation.USAGE_STATS -> stringResource(R.string.settings_screen_backend_usage_stats_desc_short) + BackendImplementation.SHIZUKU -> stringResource(R.string.settings_screen_backend_shizuku_desc_short) } } @@ -1015,12 +1091,12 @@ fun LinksSection() { val context = LocalContext.current Column { - SectionTitle(text = "Links") + SectionTitle(text = stringResource(R.string.settings_screen_links_section_title)) Column { SettingsCard(index = 0, listSize = 3) { LinkItem( - title = "Discord Community", + title = stringResource(R.string.settings_screen_link_discord), icon = Discord, onClick = { val intent = Intent( @@ -1034,7 +1110,7 @@ fun LinksSection() { SettingsCard(index = 1, listSize = 3) { LinkItem( - title = "Source Code", + title = stringResource(R.string.settings_screen_link_source_code), icon = Icons.Outlined.Code, onClick = { val intent = Intent( @@ -1048,7 +1124,7 @@ fun LinksSection() { SettingsCard(index = 2, listSize = 3) { LinkItem( - title = "Report Issue", + title = stringResource(R.string.settings_screen_link_report_issue), icon = Icons.Outlined.BugReport, onClick = { val intent = Intent( diff --git a/app/src/main/java/dev/pranav/applock/features/triggerexclusions/ui/TriggerExclusionsScreen.kt b/app/src/main/java/dev/pranav/applock/features/triggerexclusions/ui/TriggerExclusionsScreen.kt index 75f6a5f..b44b5f7 100644 --- a/app/src/main/java/dev/pranav/applock/features/triggerexclusions/ui/TriggerExclusionsScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/triggerexclusions/ui/TriggerExclusionsScreen.kt @@ -17,12 +17,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import dev.pranav.applock.R import dev.pranav.applock.features.applist.domain.AppInfo @OptIn(ExperimentalMaterial3Api::class) @@ -50,7 +52,7 @@ fun TriggerExclusionsScreen( modifier = Modifier.fillMaxSize(), topBar = { SearchTopBar( - title = "Trigger Exclusions", + title = stringResource(R.string.trigger_exclusions_screen_title), searchQuery = searchQuery, onSearchQueryChange = viewModel::updateSearchQuery, onBack = { navController.navigateUp() }, @@ -70,7 +72,7 @@ fun TriggerExclusionsScreen( ) { item { Text( - text = "Select apps that will NOT trigger locks when switching to locked apps.", + text = stringResource(R.string.trigger_exclusions_screen_desc), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -94,7 +96,7 @@ fun TriggerExclusionsScreen( if (excludedNotInList.isNotEmpty()) { item { Text( - text = "Manually Added Packages", + text = stringResource(R.string.trigger_exclusions_manual_packages_title), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(bottom = 4.dp) @@ -108,7 +110,7 @@ fun TriggerExclusionsScreen( } item { Text( - text = "Installed Apps", + text = stringResource(R.string.trigger_exclusions_installed_apps_title), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) @@ -130,19 +132,19 @@ fun TriggerExclusionsScreen( if (showManualAddDialog.value) { AlertDialog( onDismissRequest = { showManualAddDialog.value = false }, - title = { Text("Add Package Manually") }, + title = { Text(stringResource(R.string.trigger_exclusions_manual_add_dialog_title)) }, text = { Column { Text( - text = "Enter the package name of the app you want to exclude:", + text = stringResource(R.string.trigger_exclusions_manual_add_dialog_text), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 16.dp) ) OutlinedTextField( value = manualPackageName, onValueChange = viewModel::updateManualPackageName, - label = { Text("Package Name") }, - placeholder = { Text("com.example.app") }, + label = { Text(stringResource(R.string.trigger_exclusions_package_name_label)) }, + placeholder = { Text(stringResource(R.string.trigger_exclusions_package_name_placeholder)) }, modifier = Modifier.fillMaxWidth(), singleLine = true ) @@ -155,10 +157,10 @@ fun TriggerExclusionsScreen( showManualAddDialog.value = false }, enabled = manualPackageName.isNotBlank() - ) { Text("Add") } + ) { Text(stringResource(R.string.add_button)) } }, dismissButton = { - TextButton(onClick = { showManualAddDialog.value = false }) { Text("Cancel") } + TextButton(onClick = { showManualAddDialog.value = false }) { Text(stringResource(R.string.cancel_button)) } } ) } @@ -190,7 +192,7 @@ private fun SearchTopBar( verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = onBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_cd)) } Text( text = title, @@ -198,7 +200,7 @@ private fun SearchTopBar( modifier = Modifier.weight(1f) ) IconButton(onClick = onAdd) { - Icon(Icons.Default.Add, contentDescription = "Add package manually") + Icon(Icons.Default.Add, contentDescription = stringResource(R.string.trigger_exclusions_add_manual_cd)) } } @@ -211,7 +213,7 @@ private fun SearchTopBar( OutlinedTextField( value = searchQuery, onValueChange = onSearchQueryChange, - placeholder = { Text("Search apps or package names...") }, + placeholder = { Text(stringResource(R.string.trigger_exclusions_search_placeholder)) }, leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, modifier = Modifier.fillMaxWidth(), singleLine = true, @@ -294,7 +296,7 @@ private fun ManualPackageItem(packageName: String, onToggle: () -> Unit) { overflow = TextOverflow.Ellipsis ) Text( - text = "Manually added package", + text = stringResource(R.string.trigger_exclusions_manually_added_label), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) diff --git a/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt b/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt index dad4b8a..682f75a 100644 --- a/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt +++ b/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt @@ -16,7 +16,9 @@ import android.view.accessibility.AccessibilityNodeInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.core.content.getSystemService +import dev.pranav.applock.R import dev.pranav.applock.core.broadcast.DeviceAdmin +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.core.utils.LogUtils import dev.pranav.applock.core.utils.appLockRepository import dev.pranav.applock.core.utils.enableAccessibilityServiceWithShizuku @@ -38,6 +40,10 @@ class AppLockAccessibilityService : AccessibilityService() { private var overlayManager: LockScreenOverlayManager? = null private lateinit var mainHandler: Handler + override fun attachBaseContext(newBase: Context?) { + super.attachBaseContext(LocaleManager.wrap(newBase ?: return)) + } + enum class BiometricState { IDLE, AUTH_STARTED } @@ -444,7 +450,7 @@ class AppLockAccessibilityService : AccessibilityService() { performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN) Toast.makeText( this, - "Disable anti-uninstall from AppLock settings to remove this restriction.", + getString(R.string.accessibility_block_admin_deactivation_toast), Toast.LENGTH_LONG ).show() Log.w(TAG, "Blocked device admin deactivation attempt.") diff --git a/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt b/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt index f529644..19dab42 100644 --- a/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt +++ b/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt @@ -87,11 +87,13 @@ object AppLockManager { return false } - private val ALL_APP_LOCK_SERVICES = setOf( + private val BACKEND_SERVICES = setOf( ShizukuAppLockService::class.java, - UsageLockService::class.java + UsageLockService::class.java, ) + private val ALL_APP_LOCK_SERVICES = BACKEND_SERVICES + AppLockAccessibilityService::class.java + fun unlockApp(packageName: String) { temporarilyUnlockedApp = packageName appUnlockTimes[packageName] = System.currentTimeMillis() @@ -134,12 +136,17 @@ object AppLockManager { } fun stopAllOtherServices(context: Context, excludeService: Class<*>) { - ALL_APP_LOCK_SERVICES - .filter { it != excludeService } - .forEach { - context.stopService(Intent(context, it)) - } + (BACKEND_SERVICES - excludeService).forEach { + context.stopService(Intent(context, it)) + } LogUtils.d(TAG, "Stopped all main app lock services except ${excludeService.simpleName}.") } + fun stopAllServices(context: Context) { + ALL_APP_LOCK_SERVICES.forEach { + context.stopService(Intent(context, it)) + } + LogUtils.d(TAG, "Stopped all app lock services.") + } + } diff --git a/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt b/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt index ff7fa5b..8a3fe7c 100644 --- a/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt +++ b/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt @@ -6,6 +6,7 @@ import android.app.NotificationManager import android.app.Service import android.app.admin.DevicePolicyManager import android.content.ComponentName +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ServiceInfo @@ -17,6 +18,7 @@ import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import dev.pranav.applock.R import dev.pranav.applock.core.broadcast.DeviceAdmin +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.core.utils.LogUtils import dev.pranav.applock.core.utils.appLockRepository import dev.pranav.applock.data.repository.AppLockRepository @@ -31,6 +33,10 @@ class ShizukuAppLockService : Service() { private var shizukuActivityManager: ShizukuActivityManager? = null private var previousForegroundPackage = "" + override fun attachBaseContext(newBase: Context?) { + super.attachBaseContext(LocaleManager.wrap(newBase ?: return)) + } + private val notificationManager: NotificationManager by lazy { getSystemService(NotificationManager::class.java) } @@ -152,7 +158,7 @@ class ShizukuAppLockService : Service() { private fun createNotificationChannel() { val serviceChannel = NotificationChannel( CHANNEL_ID, - "AppLock Service", + getString(R.string.shizuku_service_channel_name), NotificationManager.IMPORTANCE_DEFAULT ) notificationManager.createNotificationChannel(serviceChannel) @@ -160,8 +166,8 @@ class ShizukuAppLockService : Service() { private fun createNotification(): Notification { return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("AppLock") - .setContentText("Protecting your apps with Shizuku") + .setContentTitle(getString(R.string.shizuku_service_notification_title)) + .setContentText(getString(R.string.shizuku_service_notification_text)) .setSmallIcon(R.drawable.baseline_shield_24) .setPriority(NotificationCompat.PRIORITY_MIN) .setOngoing(true) diff --git a/app/src/main/java/dev/pranav/applock/services/UsageLockService.kt b/app/src/main/java/dev/pranav/applock/services/UsageLockService.kt index c454f24..7e2935f 100644 --- a/app/src/main/java/dev/pranav/applock/services/UsageLockService.kt +++ b/app/src/main/java/dev/pranav/applock/services/UsageLockService.kt @@ -8,6 +8,7 @@ import android.app.admin.DevicePolicyManager import android.app.usage.UsageEvents import android.app.usage.UsageStatsManager import android.content.ComponentName +import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo import android.os.Build @@ -20,6 +21,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import dev.pranav.applock.R import dev.pranav.applock.core.broadcast.DeviceAdmin +import dev.pranav.applock.core.utils.LocaleManager import dev.pranav.applock.core.utils.LogUtils import dev.pranav.applock.core.utils.appLockRepository import dev.pranav.applock.core.utils.hasUsagePermission @@ -40,6 +42,10 @@ class UsageLockService: Service() { var isServiceRunning = false } + override fun attachBaseContext(newBase: Context?) { + super.attachBaseContext(LocaleManager.wrap(newBase ?: return)) + } + private val appLockRepository: AppLockRepository by lazy { applicationContext.appLockRepository() } private val usageStatsManager: UsageStatsManager by lazy { getSystemService()!! } private val notificationManager: NotificationManager by lazy { getSystemService()!! } @@ -314,7 +320,7 @@ class UsageLockService: Service() { private fun createNotificationChannel() { val serviceChannel = NotificationChannel( CHANNEL_ID, - "AppLock Service (Usage Stats)", + getString(R.string.usage_stats_service_channel_name), NotificationManager.IMPORTANCE_DEFAULT ) notificationManager.createNotificationChannel(serviceChannel) @@ -322,8 +328,8 @@ class UsageLockService: Service() { private fun createNotification(): Notification { return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("App Lock") - .setContentText("Protecting your apps") + .setContentTitle(getString(R.string.usage_stats_service_notification_title)) + .setContentText(getString(R.string.usage_stats_service_notification_text)) .setSmallIcon(R.drawable.baseline_shield_24) .setPriority(NotificationCompat.PRIORITY_MIN) .setOngoing(true) diff --git a/app/src/main/java/dev/pranav/applock/ui/components/UsageStatsPermission.kt b/app/src/main/java/dev/pranav/applock/ui/components/UsageStatsPermission.kt index 2320808..6435a36 100644 --- a/app/src/main/java/dev/pranav/applock/ui/components/UsageStatsPermission.kt +++ b/app/src/main/java/dev/pranav/applock/ui/components/UsageStatsPermission.kt @@ -13,9 +13,11 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties +import dev.pranav.applock.R @Composable fun UsageStatsPermission( @@ -32,7 +34,7 @@ fun UsageStatsPermission( ), title = { Text( - text = "Grant Usage Stats Permission", + text = stringResource(R.string.usage_stats_permission_dialog_title), style = MaterialTheme.typography.titleLarge ) }, @@ -42,12 +44,12 @@ fun UsageStatsPermission( horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = "You've enabled Usage Stats mode, but the app requires Uage access permission to function properly.", + text = stringResource(R.string.usage_stats_permission_dialog_text_1), textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Press 'Grant Permission' to open the settings and grant the necessary permissions.", + text = stringResource(R.string.usage_stats_permission_dialog_text_2), textAlign = TextAlign.Start, modifier = Modifier.fillMaxWidth() ) @@ -55,12 +57,12 @@ fun UsageStatsPermission( }, confirmButton = { FilledTonalButton(onClick = onOpenSettings) { - Text("Grant Permission") + Text(stringResource(R.string.grant_permission_button)) } }, dismissButton = { TextButton(onClick = onDismiss) { - Text("Cancel") + Text(stringResource(R.string.cancel_button)) } } ) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..4336545 --- /dev/null +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,477 @@ + + AppLock + + Impede que pessoas não autorizadas desinstalem o aplicativo. + Permite que o AppLock detecte quando aplicativos protegidos são abertos e exiba a verificação de senha. + + Digite sua senha para desativar a permissão de administrador. + PIN incorreto. Tente novamente. + Senha incorreta. Tente novamente. + Senha verificada. Agora você pode desativar a permissão de administrador. + Escolha o método de detecção de aplicativos + Selecione como o AppLock deve detectar quando aplicativos protegidos são iniciados. + Permissão de notificações + O AppLock precisa da permissão para exibir notificações, mantê-lo informado e continuar funcionando corretamente em segundo plano. Toque em "Próximo" para conceder a permissão. + A permissão de notificações é concedida automaticamente na sua versão do Android. + Boas-vindas ao AppLock + Proteja seus aplicativos e sua privacidade com o AppLock. Vamos guiá-lo por uma configuração rápida. + Proteja seus aplicativos + Mantenha seus aplicativos privados protegidos com mecanismos avançados de bloqueio. + Exibir sobre outros aplicativos + O AppLock precisa da permissão para aparecer sobre outros aplicativos e exibir a tela de bloqueio. Toque em "Próximo" e conceda a permissão. + + + A permissão de notificações é necessária para que o AppLock funcione corretamente. + Permissão do Shizuku concedida. + A permissão do Shizuku é necessária para recursos avançados. + Permita que o AppLock seja exibido sobre outros aplicativos. + Desativar otimização de bateria + Para garantir que o AppLock funcione de forma confiável em segundo plano, desative as otimizações de bateria para o aplicativo. Toque em "Próximo" para abrir as configurações. + Serviço de acessibilidade + Método padrão que funciona na maioria dos dispositivos. Requer a permissão de acessibilidade. Pode apresentar pequenos atrasos em alguns fabricantes. + Estatísticas de uso + Método experimental que utiliza as estatísticas de uso do sistema. Funciona melhor em alguns dispositivos. + Serviço Shizuku + Método avançado com melhor desempenho e experiência superior. Requer o aplicativo Shizuku instalado e configurado via ADB. + Você pode alterar isso mais tarde nas configurações. + +O serviço de acessibilidade é necessário para que o AppLock funcione corretamente. + +Se a mensagem "Configuração restrita" for exibida, acesse manualmente Configurações > Aplicativos > AppLock > menu no canto superior direito e toque em "Permitir configurações restritas". + +Toque em "Próximo" para habilitá-lo. + + Permissão de acesso às estatísticas de uso + +Esta permissão é necessária para detectar quando aplicativos bloqueados são iniciados. + +Se a mensagem "Configuração restrita" for exibida, acesse manualmente Configurações > Aplicativos > AppLock > menu no canto superior direito e toque em "Permitir configurações restritas". + +Toque em "Próximo" para habilitá-la. + + +O Shizuku fornece recursos avançados, como bloquear aplicativos do sistema e muito mais. + +Certifique-se de que o Shizuku esteja instalado e configurado via ADB. Toque em "Próximo" para conceder a permissão. + + Privacidade total + Seus dados nunca saem do dispositivo. O AppLock protege sua privacidade o tempo todo. + Todas as permissões são necessárias para continuar. + Próximo + Começar + Voltar + + + Aplicativo + este aplicativo + Desbloquear %1$s + Confirme sua biometria para continuar + Usar PIN + Digite a senha para continuar + Autenticação biométrica + + + Defina um PIN para continuar. + Autentique-se para redefinir o PIN + Use o PIN, padrão ou senha do dispositivo. + Digite o PIN atual + Confirme o PIN + Defina um novo PIN + Crie um PIN para proteger seus aplicativos bloqueados. Esse PIN será solicitado sempre que você tentar acessar um aplicativo protegido. + Digite seu PIN atual + Confirme seu novo PIN + Crie um novo PIN + Digite seu PIN atual para continuar. + Digite o mesmo PIN novamente para confirmar. + Crie um PIN com pelo menos 4 dígitos. + Informações + Os PINs não coincidem. Tente novamente. + O PIN deve ter pelo menos 4 dígitos. + Digite novamente o novo PIN para confirmar. + Redefinir usando a senha do dispositivo + Cancelar + Recomeçar + Senha definida com sucesso. + Usar padrão + Usar senha + Usar PIN + + + Definir senha + Crie uma senha + Confirme sua senha + Digite a senha atual + Digite a senha + A senha deve ter pelo menos 4 caracteres. + A senha deve ter no máximo 64 caracteres. + As senhas não coincidem. + Senha incorreta. Tente novamente. + + + A proteção contra desinstalação está ativada. + Shizuku não disponível. Instale e configure o Shizuku. + Permissão para sobreposição necessária + Conceda ao aplicativo a permissão para exibir sobre outros aplicativos nas configurações. Isso é necessário para que a tela de bloqueio possa ser exibida. + Abrir configurações + Conceda a permissão do Shizuku manualmente. + O AppLock requer a permissão de Administrador do dispositivo para impedir sua desinstalação. Isso não afetará o funcionamento normal do seu dispositivo. + Participe da comunidade + Participe da nossa comunidade no Discord para receber atualizações e obter suporte. + Entrar no Discord + Agora não + Apoie o desenvolvimento + Doar + Proteção de aplicativos + Configurações + Carregando aplicativos... + Pesquisar aplicativos + Pesquisar + Nenhum aplicativo encontrado + Tente ajustar sua pesquisa. + + + Configurações + Voltar + Apoie o desenvolvimento + Olá! Eu sou Pranav, desenvolvedor do AppLock. Sou um estudante apaixonado por criar aplicativos úteis. Se o AppLock tem sido útil para você, considere apoiar seu desenvolvimento com uma pequena doação. Qualquer valor é muito bem-vindo e me ajuda a continuar melhorando o aplicativo e desenvolvendo novos recursos. Muito obrigado pelo seu apoio! + Doar + Permissão necessária + Para ativar a proteção contra desinstalação, o AppLock precisa de duas permissões: Administrador do dispositivo e Serviço de acessibilidade. Elas são necessárias para impedir desinstalações não autorizadas e garantir uma proteção robusta. Deseja conceder essas permissões agora? + Continuar + Permissão de Administrador do dispositivo + O AppLock precisa da permissão de Administrador do dispositivo para impedir desinstalações não autorizadas. Conceda essa permissão na próxima tela. Isso não afetará o funcionamento normal do seu dispositivo. + Conceder permissão + Permissão do Serviço de acessibilidade + O AppLock também precisa da permissão do Serviço de acessibilidade para monitorar tentativas de desinstalação e reativar o Administrador do dispositivo caso ele seja desativado. Isso garante que a proteção contra desinstalação continue funcionando corretamente. Ative o Serviço de acessibilidade do AppLock nas configurações. + Abrir configurações + + Personalização da tela de bloqueio + Brilho máximo + Exibe a tela de bloqueio com brilho máximo para melhor visibilidade. + + Autenticação biométrica + Use impressão digital ou reconhecimento facial, se disponível. + A autenticação biométrica não está disponível neste dispositivo. + + Solicitar biometria automaticamente + Exibe automaticamente a solicitação de biometria na tela de bloqueio. + + Comportamento do bloqueio + + Comportamento após o desbloqueio + Desative esta opção caso o aplicativo reinicie após o desbloqueio. + Fechar a tela de bloqueio + Fechar a tela de bloqueio e abrir o aplicativo + + Desbloqueio automático + Desbloqueia o aplicativo assim que o PIN correto for digitado. + + Tempo de desbloqueio + Mantém os aplicativos desbloqueados por um período após o primeiro desbloqueio. + Definir tempo de desbloqueio + Os aplicativos permanecerão desbloqueados durante esse período após o primeiro desbloqueio. Selecione 0 segundos para desativar esse recurso. + segundos + + Segurança + + Alterar PIN + Atualize seu PIN atual. + + Proteção contra desinstalação + Impede que o AppLock seja desinstalado sem autorização. + + Resposta tátil + Desativa a vibração ao pressionar as teclas. + + Método de detecção + Selecione o método que o AppLock utilizará para detectar o aplicativo em primeiro plano. + + Serviço de acessibilidade + Método padrão. Pode apresentar pequenos atrasos em alguns fabricantes. + + Estatísticas de uso + Método experimental que funciona melhor em alguns dispositivos. + + Shizuku + Método avançado que requer o Shizuku configurado via ADB. + Shizuku indisponível ou permissão negada. + + Recursos experimentais do Shizuku + Ativa recursos experimentais para o backend do Shizuku (por exemplo, bloquear atividades específicas). Isso pode causar instabilidade. + + Sobre + Desenvolvedor + Pranav Purwar + + Código-fonte + Veja o código-fonte no GitHub. + + Apoiar o desenvolvimento + Ajude a manter o desenvolvimento deste aplicativo. + + + Permita que o AppLock ignore as otimizações de bateria. + Solicitação para ignorar otimizações de bateria enviada. + Falha ao abrir as configurações de bateria. + Localize e configure este aplicativo nas configurações de bateria. + Não foi possível localizar as configurações específicas. Remova o aplicativo das restrições de bateria. + Não foi possível abrir as configurações de bateria. + Erro ao verificar a permissão de acesso ao uso: + + + Conceder permissão ao Shizuku + Você ativou o modo Shizuku, mas o aplicativo precisa de permissões adicionais para funcionar corretamente. + Toque em "Conceder permissão" para abrir as configurações e conceder as permissões necessárias. + Conceder permissão + Abrir configurações + + + Permissão de Administrador do dispositivo necessária + Você ativou a proteção contra desinstalação. Ela requer a permissão de Administrador do dispositivo para proteger o aplicativo. + + + Permissão de acessibilidade necessária + Você ativou a proteção contra desinstalação, mas o aplicativo precisa da permissão de acessibilidade para aplicá-la. + + + Verificação de senha necessária + Digite a senha do AppLock para desativar as permissões de administrador. + Senha + A senha não pode ficar em branco. + Senha incorreta. + Verificar + + + Ativar o Serviço de acessibilidade + O AppLock precisa da permissão de acessibilidade para detectar quando aplicativos protegidos são abertos. + Siga estas etapas: + +1. Toque em "Abrir configurações" abaixo\n +2. Ative a chave\n +3. Toque em "Permitir" na caixa de confirmação. + + Abrir configurações + + Sobre e suporte + Participar da comunidade + %1$d minutos + Bloquear imediatamente + + Use a impressão digital ou o reconhecimento facial para desbloquear aplicativos. + A autenticação biométrica não está configurada ou não está disponível. + + Permissão do Shizuku concedida. + A permissão do Shizuku é necessária para recursos avançados. + O Shizuku não está em execução. + Conceda a permissão de acesso ao uso. + + Escolha por quanto tempo os aplicativos permanecerão desbloqueados após a digitação do PIN: + Bloquear imediatamente + %1$d minuto + 1 hora + + Para ativar a proteção contra desinstalação, o AppLock precisa de duas permissões: + 1. Administrador do dispositivo – impede a desinstalação\n2. Serviço de acessibilidade – monitora o uso dos aplicativos\n\nEssas permissões ajudam a proteger o aplicativo contra remoções sem sua autorização. + + O AppLock precisa da permissão de Administrador do dispositivo para impedir sua desinstalação. + Esta permissão permite que o aplicativo:\n• Impeça sua própria desinstalação\n• Proteja as configurações do AppLock\n\nEla não afetará o funcionamento normal do seu dispositivo. + + O AppLock precisa da permissão do Serviço de acessibilidade para monitorar o uso dos aplicativos. + Esta permissão permite que o aplicativo:\n• Detecte quando aplicativos são abertos\n• Exiba a tela de bloqueio quando necessário\n• Forneça proteção contínua aos aplicativos + Sua privacidade é protegida: nenhum dado é coletado ou compartilhado. + + Avançado + + Selecionar método alternativo + Escolha um método alternativo para ser utilizado quando o método principal falhar: + + Confirmar + Ativar + + Avançado + + Exportar registros + Exporte os registros do aplicativo para depuração. + + Exportar registros de segurança + Exporte os registros de segurança para análise. + + Registros exportados. + Falha ao exportar os registros. + + Versão %1$s + + + Desenhe o padrão para continuar. + Padrão incorreto. Tente novamente. + + + Exibir sobre outros aplicativos + Exibe a tela de bloqueio sobre os aplicativos protegidos. + + Serviço de acessibilidade + Detecta quando aplicativos protegidos são abertos. + + Estatísticas de uso + Detecta quando aplicativos protegidos são abertos. + + Permissão do Shizuku + Detecta com segurança quando aplicativos protegidos são abertos. + + Administrador do dispositivo + Impede que o AppLock seja desinstalado. + + necessário(a) + Participar da comunidade + Participe da nossa comunidade no Discord para conversar com outros usuários, compartilhar sugestões e ficar por dentro das últimas novidades e recursos. + Entrar no Discord + Agora não + + Doar + Ajude a manter o AppLock gratuito e sem anúncios para sempre. + Apoie o desenvolvimento + + Olá! Eu sou o Pranav. Desenvolvo o AppLock no meu tempo livre para mantê-lo privado e sem anúncios para todos. + Conciliar este projeto com meus estudos é um desafio, mas o seu apoio torna isso possível. + Se o AppLock tem sido útil para você, considere fazer uma pequena doação. Qualquer valor faz toda a diferença e ajuda a manter este projeto vivo. + Muito obrigado por acreditar no meu trabalho! + + + + Geral + Idioma + Sistema + English + Português (Brasil) + + + Desconhecida + Até desligar a tela + Compartilhar registros de segurança + Compartilhar registros + Registro de eventos + Ative o registro de depuração para ajudar na solução de problemas. + Até desligar a tela + + Serviço de acessibilidade + Estatísticas de uso + Serviço Shizuku + + Método padrão que funciona na maioria dos dispositivos. + Método experimental que utiliza as estatísticas de uso dos aplicativos. + Método avançado que utiliza o Shizuku e APIs internas. + + Links + Comunidade no Discord + Código-fonte + Relatar problema + + + ATIVADO + DESATIVADO + Exclusões de acionamento + Proteção contra desinstalação + Nenhum aplicativo protegido + Toque no botão + para selecionar os aplicativos que deseja proteger. + Selecionar aplicativos + Proteger (%1$d) + Protegido + Desbloquear %1$s + Aviso + Adicionar aplicativos + + + Desenhe o padrão atual + Confirme o padrão + Crie um padrão + Padrão incorreto. + O padrão deve conter pelo menos 4 pontos. + Padrão incorreto. + + + Fechar + Continuar para %1$s + Continuar para %1$s + Fechar + Fechar + Continuar para %1$s + + + Confirmar identidade + Confirme sua biometria para continuar. + Usar PIN + + + Proteção contra desinstalação + Selecione os aplicativos que serão protegidos contra desinstalação. + + Pacotes adicionados manualmente + Aplicativos instalados + + Adicionar pacote manualmente + Digite o nome do pacote do aplicativo que deseja proteger: + + Nome do pacote + com.example.app + + Pesquisar aplicativos ou nomes de pacotes... + + Shizuku não instalado + Shizuku não está em execução + Permissão necessária + Configuração do Shizuku + + O Shizuku é necessário para configurar a proteção contra desinstalação sem acesso root.\n\nInstale o Shizuku pela Play Store ou pelo GitHub para continuar. + + O Shizuku está instalado, mas o serviço em segundo plano ainda não foi iniciado (Binder ainda não recebido).\n\nAbra o aplicativo Shizuku e inicie o serviço usando Depuração sem fio ou Root. + + O Shizuku está ativo, mas este aplicativo precisa da sua autorização para configurar as restrições de pacotes.\n\nFelizmente, o Shizuku é utilizado apenas ao bloquear ou desbloquear desinstalações, portanto ele não precisa permanecer em execução o tempo todo. + + Instalar Shizuku + Abrir Shizuku + + Adicionar pacote manualmente + Pacote adicionado manualmente + + Voltar + Adicionar + Voltar + + + Exclusões de acionamento + Selecione os aplicativos que NÃO deverão acionar a tela de bloqueio ao alternar para aplicativos protegidos. + + Pacotes adicionados manualmente + Aplicativos instalados + + Adicionar pacote manualmente + Digite o nome do pacote do aplicativo que deseja excluir: + + Nome do pacote + com.example.app + + Pesquisar aplicativos ou nomes de pacotes... + + Adicionar pacote manualmente + Pacote adicionado manualmente + + + Conceder permissão de acesso às estatísticas de uso + Você ativou o modo de estatísticas de uso, mas o aplicativo precisa da permissão de acesso às estatísticas para funcionar corretamente. + Toque em "Conceder permissão" para abrir as configurações e conceder as permissões necessárias. + + + Serviço do AppLock + AppLock + Protegendo seus aplicativos com o Shizuku + + Serviço do AppLock (estatísticas de uso) + AppLock + Protegendo seus aplicativos + + Desative a proteção contra desinstalação nas configurações do AppLock para remover esta restrição. + + + Aplicativo + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1873486..086efdc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -58,6 +58,7 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant All permissions are required to proceed. Next Get Started + Back App @@ -190,6 +191,9 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant Please allow our app to ignore battery optimizations. + Battery optimization request sent + Failed to open battery settings + Please find and configure this app in battery settings Could not find specific settings. Please remove app from battery restrictions. Could not open any battery settings. Error checking usage permission: @@ -316,4 +320,121 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant If AppLock has helped you, please consider a small donation—any amount means the world to me and keeps this project alive. Thank you for believing in my work! - + + + General + Language + System + English + Português (Brasil) + + + Unknown + Until screen off + Share audit logs + Share logs + Logging + Enable debug logging for troubleshooting + Until Screen Off + Accessibility Service + Usage Statistics + Shizuku Service + Standard method that works on most devices + Experimental method using app usage statistics + Advanced method using Shizuku and internal APIs + Links + Discord Community + Source Code + Report Issue + + + ON + OFF + Trigger exclusions + Anti-Uninstall + No Protected Apps + Tap the + button to selectively secure your apps. + Select Apps + Protect (%1$d) + Protected + Unlock %1$s + Warning + Add Apps + + + Enter current Pattern + Confirm pattern + Create a pattern + Incorrect Pattern + Pattern length should be at least 4 + Incorrect pattern + + + Close + Continue to %1$s + Continue to %1$s + Close + Close + Continue to %1$s + + + Confirm password + Confirm biometric to continue + Use PIN + + + Anti-Uninstall Protection + Select apps that will be protected from uninstallation. + Manually Added Packages + Installed Apps + Add Package Manually + Enter the package name of the app you want to protect: + Package Name + com.example.app + Search apps or package names... + Shizuku Not Installed + Shizuku Not Running + Permission Required + Shizuku Setup + Shizuku is required to configure Anti-Uninstall protection without root.\n\nPlease install Shizuku from the Play Store or GitHub to continue. + Shizuku is installed, but the background service has not been started yet (Binder hasn\'t been received).\n\nPlease open the Shizuku application and start the service via Wireless Debugging or Root. + Shizuku is active, but this application needs your permission authorization to configure package restrictions.\n\nThankfully, Shizuku is only used while blocking/unblocking uninstalls, so it doesn\'t need to stay running forever! + Install Shizuku + Open Shizuku + Add package manually + Manually added package + Go Back + Add + Back + + + Trigger Exclusions + Select apps that will NOT trigger locks when switching to locked apps. + Manually Added Packages + Installed Apps + Add Package Manually + Enter the package name of the app you want to exclude: + Package Name + com.example.app + Search apps or package names... + Add package manually + Manually added package + + + Grant Usage Stats Permission + You\'ve enabled Usage Stats mode, but the app requires Usage access permission to function properly. + Press \'Grant Permission\' to open the settings and grant the necessary permissions. + + + AppLock Service + AppLock + Protecting your apps with Shizuku + AppLock Service (Usage Stats) + App Lock + Protecting your apps + Disable anti-uninstall from AppLock settings to remove this restriction. + + + App + + \ No newline at end of file From 460988cb6d8570c43a13284f29b2bb0b8fae2e0b Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Thu, 23 Jul 2026 23:47:03 -0300 Subject: [PATCH 2/4] Revert extra padding --- .../java/dev/pranav/applock/features/applist/ui/MainScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt b/app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt index f290601..8a652ef 100644 --- a/app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt @@ -244,7 +244,7 @@ fun MainScreen( ) { Icon( imageVector = Icons.Default.Add, - contentDescription = stringResource(R.string.main_screen_add_button_cd) + contentDescription = stringResource(R.string.main_screen_add_button_cd) ) } } From d3f3d5b6b3b89ba90f967b84c3ee059d68136010 Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Thu, 23 Jul 2026 23:51:24 -0300 Subject: [PATCH 3/4] Remove extra constant --- .../java/dev/pranav/applock/services/AppLockManager.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt b/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt index 19dab42..fc3d4ca 100644 --- a/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt +++ b/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt @@ -87,13 +87,12 @@ object AppLockManager { return false } - private val BACKEND_SERVICES = setOf( + private val ALL_APP_LOCK_SERVICES = setOf( ShizukuAppLockService::class.java, UsageLockService::class.java, + AppLockAccessibilityService::class.java, ) - private val ALL_APP_LOCK_SERVICES = BACKEND_SERVICES + AppLockAccessibilityService::class.java - fun unlockApp(packageName: String) { temporarilyUnlockedApp = packageName appUnlockTimes[packageName] = System.currentTimeMillis() @@ -136,7 +135,7 @@ object AppLockManager { } fun stopAllOtherServices(context: Context, excludeService: Class<*>) { - (BACKEND_SERVICES - excludeService).forEach { + (ALL_APP_LOCK_SERVICES - AppLockAccessibilityService::class.java - excludeService).forEach { context.stopService(Intent(context, it)) } LogUtils.d(TAG, "Stopped all main app lock services except ${excludeService.simpleName}.") From d6a0b42d816a2bf4eb6b98e9a3d9defb647a2eb8 Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Thu, 23 Jul 2026 23:54:06 -0300 Subject: [PATCH 4/4] Rename section titles in strings.xml --- app/src/main/res/values-pt-rBR/strings.xml | 14 +++++++------- app/src/main/res/values/strings.xml | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4336545..e42d49b 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -344,7 +344,7 @@ Certifique-se de que o Shizuku esteja instalado e configurado via ADB. Toque em English Português (Brasil) - + Desconhecida Até desligar a tela Compartilhar registros de segurança @@ -366,7 +366,7 @@ Certifique-se de que o Shizuku esteja instalado e configurado via ADB. Toque em Código-fonte Relatar problema - + ATIVADO DESATIVADO Exclusões de acionamento @@ -380,7 +380,7 @@ Certifique-se de que o Shizuku esteja instalado e configurado via ADB. Toque em Aviso Adicionar aplicativos - + Desenhe o padrão atual Confirme o padrão Crie um padrão @@ -388,7 +388,7 @@ Certifique-se de que o Shizuku esteja instalado e configurado via ADB. Toque em O padrão deve conter pelo menos 4 pontos. Padrão incorreto. - + Fechar Continuar para %1$s Continuar para %1$s @@ -401,7 +401,7 @@ Certifique-se de que o Shizuku esteja instalado e configurado via ADB. Toque em Confirme sua biometria para continuar. Usar PIN - + Proteção contra desinstalação Selecione os aplicativos que serão protegidos contra desinstalação. @@ -437,7 +437,7 @@ Certifique-se de que o Shizuku esteja instalado e configurado via ADB. Toque em Adicionar Voltar - + Exclusões de acionamento Selecione os aplicativos que NÃO deverão acionar a tela de bloqueio ao alternar para aplicativos protegidos. @@ -455,7 +455,7 @@ Certifique-se de que o Shizuku esteja instalado e configurado via ADB. Toque em Adicionar pacote manualmente Pacote adicionado manualmente - + Conceder permissão de acesso às estatísticas de uso Você ativou o modo de estatísticas de uso, mas o aplicativo precisa da permissão de acesso às estatísticas para funcionar corretamente. Toque em "Conceder permissão" para abrir as configurações e conceder as permissões necessárias. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 086efdc..45cb219 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -328,7 +328,7 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant English Português (Brasil) - + Unknown Until screen off Share audit logs @@ -347,7 +347,7 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant Source Code Report Issue - + ON OFF Trigger exclusions @@ -361,7 +361,7 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant Warning Add Apps - + Enter current Pattern Confirm pattern Create a pattern @@ -369,7 +369,7 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant Pattern length should be at least 4 Incorrect pattern - + Close Continue to %1$s Continue to %1$s @@ -382,7 +382,7 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant Confirm biometric to continue Use PIN - + Anti-Uninstall Protection Select apps that will be protected from uninstallation. Manually Added Packages @@ -407,7 +407,7 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant Add Back - + Trigger Exclusions Select apps that will NOT trigger locks when switching to locked apps. Manually Added Packages @@ -420,7 +420,7 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant Add package manually Manually added package - + Grant Usage Stats Permission You\'ve enabled Usage Stats mode, but the app requires Usage access permission to function properly. Press \'Grant Permission\' to open the settings and grant the necessary permissions. @@ -437,4 +437,4 @@ Make sure you have Shizuku installed and enabled via ADB. Tap \'Next\' to grant App - \ No newline at end of file +