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..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 @@ -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..fc3d4ca 100644 --- a/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt +++ b/app/src/main/java/dev/pranav/applock/services/AppLockManager.kt @@ -89,7 +89,8 @@ object AppLockManager { private val ALL_APP_LOCK_SERVICES = setOf( ShizukuAppLockService::class.java, - UsageLockService::class.java + UsageLockService::class.java, + AppLockAccessibilityService::class.java, ) fun unlockApp(packageName: String) { @@ -134,12 +135,17 @@ object AppLockManager { } fun stopAllOtherServices(context: Context, excludeService: Class<*>) { - ALL_APP_LOCK_SERVICES - .filter { it != excludeService } - .forEach { - context.stopService(Intent(context, it)) - } + (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}.") } + 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..e42d49b --- /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..45cb219 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 +