diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 07f0b0505..4c589f3e1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + @@ -695,6 +697,14 @@ + + + + + + , - val imageUrls: List = emptyList() + val imageUrls: List = emptyList(), + val localImagePaths: List = emptyList(), + val lastUpdated: Long = System.currentTimeMillis() ) diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index 1aac598d9..32313248e 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -8,6 +8,7 @@ import com.sameerasw.essentials.domain.model.AppSelection import com.sameerasw.essentials.domain.model.NotificationLightingColorMode import com.sameerasw.essentials.domain.model.NotificationLightingSide import com.sameerasw.essentials.domain.model.NotificationLightingStyle +import com.sameerasw.essentials.domain.model.DnsPreset import com.sameerasw.essentials.domain.model.TrackedRepo import com.sameerasw.essentials.domain.model.github.GitHubUser import com.sameerasw.essentials.utils.RootUtils @@ -100,6 +101,7 @@ class SettingsRepository(private val context: Context) { const val KEY_FREEZE_AUTO_EXCLUDED_APPS = "freeze_auto_excluded_apps" const val KEY_FREEZE_SELECTED_APPS = "freeze_selected_apps" const val KEY_FREEZE_DONT_FREEZE_ACTIVE_APPS = "freeze_dont_freeze_active_apps" + const val KEY_FREEZE_MODE = "freeze_mode" const val KEY_DEVELOPER_MODE_ENABLED = "developer_mode_enabled" const val KEY_HAPTIC_FEEDBACK_TYPE = "haptic_feedback_type" @@ -120,6 +122,7 @@ class SettingsRepository(private val context: Context) { const val KEY_KEYBOARD_PITCH_BLACK = "keyboard_pitch_black" const val KEY_KEYBOARD_CLIPBOARD_ENABLED = "keyboard_clipboard_enabled" const val KEY_KEYBOARD_LONG_PRESS_SYMBOLS = "keyboard_long_press_symbols" + const val KEY_KEYBOARD_ACCENTED_CHARACTERS = "keyboard_accented_characters" // Essentials-AirSync Bridge const val KEY_AIRSYNC_CONNECTION_ENABLED = "airsync_connection_enabled" @@ -166,6 +169,7 @@ class SettingsRepository(private val context: Context) { const val KEY_USE_BLUR = "use_blur" const val KEY_SENTRY_REPORT_MODE = "sentry_report_mode" const val KEY_ONBOARDING_COMPLETED = "onboarding_completed" + const val KEY_PRIVATE_DNS_PRESETS = "private_dns_presets" } // Observe changes @@ -285,6 +289,8 @@ class SettingsRepository(private val context: Context) { putString(KEY_FREEZE_AUTO_EXCLUDED_APPS, json) } + fun getFreezeMode(): Int = getInt(KEY_FREEZE_MODE, 0) + fun getHapticFeedbackType(): HapticFeedbackType { val typeName = prefs.getString(KEY_HAPTIC_FEEDBACK_TYPE, HapticFeedbackType.SUBTLE.name) return try { @@ -717,6 +723,9 @@ class SettingsRepository(private val context: Context) { fun isUserDictionaryEnabled(): Boolean = getBoolean(KEY_USER_DICTIONARY_ENABLED, false) fun setUserDictionaryEnabled(enabled: Boolean) = putBoolean(KEY_USER_DICTIONARY_ENABLED, enabled) + fun isAccentedCharactersEnabled(): Boolean = getBoolean(KEY_KEYBOARD_ACCENTED_CHARACTERS, false) + fun setAccentedCharactersEnabled(enabled: Boolean) = putBoolean(KEY_KEYBOARD_ACCENTED_CHARACTERS, enabled) + fun isBatteryNotificationEnabled(): Boolean = getBoolean(KEY_BATTERY_NOTIFICATION_ENABLED, false) fun setBatteryNotificationEnabled(enabled: Boolean) = putBoolean(KEY_BATTERY_NOTIFICATION_ENABLED, enabled) @@ -866,5 +875,37 @@ class SettingsRepository(private val context: Context) { e.printStackTrace() } } + + fun getPrivateDnsPresets(): List { + val json = prefs.getString(KEY_PRIVATE_DNS_PRESETS, null) + return if (json != null) { + try { + gson.fromJson(json, Array::class.java).toList() + } catch (e: Exception) { + getDefaultDnsPresets() + } + } else { + getDefaultDnsPresets().also { savePrivateDnsPresets(it) } + } + } + + private fun getDefaultDnsPresets(): List { + return listOf( + DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_adguard), hostname = "dns.adguard.com", isDefault = true), + DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_google), hostname = "dns.google", isDefault = true), + DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_cloudflare), hostname = "1dot1dot1dot1.cloudflare-dns.com", isDefault = true), + DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_quad9), hostname = "dns.quad9.net", isDefault = true), + DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_cleanbrowsing), hostname = "adult-filter-dns.cleanbrowsing.org", isDefault = true) + ) + } + + fun savePrivateDnsPresets(presets: List) { + val json = gson.toJson(presets) + putString(KEY_PRIVATE_DNS_PRESETS, json) + } + + fun resetPrivateDnsPresets() { + savePrivateDnsPresets(getDefaultDnsPresets()) + } } diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt index 6f7f1bbbf..22e28fee7 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt @@ -79,4 +79,14 @@ sealed interface Action { override val permissions: List = listOf("notification_policy") override val isConfigurable: Boolean = true } + + data object TurnOnLowPower : Action { + override val title: Int = R.string.diy_action_low_power_on + override val icon: Int = R.drawable.rounded_battery_android_frame_shield_24 + } + + data object TurnOffLowPower : Action { + override val title: Int = R.string.diy_action_low_power_off + override val icon: Int = R.drawable.rounded_battery_android_frame_shield_24 + } } diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/State.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/State.kt index 4093e0bc1..6a1140c0c 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/State.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/State.kt @@ -20,4 +20,15 @@ sealed interface State { override val title: Int = R.string.diy_state_screen_on override val icon: Int = R.drawable.rounded_mobile_text_2_24 } + + data class TimePeriod( + val startHour: Int = 0, + val startMinute: Int = 0, + val endHour: Int = 0, + val endMinute: Int = 0, + val days: Set = emptySet() + ) : State { + override val title: Int = R.string.diy_state_time_period + override val icon: Int = R.drawable.rounded_timelapse_24 + } } diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt index 866219279..a6205ae73 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt @@ -34,4 +34,14 @@ sealed interface Trigger { override val title: Int = R.string.diy_trigger_charger_disconnected override val icon: Int = R.drawable.rounded_battery_android_frame_3_24 } + + data class Schedule( + val hour: Int = 0, + val minute: Int = 0, + val days: Set = emptySet() + ) : Trigger { + override val title: Int = R.string.diy_trigger_schedule + override val icon: Int = R.drawable.rounded_nest_clock_farsight_analog_24 + override val isConfigurable: Boolean = true + } } diff --git a/app/src/main/java/com/sameerasw/essentials/domain/model/DnsPreset.kt b/app/src/main/java/com/sameerasw/essentials/domain/model/DnsPreset.kt new file mode 100644 index 000000000..9f05839c1 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/domain/model/DnsPreset.kt @@ -0,0 +1,8 @@ +package com.sameerasw.essentials.domain.model + +data class DnsPreset( + val id: String = java.util.UUID.randomUUID().toString(), + val name: String, + val hostname: String, + val isDefault: Boolean = false +) diff --git a/app/src/main/java/com/sameerasw/essentials/domain/model/FreezeMode.kt b/app/src/main/java/com/sameerasw/essentials/domain/model/FreezeMode.kt new file mode 100644 index 000000000..7b0878e14 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/domain/model/FreezeMode.kt @@ -0,0 +1,10 @@ +package com.sameerasw.essentials.domain.model + +enum class FreezeMode(val value: Int) { + FREEZE(0), + SUSPEND(1); + + companion object { + fun fromInt(value: Int) = entries.find { it.value == value } ?: FREEZE + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt b/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt index 8ce43b36e..472fc6702 100644 --- a/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt +++ b/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt @@ -267,6 +267,15 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM ) } + var isAccentedCharactersEnabled by remember { + mutableStateOf( + prefs.getBoolean( + SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS, + false + ) + ) + } + // Observe SharedPreferences changes DisposableEffect(prefs) { val listener = @@ -377,6 +386,12 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM false ) } + SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS -> { + isAccentedCharactersEnabled = sharedPreferences.getBoolean( + SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS, + false + ) + } } } prefs.registerOnSharedPreferenceChangeListener(listener) @@ -404,6 +419,7 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM functionsPadding = functionsPadding.dp, isClipboardEnabled = isKeyboardClipboardEnabled, isLongPressSymbolsEnabled = isLongPressSymbolsEnabled, + isAccentedCharactersEnabled = isAccentedCharactersEnabled, suggestions = suggestions, clipboardHistory = _clipboardHistory.collectAsState().value, onOpened = resetTrigger, diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/AutomationManager.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/AutomationManager.kt index cd21718b0..e37c9f95a 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/AutomationManager.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/AutomationManager.kt @@ -8,6 +8,7 @@ import com.sameerasw.essentials.domain.diy.Trigger import com.sameerasw.essentials.services.automation.modules.AutomationModule import com.sameerasw.essentials.services.automation.modules.DisplayModule import com.sameerasw.essentials.services.automation.modules.PowerModule +import com.sameerasw.essentials.services.automation.modules.TimeModule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -61,6 +62,7 @@ object AutomationManager { val requiredModuleIds = mutableSetOf() val powerAutomations = mutableListOf() val displayAutomations = mutableListOf() + val timeAutomations = mutableListOf() enabledAutomations.forEach { automation -> when (automation.type) { @@ -76,6 +78,11 @@ object AutomationManager { displayAutomations.add(automation) } + is Trigger.Schedule -> { + requiredModuleIds.add(TimeModule.ID) + timeAutomations.add(automation) + } + else -> {} } } @@ -92,6 +99,11 @@ object AutomationManager { displayAutomations.add(automation) } + is DIYState.TimePeriod -> { + requiredModuleIds.add(TimeModule.ID) + timeAutomations.add(automation) + } + else -> {} } } @@ -132,6 +144,16 @@ object AutomationManager { } else { activeModules.remove(DisplayModule.ID)?.stop(context) } + + // Time Module + if (requiredModuleIds.contains(TimeModule.ID)) { + val module = activeModules.getOrPut(TimeModule.ID) { + TimeModule().also { it.start(context) } + } + module.updateAutomations(timeAutomations) + } else { + activeModules.remove(TimeModule.ID)?.stop(context) + } } private fun startService(context: Context) { diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt index a748351d1..fbfc60405 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt @@ -8,8 +8,11 @@ import com.sameerasw.essentials.domain.diy.Action object CombinedActionExecutor { - suspend fun execute(context: Context, action: Action) { - when (action) { + suspend fun execute(context: Context, action: com.sameerasw.essentials.domain.diy.Action) { + kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) { + when (action) { + is Action.TurnOnLowPower -> setLowPowerMode(context, true) + is Action.TurnOffLowPower -> setLowPowerMode(context, false) is Action.HapticVibration -> { val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val manager = @@ -90,7 +93,7 @@ object CombinedActionExecutor { } catch (refE: Exception) { null } - } ?: return + } ?: return@withContext effectsBuilder.setShouldDisplayGrayscale(action.grayscale) .setShouldSuppressAmbientDisplay(action.suppressAmbient) @@ -189,6 +192,7 @@ object CombinedActionExecutor { e.printStackTrace() } } + } } } @@ -201,4 +205,13 @@ object CombinedActionExecutor { e.printStackTrace() } } + + private fun setLowPowerMode(context: Context, on: Boolean) { + val value = if (on) 1 else 0 + try { + android.provider.Settings.Global.putInt(context.contentResolver, "low_power", value) + } catch (e: Exception) { + e.printStackTrace() + } + } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/modules/PowerModule.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/modules/PowerModule.kt index 4fc1e0c97..14c2e66b6 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/modules/PowerModule.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/modules/PowerModule.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager +import android.os.PowerManager import com.sameerasw.essentials.domain.diy.Automation import com.sameerasw.essentials.domain.diy.Trigger import com.sameerasw.essentials.services.automation.executors.CombinedActionExecutor diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/modules/TimeModule.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/modules/TimeModule.kt new file mode 100644 index 000000000..b09ce8d71 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/modules/TimeModule.kt @@ -0,0 +1,207 @@ +package com.sameerasw.essentials.services.automation.modules + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log +import com.sameerasw.essentials.domain.diy.Automation +import com.sameerasw.essentials.domain.diy.Trigger +import com.sameerasw.essentials.services.automation.executors.CombinedActionExecutor +import com.sameerasw.essentials.services.automation.receivers.TimeAutomationReceiver +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.* +import com.sameerasw.essentials.domain.diy.State as DIYState + +class TimeModule : AutomationModule { + companion object { + const val ID = "time_module" + } + + override val id: String = ID + private var automations: List = emptyList() + private val scope = CoroutineScope(Dispatchers.IO) + private var appContext: Context? = null + + override fun start(context: Context) { + appContext = context.applicationContext + // We'll run initial check only after we get automations + } + + override fun stop(context: Context) { + appContext?.let { cancelAllAlarms(it) } + appContext = null + } + + override fun updateAutomations(automations: List) { + this.automations = automations + appContext?.let { + checkCurrentStates(it) + scheduleAllAlarms(it) + } + } + + private fun scheduleAllAlarms(context: Context) { + cancelAllAlarms(context) + + automations.forEach { automation -> + when (automation.type) { + Automation.Type.TRIGGER -> { + (automation.trigger as? Trigger.Schedule)?.let { schedule -> + scheduleAlarm(context, automation.id, schedule.hour, schedule.minute, schedule.days, true) + } + } + Automation.Type.STATE -> { + (automation.state as? DIYState.TimePeriod)?.let { period -> + scheduleAlarm(context, automation.id, period.startHour, period.startMinute, period.days, true) + scheduleAlarm(context, automation.id, period.endHour, period.endMinute, period.days, false) + } + } + else -> {} + } + } + } + + private fun scheduleAlarm(context: Context, id: String, hour: Int, minute: Int, days: Set, isEntry: Boolean) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val intent = Intent(context, TimeAutomationReceiver::class.java).apply { + action = TimeAutomationReceiver.ACTION_TRIGGER + putExtra(TimeAutomationReceiver.EXTRA_AUTOMATION_ID, id) + putExtra(TimeAutomationReceiver.EXTRA_IS_ENTRY, isEntry) + } + + val requestCode = (id + isEntry.toString()).hashCode() + val pendingIntent = PendingIntent.getBroadcast( + context, + requestCode, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val calendar = calculateNextOccurrence(hour, minute, days) + android.util.Log.d(ID, "Scheduling alarm for automation $id (entry=$isEntry) at ${calendar.time}") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + if (alarmManager.canScheduleExactAlarms()) { + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + calendar.timeInMillis, + pendingIntent + ) + } else { + // Fallback to inexact + alarmManager.setAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + calendar.timeInMillis, + pendingIntent + ) + } + } else { + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + calendar.timeInMillis, + pendingIntent + ) + } + } catch (e: SecurityException) { + alarmManager.setAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + calendar.timeInMillis, + pendingIntent + ) + } +} else { + alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent) + } + } + + private fun cancelAllAlarms(context: Context) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + automations.forEach { automation -> + val intent = Intent(context, TimeAutomationReceiver::class.java).apply { + action = TimeAutomationReceiver.ACTION_TRIGGER + } + + val rc1 = (automation.id + "true").hashCode() + PendingIntent.getBroadcast(context, rc1, intent, PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)?.let { + alarmManager.cancel(it) + it.cancel() + } + + val rc2 = (automation.id + "false").hashCode() + PendingIntent.getBroadcast(context, rc2, intent, PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)?.let { + alarmManager.cancel(it) + it.cancel() + } + } + } + + private fun calculateNextOccurrence(hour: Int, minute: Int, days: Set): Calendar { + val now = Calendar.getInstance() + val target = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, hour) + set(Calendar.MINUTE, minute) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + if (target.before(now)) { + target.add(Calendar.DAY_OF_YEAR, 1) + } + + if (days.isNotEmpty()) { + while (!days.contains(target.get(Calendar.DAY_OF_WEEK))) { + target.add(Calendar.DAY_OF_YEAR, 1) + } + } + + return target + } + + private val activeStateAutomations = mutableSetOf() + + private fun checkCurrentStates(context: Context) { + scope.launch { + val now = Calendar.getInstance() + val currentHour = now.get(Calendar.HOUR_OF_DAY) + val currentMinute = now.get(Calendar.MINUTE) + val currentDay = now.get(Calendar.DAY_OF_WEEK) + + android.util.Log.d(ID, "Checking current states at $currentHour:$currentMinute on day $currentDay") + + automations.filter { it.type == Automation.Type.STATE && it.isEnabled } + .forEach { automation -> + (automation.state as? DIYState.TimePeriod)?.let { period -> + if (period.days.isEmpty() || period.days.contains(currentDay)) { + val startTime = period.startHour * 60 + period.startMinute + val endTime = period.endHour * 60 + period.endMinute + val currentTime = currentHour * 60 + currentMinute + + val isActive = if (startTime < endTime) { + currentTime in startTime until endTime + } else { + currentTime >= startTime || currentTime < endTime + } + + val wasActive = activeStateAutomations.contains(automation.id) + + if (isActive && !wasActive) { + Log.d(ID, "State ${automation.id} became active. Executing entry actions.") + activeStateAutomations.add(automation.id) + automation.entryAction?.let { CombinedActionExecutor.execute(context, it) } + } else if (!isActive && wasActive) { + Log.d(ID, "State ${automation.id} became inactive. Executing exit actions.") + activeStateAutomations.remove(automation.id) + automation.exitAction?.let { CombinedActionExecutor.execute(context, it) } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/receivers/TimeAutomationReceiver.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/receivers/TimeAutomationReceiver.kt new file mode 100644 index 000000000..192ad404e --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/receivers/TimeAutomationReceiver.kt @@ -0,0 +1,56 @@ +package com.sameerasw.essentials.services.automation.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.sameerasw.essentials.domain.diy.Automation +import com.sameerasw.essentials.domain.diy.DIYRepository +import com.sameerasw.essentials.services.automation.executors.CombinedActionExecutor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class TimeAutomationReceiver : BroadcastReceiver() { + companion object { + const val ACTION_TRIGGER = "com.sameerasw.essentials.ACTION_TIME_AUTOMATION_TRIGGER" + const val EXTRA_AUTOMATION_ID = "automation_id" + const val EXTRA_IS_ENTRY = "is_entry" + } + + private val scope = CoroutineScope(Dispatchers.IO) + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != ACTION_TRIGGER) return + + val automationId = intent.getStringExtra(EXTRA_AUTOMATION_ID) ?: return + val isEntry = intent.getBooleanExtra(EXTRA_IS_ENTRY, true) + + val pendingResult = goAsync() + scope.launch { + try { + DIYRepository.init(context) + val automation = DIYRepository.getAutomation(automationId) ?: return@launch + if (!automation.isEnabled) { + return@launch + } + + when (automation.type) { + Automation.Type.TRIGGER -> { + automation.actions.forEach { action -> + CombinedActionExecutor.execute(context, action) + } + } + Automation.Type.STATE -> { + val action = if (isEntry) automation.entryAction else automation.exitAction + action?.let { CombinedActionExecutor.execute(context, it) } + } + else -> {} + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + pendingResult.finish() + } + } + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/PrivateDnsTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/PrivateDnsTileService.kt index 978bb83e9..936f108b4 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/PrivateDnsTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/PrivateDnsTileService.kt @@ -37,9 +37,19 @@ class PrivateDnsTileService : BaseTileService() { override fun getTileLabel(): String = getString(R.string.tile_private_dns) override fun getTileSubtitle(): String { - return when (getPrivateDnsMode()) { + val mode = getPrivateDnsMode() + return when (mode) { MODE_AUTO -> getString(R.string.tile_private_dns_auto) - MODE_HOSTNAME -> getPrivateDnsHostname() ?: getString(R.string.feat_qs_tiles_title) + MODE_HOSTNAME -> { + val hostname = getPrivateDnsHostname() + if (!hostname.isNullOrEmpty()) { + val settingsRepository = com.sameerasw.essentials.data.repository.SettingsRepository(this) + val preset = settingsRepository.getPrivateDnsPresets().find { it.hostname == hostname } + preset?.name ?: hostname + } else { + getString(R.string.feat_qs_tiles_title) + } + } else -> getString(R.string.tile_private_dns_off) } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt index e36b67ac5..74e28bdcd 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt @@ -226,6 +226,7 @@ class AutomationEditorActivity : ComponentActivity() { var showDimSettings by remember { mutableStateOf(false) } var showDeviceEffectsSettings by remember { mutableStateOf(false) } var showSoundModeSettings by remember { mutableStateOf(false) } + var showTimeSettings by remember { mutableStateOf(false) } var configAction by remember { mutableStateOf(null) } // Generic config action // Validation @@ -456,7 +457,12 @@ class AutomationEditorActivity : ComponentActivity() { Trigger.ScreenOn, Trigger.DeviceUnlock, Trigger.ChargerConnected, - Trigger.ChargerDisconnected + Trigger.ChargerDisconnected, + Trigger.Schedule( + hour = (selectedTrigger as? Trigger.Schedule)?.hour ?: 0, + minute = (selectedTrigger as? Trigger.Schedule)?.minute ?: 0, + days = (selectedTrigger as? Trigger.Schedule)?.days ?: emptySet() + ) ) triggers.forEach { trigger -> EditorActionItem( @@ -466,21 +472,36 @@ class AutomationEditorActivity : ComponentActivity() { isConfigurable = trigger.isConfigurable, onClick = { selectedTrigger = trigger }, onSettingsClick = { - // Handle trigger settings if needed later + if (trigger is Trigger.Schedule) { + showTimeSettings = true + } } ) } } else { val states = listOf( DIYState.Charging, - DIYState.ScreenOn + DIYState.ScreenOn, + DIYState.TimePeriod( + startHour = (selectedState as? DIYState.TimePeriod)?.startHour ?: 0, + startMinute = (selectedState as? DIYState.TimePeriod)?.startMinute ?: 0, + endHour = (selectedState as? DIYState.TimePeriod)?.endHour ?: 0, + endMinute = (selectedState as? DIYState.TimePeriod)?.endMinute ?: 0, + days = (selectedState as? DIYState.TimePeriod)?.days ?: emptySet() + ) ) states.forEach { state -> EditorActionItem( title = stringResource(state.title), iconRes = state.icon, isSelected = selectedState == state, - onClick = { selectedState = state } + onClick = { selectedState = state }, + isConfigurable = state is DIYState.TimePeriod, + onSettingsClick = { + if (state is DIYState.TimePeriod) { + showTimeSettings = true + } + } ) } } @@ -530,7 +551,9 @@ class AutomationEditorActivity : ComponentActivity() { Action.ToggleFlashlight, Action.HapticVibration, Action.DimWallpaper(), - Action.SoundMode() + Action.SoundMode(), + Action.TurnOnLowPower, + Action.TurnOffLowPower ) // Only show Device Effects on Android 15+ actions.add(Action.DeviceEffects()) @@ -615,6 +638,22 @@ class AutomationEditorActivity : ComponentActivity() { } } + if (showTimeSettings) { + com.sameerasw.essentials.ui.components.sheets.TimeSelectionSheet( + initialTrigger = selectedTrigger as? Trigger.Schedule, + initialState = selectedState as? DIYState.TimePeriod, + onDismiss = { showTimeSettings = false }, + onSaveTrigger = { + selectedTrigger = it + showTimeSettings = false + }, + onSaveState = { + selectedState = it + showTimeSettings = false + } + ) + } + if (showDimSettings && configAction is Action.DimWallpaper) { DimWallpaperSettingsSheet( initialAction = configAction as Action.DimWallpaper, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/PrivateDnsSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/PrivateDnsSettingsActivity.kt index 8f2a00957..c96a4e281 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/PrivateDnsSettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/PrivateDnsSettingsActivity.kt @@ -52,6 +52,11 @@ import com.sameerasw.essentials.R import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.theme.EssentialsTheme import com.sameerasw.essentials.utils.HapticUtil +import androidx.compose.material3.IconButton +import androidx.compose.material3.AlertDialog +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import com.sameerasw.essentials.domain.model.DnsPreset class PrivateDnsSettingsActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -77,11 +82,14 @@ class PrivateDnsSettingsActivity : ComponentActivity() { fun PrivateDnsSettingsOverlay(onDismiss: () -> Unit) { val context = LocalContext.current val view = LocalView.current + val viewModel: com.sameerasw.essentials.viewmodels.MainViewModel = androidx.lifecycle.viewmodel.compose.viewModel() val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val PRIVATE_DNS_MODE = "private_dns_mode" val PRIVATE_DNS_SPECIFIER = "private_dns_specifier" + var showAddDialog by remember { mutableStateOf(false) } + val currentMode = remember { Settings.Global.getString(context.contentResolver, PRIVATE_DNS_MODE) ?: "off" } @@ -177,30 +185,81 @@ fun PrivateDnsSettingsOverlay(onDismiss: () -> Unit) { ) } - Text( - text = stringResource(R.string.private_dns_presets_title), - style = MaterialTheme.typography.labelLarge, - modifier = Modifier.padding(horizontal = 16.dp), - color = MaterialTheme.colorScheme.primary - ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.private_dns_presets_title), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary + ) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedButton( + onClick = { + (viewModel as? com.sameerasw.essentials.viewmodels.MainViewModel)?.resetDnsPresets() + HapticUtil.performUIHaptic(view) + }, + modifier = Modifier.height(32.dp), + contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 12.dp, vertical = 0.dp), + shape = RoundedCornerShape(16.dp) + ) { + Text( + text = stringResource(R.string.dns_preset_reset_action), + style = MaterialTheme.typography.labelMedium + ) + } + Button( + onClick = { + showAddDialog = true + HapticUtil.performUIHaptic(view) + }, + modifier = Modifier.height(32.dp), + contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 12.dp, vertical = 0.dp), + shape = RoundedCornerShape(16.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_add_24), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.action_add), + style = MaterialTheme.typography.labelMedium + ) + } + } + } - RoundedCardContainer { - val presets = listOf( - Pair(R.string.dns_preset_adguard, R.string.dns_preset_adguard_hostname), - Pair(R.string.dns_preset_google, R.string.dns_preset_google_hostname), - Pair(R.string.dns_preset_cloudflare, R.string.dns_preset_cloudflare_hostname), - Pair(R.string.dns_preset_quad9, R.string.dns_preset_quad9_hostname), - Pair(R.string.dns_preset_cleanbrowsing, R.string.dns_preset_cleanbrowsing_hostname) + if (showAddDialog) { + AddDnsPresetDialog( + onDismiss = { showAddDialog = false }, + onConfirm = { name, host -> + (viewModel as? com.sameerasw.essentials.viewmodels.MainViewModel)?.addDnsPreset(name, host) + showAddDialog = false + HapticUtil.performUIHaptic(view) + } ) + } - presets.forEach { (nameRes, hostRes) -> - val host = stringResource(hostRes) + RoundedCardContainer { + val presets = (viewModel as? com.sameerasw.essentials.viewmodels.MainViewModel)?.dnsPresets ?: emptyList() + + presets.forEach { preset -> DnsPresetItem( - name = stringResource(nameRes), - hostname = host, - isSelected = customHostname == host, + name = preset.name, + hostname = preset.hostname, + isSelected = customHostname == preset.hostname, onClick = { - customHostname = host + customHostname = preset.hostname + HapticUtil.performUIHaptic(view) + }, + onDelete = { + (viewModel as? com.sameerasw.essentials.viewmodels.MainViewModel)?.removeDnsPreset(preset) HapticUtil.performUIHaptic(view) } ) @@ -291,30 +350,104 @@ fun DnsPresetItem( name: String, hostname: String, isSelected: Boolean, - onClick: () -> Unit + onClick: () -> Unit, + onDelete: () -> Unit ) { Card( - modifier = Modifier.fillMaxWidth().clickable { onClick() }, + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, shape = MaterialTheme.shapes.extraSmall, colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surfaceBright ) ) { - Column( + Row( modifier = Modifier - .padding(16.dp) + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { - Text( - text = name, - style = MaterialTheme.typography.bodyLarge, - fontWeight = FontWeight.Medium, - color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface - ) - Text( - text = hostname, - style = MaterialTheme.typography.bodySmall, - color = if (isSelected) MaterialTheme.colorScheme.primary.copy(alpha = 0.8f) else MaterialTheme.colorScheme.onSurfaceVariant - ) + Column(modifier = Modifier.weight(1f)) { + Text( + text = name, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium, + color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface + ) + Text( + text = hostname, + style = MaterialTheme.typography.bodySmall, + color = if (isSelected) MaterialTheme.colorScheme.primary.copy(alpha = 0.8f) else MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + if (isSelected) { + Icon( + painter = painterResource(id = R.drawable.rounded_check_24), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + } + Icon( + painter = painterResource(id = R.drawable.rounded_delete_24), + contentDescription = stringResource(R.string.dns_preset_delete_content_description), + tint = MaterialTheme.colorScheme.error.copy(alpha = 0.6f), + modifier = Modifier + .size(20.dp) + .clickable { onDelete() } + ) + } } } } + +@Composable +fun AddDnsPresetDialog( + onDismiss: () -> Unit, + onConfirm: (String, String) -> Unit +) { + var name by remember { mutableStateOf("") } + var hostname by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(stringResource(R.string.dns_preset_add_title)) }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text(stringResource(R.string.dns_preset_name_label)) }, + singleLine = true, + shape = RoundedCornerShape(12.dp) + ) + OutlinedTextField( + value = hostname, + onValueChange = { hostname = it }, + label = { Text(stringResource(R.string.private_dns_hostname_label)) }, + singleLine = true, + shape = RoundedCornerShape(12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { if (name.isNotBlank() && hostname.isNotBlank()) onConfirm(name, hostname) }, + enabled = name.isNotBlank() && hostname.isNotBlank() + ) { + Text(stringResource(R.string.action_add)) + } + }, + dismissButton = { + OutlinedButton(onClick = onDismiss) { + Text(stringResource(R.string.action_cancel)) + } + }, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + shape = RoundedCornerShape(28.dp) + ) +} diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt index cb32b5136..6f5bff2f4 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt @@ -80,17 +80,36 @@ class YourAndroidViewModel : ViewModel() { private val _isSpecsLoading = MutableStateFlow(true) val isSpecsLoading = _isSpecsLoading.asStateFlow() + private val _isRefreshing = MutableStateFlow(false) + val isRefreshing = _isRefreshing.asStateFlow() + var hasRunStartupAnimation = false - fun loadDeviceSpecs(deviceInfo: DeviceInfo) { - if (_deviceSpecs.value != null) { + fun loadDeviceSpecs(context: android.content.Context, deviceInfo: com.sameerasw.essentials.utils.DeviceInfo, forceRefresh: Boolean = false) { + if (!forceRefresh && _deviceSpecs.value != null) { _isSpecsLoading.value = false return } viewModelScope.launch { - _isSpecsLoading.value = true - val specs = withContext(Dispatchers.IO) { + if (forceRefresh) { + _isRefreshing.value = true + } else { + _isSpecsLoading.value = true + + // Try to load from cache first + val cached = withContext(Dispatchers.IO) { + com.sameerasw.essentials.utils.DeviceSpecsCache.getCachedSpecs(context) + } + + if (cached != null) { + _deviceSpecs.value = cached + _isSpecsLoading.value = false + return@launch + } + } + + val fetchedSpecs = withContext(Dispatchers.IO) { val manufacturer = deviceInfo.manufacturer val model = deviceInfo.model val deviceName = deviceInfo.deviceName @@ -105,30 +124,49 @@ class YourAndroidViewModel : ViewModel() { } else { queries.add("$manufacturer $model") } + + // 2. User-defined device name (often it's the marketing name like "Galaxy S21 FE 5G") + if (deviceName.isNotBlank() && !queries.contains(deviceName)) { + queries.add(deviceName) + } + + // 3. Handle model numbers by stripping common prefixes (SM-, Redmi, Mi, POCO, etc.) + val prefixes = listOf("SM-", "Redmi ", "Mi ", "POCO ") + for (prefix in prefixes) { + if (model.startsWith(prefix, ignoreCase = true)) { + val stripped = model.substring(prefix.length).trim() + if (stripped.isNotBlank() && !queries.contains(stripped)) { + queries.add(stripped) + queries.add("$manufacturer $stripped") + } + } + } - // 2. Model number directly if it's different from marketing name + // 4. Model number directly if it's different from marketing name if (!queries.contains(model)) { queries.add(model) } - // 3. User-defined device name (sometimes it's the marketing name) - if (deviceName.isNotBlank() && !queries.contains(deviceName)) { - queries.add(deviceName) - } - - // 4. Device codename (e.g., "shiba", "a51") + // 5. Device codename (e.g., "shiba", "a51", "r9q") if (deviceCodename.isNotBlank() && !queries.contains(deviceCodename)) { queries.add(deviceCodename) } - GSMArenaService.fetchSpecs( + com.sameerasw.essentials.utils.GSMArenaService.fetchSpecs( preferredName = manufacturer, preferredModel = model, queries = queries.toTypedArray() ) } - _deviceSpecs.value = specs + + if (fetchedSpecs != null) { + // Download and cache images + val specsWithImages = com.sameerasw.essentials.utils.DeviceSpecsCache.downloadImages(context, fetchedSpecs) + _deviceSpecs.value = specsWithImages + } + _isSpecsLoading.value = false + _isRefreshing.value = false } } } @@ -150,6 +188,8 @@ class YourAndroidActivity : ComponentActivity() { val viewModel: YourAndroidViewModel = androidx.lifecycle.viewmodel.compose.viewModel() val deviceSpecs by viewModel.deviceSpecs.collectAsState() val isSpecsLoading by viewModel.isSpecsLoading.collectAsState() + val isRefreshing by viewModel.isRefreshing.collectAsState() + val context = androidx.compose.ui.platform.LocalContext.current val deviceInfo = remember { DeviceUtils.getDeviceInfo(context) } var showHelpSheet by remember { mutableStateOf(false) } @@ -172,7 +212,7 @@ class YourAndroidActivity : ComponentActivity() { LaunchedEffect(Unit) { mainViewModel.check(context) - viewModel.loadDeviceSpecs(deviceInfo) + viewModel.loadDeviceSpecs(context, deviceInfo) } EssentialsTheme(pitchBlackTheme = isPitchBlackThemeEnabled) { @@ -194,14 +234,22 @@ class YourAndroidActivity : ComponentActivity() { } else Modifier ) ) { - YourAndroidContent( - deviceInfo = deviceInfo, - deviceSpecs = deviceSpecs, - isSpecsLoading = isSpecsLoading, - hasRunStartupAnimation = viewModel.hasRunStartupAnimation, - onAnimationRun = { viewModel.hasRunStartupAnimation = true }, + androidx.compose.material3.pulltorefresh.PullToRefreshBox( + isRefreshing = isRefreshing, + onRefresh = { + viewModel.loadDeviceSpecs(context, deviceInfo, forceRefresh = true) + }, modifier = Modifier.fillMaxSize() - ) + ) { + YourAndroidContent( + deviceInfo = deviceInfo, + deviceSpecs = deviceSpecs, + isSpecsLoading = isSpecsLoading, + hasRunStartupAnimation = viewModel.hasRunStartupAnimation, + onAnimationRun = { viewModel.hasRunStartupAnimation = true }, + modifier = Modifier.fillMaxSize() + ) + } EssentialsFloatingToolbar( title = stringResource(R.string.tab_your_android), diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/DeviceHeroCard.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/DeviceHeroCard.kt index 6cb2789c1..32f08e696 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/DeviceHeroCard.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/DeviceHeroCard.kt @@ -95,10 +95,17 @@ fun DeviceHeroCard( .fillMaxWidth(0.85f) ) } else { - // real image from gsmarena + // real image from gsmarena (or local cache) val imageIndex = if (showIllustration) page - 1 else page + val imageModel = if (deviceSpecs?.localImagePaths?.isNotEmpty() == true && + deviceSpecs.localImagePaths.size > imageIndex) { + deviceSpecs.localImagePaths[imageIndex] + } else { + imageUrls[imageIndex] + } + AsyncImage( - model = imageUrls[imageIndex], + model = imageModel, contentDescription = "Device Image", contentScale = ContentScale.Fit, modifier = Modifier diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/MultiSegmentedPicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/MultiSegmentedPicker.kt index de03a5305..026ef9dee 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/MultiSegmentedPicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/MultiSegmentedPicker.kt @@ -26,7 +26,9 @@ fun MultiSegmentedPicker( selectedItems: Set, onItemsSelected: (Set) -> Unit, labelProvider: (T) -> String, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + shape: androidx.compose.ui.graphics.Shape = RoundedCornerShape(24.dp), + allowEmpty: Boolean = true ) { val view = LocalView.current @@ -34,7 +36,7 @@ fun MultiSegmentedPicker( modifier = modifier .background( color = MaterialTheme.colorScheme.surfaceBright, - shape = RoundedCornerShape(MaterialTheme.shapes.extraSmall.bottomEnd) + shape = shape ) .padding(10.dp), horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween), @@ -51,7 +53,7 @@ fun MultiSegmentedPicker( val newSelection = if (checked) { selectedItems + item } else { - if (selectedItems.size > 1) selectedItems - item else selectedItems + if (allowEmpty || selectedItems.size > 1) selectedItems - item else selectedItems } onItemsSelected(newSelection) }, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/TimeSelectionSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/TimeSelectionSheet.kt new file mode 100644 index 000000000..899b9bf66 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/TimeSelectionSheet.kt @@ -0,0 +1,264 @@ +package com.sameerasw.essentials.ui.components.sheets + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.sameerasw.essentials.R +import com.sameerasw.essentials.domain.diy.State as DIYState +import com.sameerasw.essentials.domain.diy.Trigger +import com.sameerasw.essentials.ui.components.pickers.MultiSegmentedPicker +import com.sameerasw.essentials.utils.HapticUtil +import java.util.* + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TimeSelectionSheet( + initialTrigger: Trigger.Schedule? = null, + initialState: DIYState.TimePeriod? = null, + onDismiss: () -> Unit, + onSaveTrigger: (Trigger.Schedule) -> Unit = {}, + onSaveState: (DIYState.TimePeriod) -> Unit = {} +) { + val context = androidx.compose.ui.platform.LocalContext.current + val view = LocalView.current + val isRange = initialState != null + val is24Hour = android.text.format.DateFormat.is24HourFormat(context) + + val startPickerState = rememberTimePickerState( + initialHour = initialTrigger?.hour ?: initialState?.startHour ?: 0, + initialMinute = initialTrigger?.minute ?: initialState?.startMinute ?: 0, + is24Hour = is24Hour + ) + val endPickerState = rememberTimePickerState( + initialHour = initialState?.endHour ?: 0, + initialMinute = initialState?.endMinute ?: 0, + is24Hour = is24Hour + ) + var selectedDays by remember { mutableStateOf(initialTrigger?.days ?: initialState?.days ?: emptySet()) } + + var showingEndPicker by remember { mutableStateOf(false) } + + LaunchedEffect(startPickerState.hour, startPickerState.minute) { + HapticUtil.performSliderHaptic(view) + } + LaunchedEffect(endPickerState.hour, endPickerState.minute) { + if (showingEndPicker) HapticUtil.performSliderHaptic(view) + } + + ModalBottomSheet( + onDismissRequest = onDismiss, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + dragHandle = null + ) { + Column( + modifier = Modifier + .padding(24.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(if (isRange) R.string.diy_time_range_selection_title else R.string.diy_time_selection_title), + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.align(Alignment.Start) + ) + + if (isRange) { + // Range display with Start/End tabs + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + TimeDisplayCard( + label = stringResource(R.string.diy_start_time_label), + hour = startPickerState.hour, + minute = startPickerState.minute, + isSelected = !showingEndPicker, + modifier = Modifier.weight(1f), + onClick = { showingEndPicker = false } + ) + TimeDisplayCard( + label = stringResource(R.string.diy_end_time_label), + hour = endPickerState.hour, + minute = endPickerState.minute, + isSelected = showingEndPicker, + modifier = Modifier.weight(1f), + onClick = { showingEndPicker = true } + ) + } + } + + // Time Picker + Box( + modifier = Modifier + .fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.surfaceBright, + shape = RoundedCornerShape(28.dp) + ) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + TimePicker( + state = if (showingEndPicker) endPickerState else startPickerState + ) + } + + // Days Selection + Column( + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = stringResource(R.string.diy_repeat_days_label), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface + ) + + val days = listOf( + Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, + Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY, Calendar.SUNDAY + ) + val dayLabels = mapOf( + Calendar.SUNDAY to "S", + Calendar.MONDAY to "M", + Calendar.TUESDAY to "T", + Calendar.WEDNESDAY to "W", + Calendar.THURSDAY to "T", + Calendar.FRIDAY to "F", + Calendar.SATURDAY to "S" + ) + + MultiSegmentedPicker( + items = days, + selectedItems = selectedDays, + onItemsSelected = { selectedDays = it }, + labelProvider = { dayLabels[it]!! }, + modifier = Modifier.fillMaxWidth() + ) + } + + // Buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + onDismiss() + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceBright, + contentColor = MaterialTheme.colorScheme.onSurface + ) + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_close_24), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.size(8.dp)) + Text(stringResource(R.string.action_cancel)) + } + + Button( + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + if (isRange) { + onSaveState( + DIYState.TimePeriod( + startHour = startPickerState.hour, + startMinute = startPickerState.minute, + endHour = endPickerState.hour, + endMinute = endPickerState.minute, + days = selectedDays + ) + ) + } else { + onSaveTrigger( + Trigger.Schedule( + hour = startPickerState.hour, + minute = startPickerState.minute, + days = selectedDays + ) + ) + } + }, + modifier = Modifier.weight(1f) + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_check_24), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.size(8.dp)) + Text(stringResource(R.string.action_save)) + } + } + } + } +} + +@Composable +private fun TimeDisplayCard( + label: String, + hour: Int, + minute: Int, + isSelected: Boolean, + modifier: Modifier = Modifier, + onClick: () -> Unit +) { + val context = androidx.compose.ui.platform.LocalContext.current + val view = LocalView.current + + val formattedTime = remember(hour, minute) { + val calendar = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, hour) + set(Calendar.MINUTE, minute) + } + android.text.format.DateFormat.getTimeFormat(context).format(calendar.time) + } + + Surface( + onClick = { + HapticUtil.performUIHaptic(view) + onClick() + }, + modifier = modifier.fillMaxWidth(), + color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceBright, + shape = RoundedCornerShape(16.dp) + ) { + Column( + modifier = Modifier.padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + color = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = formattedTime, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface + ) + } + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt index ae5357e05..825affa4c 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt @@ -40,10 +40,12 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.sameerasw.essentials.R +import com.sameerasw.essentials.domain.model.FreezeMode import com.sameerasw.essentials.ui.components.cards.AppToggleItem import com.sameerasw.essentials.ui.components.cards.FeatureCard import com.sameerasw.essentials.ui.components.cards.IconToggleItem import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer +import com.sameerasw.essentials.ui.components.pickers.SegmentedPicker import com.sameerasw.essentials.ui.components.sheets.AppSelectionSheet import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet import com.sameerasw.essentials.ui.modifiers.highlight @@ -63,6 +65,7 @@ fun FreezeSettingsUI( val view = LocalView.current var isAppSelectionSheetOpen by remember { mutableStateOf(false) } var showPermissionSheet by remember { mutableStateOf(false) } + var showModeWarningResult by remember { mutableStateOf(false) } var permissionsToRequest by remember { mutableStateOf>(emptyList()) } val isShizukuAvailable by viewModel.isShizukuAvailable @@ -277,6 +280,48 @@ fun FreezeSettingsUI( ) } + Text( + text = stringResource(R.string.freeze_mode_title), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + RoundedCardContainer{ + SegmentedPicker( + items = FreezeMode.entries, + selectedItem = FreezeMode.fromInt(viewModel.freezeMode.intValue), + onItemSelected = { mode -> + if (viewModel.freezeMode.intValue != mode.value) { + if (viewModel.anyAppsCurrentlyFrozen(context)) { + showModeWarningResult = true + } else { + HapticUtil.performVirtualKeyHaptic(view) + viewModel.setFreezeMode(mode.value, context) + } + } + }, + labelProvider = { mode -> + when (mode) { + FreezeMode.FREEZE -> context.getString(R.string.freeze_mode_freeze) + FreezeMode.SUSPEND -> context.getString(R.string.freeze_mode_suspend) + } + }, + iconProvider = { mode -> + Icon( + painter = painterResource( + id = when (mode) { + FreezeMode.FREEZE -> R.drawable.rounded_mode_cool_24 + FreezeMode.SUSPEND -> R.drawable.rounded_pause_24 + } + ), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + }, + ) + } + Text( text = stringResource(R.string.settings_section_automation), style = MaterialTheme.typography.titleMedium, @@ -484,5 +529,18 @@ fun FreezeSettingsUI( showPermissionSheet = false } } + + if (showModeWarningResult) { + androidx.compose.material3.AlertDialog( + onDismissRequest = { showModeWarningResult = false }, + confirmButton = { + androidx.compose.material3.TextButton(onClick = { showModeWarningResult = false }) { + Text(stringResource(id = R.string.action_ok)) + } + }, + title = { Text(stringResource(id = R.string.warning_title)) }, + text = { Text(stringResource(id = R.string.freeze_mode_warning_desc)) } + ) + } } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/KeyboardSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/KeyboardSettingsUI.kt index 789537263..41db7d279 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/KeyboardSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/KeyboardSettingsUI.kt @@ -284,12 +284,21 @@ fun KeyboardSettingsUI( IconToggleItem( iconRes = R.drawable.rounded_keyboard_24, - title = "Long press for symbols", + title = stringResource(R.string.label_keyboard_long_press_symbols), isChecked = viewModel.isLongPressSymbolsEnabled.value, onCheckedChange = { viewModel.setLongPressSymbolsEnabled(it, context) }, modifier = Modifier.highlight(highlightSetting == "keyboard_long_press_symbols") ) + IconToggleItem( + iconRes = R.drawable.rounded_keyboard_24, + title = stringResource(R.string.label_keyboard_accented_characters), + isChecked = viewModel.isAccentedCharactersEnabled.value, + onCheckedChange = { viewModel.setAccentedCharactersEnabled(it, context) }, + enabled = viewModel.isLongPressSymbolsEnabled.value, + modifier = Modifier.highlight(highlightSetting == "keyboard_accented_characters") + ) + IconToggleItem( iconRes = R.drawable.rounded_book_2_24, title = "User Dictionary (Learn words)", diff --git a/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt b/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt index 5d9cd7745..f3b3a6b59 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -52,13 +53,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.graphicsLayer @@ -66,6 +71,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.changedToUp import androidx.compose.ui.platform.LocalDensity @@ -80,6 +86,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import androidx.compose.ui.zIndex import com.sameerasw.essentials.R import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.ime.EssentialsInputMethodService @@ -89,6 +98,73 @@ import com.sameerasw.essentials.utils.HapticUtil import kotlinx.coroutines.delay import kotlinx.coroutines.launch +private val KeyAccentMap = mapOf( + "a" to listOf("à", "á", "â", "ä", "æ", "ã", "å", "ā"), + "e" to listOf("è", "é", "ê", "ë", "ē", "ė", "ę"), + "i" to listOf("ì", "í", "î", "ï", "ī", "į"), + "o" to listOf("ò", "ó", "ô", "ö", "ø", "õ", "œ", "ō"), + "u" to listOf("ù", "ú", "û", "ü", "ū"), + "n" to listOf("ñ", "ń"), + "s" to listOf("ś", "š", "ß"), + "z" to listOf("ź", "ż", "ž"), + "c" to listOf("ç", "ć", "č"), + "l" to listOf("ł"), + "y" to listOf("ÿ"), + "A" to listOf("À", "Á", "Â", "Ä", "Æ", "Ã", "Å", "Ā"), + "E" to listOf("È", "É", "Ê", "Ë", "Ē", "Ė", "Ę"), + "I" to listOf("Ì", "Í", "Î", "Ï", "Ī", "Į"), + "O" to listOf("Ò", "Ó", "Ô", "Ö", "Ø", "Õ", "Œ", "Ō"), + "U" to listOf("Ù", "Ú", "Û", "Ü", "Ū"), + "S" to listOf("Ś", "Š"), + "N" to listOf("Ñ", "Ń"), + "Z" to listOf("Ž", "Ź", "Ż"), + "C" to listOf("Ç", "Ć", "Č"), + "L" to listOf("Ł"), + "Y" to listOf("Ÿ") +) + +@Composable +fun AccentedKeysPopup( + variants: List, + selectedIndex: Int, + keyRoundness: Dp, + modifier: Modifier = Modifier +) { + androidx.compose.material3.Surface( + modifier = modifier.zIndex(100f), + color = MaterialTheme.colorScheme.surfaceContainerHighest, + shape = RoundedCornerShape(keyRoundness), + shadowElevation = 8.dp, + tonalElevation = 4.dp + ) { + Row( + modifier = Modifier.padding(4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + variants.forEachIndexed { index, char -> + val isSelected = index == selectedIndex + Box( + modifier = Modifier + .size(44.dp) + .background( + color = if (isSelected) MaterialTheme.colorScheme.primary else androidx.compose.ui.graphics.Color.Transparent, + shape = RoundedCornerShape(keyRoundness / 2) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = char, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface + ) + } + } + } + } +} + enum class ShiftState { OFF, @@ -295,6 +371,7 @@ fun KeyboardInputView( onCursorMove: (Int, Boolean, Boolean) -> Unit = { keyCode, _, _ -> onKeyPress(keyCode) }, onCursorDrag: (Boolean) -> Unit = {}, isLongPressSymbolsEnabled: Boolean = false, + isAccentedCharactersEnabled: Boolean = false, onOpened: Int = 0, canDelete: () -> Boolean = { true } ) { @@ -308,18 +385,31 @@ fun KeyboardInputView( var isEmojiMode by remember { mutableStateOf(false) } var isSuggestionsCollapsed by remember { mutableStateOf(false) } var currentWord by remember { mutableStateOf("") } - + // Track if Shift was used for selection var isSelectionPerformed by remember { mutableStateOf(false) } // Track if Symbols was used for word jump var isWordJumpPerformed by remember { mutableStateOf(false) } + // Accented Characters State + var longPressKey by remember { mutableStateOf(null) } + var longPressVariants by remember { mutableStateOf>(emptyList()) } + var selectedAccentIndex by remember { mutableIntStateOf(0) } + var initialAccentIndex by remember { mutableIntStateOf(0) } + var longPressXRatio by remember { mutableFloatStateOf(0.5f) } + var longPressYRatio by remember { mutableFloatStateOf(0.5f) } + val emojiCandidates = remember(currentWord) { if (currentWord.length >= 3) { EmojiData.allEmojis .filter { it.name.contains(currentWord, ignoreCase = true) } .take(5) - .map { Suggestion(it.emoji, SuggestionType.Prediction) } // Wrap emojis as Suggestions + .map { + Suggestion( + it.emoji, + SuggestionType.Prediction + ) + } // Wrap emojis as Suggestions } else { emptyList() } @@ -328,7 +418,9 @@ fun KeyboardInputView( val mergedSuggestions = remember(suggestions, emojiCandidates) { if (emojiCandidates.isNotEmpty() && suggestions.isNotEmpty()) { // Priority: Text 1 -> Emoji 1 -> Remaining Text -> Remaining Emojis - listOf(suggestions[0]) + emojiCandidates.take(1) + suggestions.drop(1) + emojiCandidates.drop(1) + listOf(suggestions[0]) + emojiCandidates.take(1) + suggestions.drop(1) + emojiCandidates.drop( + 1 + ) } else { emojiCandidates + suggestions } @@ -365,6 +457,11 @@ fun KeyboardInputView( ) val totalHeight = animatedTotalHeight + val animatedBlurRadius by animateDpAsState( + targetValue = if (longPressKey != null) 8.dp else 0.dp, + label = "blur" + ) + // Pre-load Emoji data on startup (Background thread) LaunchedEffect(Unit) { EmojiData.load(view.context, scope) @@ -411,7 +508,7 @@ fun KeyboardInputView( val row1Symbols = remember { listOf("~", "\\", "|", "^", "%", "=", "<", ">", "[", "]") } val row2Symbols = remember { listOf("@", "#", "$", "_", "&", "-", "+", "(", ")", "/") } val row3Symbols = remember { listOf("*", "\"", "'", ":", ";", "!", "?") } - + // Long Press Symbols Mapping val row1LongPress = remember { listOf("%", "\\", "|", "=", "[", "]", "<", ">", "{", "}") } val row2LongPress = remember { listOf("@", "#", "$", "_", "&", "-", "+", "(", ")") } @@ -434,501 +531,541 @@ fun KeyboardInputView( if (keyboardShape == 2) keyRoundness else 0.dp } - Column( + Box( modifier = Modifier .fillMaxWidth() .height(totalHeight) - .clip(containerShape) - .background(MaterialTheme.colorScheme.surfaceContainer) - .pointerInput(Unit) { - detectVerticalDragGestures { change, dragAmount -> - if (!isEmojiMode && dragAmount < -20f) { // Swipe up - isEmojiMode = true - isClipboardMode = false - performHeavyHaptic() - } - } - } - .padding( - bottom = if (isEmojiMode) 0.dp else bottomPadding, - start = 6.dp, - end = 6.dp, - top = 6.dp + extraTopPadding - ), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp) ) { - val FunctionRow: @Composable (Modifier) -> Unit = { modifier -> - val hasSuggestions = mergedSuggestions.isNotEmpty() - val showSuggestions = hasSuggestions && !isEmojiMode && !isSuggestionsCollapsed - - val rotation by animateFloatAsState( - targetValue = if (showSuggestions) 45f else 0f, - animationSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessMedium - ), - label = "controlIconRotation" - ) - - AnimatedContent( - targetState = showSuggestions, - transitionSpec = { - val springSpec = spring( - dampingRatio = Spring.DampingRatioLowBouncy, - stiffness = Spring.StiffnessMedium - ) - if (targetState) { - // Expand - (fadeIn() + slideInHorizontally(animationSpec = springSpec) { it }) - .togetherWith(fadeOut() + slideOutHorizontally(animationSpec = springSpec) { -it }) - } else { - // Collapse - (fadeIn() + slideInHorizontally(animationSpec = springSpec) { -it }) - .togetherWith(fadeOut() + slideOutHorizontally(animationSpec = springSpec) { it }) + Column( + modifier = Modifier + .fillMaxSize() + .clip(containerShape) + .background(MaterialTheme.colorScheme.surfaceContainer) + .pointerInput(Unit) { + detectVerticalDragGestures { change, dragAmount -> + if (!isEmojiMode && dragAmount < -20f) { // Swipe up + isEmojiMode = true + isClipboardMode = false + performHeavyHaptic() + } } - }, - label = "FunctionRowTransition", - modifier = modifier - ) { targetShowSuggestions -> - if (targetShowSuggestions) { - val collapseInteraction = remember { MutableInteractionSource() } - val carouselState = rememberCarouselState { mergedSuggestions.count() } - - Row( - modifier = Modifier.fillMaxSize(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - val nestedScrollConnection = remember { - object : NestedScrollConnection { - var accumulatedScroll = 0f - val threshold = 70f - - override fun onPreScroll( - available: Offset, - source: NestedScrollSource - ): Offset { - if (source == NestedScrollSource.UserInput) { - accumulatedScroll += available.x - if (kotlin.math.abs(accumulatedScroll) >= threshold) { - performScrollHaptic() - accumulatedScroll = 0f - } - } - return Offset.Zero + } + .padding( + bottom = if (isEmojiMode) 0.dp else bottomPadding, + start = 6.dp, + end = 6.dp, + top = 6.dp + extraTopPadding + ) + .pointerInput(longPressKey) { + if (longPressKey == null) return@pointerInput + awaitPointerEventScope { + var baselineX: Float? = null + while (longPressKey != null) { + val event = awaitPointerEvent(PointerEventPass.Initial) + val change = event.changes.firstOrNull() + + if (change == null || change.changedToUp() || !change.pressed) { + // Commit selection and dismiss + val selectedChar = + longPressVariants.getOrNull(selectedAccentIndex) + if (selectedChar != null) { + handleType(selectedChar) + performHeavyHaptic() + } + longPressKey = null + break + } else { + if (baselineX == null) { + baselineX = change.position.x + } + + // Slide selection - relative movement from start + val deltaX = change.position.x - (baselineX ?: change.position.x) + val stepWidthPx = with(density) { 40.dp.toPx() } + + val index = ((deltaX / stepWidthPx) + initialAccentIndex).toInt() + .coerceIn(0, longPressVariants.size - 1) + + if (index != selectedAccentIndex) { + selectedAccentIndex = index + performLightHaptic() } } } + } + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + val FunctionRow: @Composable (Modifier) -> Unit = { modifier -> + val hasSuggestions = mergedSuggestions.isNotEmpty() + val showSuggestions = hasSuggestions && !isEmojiMode && !isSuggestionsCollapsed + + val rotation by animateFloatAsState( + targetValue = if (showSuggestions) 45f else 0f, + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessMedium + ), + label = "controlIconRotation" + ) - HorizontalMultiBrowseCarousel( - state = carouselState, - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .nestedScroll(nestedScrollConnection), - preferredItemWidth = 150.dp, - itemSpacing = 4.dp, - minSmallItemWidth = 10.dp, - maxSmallItemWidth = 20.dp, - contentPadding = PaddingValues(start = functionsPadding) - ) { i -> - val suggestion = mergedSuggestions[i] - val isLearned = suggestion.type == SuggestionType.Learned - val suggInteraction = remember { MutableInteractionSource() } - val animatedRadius by animateDpAsState( - targetValue = keyRoundness, - label = "cornerRadius" - ) - - KeyButton( - onClick = { - onSuggestionClick(suggestion) - // If it's an emoji (single char usually, or check length), don't add space if app does, - // but we just pass it out. Let's reset currentWord if it's a COMMIT. - currentWord = "" - }, - onPress = { performLightHaptic() }, - interactionSource = suggInteraction, - containerColor = if (isLearned) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.7f), - contentColor = MaterialTheme.colorScheme.onSecondaryContainer, - shape = RoundedCornerShape(animatedRadius), - modifier = Modifier - .fillMaxHeight() - .fillMaxWidth() - .maskClip(RoundedCornerShape(animatedRadius)) - ) { - Text( - text = suggestion.text, - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Bold, - fontFamily = CustomFontFamily, - maxLines = 1 - ) - } - } - - // Collapse Button (Far Right) - val isCollapsePressed by collapseInteraction.collectIsPressedAsState() - val collapseRadius by animateDpAsState( - targetValue = if (isCollapsePressed) 4.dp else keyRoundness, - label = "collapseRadius" + AnimatedContent( + targetState = showSuggestions, + transitionSpec = { + val springSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessMedium ) + if (targetState) { + // Expand + (fadeIn() + slideInHorizontally(animationSpec = springSpec) { it }) + .togetherWith(fadeOut() + slideOutHorizontally(animationSpec = springSpec) { -it }) + } else { + // Collapse + (fadeIn() + slideInHorizontally(animationSpec = springSpec) { -it }) + .togetherWith(fadeOut() + slideOutHorizontally(animationSpec = springSpec) { it }) + } + }, + label = "FunctionRowTransition", + modifier = modifier + ) { targetShowSuggestions -> + if (targetShowSuggestions) { + val collapseInteraction = remember { MutableInteractionSource() } + val carouselState = rememberCarouselState { mergedSuggestions.count() } - KeyButton( - onClick = { - isSuggestionsCollapsed = true - performLightHaptic() - }, - onPress = { performLightHaptic() }, - interactionSource = collapseInteraction, - containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, - contentColor = MaterialTheme.colorScheme.onSurface, - shape = RoundedCornerShape(collapseRadius), - modifier = Modifier - .fillMaxHeight() - .width(50.dp) - .padding(end = functionsPadding) + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - Icon( - painter = painterResource(id = R.drawable.rounded_add_24), - contentDescription = "Collapse Suggestions", - modifier = Modifier - .size(18.dp) - .graphicsLayer { rotationZ = rotation } - ) - } - } - } else { - ButtonGroup( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(horizontal = functionsPadding), - horizontalArrangement = Arrangement.spacedBy(4.dp), - content = { - val functions = remember(isClipboardEnabled, isEmojiMode, isSuggestionsCollapsed, hasSuggestions) { - val list = mutableListOf( - R.drawable.ic_emoji to "Emoji", - if (isEmojiMode) R.drawable.rounded_backspace_24 to "Backspace" - else R.drawable.ic_undo to "Undo" - ) - if (isClipboardEnabled) { - list.add(1, R.drawable.ic_clipboard to "Clipboard") - } - // Add Expand button if collapsed and suggestions exist - if (isSuggestionsCollapsed && hasSuggestions && !isEmojiMode) { - list.add(R.drawable.rounded_add_24 to "Expand") + val nestedScrollConnection = remember { + object : NestedScrollConnection { + var accumulatedScroll = 0f + val threshold = 70f + + override fun onPreScroll( + available: Offset, + source: NestedScrollSource + ): Offset { + if (source == NestedScrollSource.UserInput) { + accumulatedScroll += available.x + if (kotlin.math.abs(accumulatedScroll) >= threshold) { + performScrollHaptic() + accumulatedScroll = 0f + } + } + return Offset.Zero + } } - list } - functions.forEach { (iconRes, desc) -> - val fnInteraction = remember { MutableInteractionSource() } - val isPressed by fnInteraction.collectIsPressedAsState() + HorizontalMultiBrowseCarousel( + state = carouselState, + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .nestedScroll(nestedScrollConnection), + preferredItemWidth = 150.dp, + itemSpacing = 4.dp, + minSmallItemWidth = 10.dp, + maxSmallItemWidth = 20.dp, + contentPadding = PaddingValues(start = functionsPadding) + ) { i -> + val suggestion = mergedSuggestions[i] + val isLearned = suggestion.type == SuggestionType.Learned + val suggInteraction = remember { MutableInteractionSource() } val animatedRadius by animateDpAsState( - targetValue = if (isPressed) 4.dp else keyRoundness, + targetValue = keyRoundness, label = "cornerRadius" ) KeyButton( onClick = { - if (desc == "Clipboard") { - isClipboardMode = !isClipboardMode - if (isClipboardMode) isEmojiMode = false - } else if (desc == "Undo") { - onUndoClick() - } else if (desc == "Emoji") { - isEmojiMode = !isEmojiMode - if (isEmojiMode) isClipboardMode = false - } else if (desc == "Backspace") { - onKeyPress(android.view.KeyEvent.KEYCODE_DEL) - } else if (desc == "Expand") { - isSuggestionsCollapsed = false - } - }, - onRepeat = { - if (desc == "Backspace") { - onKeyPress(android.view.KeyEvent.KEYCODE_DEL) - performLightHaptic() - } - }, - canRepeat = { - if (desc == "Backspace") canDelete() else true + onSuggestionClick(suggestion) + // If it's an emoji (single char usually, or check length), don't add space if app does, + // but we just pass it out. Let's reset currentWord if it's a COMMIT. + currentWord = "" }, onPress = { performLightHaptic() }, - interactionSource = fnInteraction, - containerColor = if ((desc == "Clipboard" && isClipboardMode) || (desc == "Emoji" && isEmojiMode)) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainerHighest, - contentColor = if ((desc == "Clipboard" && isClipboardMode) || (desc == "Emoji" && isEmojiMode)) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface, + interactionSource = suggInteraction, + containerColor = if (isLearned) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.secondaryContainer.copy( + alpha = 0.7f + ), + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, shape = RoundedCornerShape(animatedRadius), - modifier = if (desc == "Expand") { - Modifier.width(50.dp).fillMaxHeight() - } else { - Modifier.weight(1.3f).fillMaxHeight() - } + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth() + .maskClip(RoundedCornerShape(animatedRadius)) ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = desc, - modifier = Modifier - .size(if (desc == "Expand") 18.dp else 20.dp) - .then( - if (desc == "Expand") { - Modifier.graphicsLayer { rotationZ = rotation } - } else { - Modifier - } - ) + Text( + text = suggestion.text, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + fontFamily = CustomFontFamily, + maxLines = 1 ) } } - } - ) - } - } - } - - if (!isFunctionsBottom) { - FunctionRow( - Modifier - .height(48.dp) - .fillMaxWidth() - ) - } - - val currentMode = when { - isEmojiMode -> 1 - isClipboardMode && isClipboardEnabled -> 2 - else -> 0 - } - AnimatedContent( - targetState = currentMode, - transitionSpec = { - (fadeIn(animationSpec = spring(stiffness = Spring.StiffnessMedium)) togetherWith - fadeOut(animationSpec = spring(stiffness = Spring.StiffnessMedium))) - }, - modifier = Modifier - .fillMaxWidth() - .weight(5f), - label = "KeyboardModeAnimation" - ) { mode -> - when (mode) { - 2 -> { - Box( - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(keyRoundness)) - .background(MaterialTheme.colorScheme.surfaceContainerLow) - .padding(8.dp) - ) { - if (clipboardHistory.isEmpty()) { - Text( - text = "Clipboard is empty", - modifier = Modifier.align(Alignment.Center), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + // Collapse Button (Far Right) + val isCollapsePressed by collapseInteraction.collectIsPressedAsState() + val collapseRadius by animateDpAsState( + targetValue = if (isCollapsePressed) 4.dp else keyRoundness, + label = "collapseRadius" ) - } else { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.fillMaxSize() - ) { - items(clipboardHistory, key = { it }) { clipText -> - val dismissState = androidx.compose.material3.rememberSwipeToDismissBoxState( - confirmValueChange = { value -> - if (value == androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd || - value == androidx.compose.material3.SwipeToDismissBoxValue.EndToStart) { - onDeleteClipboardItem(clipText) - performHeavyHaptic() - true - } else false - } - ) - androidx.compose.material3.SwipeToDismissBox( - state = dismissState, - backgroundContent = { - val color by animateColorAsState( - when (dismissState.targetValue) { - androidx.compose.material3.SwipeToDismissBoxValue.Settled -> MaterialTheme.colorScheme.surfaceContainerLow - else -> MaterialTheme.colorScheme.errorContainer - }, label = "dismissBackground" - ) - Box( - Modifier - .fillMaxSize() - .clip(RoundedCornerShape(keyRoundness)) - .background(color) - .padding(horizontal = 16.dp), - contentAlignment = if (dismissState.dismissDirection == androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd) - Alignment.CenterStart else Alignment.CenterEnd - ) { - Icon( - painter = painterResource(R.drawable.rounded_delete_24), - contentDescription = "Delete", - tint = MaterialTheme.colorScheme.onErrorContainer - ) - } - }, - content = { - ClipboardItem( - text = clipText, - shape = RoundedCornerShape(keyRoundness), - onClick = { - onPasteClick(clipText) - isClipboardMode = false - }, - modifier = Modifier.fillMaxWidth() - ) - } - ) - } + KeyButton( + onClick = { + isSuggestionsCollapsed = true + performLightHaptic() + }, + onPress = { performLightHaptic() }, + interactionSource = collapseInteraction, + containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = MaterialTheme.colorScheme.onSurface, + shape = RoundedCornerShape(collapseRadius), + modifier = Modifier + .fillMaxHeight() + .width(50.dp) + .padding(end = functionsPadding) + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_add_24), + contentDescription = "Collapse Suggestions", + modifier = Modifier + .size(18.dp) + .graphicsLayer { rotationZ = rotation } + ) } } - } - } - 1 -> { - EmojiPicker( - modifier = Modifier.fillMaxSize(), - keyRoundness = keyRoundness, - isHapticsEnabled = isHapticsEnabled, - hapticStrength = hapticStrength, - onEmojiSelected = { emoji -> - handleType(emoji) - }, - onSwipeDownToExit = { - if (isEmojiMode) { - isEmojiMode = false - performHeavyHaptic() - } - }, - bottomContentPadding = bottomPadding - ) - } - else -> { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - // Dedicated Number Row + } else { ButtonGroup( modifier = Modifier - .weight(1f) - .fillMaxWidth(), + .fillMaxWidth() + .fillMaxHeight() + .padding(horizontal = functionsPadding), horizontalArrangement = Arrangement.spacedBy(4.dp), content = { - numberRow.forEach { char -> - key(char) { - val numInteraction = remember { MutableInteractionSource() } - val isPressed by numInteraction.collectIsPressedAsState() - KeyButton( - onClick = { handleType(char) }, - onPress = { performLightHaptic() }, - interactionSource = numInteraction, - containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, - contentColor = MaterialTheme.colorScheme.onSurface, - shape = RoundedCornerShape(keyRoundness), - modifier = Modifier - .weight(1f) - .fillMaxHeight() - ) { - Text( - text = char, - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Medium, - fontFamily = CustomFontFamily - ) + val functions = remember( + isClipboardEnabled, + isEmojiMode, + isSuggestionsCollapsed, + hasSuggestions + ) { + val list = mutableListOf( + R.drawable.ic_emoji to "Emoji", + if (isEmojiMode) R.drawable.rounded_backspace_24 to "Backspace" + else R.drawable.ic_undo to "Undo" + ) + if (isClipboardEnabled) { + list.add(1, R.drawable.ic_clipboard to "Clipboard") + } + // Add Expand button if collapsed and suggestions exist + if (isSuggestionsCollapsed && hasSuggestions && !isEmojiMode) { + list.add(R.drawable.rounded_add_24 to "Expand") + } + list + } + + functions.forEach { (iconRes, desc) -> + val fnInteraction = remember { MutableInteractionSource() } + val isPressed by fnInteraction.collectIsPressedAsState() + val animatedRadius by animateDpAsState( + targetValue = if (isPressed) 4.dp else keyRoundness, + label = "cornerRadius" + ) + + KeyButton( + onClick = { + if (desc == "Clipboard") { + isClipboardMode = !isClipboardMode + if (isClipboardMode) isEmojiMode = false + } else if (desc == "Undo") { + onUndoClick() + } else if (desc == "Emoji") { + isEmojiMode = !isEmojiMode + if (isEmojiMode) isClipboardMode = false + } else if (desc == "Backspace") { + onKeyPress(android.view.KeyEvent.KEYCODE_DEL) + } else if (desc == "Expand") { + isSuggestionsCollapsed = false + } + }, + onRepeat = { + if (desc == "Backspace") { + onKeyPress(android.view.KeyEvent.KEYCODE_DEL) + performLightHaptic() + } + }, + canRepeat = { + if (desc == "Backspace") canDelete() else true + }, + onPress = { performLightHaptic() }, + interactionSource = fnInteraction, + containerColor = if ((desc == "Clipboard" && isClipboardMode) || (desc == "Emoji" && isEmojiMode)) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = if ((desc == "Clipboard" && isClipboardMode) || (desc == "Emoji" && isEmojiMode)) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface, + shape = RoundedCornerShape(animatedRadius), + modifier = if (desc == "Expand") { + Modifier.width(50.dp).fillMaxHeight() + } else { + Modifier.weight(1.3f).fillMaxHeight() } + ) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = desc, + modifier = Modifier + .size(if (desc == "Expand") 18.dp else 20.dp) + .then( + if (desc == "Expand") { + Modifier.graphicsLayer { + rotationZ = rotation + } + } else { + Modifier + } + ) + ) } } } ) + } + } + } - // Row 1 - ButtonGroup( + if (!isFunctionsBottom) { + FunctionRow( + Modifier + .height(48.dp) + .fillMaxWidth() + ) + } + + val currentMode = when { + isEmojiMode -> 1 + isClipboardMode && isClipboardEnabled -> 2 + else -> 0 + } + + AnimatedContent( + targetState = currentMode, + transitionSpec = { + (fadeIn(animationSpec = spring(stiffness = Spring.StiffnessMedium)) togetherWith + fadeOut(animationSpec = spring(stiffness = Spring.StiffnessMedium))) + }, + modifier = Modifier + .fillMaxWidth() + .weight(5f), + label = "KeyboardModeAnimation" + ) { mode -> + when (mode) { + 2 -> { + Box( modifier = Modifier - .weight(1f) - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(4.dp), - content = { - currentRow1.forEachIndexed { index, char -> - key(char) { - val displayLabel = - if (shiftState != ShiftState.OFF && !isSymbols) char.uppercase() else char - val row1Interaction = remember { MutableInteractionSource() } - val isPressed by row1Interaction.collectIsPressedAsState() - val animatedRadius by animateDpAsState( - targetValue = if (isPressed) 4.dp else keyRoundness, - label = "cornerRadius" - ) - - val secondary = if (!isSymbols && isLongPressSymbolsEnabled && index < row1LongPress.size) row1LongPress[index] else null - - KeyButton( - onClick = { - handleType(displayLabel) - if (shiftState == ShiftState.ON) shiftState = ShiftState.OFF - }, - onPress = { performLightHaptic() }, - onLongClick = if (secondary != null) { { handleType(secondary) } } else null, - secondaryText = secondary, - interactionSource = row1Interaction, - containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, - contentColor = MaterialTheme.colorScheme.onSurface, - shape = RoundedCornerShape(animatedRadius), - modifier = Modifier - .weight(1f) - .fillMaxHeight() - ) { - Text( - text = displayLabel, - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.onSurface, - fontFamily = CustomFontFamily + .fillMaxSize() + .clip(RoundedCornerShape(keyRoundness)) + .background(MaterialTheme.colorScheme.surfaceContainerLow) + .padding(8.dp) + ) { + if (clipboardHistory.isEmpty()) { + Text( + text = "Clipboard is empty", + modifier = Modifier.align(Alignment.Center), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } else { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxSize() + ) { + items(clipboardHistory, key = { it }) { clipText -> + val dismissState = + androidx.compose.material3.rememberSwipeToDismissBoxState( + confirmValueChange = { value -> + if (value == androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd || + value == androidx.compose.material3.SwipeToDismissBoxValue.EndToStart + ) { + onDeleteClipboardItem(clipText) + performHeavyHaptic() + true + } else false + } ) - } + + androidx.compose.material3.SwipeToDismissBox( + state = dismissState, + backgroundContent = { + val color by animateColorAsState( + when (dismissState.targetValue) { + androidx.compose.material3.SwipeToDismissBoxValue.Settled -> MaterialTheme.colorScheme.surfaceContainerLow + else -> MaterialTheme.colorScheme.errorContainer + }, label = "dismissBackground" + ) + Box( + Modifier + .fillMaxSize() + .clip(RoundedCornerShape(keyRoundness)) + .background(color) + .padding(horizontal = 16.dp), + contentAlignment = if (dismissState.dismissDirection == androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd) + Alignment.CenterStart else Alignment.CenterEnd + ) { + Icon( + painter = painterResource(R.drawable.rounded_delete_24), + contentDescription = "Delete", + tint = MaterialTheme.colorScheme.onErrorContainer + ) + } + }, + content = { + ClipboardItem( + text = clipText, + shape = RoundedCornerShape(keyRoundness), + onClick = { + onPasteClick(clipText) + isClipboardMode = false + }, + modifier = Modifier.fillMaxWidth() + ) + } + ) } } } + } + } + + 1 -> { + EmojiPicker( + modifier = Modifier.fillMaxSize(), + keyRoundness = keyRoundness, + isHapticsEnabled = isHapticsEnabled, + hapticStrength = hapticStrength, + onEmojiSelected = { emoji -> + handleType(emoji) + }, + onSwipeDownToExit = { + if (isEmojiMode) { + isEmojiMode = false + performHeavyHaptic() + } + }, + bottomContentPadding = bottomPadding ) + } - // Row 2 - Row( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(4.dp) + else -> { + Column( + modifier = Modifier.fillMaxSize() + .blur(animatedBlurRadius), + verticalArrangement = Arrangement.spacedBy(4.dp) ) { - if (!isSymbols) Spacer(modifier = Modifier.weight(0.5f)) + // Dedicated Number Row + ButtonGroup( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp), + content = { + numberRow.forEach { char -> + key(char) { + val numInteraction = + remember { MutableInteractionSource() } + val isPressed by numInteraction.collectIsPressedAsState() + KeyButton( + onClick = { handleType(char) }, + onPress = { performLightHaptic() }, + interactionSource = numInteraction, + containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = MaterialTheme.colorScheme.onSurface, + shape = RoundedCornerShape(keyRoundness), + modifier = Modifier + .weight(1f) + .fillMaxHeight() + ) { + Text( + text = char, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Medium, + fontFamily = CustomFontFamily + ) + } + } + } + } + ) + // Row 1 ButtonGroup( - modifier = Modifier.weight(currentRow2.size.toFloat()), + modifier = Modifier + .weight(1f) + .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(4.dp), content = { - currentRow2.forEachIndexed { index, char -> + currentRow1.forEachIndexed { index, char -> key(char) { val displayLabel = if (shiftState != ShiftState.OFF && !isSymbols) char.uppercase() else char - val row2Interaction = remember { MutableInteractionSource() } - val isPressed by row2Interaction.collectIsPressedAsState() + val row1Interaction = + remember { MutableInteractionSource() } + val isPressed by row1Interaction.collectIsPressedAsState() val animatedRadius by animateDpAsState( targetValue = if (isPressed) 4.dp else keyRoundness, label = "cornerRadius" ) - - val secondary = if (!isSymbols && isLongPressSymbolsEnabled && index < row2LongPress.size) row2LongPress[index] else null - + + val secondary = + if (!isSymbols && isLongPressSymbolsEnabled && index < row1LongPress.size) row1LongPress[index] else null + KeyButton( onClick = { handleType(displayLabel) - if (shiftState == ShiftState.ON) shiftState = ShiftState.OFF + if (shiftState == ShiftState.ON) shiftState = + ShiftState.OFF }, onPress = { performLightHaptic() }, - onLongClick = if (secondary != null) { { handleType(secondary) } } else null, + onLongClick = { + if (isLongPressSymbolsEnabled) { + val accents = + if (isAccentedCharactersEnabled) KeyAccentMap[char] + ?: emptyList() else emptyList() + val variants = mutableListOf() + val xRatio = (index + 0.5f) / 10f + var startIndex = 0 + + if (xRatio < 0.35f) { + if (secondary != null) variants.add(secondary) + variants.addAll(accents) + startIndex = 0 + } else if (xRatio > 0.65f) { + variants.addAll(accents) + if (secondary != null) variants.add(secondary) + startIndex = variants.size - 1 + } else { + val half = accents.size / 2 + variants.addAll(accents.take(half)) + if (secondary != null) variants.add(secondary) + startIndex = variants.size - 1 + variants.addAll(accents.drop(half)) + } + + + if (variants.isNotEmpty()) { + longPressKey = char + longPressVariants = variants + initialAccentIndex = startIndex + selectedAccentIndex = startIndex + longPressXRatio = xRatio + longPressYRatio = 0.2f // Row 1 + } + } + }, secondaryText = secondary, - interactionSource = row2Interaction, + interactionSource = row1Interaction, containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, contentColor = MaterialTheme.colorScheme.onSurface, shape = RoundedCornerShape(animatedRadius), @@ -949,522 +1086,719 @@ fun KeyboardInputView( } ) - if (!isSymbols) Spacer(modifier = Modifier.weight(0.5f)) - } + // Row 2 + Row( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + if (!isSymbols) Spacer(modifier = Modifier.weight(0.5f)) + + ButtonGroup( + modifier = Modifier.weight(currentRow2.size.toFloat()), + horizontalArrangement = Arrangement.spacedBy(4.dp), + content = { + currentRow2.forEachIndexed { index, char -> + key(char) { + val displayLabel = + if (shiftState != ShiftState.OFF && !isSymbols) char.uppercase() else char + val row2Interaction = + remember { MutableInteractionSource() } + val isPressed by row2Interaction.collectIsPressedAsState() + val animatedRadius by animateDpAsState( + targetValue = if (isPressed) 4.dp else keyRoundness, + label = "cornerRadius" + ) - // Row 3 (with Shift/Backspace logic) - ButtonGroup( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(4.dp), - content = { - // Shift Key - val shiftInteraction = remember { MutableInteractionSource() } - val isPressed by shiftInteraction.collectIsPressedAsState() - - var shiftPressTime by remember { androidx.compose.runtime.mutableLongStateOf(0L) } - var wasShiftOffAtDown by remember { mutableStateOf(false) } - - // Reset Shift on release if used for selection - LaunchedEffect(isPressed) { - if (!isPressed) { - if (isSelectionPerformed && wasShiftOffAtDown) { - shiftState = ShiftState.OFF - } - } - } + val secondary = + if (!isSymbols && isLongPressSymbolsEnabled && index < row2LongPress.size) row2LongPress[index] else null + + KeyButton( + onClick = { + handleType(displayLabel) + if (shiftState == ShiftState.ON) shiftState = + ShiftState.OFF + }, + onPress = { performLightHaptic() }, + onLongClick = { + if (isLongPressSymbolsEnabled) { + val accents = + if (isAccentedCharactersEnabled) KeyAccentMap[char] + ?: emptyList() else emptyList() + val variants = mutableListOf() + val xRatio = (index + 0.5f) / currentRow2.size.toFloat() + var startIndex = 0 + + if (xRatio < 0.35f) { + if (secondary != null) variants.add(secondary) + variants.addAll(accents) + startIndex = 0 + } else if (xRatio > 0.65f) { + variants.addAll(accents) + if (secondary != null) variants.add(secondary) + startIndex = variants.size - 1 + } else { + val half = accents.size / 2 + variants.addAll(accents.take(half)) + if (secondary != null) variants.add(secondary) + startIndex = variants.size - 1 + variants.addAll(accents.drop(half)) + } - val animatedRadius by animateDpAsState( - targetValue = if (isPressed) 4.dp else keyRoundness, - label = "cornerRadius" + if (variants.isNotEmpty()) { + longPressKey = char + longPressVariants = variants + initialAccentIndex = startIndex + selectedAccentIndex = startIndex + longPressXRatio = xRatio + longPressYRatio = 0.4f // Row 2 + } + } + }, + secondaryText = secondary, + interactionSource = row2Interaction, + containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = MaterialTheme.colorScheme.onSurface, + shape = RoundedCornerShape(animatedRadius), + modifier = Modifier + .weight(1f) + .fillMaxHeight() + ) { + Text( + text = displayLabel, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface, + fontFamily = CustomFontFamily + ) + } + } + } + } ) - KeyButton( - onClick = { - if (!isSymbols) { - val pressDuration = System.currentTimeMillis() - shiftPressTime - - if (pressDuration < 250) { - if (wasShiftOffAtDown) { + if (!isSymbols) Spacer(modifier = Modifier.weight(0.5f)) + } + + // Row 3 (with Shift/Backspace logic) + ButtonGroup( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp), + content = { + // Shift Key + val shiftInteraction = remember { MutableInteractionSource() } + val isPressed by shiftInteraction.collectIsPressedAsState() + + var shiftPressTime by remember { + androidx.compose.runtime.mutableLongStateOf( + 0L + ) + } + var wasShiftOffAtDown by remember { mutableStateOf(false) } + + // Reset Shift on release if used for selection + LaunchedEffect(isPressed) { + if (!isPressed) { + if (isSelectionPerformed && wasShiftOffAtDown) { + shiftState = ShiftState.OFF + } + } + } + + val animatedRadius by animateDpAsState( + targetValue = if (isPressed) 4.dp else keyRoundness, + label = "cornerRadius" + ) + + KeyButton( + onClick = { + if (!isSymbols) { + val pressDuration = + System.currentTimeMillis() - shiftPressTime + + if (pressDuration < 250) { + if (wasShiftOffAtDown) { + } else { + shiftState = ShiftState.OFF + } } else { - shiftState = ShiftState.OFF + if (wasShiftOffAtDown) { + shiftState = ShiftState.OFF + } } - } else { + } + }, + onPress = { + performLightHaptic() + if (!isSymbols) { + shiftPressTime = System.currentTimeMillis() + wasShiftOffAtDown = (shiftState == ShiftState.OFF) + isSelectionPerformed = + false // Reset selection tracker if (wasShiftOffAtDown) { - shiftState = ShiftState.OFF + shiftState = ShiftState.ON } } - } - }, - onPress = { - performLightHaptic() - if (!isSymbols) { - shiftPressTime = System.currentTimeMillis() - wasShiftOffAtDown = (shiftState == ShiftState.OFF) - isSelectionPerformed = false // Reset selection tracker - if (wasShiftOffAtDown) { - shiftState = ShiftState.ON + }, + onLongClick = { + if (!isSymbols && !isSelectionPerformed) { + performHeavyHaptic() + shiftState = ShiftState.LOCKED } - } - }, - onLongClick = { - if (!isSymbols && !isSelectionPerformed) { - performHeavyHaptic() - shiftState = ShiftState.LOCKED - } - }, - interactionSource = shiftInteraction, - containerColor = if (isSymbols) { - MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.5f) - } else if (shiftState != ShiftState.OFF) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.primaryContainer - }, - contentColor = if (isSymbols) { - MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) - } else if (shiftState != ShiftState.OFF) { - MaterialTheme.colorScheme.onPrimary - } else { - MaterialTheme.colorScheme.onPrimaryContainer - }, - shape = RoundedCornerShape(animatedRadius), - modifier = Modifier - .weight(1.5f) - .fillMaxHeight() - ) { - Icon( - painter = painterResource(id = R.drawable.key_shift), - contentDescription = "Shift", - modifier = Modifier.size(24.dp), - tint = if (isSymbols) { + }, + interactionSource = shiftInteraction, + containerColor = if (isSymbols) { + MaterialTheme.colorScheme.surfaceContainerHighest.copy( + alpha = 0.5f + ) + } else if (shiftState != ShiftState.OFF) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.primaryContainer + }, + contentColor = if (isSymbols) { MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) } else if (shiftState != ShiftState.OFF) { MaterialTheme.colorScheme.onPrimary } else { MaterialTheme.colorScheme.onPrimaryContainer - } - ) - } - - currentRow3.forEachIndexed { index, char -> - key(char) { - val displayLabel = - if (shiftState != ShiftState.OFF && !isSymbols) char.uppercase() else char - val row3Interaction = remember { MutableInteractionSource() } - val isPressed by row3Interaction.collectIsPressedAsState() - val animatedRadius by animateDpAsState( - targetValue = if (isPressed) 4.dp else keyRoundness, - label = "cornerRadius" + }, + shape = RoundedCornerShape(animatedRadius), + modifier = Modifier + .weight(1.5f) + .fillMaxHeight() + ) { + Icon( + painter = painterResource(id = R.drawable.key_shift), + contentDescription = "Shift", + modifier = Modifier.size(24.dp), + tint = if (isSymbols) { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) + } else if (shiftState != ShiftState.OFF) { + MaterialTheme.colorScheme.onPrimary + } else { + MaterialTheme.colorScheme.onPrimaryContainer + } ) - - val secondary = if (!isSymbols && isLongPressSymbolsEnabled && index < row3LongPress.size) row3LongPress[index] else null + } - KeyButton( - onClick = { - handleType(displayLabel) - if (shiftState == ShiftState.ON) shiftState = ShiftState.OFF - }, - onPress = { performLightHaptic() }, - onLongClick = if (secondary != null) { { handleType(secondary) } } else null, - secondaryText = secondary, - interactionSource = row3Interaction, - containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, - contentColor = MaterialTheme.colorScheme.onSurface, - shape = RoundedCornerShape(animatedRadius), - modifier = Modifier - .weight(1f) - .fillMaxHeight() - ) { - Text( - text = displayLabel, - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.onSurface, - fontFamily = CustomFontFamily + currentRow3.forEachIndexed { index, char -> + key(char) { + val displayLabel = + if (shiftState != ShiftState.OFF && !isSymbols) char.uppercase() else char + val row3Interaction = + remember { MutableInteractionSource() } + val isPressed by row3Interaction.collectIsPressedAsState() + val animatedRadius by animateDpAsState( + targetValue = if (isPressed) 4.dp else keyRoundness, + label = "cornerRadius" ) + + val secondary = + if (!isSymbols && isLongPressSymbolsEnabled && index < row3LongPress.size) row3LongPress[index] else null + + KeyButton( + onClick = { + handleType(displayLabel) + if (shiftState == ShiftState.ON) shiftState = + ShiftState.OFF + }, + onPress = { performLightHaptic() }, + onLongClick = { + if (isLongPressSymbolsEnabled) { + val accents = + if (isAccentedCharactersEnabled) KeyAccentMap[char] + ?: emptyList() else emptyList() + val variants = mutableListOf() + val xRatio = (index + 2.0f) / 10.5f + var startIndex = 0 + + if (xRatio < 0.35f) { + if (secondary != null) variants.add(secondary) + variants.addAll(accents) + startIndex = 0 + } else if (xRatio > 0.65f) { + variants.addAll(accents) + if (secondary != null) variants.add(secondary) + startIndex = variants.size - 1 + } else { + val half = accents.size / 2 + variants.addAll(accents.take(half)) + if (secondary != null) variants.add(secondary) + startIndex = variants.size - 1 + variants.addAll(accents.drop(half)) + } + + if (variants.isNotEmpty()) { + longPressKey = char + longPressVariants = variants + initialAccentIndex = startIndex + selectedAccentIndex = startIndex + longPressXRatio = xRatio + longPressYRatio = 0.6f // Row 3 + } + } + }, + secondaryText = secondary, + interactionSource = row3Interaction, + containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = MaterialTheme.colorScheme.onSurface, + shape = RoundedCornerShape(animatedRadius), + modifier = Modifier + .weight(1f) + .fillMaxHeight() + ) { + Text( + text = displayLabel, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface, + fontFamily = CustomFontFamily + ) + } } } - } - // Backspace Key - val backspaceInteraction = remember { MutableInteractionSource() } - val isPressedDel by backspaceInteraction.collectIsPressedAsState() - val animatedRadiusDel by animateDpAsState( - targetValue = if (isPressedDel) 4.dp else keyRoundness, - label = "cornerRadius" - ) - var delAccumulatedDx by remember { mutableStateOf(0f) } - var isDraggingDel by remember { mutableStateOf(false) } - val delSweepThreshold = 25f + // Backspace Key + val backspaceInteraction = + remember { MutableInteractionSource() } + val isPressedDel by backspaceInteraction.collectIsPressedAsState() + val animatedRadiusDel by animateDpAsState( + targetValue = if (isPressedDel) 4.dp else keyRoundness, + label = "cornerRadius" + ) + var delAccumulatedDx by remember { mutableStateOf(0f) } + var isDraggingDel by remember { mutableStateOf(false) } + val delSweepThreshold = 25f - val animatedColorDel by animateColorAsState( - targetValue = if (isPressedDel) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.primaryContainer, - label = "DelColor" - ) - val animatedContentColorDel by animateColorAsState( - targetValue = if (isPressedDel) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onPrimaryContainer, - label = "DelContentColor" - ) + val animatedColorDel by animateColorAsState( + targetValue = if (isPressedDel) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.primaryContainer, + label = "DelColor" + ) + val animatedContentColorDel by animateColorAsState( + targetValue = if (isPressedDel) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onPrimaryContainer, + label = "DelContentColor" + ) - Box( - modifier = Modifier - .weight(1.5f) - .fillMaxHeight() - .bounceClick(backspaceInteraction) - .clip(RoundedCornerShape(animatedRadiusDel)) - .pointerInput(Unit) { - detectHorizontalDragGestures( - onDragStart = { - delAccumulatedDx = 0f - isDraggingDel = true - }, - onDragEnd = { isDraggingDel = false }, - onDragCancel = { isDraggingDel = false }, - onHorizontalDrag = { change, dragAmount -> - change.consume() - delAccumulatedDx += dragAmount - // Moving left (negative dx) for delete - if (delAccumulatedDx <= -delSweepThreshold) { - val steps = - (kotlin.math.abs(delAccumulatedDx) / delSweepThreshold).toInt() - repeat(steps) { - performLightHaptic() - handleKeyPress(KeyEvent.KEYCODE_DEL) + Box( + modifier = Modifier + .weight(1.5f) + .fillMaxHeight() + .bounceClick(backspaceInteraction) + .clip(RoundedCornerShape(animatedRadiusDel)) + .pointerInput(Unit) { + detectHorizontalDragGestures( + onDragStart = { + delAccumulatedDx = 0f + isDraggingDel = true + }, + onDragEnd = { isDraggingDel = false }, + onDragCancel = { isDraggingDel = false }, + onHorizontalDrag = { change, dragAmount -> + change.consume() + delAccumulatedDx += dragAmount + // Moving left (negative dx) for delete + if (delAccumulatedDx <= -delSweepThreshold) { + val steps = + (kotlin.math.abs(delAccumulatedDx) / delSweepThreshold).toInt() + repeat(steps) { + performLightHaptic() + handleKeyPress(KeyEvent.KEYCODE_DEL) + } + delAccumulatedDx %= delSweepThreshold } - delAccumulatedDx %= delSweepThreshold } - } - ) - } - .pointerInput(Unit) { - detectTapGestures( - onPress = { offset -> - val press = PressInteraction.Press(offset) - performLightHaptic() - scope.launch { backspaceInteraction.emit(press) } - - var isReleased = false - val repeatJob = scope.launch { - delay(500) - while (!isReleased && !isDraggingDel) { - if (canDelete()) { - handleKeyPress(KeyEvent.KEYCODE_DEL) - performLightHaptic() - delay(50) - } else { - break - } - } - } - - try { - if (tryAwaitRelease()) { - isReleased = true - repeatJob.cancel() - scope.launch { - backspaceInteraction.emit( - PressInteraction.Release(press) - ) + ) + } + .pointerInput(Unit) { + detectTapGestures( + onPress = { offset -> + val press = PressInteraction.Press(offset) + performLightHaptic() + scope.launch { + backspaceInteraction.emit( + press + ) + } + + var isReleased = false + val repeatJob = scope.launch { + delay(500) + while (!isReleased && !isDraggingDel) { + if (canDelete()) { + handleKeyPress(KeyEvent.KEYCODE_DEL) + performLightHaptic() + delay(50) + } else { + break + } } - if (!isDraggingDel) { - handleKeyPress(KeyEvent.KEYCODE_DEL) + } + + try { + if (tryAwaitRelease()) { + isReleased = true + repeatJob.cancel() + scope.launch { + backspaceInteraction.emit( + PressInteraction.Release( + press + ) + ) + } + if (!isDraggingDel) { + handleKeyPress(KeyEvent.KEYCODE_DEL) + } + } else { + isReleased = true + repeatJob.cancel() + scope.launch { + backspaceInteraction.emit( + PressInteraction.Cancel( + press + ) + ) + } } - } else { + } catch (e: Exception) { isReleased = true repeatJob.cancel() - scope.launch { - backspaceInteraction.emit( - PressInteraction.Cancel(press) - ) - } } - } catch (e: Exception) { - isReleased = true - repeatJob.cancel() } - } - ) - } - .background(animatedColorDel), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(id = R.drawable.rounded_backspace_24), - contentDescription = "Backspace", - modifier = Modifier.size(24.dp), - tint = animatedContentColorDel - ) + ) + } + .background(animatedColorDel), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_backspace_24), + contentDescription = "Backspace", + modifier = Modifier.size(24.dp), + tint = animatedContentColorDel + ) + } } - } - ) + ) - // Row 4 (Sym, Space, Return) - ButtonGroup( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(4.dp), - content = { - // Symbols Toggle - val symInteraction = remember { MutableInteractionSource() } - val isPressedSym by symInteraction.collectIsPressedAsState() - var symPressTime by remember { androidx.compose.runtime.mutableLongStateOf(0L) } - var wasSymOffAtDown by remember { mutableStateOf(false) } - - // Reset Symbols on release if used for modifier - LaunchedEffect(isPressedSym) { - if (!isPressedSym) { - if (isWordJumpPerformed && wasSymOffAtDown) { - isSymbols = false - } + // Row 4 (Sym, Space, Return) + ButtonGroup( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp), + content = { + // Symbols Toggle + val symInteraction = remember { MutableInteractionSource() } + val isPressedSym by symInteraction.collectIsPressedAsState() + var symPressTime by remember { + androidx.compose.runtime.mutableLongStateOf( + 0L + ) } - } + var wasSymOffAtDown by remember { mutableStateOf(false) } - val animatedRadiusSym by animateDpAsState( - targetValue = if (isPressedSym) 4.dp else keyRoundness, - label = "cornerRadius" - ) - KeyButton( - onClick = { - val pressDuration = System.currentTimeMillis() - symPressTime - if (isWordJumpPerformed) { - if (wasSymOffAtDown) isSymbols = false - return@KeyButton - } - if (pressDuration < 250) { - if (!wasSymOffAtDown) { - // Was ON at start. Tap means toggle OFF. - isSymbols = false + // Reset Symbols on release if used for modifier + LaunchedEffect(isPressedSym) { + if (!isPressedSym) { + if (isWordJumpPerformed && wasSymOffAtDown) { + isSymbols = false } - // Else: Was OFF at start. onPress turned it ON. Tap means "Commit" (keep it ON). Do nothing. - } else { - if (wasSymOffAtDown) isSymbols = false - } - }, - onPress = { - performLightHaptic() - symPressTime = System.currentTimeMillis() - wasSymOffAtDown = !isSymbols - isWordJumpPerformed = false - if (wasSymOffAtDown) { - isSymbols = true } - }, - interactionSource = symInteraction, - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - shape = RoundedCornerShape(animatedRadiusSym), - modifier = Modifier - .weight(1.2f) - .fillMaxHeight() - ) { - Text( - text = stringResource(if (isSymbols) R.string.label_kbd_abc else R.string.label_kbd_symbols), - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Medium, - fontFamily = CustomFontFamily + } + + val animatedRadiusSym by animateDpAsState( + targetValue = if (isPressedSym) 4.dp else keyRoundness, + label = "cornerRadius" ) - } + KeyButton( + onClick = { + val pressDuration = + System.currentTimeMillis() - symPressTime + if (isWordJumpPerformed) { + if (wasSymOffAtDown) isSymbols = false + return@KeyButton + } + if (pressDuration < 250) { + if (!wasSymOffAtDown) { + // Was ON at start. Tap means toggle OFF. + isSymbols = false + } + // Else: Was OFF at start. onPress turned it ON. Tap means "Commit" (keep it ON). Do nothing. + } else { + if (wasSymOffAtDown) isSymbols = false + } + }, + onPress = { + performLightHaptic() + symPressTime = System.currentTimeMillis() + wasSymOffAtDown = !isSymbols + isWordJumpPerformed = false + if (wasSymOffAtDown) { + isSymbols = true + } + }, + interactionSource = symInteraction, + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + shape = RoundedCornerShape(animatedRadiusSym), + modifier = Modifier + .weight(1.2f) + .fillMaxHeight() + ) { + Text( + text = stringResource(if (isSymbols) R.string.label_kbd_abc else R.string.label_kbd_symbols), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Medium, + fontFamily = CustomFontFamily + ) + } - // Comma Key - val commaInteraction = remember { MutableInteractionSource() } - val isPressedComma by commaInteraction.collectIsPressedAsState() - val animatedRadiusComma by animateDpAsState( - targetValue = if (isPressedComma) 4.dp else keyRoundness, - label = "cornerRadius" - ) - KeyButton( - onClick = { handleType(",") }, - onPress = { performLightHaptic() }, - interactionSource = commaInteraction, - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - shape = RoundedCornerShape(animatedRadiusComma), - modifier = Modifier - .weight(0.7f) - .fillMaxHeight() - ) { - Text( - text = ",", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Medium, - fontFamily = CustomFontFamily + // Comma Key + val commaInteraction = remember { MutableInteractionSource() } + val isPressedComma by commaInteraction.collectIsPressedAsState() + val animatedRadiusComma by animateDpAsState( + targetValue = if (isPressedComma) 4.dp else keyRoundness, + label = "cornerRadius" ) - } + KeyButton( + onClick = { handleType(",") }, + onPress = { performLightHaptic() }, + interactionSource = commaInteraction, + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + shape = RoundedCornerShape(animatedRadiusComma), + modifier = Modifier + .weight(0.7f) + .fillMaxHeight() + ) { + Text( + text = ",", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Medium, + fontFamily = CustomFontFamily + ) + } - // Space - val spaceInteraction = remember { MutableInteractionSource() } - val isPressedSpace by spaceInteraction.collectIsPressedAsState() - val animatedRadiusSpace by animateDpAsState( - targetValue = if (isPressedSpace) 4.dp else keyRoundness, - label = "cornerRadius" - ) - var accumulatedDx by remember { mutableStateOf(0f) } - val sweepThreshold = 25f // pixels per cursor move + // Space + val spaceInteraction = remember { MutableInteractionSource() } + val isPressedSpace by spaceInteraction.collectIsPressedAsState() + val animatedRadiusSpace by animateDpAsState( + targetValue = if (isPressedSpace) 4.dp else keyRoundness, + label = "cornerRadius" + ) + var accumulatedDx by remember { mutableStateOf(0f) } + val sweepThreshold = 25f // pixels per cursor move - val animatedColorSpace by animateColorAsState( - targetValue = if (isPressedSpace) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainerHighest, - label = "SpaceColor" - ) + val animatedColorSpace by animateColorAsState( + targetValue = if (isPressedSpace) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainerHighest, + label = "SpaceColor" + ) - Box( - modifier = Modifier - .weight(3f) - .fillMaxHeight() - .bounceClick(spaceInteraction) - .clip(RoundedCornerShape(animatedRadiusSpace)) - .pointerInput(Unit) { - val viewConfig = viewConfiguration - awaitEachGesture { - val down = awaitFirstDown(requireUnconsumed = false) - val press = PressInteraction.Press(down.position) - scope.launch { spaceInteraction.emit(press) } - performLightHaptic() + Box( + modifier = Modifier + .weight(3f) + .fillMaxHeight() + .bounceClick(spaceInteraction) + .clip(RoundedCornerShape(animatedRadiusSpace)) + .pointerInput(Unit) { + val viewConfig = viewConfiguration + awaitEachGesture { + val down = + awaitFirstDown(requireUnconsumed = false) + val press = + PressInteraction.Press(down.position) + scope.launch { spaceInteraction.emit(press) } + performLightHaptic() - var isDragStarted = false - var totalDx = 0f - var cursorAccumulator = 0f - - // Increased slop for spacebar to prevent accidental cursor moves - val customSlop = viewConfig.touchSlop * 2.5f - - var upOrCancel: PointerInputChange? = null - - while (true) { - val event = awaitPointerEvent() - val change = event.changes.find { it.id == down.id } - - if (change == null || change.isConsumed) { - break - } + var isDragStarted = false + var totalDx = 0f + var cursorAccumulator = 0f - if (change.changedToUp()) { - upOrCancel = change - break - } + // Increased slop for spacebar to prevent accidental cursor moves + val customSlop = viewConfig.touchSlop * 2.5f - if (change.positionChange() != Offset.Zero) { - totalDx += (change.position.x - change.previousPosition.x) - val dxFromOrigin = totalDx - - if (!isDragStarted) { - if (kotlin.math.abs(dxFromOrigin) > customSlop) { - isDragStarted = true - onCursorDrag(true) - cursorAccumulator = 0f - } + var upOrCancel: PointerInputChange? = null + + while (true) { + val event = awaitPointerEvent() + val change = + event.changes.find { it.id == down.id } + + if (change == null || change.isConsumed) { + break } - - if (isDragStarted) { - change.consume() - cursorAccumulator += (change.position.x - change.previousPosition.x) - - val absDx = kotlin.math.abs(cursorAccumulator) - if (absDx >= sweepThreshold) { - val steps = (absDx / sweepThreshold).toInt() - val keycode = if (cursorAccumulator > 0) KeyEvent.KEYCODE_DPAD_RIGHT else KeyEvent.KEYCODE_DPAD_LEFT - repeat(steps) { - performLightHaptic() - // Use Shift state to decide if we are selecting text - val isSelection = shiftState != ShiftState.OFF - if (isSelection) { - isSelectionPerformed = true - } - // Use Symbols press state to decide if we are jumping words - val isWordJump = isPressedSym - if (isWordJump) { - isWordJumpPerformed = true + + if (change.changedToUp()) { + upOrCancel = change + break + } + + if (change.positionChange() != Offset.Zero) { + totalDx += (change.position.x - change.previousPosition.x) + val dxFromOrigin = totalDx + + if (!isDragStarted) { + if (kotlin.math.abs(dxFromOrigin) > customSlop) { + isDragStarted = true + onCursorDrag(true) + cursorAccumulator = 0f + } + } + + if (isDragStarted) { + change.consume() + cursorAccumulator += (change.position.x - change.previousPosition.x) + + val absDx = kotlin.math.abs( + cursorAccumulator + ) + if (absDx >= sweepThreshold) { + val steps = + (absDx / sweepThreshold).toInt() + val keycode = + if (cursorAccumulator > 0) KeyEvent.KEYCODE_DPAD_RIGHT else KeyEvent.KEYCODE_DPAD_LEFT + repeat(steps) { + performLightHaptic() + // Use Shift state to decide if we are selecting text + val isSelection = + shiftState != ShiftState.OFF + if (isSelection) { + isSelectionPerformed = + true + } + // Use Symbols press state to decide if we are jumping words + val isWordJump = + isPressedSym + if (isWordJump) { + isWordJumpPerformed = + true + } + onCursorMove( + keycode, + isSelection, + isWordJump + ) } - onCursorMove(keycode, isSelection, isWordJump) + cursorAccumulator %= sweepThreshold } - cursorAccumulator %= sweepThreshold } } } - } - if (upOrCancel != null && !isDragStarted) { - handleType(" ") - scope.launch { spaceInteraction.emit(PressInteraction.Release(press)) } - } else { - if (isDragStarted) { - onCursorDrag(false) - } - scope.launch { spaceInteraction.emit(PressInteraction.Cancel(press)) } + if (upOrCancel != null && !isDragStarted) { + handleType(" ") + scope.launch { + spaceInteraction.emit( + PressInteraction.Release(press) + ) + } + } else { + if (isDragStarted) { + onCursorDrag(false) + } + scope.launch { + spaceInteraction.emit( + PressInteraction.Cancel(press) + ) + } + } } } - } - .background(animatedColorSpace), - contentAlignment = Alignment.Center - ) { - // Empty space - } + .background(animatedColorSpace), + contentAlignment = Alignment.Center + ) { + // Empty space + } - // Dot Key - val dotInteraction = remember { MutableInteractionSource() } - val isPressedDot by dotInteraction.collectIsPressedAsState() - val animatedRadiusDot by animateDpAsState( - targetValue = if (isPressedDot) 4.dp else keyRoundness, - label = "cornerRadius" - ) - KeyButton( - onClick = { handleType(".") }, - onPress = { performLightHaptic() }, - interactionSource = dotInteraction, - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - shape = RoundedCornerShape(animatedRadiusDot), - modifier = Modifier - .weight(0.7f) - .fillMaxHeight() - ) { - Text( - text = ".", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Medium, - fontFamily = CustomFontFamily + // Dot Key + val dotInteraction = remember { MutableInteractionSource() } + val isPressedDot by dotInteraction.collectIsPressedAsState() + val animatedRadiusDot by animateDpAsState( + targetValue = if (isPressedDot) 4.dp else keyRoundness, + label = "cornerRadius" ) - } + KeyButton( + onClick = { handleType(".") }, + onPress = { performLightHaptic() }, + interactionSource = dotInteraction, + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + shape = RoundedCornerShape(animatedRadiusDot), + modifier = Modifier + .weight(0.7f) + .fillMaxHeight() + ) { + Text( + text = ".", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Medium, + fontFamily = CustomFontFamily + ) + } - // Return - val returnInteraction = remember { MutableInteractionSource() } - val isPressedReturn by returnInteraction.collectIsPressedAsState() - val animatedRadiusReturn by animateDpAsState( - targetValue = if (isPressedReturn) 4.dp else keyRoundness, - label = "cornerRadius" - ) - KeyButton( - onClick = { handleKeyPress(KeyEvent.KEYCODE_ENTER) }, - onPress = { performLightHaptic() }, - interactionSource = returnInteraction, - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - shape = RoundedCornerShape(animatedRadiusReturn), - modifier = Modifier - .weight(1.5f) - .fillMaxHeight() - ) { - Icon( - painter = painterResource(id = R.drawable.rounded_keyboard_return_24), - contentDescription = "Return", - modifier = Modifier.size(24.dp) + // Return + val returnInteraction = remember { MutableInteractionSource() } + val isPressedReturn by returnInteraction.collectIsPressedAsState() + val animatedRadiusReturn by animateDpAsState( + targetValue = if (isPressedReturn) 4.dp else keyRoundness, + label = "cornerRadius" ) + KeyButton( + onClick = { handleKeyPress(KeyEvent.KEYCODE_ENTER) }, + onPress = { performLightHaptic() }, + interactionSource = returnInteraction, + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + shape = RoundedCornerShape(animatedRadiusReturn), + modifier = Modifier + .weight(1.5f) + .fillMaxHeight() + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_keyboard_return_24), + contentDescription = "Return", + modifier = Modifier.size(24.dp) + ) + } } - } - ) + ) + } } } } + + if (isFunctionsBottom) { + FunctionRow( + Modifier + .height(48.dp) + .fillMaxWidth() + ) + } } - if (isFunctionsBottom) { - FunctionRow( - Modifier - .height(48.dp) - .fillMaxWidth() - ) + // Accented Popup Overlay + if (longPressKey != null && longPressVariants.isNotEmpty()) { + val density = LocalDensity.current + Popup( + alignment = BiasAlignment(-1f + 2f * longPressXRatio, -1f + 2f * longPressYRatio), + offset = IntOffset(0, with(density) { (-20).dp.roundToPx() }), + properties = PopupProperties( + focusable = false, + dismissOnClickOutside = false, + dismissOnBackPress = true, + clippingEnabled = false, + excludeFromSystemGesture = true + ) + ) { + AccentedKeysPopup( + variants = longPressVariants, + selectedIndex = selectedAccentIndex, + keyRoundness = keyRoundness + ) + } } } } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/DeviceSpecsCache.kt b/app/src/main/java/com/sameerasw/essentials/utils/DeviceSpecsCache.kt new file mode 100644 index 000000000..0df4c1e87 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/utils/DeviceSpecsCache.kt @@ -0,0 +1,97 @@ +package com.sameerasw.essentials.utils + +import android.content.Context +import com.google.gson.Gson +import com.sameerasw.essentials.data.model.DeviceSpecs +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream +import java.net.URL + +/** + * Utility for caching device specifications and images locally to avoid redundant network requests. + */ +object DeviceSpecsCache { + private const val SPECS_FILE = "device_specs_cache.json" + private const val IMAGES_DIR = "device_info_images" + private val gson = Gson() + + /** + * Retrieves the cached device specifications if available. + */ + fun getCachedSpecs(context: Context): DeviceSpecs? { + return try { + val file = File(context.filesDir, SPECS_FILE) + if (!file.exists()) return null + val json = file.readText() + gson.fromJson(json, DeviceSpecs::class.java) + } catch (e: Exception) { + null + } + } + + /** + * Saves the device specifications to local storage. + */ + fun saveSpecs(context: Context, specs: DeviceSpecs) { + try { + val json = gson.toJson(specs) + File(context.filesDir, SPECS_FILE).writeText(json) + } catch (e: Exception) { + e.printStackTrace() + } + } + + /** + * Clears all cached device data. + */ + fun clearCache(context: Context) { + try { + File(context.filesDir, SPECS_FILE).delete() + val imagesDir = File(context.filesDir, IMAGES_DIR) + if (imagesDir.exists()) { + imagesDir.deleteRecursively() + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + /** + * Downloads and saves device images locally. + * Returns a copy of [specs] with the [DeviceSpecs.localImagePaths] populated. + */ + suspend fun downloadImages(context: Context, specs: DeviceSpecs): DeviceSpecs = withContext(Dispatchers.IO) { + val dir = File(context.filesDir, IMAGES_DIR) + if (!dir.exists()) dir.mkdirs() + + val localPaths = mutableListOf() + specs.imageUrls.forEachIndexed { index, url -> + try { + // Use a stable filename based on index and extension + val extension = url.substringAfterLast(".", "jpg").split("?").first() + val fileName = "device_image_${index}.${extension}" + val file = File(dir, fileName) + + // Only download if it doesn't already exist or if it's the first image (often updated) + if (!file.exists()) { + URL(url).openStream().use { input -> + FileOutputStream(file).use { output -> + input.copyTo(output) + } + } + } + localPaths.add(file.absolutePath) + } catch (e: Exception) { + e.printStackTrace() + // If download fails, we don't add to localPaths, + // UI will fall back to imageUrls + } + } + + val updatedSpecs = specs.copy(localImagePaths = localPaths) + saveSpecs(context, updatedSpecs) + updatedSpecs + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt b/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt index 5c48d0262..fa2a4b3d4 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt @@ -1,6 +1,13 @@ package com.sameerasw.essentials.utils import android.content.Context +import android.os.Build +import android.os.IBinder +import android.os.PersistableBundle +import org.lsposed.hiddenapibypass.HiddenApiBypass +import rikka.shizuku.Shizuku +import rikka.shizuku.ShizukuBinderWrapper +import rikka.shizuku.SystemServiceHelper object FreezeManager { private const val TAG = "FreezeManager" @@ -13,23 +20,37 @@ object FreezeManager { private const val COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4 /** - * Freeze an application using Shizuku. - * Sets state to COMPONENT_ENABLED_STATE_DISABLED_USER (3). + * Freeze an application using Shizuku or Root. + * Uses either 'pm disable-user' or 'pm suspend' based on configuration. */ fun freezeApp(context: Context, packageName: String): Boolean { - return setApplicationEnabledSetting( - context, - packageName, - COMPONENT_ENABLED_STATE_DISABLED_USER - ) + val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) + val mode = prefs.getInt("freeze_mode", 0) // 0: FREEZE, 1: SUSPEND + + return if (mode == 1) { + suspendApp(context, packageName) + } else { + setApplicationEnabledSetting( + context, + packageName, + COMPONENT_ENABLED_STATE_DISABLED_USER + ) + } } /** - * Unfreeze an application using Shizuku. - * Sets state to COMPONENT_ENABLED_STATE_ENABLED (1). + * Unfreeze an application using Shizuku or Root. + * Uses either 'pm enable' or 'pm unsuspend' based on configuration. */ fun unfreezeApp(context: Context, packageName: String): Boolean { - return setApplicationEnabledSetting(context, packageName, COMPONENT_ENABLED_STATE_ENABLED) + val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) + val mode = prefs.getInt("freeze_mode", 0) + + return if (mode == 1) { + unsuspendApp(context, packageName) + } else { + setApplicationEnabledSetting(context, packageName, COMPONENT_ENABLED_STATE_ENABLED) + } } /** @@ -45,7 +66,10 @@ object FreezeManager { val gson = com.google.gson.Gson() try { val apps: List = - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() val excludedSet: Set = if (excludedJson != null) { gson.fromJson(excludedJson, Array::class.java).toSet() } else emptySet() @@ -144,7 +168,10 @@ object FreezeManager { val gson = com.google.gson.Gson() try { val apps: List = - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() apps.forEach { app -> freezeApp(context, app.packageName) } @@ -166,7 +193,10 @@ object FreezeManager { val gson = com.google.gson.Gson() try { val apps: List = - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() val excludedSet: Set = if (excludedJson != null) { gson.fromJson(excludedJson, Array::class.java).toSet() } else emptySet() @@ -192,7 +222,10 @@ object FreezeManager { val gson = com.google.gson.Gson() try { val apps: List = - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() apps.forEach { app -> unfreezeApp(context, app.packageName) } @@ -203,25 +236,154 @@ object FreezeManager { } /** - * Check if an application is currently frozen/disabled. + * Check if an application is currently frozen/disabled/suspended. */ fun isAppFrozen(context: Context, packageName: String): Boolean { return try { val state = context.packageManager.getApplicationEnabledSetting(packageName) - state == COMPONENT_ENABLED_STATE_DISABLED_USER || state == COMPONENT_ENABLED_STATE_DISABLED + val isSuspended = context.packageManager.isPackageSuspended(packageName) + state == COMPONENT_ENABLED_STATE_DISABLED_USER || state == COMPONENT_ENABLED_STATE_DISABLED || isSuspended } catch (e: Exception) { false } } + private fun suspendApp(context: Context, packageName: String): Boolean { + if (ShizukuUtils.isShizukuAvailable() && ShizukuUtils.hasPermission()) { + if (setAppSuspendedWithShizuku(packageName, true)) return true + } + + if (!ShellUtils.hasPermission(context)) return false + return try { + ShellUtils.runCommand(context, "pm suspend --user 0 $packageName") + true + } catch (e: Exception) { + e.printStackTrace() + false + } + } + + private fun unsuspendApp(context: Context, packageName: String): Boolean { + if (ShizukuUtils.isShizukuAvailable() && ShizukuUtils.hasPermission()) { + if (setAppSuspendedWithShizuku(packageName, false)) return true + } + + if (!ShellUtils.hasPermission(context)) return false + return try { + ShellUtils.runCommand(context, "pm unsuspend --user 0 $packageName") + true + } catch (e: Exception) { + e.printStackTrace() + false + } + } + + private fun setAppSuspendedWithShizuku(packageName: String, suspended: Boolean): Boolean { + return try { + if (suspended) forceStopAppWithShizuku(packageName) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + setAppRestrictedWithShizuku(packageName, suspended) + } + + val pm = getService("package", "android.content.pm.IPackageManager\$Stub") ?: return false + val callerPackage = getSuspenderPackage() + val userId = getUserId() + + val dialogInfo = if (suspended) { + val builderClass = Class.forName("android.content.pm.SuspendDialogInfo\$Builder") + val builder = HiddenApiBypass.newInstance(builderClass) + HiddenApiBypass.invoke(builderClass, builder, "setNeutralButtonAction", 1 /*BUTTON_ACTION_UNSUSPEND*/) + HiddenApiBypass.invoke(builderClass, builder, "build") + } else null + + fun callSetPackagesSuspended(version: Int): Array<*>? { + return try { + when (version) { + 0 -> HiddenApiBypass.invoke( + pm.javaClass, pm, "setPackagesSuspendedAsUser", + arrayOf(packageName), suspended, null, null, dialogInfo, 0, callerPackage, userId, userId + ) as? Array<*> + 1 -> HiddenApiBypass.invoke( + pm.javaClass, pm, "setPackagesSuspendedAsUser", + arrayOf(packageName), suspended, null, null, dialogInfo, callerPackage, userId + ) as? Array<*> + 2 -> HiddenApiBypass.invoke( + pm.javaClass, pm, "setPackagesSuspendedAsUser", + arrayOf(packageName), suspended, null, null, null, callerPackage, userId + ) as? Array<*> + else -> pm.javaClass.getMethod("setPackagesSuspendedAsUser", Array::class.java, Boolean::class.javaPrimitiveType, Int::class.javaPrimitiveType) + .invoke(pm, arrayOf(packageName), suspended, userId) as? Array<*> + } + } catch (_: Exception) { null } + } + + val result = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> callSetPackagesSuspended(0) ?: callSetPackagesSuspended(1) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> callSetPackagesSuspended(1) ?: callSetPackagesSuspended(2) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> callSetPackagesSuspended(2) + else -> callSetPackagesSuspended(3) + } + + result?.isEmpty() ?: false + } catch (e: Exception) { + e.printStackTrace() + false + } + } + + private fun forceStopAppWithShizuku(packageName: String) { + val am = getService(Context.ACTIVITY_SERVICE, "android.app.IActivityManager\$Stub") ?: return + try { + HiddenApiBypass.invoke(am.javaClass, am, "forceStopPackage", packageName, getUserId()) + } catch (e: Exception) { e.printStackTrace() } + } + + private fun setAppRestrictedWithShizuku(packageName: String, restricted: Boolean) { + val appops = getService(Context.APP_OPS_SERVICE, "com.android.internal.app.IAppOpsService\$Stub") ?: return + try { + val appOpsManagerClass = Class.forName("android.app.AppOpsManager") + val op = HiddenApiBypass.invoke(appOpsManagerClass, null, "strOpToOp", "android:run_any_in_background") as Int + val uid = getPackageUid(packageName) + if (uid != -1) { + val mode = if (restricted) 1 /*MODE_IGNORED*/ else 0 /*MODE_ALLOWED*/ + HiddenApiBypass.invoke(appops.javaClass, appops, "setMode", op, uid, packageName, mode) + } + } catch (e: Exception) { e.printStackTrace() } + } + + private fun getPackageUid(packageName: String): Int { + val pm = getService("package", "android.content.pm.IPackageManager\$Stub") ?: return -1 + return try { + HiddenApiBypass.invoke(pm.javaClass, pm, "getPackageUid", packageName, 0, 0) as Int + } catch (e: Exception) { + e.printStackTrace() + -1 + } + } + + private fun getService(serviceName: String, stubClassName: String): Any? { + return try { + val binder = SystemServiceHelper.getSystemService(serviceName) ?: return null + val stubClass = Class.forName(stubClassName) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + HiddenApiBypass.invoke(stubClass, null, "asInterface", ShizukuBinderWrapper(binder)) + } else { + stubClass.getMethod("asInterface", IBinder::class.java).invoke(null, ShizukuBinderWrapper(binder)) + } + } catch (_: Exception) { null } + } + + private fun getSuspenderPackage(): String = + if (Shizuku.getUid() == 0) "com.sameerasw.essentials" else "com.android.shell" + + private fun getUserId(): Int = android.os.Process.myUserHandle().hashCode() + private fun setApplicationEnabledSetting( context: Context, packageName: String, newState: Int ): Boolean { - if (!ShellUtils.hasPermission(context)) { - return false - } + if (!ShellUtils.hasPermission(context)) return false val cmd = when (newState) { COMPONENT_ENABLED_STATE_DISABLED_USER -> "pm disable-user --user 0 $packageName" diff --git a/app/src/main/java/com/sameerasw/essentials/utils/GSMArenaService.kt b/app/src/main/java/com/sameerasw/essentials/utils/GSMArenaService.kt index f3d3ae3e2..75db61b10 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/GSMArenaService.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/GSMArenaService.kt @@ -30,12 +30,23 @@ object GSMArenaService { val formattedQuery = query.replace(" ", "+") val searchUrl = "$BASE_URL/results.php3?sQuickSearch=yes&sName=$formattedQuery" - val searchDoc: Document = Jsoup.connect(searchUrl) + var searchDoc: Document = Jsoup.connect(searchUrl) .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") .timeout(30000) .get() - val results = searchDoc.select(".makers li") + var results = searchDoc.select(".makers li") + + // Fallback for model numbers (SM-G990B etc) which often don't show up in quick search + if (results.isEmpty()) { + val freeSearchUrl = "$BASE_URL/results.php3?sFreeSearch=yes&sFreeText=$formattedQuery" + searchDoc = Jsoup.connect(freeSearchUrl) + .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") + .timeout(30000) + .get() + results = searchDoc.select(".makers li") + } + if (results.isEmpty()) return null val bestMatchingElement = results.firstOrNull { element -> @@ -138,7 +149,7 @@ object GSMArenaService { val prefName = preferredName.lowercase() val prefModel = preferredModel.lowercase() - val variants = listOf("pro", "max", "plus", "xl", "ultra", "fold", "flip", "power", "neo", "gt", "lite", "ace", "prime", "edge") + val variants = listOf("pro", "max", "plus", "xl", "ultra", "fold", "flip", "power", "neo", "gt", "lite", "ace", "prime", "edge", "fe") for (variant in variants) { if (found.contains(variant) && !prefName.contains(variant) && !prefModel.contains(variant)) { return false diff --git a/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt index d0e67d494..c88f41dc0 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt @@ -77,4 +77,28 @@ object ShizukuUtils { false } } + + fun getSystemBinder(name: String): IBinder? { + if (!hasPermission() || !isBinderAlive) return null + + val service = moe.shizuku.server.IShizukuService.Stub.asInterface(binder) + return try { + // Try known method names for Shizuku v13 + val method = service.javaClass.methods.find { it.name == "getSystemBinder" || it.name == "getService" } + if (method != null) { + if (method.parameterCount == 1) { + method.invoke(service, name) as? IBinder + } else if (method.parameterCount == 2) { + method.invoke(service, name, null) as? IBinder + } else { + null + } + } else { + null + } + } catch (e: Exception) { + e.printStackTrace() + null + } + } } diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index 548989505..835f9b88d 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -2,6 +2,7 @@ package com.sameerasw.essentials.viewmodels import android.Manifest import android.app.Activity +import com.sameerasw.essentials.domain.model.DnsPreset import android.app.ActivityManager import android.app.admin.DevicePolicyManager import android.content.BroadcastReceiver @@ -118,6 +119,7 @@ class MainViewModel : ViewModel() { val isAutoAccessibilityEnabled = mutableStateOf(false) val isNotificationGlanceSameAsLightingEnabled = mutableStateOf(true) val isOnboardingCompleted = mutableStateOf(true) // Default to true so it doesn't flash on first check if not loaded + val dnsPresets = mutableStateListOf() data class CalendarAccount( @@ -154,6 +156,7 @@ class MainViewModel : ViewModel() { val isFreezePickedAppsLoading = mutableStateOf(false) val freezeAutoExcludedApps = mutableStateOf>(emptySet()) val isFreezeDontFreezeActiveAppsEnabled = mutableStateOf(false) + val freezeMode = mutableIntStateOf(0) // Search state val searchQuery = mutableStateOf("") @@ -199,6 +202,7 @@ class MainViewModel : ViewModel() { val userDictionaryWords = mutableStateOf>(emptyMap()) val isUserDictionarySheetVisible = mutableStateOf(false) val isLongPressSymbolsEnabled = mutableStateOf(false) + val isAccentedCharactersEnabled = mutableStateOf(false) // AirSync Bridge val isAirSyncConnectionEnabled = mutableStateOf(false) @@ -296,6 +300,10 @@ class MainViewModel : ViewModel() { freezeAutoExcludedApps.value = settingsRepository.getFreezeAutoExcludedApps() } + SettingsRepository.KEY_FREEZE_MODE -> { + freezeMode.intValue = settingsRepository.getFreezeMode() + } + SettingsRepository.KEY_USE_ROOT -> isRootEnabled.value = settingsRepository.getBoolean(key) @@ -345,6 +353,9 @@ class MainViewModel : ViewModel() { SettingsRepository.KEY_KEYBOARD_LONG_PRESS_SYMBOLS -> isLongPressSymbolsEnabled.value = settingsRepository.getBoolean(key, false) + SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS -> isAccentedCharactersEnabled.value = + settingsRepository.getBoolean(key, false) + SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED -> isAirSyncConnectionEnabled.value = settingsRepository.getBoolean(key) @@ -425,6 +436,11 @@ class MainViewModel : ViewModel() { SettingsRepository.KEY_USE_BLUR -> { appContext?.let { updateBlurState(it) } } + + SettingsRepository.KEY_PRIVATE_DNS_PRESETS -> { + dnsPresets.clear() + dnsPresets.addAll(settingsRepository.getPrivateDnsPresets()) + } } } } @@ -755,6 +771,8 @@ class MainViewModel : ViewModel() { settingsRepository.getBoolean(SettingsRepository.KEY_USER_DICTIONARY_ENABLED, false) isLongPressSymbolsEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_KEYBOARD_LONG_PRESS_SYMBOLS, false) + isAccentedCharactersEnabled.value = + settingsRepository.getBoolean(SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS, false) isAirSyncConnectionEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED) @@ -785,6 +803,7 @@ class MainViewModel : ViewModel() { settingsRepository.getBoolean(SettingsRepository.KEY_AUTO_UPDATE_ENABLED, true) isUpdateNotificationEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_UPDATE_NOTIFICATION_ENABLED, true) + freezeMode.intValue = settingsRepository.getFreezeMode() lastUpdateCheckTime = settingsRepository.getLong(SettingsRepository.KEY_LAST_UPDATE_CHECK_TIME) isAppLockEnabled.value = @@ -798,6 +817,10 @@ class MainViewModel : ViewModel() { freezeAutoExcludedApps.value = settingsRepository.getFreezeAutoExcludedApps() isDeveloperModeEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_DEVELOPER_MODE_ENABLED) + + dnsPresets.clear() + dnsPresets.addAll(settingsRepository.getPrivateDnsPresets()) + isPreReleaseCheckEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_CHECK_PRE_RELEASES_ENABLED) pinnedFeatureKeys.value = settingsRepository.getPinnedFeatures() @@ -915,6 +938,11 @@ class MainViewModel : ViewModel() { isLongPressSymbolsEnabled.value = enabled settingsRepository.putBoolean(SettingsRepository.KEY_KEYBOARD_LONG_PRESS_SYMBOLS, enabled) } + + fun setAccentedCharactersEnabled(enabled: Boolean, context: Context) { + isAccentedCharactersEnabled.value = enabled + settingsRepository.setAccentedCharactersEnabled(enabled) + } fun loadUserDictionaryWords(context: Context) { val ims = context.applicationContext as? com.sameerasw.essentials.ime.EssentialsInputMethodService @@ -2077,6 +2105,16 @@ class MainViewModel : ViewModel() { } } + fun anyAppsCurrentlyFrozen(context: Context): Boolean { + val picked = freezePickedApps.value + return picked.any { com.sameerasw.essentials.utils.FreezeManager.isAppFrozen(context, it.packageName) } + } + + fun setFreezeMode(mode: Int, context: Context) { + freezeMode.intValue = mode + settingsRepository.putInt(SettingsRepository.KEY_FREEZE_MODE, mode) + } + fun loadSnoozeChannels(context: Context) { val discovered = settingsRepository.loadSnoozeDiscoveredChannels() val blocked = settingsRepository.loadSnoozeBlockedChannels() @@ -2326,4 +2364,20 @@ class MainViewModel : ViewModel() { // Reset tab to ESSENTIALS setDefaultTab(com.sameerasw.essentials.domain.DIYTabs.ESSENTIALS, context) } + + fun resetDnsPresets() { + settingsRepository.resetPrivateDnsPresets() + } + + fun addDnsPreset(name: String, hostname: String) { + val current = settingsRepository.getPrivateDnsPresets().toMutableList() + current.add(DnsPreset(name = name, hostname = hostname)) + settingsRepository.savePrivateDnsPresets(current) + } + + fun removeDnsPreset(preset: DnsPreset) { + val current = settingsRepository.getPrivateDnsPresets().toMutableList() + current.removeAll { it.id == preset.id } + settingsRepository.savePrivateDnsPresets(current) + } } diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 2a5a04f32..0ad213730 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,49 +1,58 @@ - - - + android:viewportWidth="192" + android:viewportHeight="192"> + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_monochrome.xml b/app/src/main/res/drawable/ic_launcher_monochrome.xml index 2a5a04f32..1cd199948 100644 --- a/app/src/main/res/drawable/ic_launcher_monochrome.xml +++ b/app/src/main/res/drawable/ic_launcher_monochrome.xml @@ -1,49 +1,58 @@ - - - + android:viewportWidth="192" + android:viewportHeight="192"> + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/rounded_nest_clock_farsight_analog_24.xml b/app/src/main/res/drawable/rounded_nest_clock_farsight_analog_24.xml index ca55d0a57..e0e417874 100644 --- a/app/src/main/res/drawable/rounded_nest_clock_farsight_analog_24.xml +++ b/app/src/main/res/drawable/rounded_nest_clock_farsight_analog_24.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/rounded_timelapse_24.xml b/app/src/main/res/drawable/rounded_timelapse_24.xml new file mode 100644 index 000000000..b965e5921 --- /dev/null +++ b/app/src/main/res/drawable/rounded_timelapse_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 04091bd10..1084c2408 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 04091bd10..1084c2408 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp index f2874b5e4..71cd7d2c7 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 18eec59d7..78ed2027f 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp index 48acbd681..41394c577 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 0a0da75d3..997db46f6 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 4413e8124..be82ccf8c 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index 3d814ddb8..58f8e3e64 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index b03744e76..3c3c23b87 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 5fbbcffb6..3358655ce 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index dd7c59871..2a3399e4d 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index f9eec8490..2a83a1e35 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-ach/strings.xml b/app/src/main/res/values-ach/strings.xml index c301e5d43..35e37fd39 100644 --- a/app/src/main/res/values-ach/strings.xml +++ b/app/src/main/res/values-ach/strings.xml @@ -137,6 +137,10 @@ Wel ma ki tiyo kwede Mitte me ngeyo app mene ma tye i nyim me gengo juku gi Mitte me neno jami ma tye ka tuko ki ngec ma tye ka tic me gengo juku gi + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Nyut keken ka gin ma ki neno ni tye ka tic Wek lok ma ki cwalo ma pe ki lok @@ -204,7 +208,12 @@ I Pe tye Custom Private DNS - DNS ma ki keto con + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Nying latic AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Yabo jami tic Charger ki kubu Charger ki kwanyo woko + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Charge Gin ma ki neno i cim Yeny @@ -661,6 +677,8 @@ Yab mac ma ki lwongo ni Flashlight Neko mac ma ki lwongo ni Flashlight Lok mac ma ki lwongo ni Flashlight + Turn On Low Power Mode + Turn Off Low Power Mode Karatac me cel ma nen maber Tic man mito Shizuku onyo Root me loko rangi pa cal ma ki keto i cim. Yer Trigger @@ -801,6 +819,8 @@ Kare ducu lok macol Wi lok macol Lok macon me Clipboard + Long press for symbols + Accented characters lis lakwany diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index 343760f27..3967e38bc 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -137,6 +137,10 @@ Gebruik Statistiek Vereis om te bespeur watter programme tans op die voorgrond is om te verhoed dat hulle vries Vereis om speelmedia en aktiewe kennisgewings op te spoor om te verhoed dat dit vries + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Wys net wanneer die skerm af is Slaan stil kennisgewings oor @@ -204,7 +208,12 @@ Aan Af Pasgemaakte privaat DNS - Algemene DNS-voorinstellings + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Verskaffer gasheernaam AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Toestel ontsluit Laaier gekoppel Laaier ontkoppel + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Laai Skerm aan Vibreer @@ -661,6 +677,8 @@ Skakel flitslig aan Skakel flitslig af Wissel flitslig + Turn On Low Power Mode + Turn Off Low Power Mode Dowwe muurpapier Hierdie aksie vereis Shizuku of Root om stelselpapierverduistering aan te pas. Kies Sneller @@ -801,6 +819,8 @@ Altyd donker tema Pikswart tema Knipbord Geskiedenis + Long press for symbols + Accented characters lys plukker diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 8c2b69e96..d57b8d00a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -137,6 +137,10 @@ إحصائيات الاستخدام مطلوب لاكتشاف التطبيقات الموجودة حاليًا في المقدمة لتجنب تجميدها مطلوب لاكتشاف تشغيل الوسائط والإشعارات النشطة لتجنب تجميدها + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. تظهر فقط عندما تكون الشاشة مغلقة تخطي الإشعارات الصامتة @@ -204,7 +208,12 @@ على عن DNS الخاص المخصص - إعدادات DNS الشائعة + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. اسم مضيف الموفر AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ فتح الجهاز الشاحن متصل تم فصل الشاحن + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on الشحن تشغيل الشاشة تذبذب @@ -661,6 +677,8 @@ قم بتشغيل المصباح قم بإيقاف تشغيل المصباح تبديل المصباح + Turn On Low Power Mode + Turn Off Low Power Mode خلفية خافتة يتطلب هذا الإجراء Shizuku أو Root لضبط تعتيم خلفية النظام. حدد المشغل @@ -801,6 +819,8 @@ دائما موضوع مظلم موضوع الملعب الأسود تاريخ الحافظة + Long press for symbols + Accented characters قائمة منقار diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 9ddbe6064..15e7f9f5d 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -137,6 +137,10 @@ Estadístiques d\'ús Necessari per detectar quines aplicacions es troben actualment en primer pla per evitar congelar-les Necessari per detectar contingut multimèdia en reproducció i notificacions actives per evitar congelar-los + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Mostra només quan la pantalla està apagada Omet les notificacions silencioses @@ -204,7 +208,12 @@ Encès Apagat DNS privat personalitzat - Valors predefinits de DNS comuns + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Nom d\'amfitrió del proveïdor AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Desbloqueig del dispositiu Carregador connectat Carregador desconnectat + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Carregant Pantalla activada Vibrar @@ -661,6 +677,8 @@ Enceneu la llanterna Apagueu la llanterna Commuta la llanterna + Turn On Low Power Mode + Turn Off Low Power Mode Fons de pantalla enfosquit Aquesta acció requereix Shizuku o Root per ajustar l\'atenuació del fons de pantalla del sistema. Seleccioneu Activador @@ -801,6 +819,8 @@ Tema sempre fosc Tema negre del to Historial del porta-retalls + Long press for symbols + Accented characters llista selector diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 3655f4d4b..48c246b20 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -137,6 +137,10 @@ Statistiky použití Vyžadováno ke zjištění, které aplikace jsou aktuálně v popředí, aby nedošlo k jejich zamrznutí Vyžadováno k detekci přehrávaných médií a aktivních oznámení, aby nedošlo k jejich zamrznutí + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Zobrazit pouze při vypnuté obrazovce Přeskočit tichá oznámení @@ -204,7 +208,12 @@ Na Vypnuto Vlastní privátní DNS - Společné předvolby DNS + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Název hostitele poskytovatele AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Odemknutí zařízení Nabíječka připojena Nabíječka odpojena + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Nabíjení Obrazovka zapnuta Vibrovat @@ -661,6 +677,8 @@ Zapněte svítilnu Vypnout svítilnu Přepnout svítilnu + Turn On Low Power Mode + Turn Off Low Power Mode Ztlumit tapety Tato akce vyžaduje, aby Shizuku nebo Root upravili stmívání tapety systému. Vyberte možnost Spustit @@ -801,6 +819,8 @@ Vždy temné téma Černý motiv Historie schránky + Long press for symbols + Accented characters seznam sběrač diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 239f78634..b42e6b386 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -137,6 +137,10 @@ Brugsstatistik Nødvendig for at registrere, hvilke apps der i øjeblikket er i forgrunden for at undgå at fryse dem Påkrævet for at registrere afspillende medier og aktive meddelelser for at undgå at fryse dem + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Vises kun, når skærmen er slukket Spring over lydløse meddelelser @@ -204,7 +208,12 @@ Slukket Brugerdefineret privat DNS - Almindelige DNS-forudindstillinger + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Udbyderens værtsnavn AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Lås enheden op Oplader tilsluttet Oplader afbrudt + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Opladning Skærm tændt Vibrere @@ -661,6 +677,8 @@ Tænd lommelygte Sluk lommelygten Skift lommelygte + Turn On Low Power Mode + Turn Off Low Power Mode Dim tapet Denne handling kræver Shizuku eller Root for at justere systemets tapetdæmpning. Vælg Trigger @@ -801,6 +819,8 @@ Altid mørkt tema Kulsort tema Udklipsholder historie + Long press for symbols + Accented characters liste plukker diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index adac914a7..9cbbd5589 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -137,6 +137,10 @@ Nutzungsstatistiken Benötigt, um zu erkennen, welche Apps derzeit im Vordergrund sind, um deren Einfrieren zu vermeiden Benötigt, um wiedergegebene Medien und aktive Benachrichtigungen zu erkennen, um deren Einfrieren zu vermeiden + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Nur bei ausgeschaltetem Bildschirm anzeigen Stille Benachrichtigungen überspringen @@ -204,7 +208,12 @@ An Aus Benutzerdefiniertes privates DNS - Gängige DNS-Vorlagen + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Hostname des Anbieters AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Gerät entsperren Ladegerät verbunden Ladegerät getrennt + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Lädt auf Display an Vibrieren @@ -661,6 +677,8 @@ Taschenlampe aktivieren Taschenlampe deaktivieren Taschenlampe ein-/ausschalten + Turn On Low Power Mode + Turn Off Low Power Mode Hintergrund dimmen Diese Aktion erfordert Shizuku oder einen Root-Zugang um den Hintergrund zu dimmen. Auslöser auswählen @@ -801,6 +819,8 @@ Immer dunkles Design Tiefschwarzes Design Zwischenablage-Verlauf + Long press for symbols + Accented characters liste wähler diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index b9cfeca67..11d1cda6c 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -137,6 +137,10 @@ Στατιστικά χρήσης Απαιτείται για τον εντοπισμό των εφαρμογών που βρίσκονται αυτήν τη στιγμή στο προσκήνιο για την αποφυγή παγώματος Απαιτείται για την ανίχνευση πολυμέσων αναπαραγωγής και ενεργών ειδοποιήσεων για την αποφυγή παγώματος + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Εμφανίζεται μόνο όταν η οθόνη είναι απενεργοποιημένη Παράλειψη σιωπηλών ειδοποιήσεων @@ -204,7 +208,12 @@ Επί Μακριά από Προσαρμοσμένο ιδιωτικό DNS - Κοινές προεπιλογές DNS + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Όνομα κεντρικού υπολογιστή παρόχου AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Ξεκλείδωμα συσκευής Συνδέθηκε φορτιστής Ο φορτιστής αποσυνδέθηκε + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Φόρτιση Ενεργοποιημένη οθόνη Δονούμαι @@ -661,6 +677,8 @@ Ενεργοποιήστε τον φακό Απενεργοποιήστε τον φακό Εναλλαγή φακού + Turn On Low Power Mode + Turn Off Low Power Mode Θαμπή ταπετσαρία Αυτή η ενέργεια απαιτεί το Shizuku ή το Root για να προσαρμόσουν τη μείωση της φωτεινότητας της ταπετσαρίας του συστήματος. Επιλέξτε Trigger @@ -801,6 +819,8 @@ Πάντα σκοτεινό θέμα Μαύρο θέμα Ιστορικό πρόχειρου + Long press for symbols + Accented characters λίστα συλλέκτης diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 05e96cae0..f02e320b2 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -137,6 +137,10 @@ Usage Stats Required to detect which apps are currently in the foreground to avoid freezing them Required to detect playing media and active notifications to avoid freezing them + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Only show when screen off Skip silent notifications @@ -204,7 +208,12 @@ On Off Custom Private DNS - Common DNS Presets + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Provider hostname AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Device Unlock Charger Connected Charger Disconnected + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Charging Screen On Vibrate @@ -661,6 +677,8 @@ Turn On Flashlight Turn Off Flashlight Toggle Flashlight + Turn On Low Power Mode + Turn Off Low Power Mode Dim Wallpaper This action requires Shizuku or Root to adjust system wallpaper dimming. Select Trigger @@ -801,6 +819,8 @@ Always dark theme Pitch black theme Clipboard History + Long press for symbols + Accented characters list picker diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6567cf647..b864df315 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -137,6 +137,10 @@ Estadísticas de uso Requerido para detectar qué aplicaciones están actualmente en primer plano para evitar congelarlas Requerido para detectar medios en reproducción y notificaciones activas para evitar congelarlas + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Mostrar solo cuando la pantalla está apagada Saltar notificaciones silenciosas @@ -204,7 +208,12 @@ En Apagado DNS privado personalizado - Ajustes preestablecidos de DNS comunes + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Nombre de host del proveedor AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Desbloqueo del dispositivo Cargador conectado Cargador desconectado + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Cargando Pantalla encendida Vibrar @@ -661,6 +677,8 @@ Encender la linterna Apagar la linterna Alternar linterna + Turn On Low Power Mode + Turn Off Low Power Mode Fondo de pantalla oscuro Esta acción requiere que Shizuku o Root ajusten la atenuación del fondo de pantalla del sistema. Seleccionar disparador @@ -801,6 +819,8 @@ Tema siempre oscuro Tema negro Historial del portapapeles + Long press for symbols + Accented characters lista recogedor diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 353bf6951..a394d98d1 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -137,6 +137,10 @@ Käyttötilastot Pakollinen tunnistamaan, mitkä sovellukset ovat tällä hetkellä etualalla, jotta ne eivät jäädy Vaaditaan toistavan median ja aktiivisten ilmoitusten havaitsemiseksi niiden jäätymisen välttämiseksi + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Näytä vain näytön ollessa pois päältä Ohita äänettömät ilmoitukset @@ -204,7 +208,12 @@ Päällä Pois Mukautettu yksityinen DNS - Yleiset DNS-esiasetukset + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Palveluntarjoajan isäntänimi AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Laitteen lukituksen avaus Laturi kytketty Laturi irrotettu + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Lataus Näyttö päällä Värinä @@ -661,6 +677,8 @@ Laita taskulamppu päälle Sammuta taskulamppu Taskulamppu päälle/pois + Turn On Low Power Mode + Turn Off Low Power Mode Himmeä taustakuva Tämä toiminto vaatii Shizukun tai Rootin säätämään järjestelmän taustakuvan himmennystä. Valitse Trigger @@ -801,6 +819,8 @@ Aina tumma teema Pisimusta teema Leikepöydän historia + Long press for symbols + Accented characters lista poimija diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0e19dee89..bec9692c5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -137,6 +137,10 @@ Statistiques d\'utilisation Requises pour détecter quelles applis sont actuellement en avant-plan pour ne pas les geler Requis pour détecter le média joué et les notifications actives pour ne pas les geler + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Afficher seulement quand l\'écran est éteint Ignorer les notifications silencieuses @@ -204,7 +208,12 @@ Activer Désactiver DNS Privé personnalisé - Préréglages DNS communs + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Nom d\'hôte du fournisseur AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Déverrouillage de l\'appareil Câble branché Câble débranché + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on En charge Écran allumé Vibreur @@ -661,6 +677,8 @@ Allumer la lampe-torche Éteindre la lampe-torche Allumer/éteindre la lampe-torche + Turn On Low Power Mode + Turn Off Low Power Mode Fond d\'écran assombri Cette action nécessite Shizuku ou un accès racine (root) pour ajuster l\'assombrissement du fond d\'écran. Déclencheur @@ -801,6 +819,8 @@ Toujours le mode sombre Thème noir absolu Historique du presse-papiers + Long press for symbols + Accented characters liste sélecteur diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index d941c2514..94a250e3b 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -137,6 +137,10 @@ Usage Stats Required to detect which apps are currently in the foreground to avoid freezing them Required to detect playing media and active notifications to avoid freezing them + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Only show when screen off Skip silent notifications @@ -204,7 +208,12 @@ On Off Custom Private DNS - Common DNS Presets + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Provider hostname AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Device Unlock Charger Connected Charger Disconnected + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Charging Screen On Vibrate @@ -661,6 +677,8 @@ Turn On Flashlight Turn Off Flashlight Toggle Flashlight + Turn On Low Power Mode + Turn Off Low Power Mode Dim Wallpaper This action requires Shizuku or Root to adjust system wallpaper dimming. Select Trigger @@ -801,6 +819,8 @@ Always dark theme Pitch black theme Clipboard History + Long press for symbols + Accented characters list picker diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 8921f49ba..820762a0d 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -137,6 +137,10 @@ Használati statisztika Szükséges annak észleléséhez, hogy mely alkalmazások vannak jelenleg az előtérben, hogy elkerüljék azok lefagyását Szükséges a lejátszott média és az aktív értesítések észleléséhez, hogy elkerülje azok lefagyását + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Csak kikapcsolt képernyő esetén jelenik meg A néma értesítések kihagyása @@ -204,7 +208,12 @@ On Le Egyéni privát DNS - Általános DNS-előbeállítások + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Szolgáltató gazdagépneve AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Eszköz feloldása Töltő csatlakoztatva Töltő lekapcsolva + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Töltés Képernyő bekapcsolva Rezeg @@ -661,6 +677,8 @@ Kapcsolja be a zseblámpát Kapcsolja ki a zseblámpát Zseblámpa váltása + Turn On Low Power Mode + Turn Off Low Power Mode Dim Háttérkép Ehhez a művelethez Shizuku vagy Root szükséges a rendszer háttérképének elsötétítésének beállításához. Válassza a Trigger lehetőséget @@ -801,6 +819,8 @@ Mindig sötét téma Koromsötét téma Vágólap előzmények + Long press for symbols + Accented characters lista válogató diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index fa6141307..851199e65 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -137,6 +137,10 @@ Statistiche di utilizzo Necessario per rilevare quali app sono attualmente in primo piano per evitare di congelarle Necessario per rilevare i contenuti multimediali in riproduzione e le notifiche attive per evitare di congelarli + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Mostra solo quando lo schermo è spento Ignora le notifiche silenziose @@ -204,7 +208,12 @@ Attivo Disattivo DNS privato personalizzato - Preimpostazioni DNS comuni + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Nome host del provider DNS di AdGuard dns.adguard.com @@ -653,6 +662,13 @@ Sblocco del dispositivo Caricabatterie collegato Caricabatterie disconnesso + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on In carica Schermo acceso Vibrare @@ -661,6 +677,8 @@ Accendi la torcia Spegni la torcia Attiva/disattiva la torcia + Turn On Low Power Mode + Turn Off Low Power Mode Sfondo scuro Questa azione richiede che Shizuku o Root regolino l\'oscuramento dello sfondo del sistema. Seleziona Attiva @@ -801,6 +819,8 @@ Tema sempre oscuro Tema nero come la pece Cronologia degli appunti + Long press for symbols + Accented characters lista raccoglitore diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c7b6dc24a..6abca6d91 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -137,6 +137,10 @@ 使用状況へのアクセス アプリをフリーズしないために、フォアグラウンドでアプリが動作しているかを確認するのに必要です 再生中のメディアやアクティブな通知を検知してフリーズを回避するのに必要です + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. 画面がオフのときにのみ表示 マナーモードを無視 @@ -204,7 +208,12 @@ オン オフ カスタムプライベートDNS - 一般的なDNSプリセット + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. プロバイダーホスト名 AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ ロック解除 充電器を接続 充電器を切断 + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on 充電中 画面点灯中 バイブレーション @@ -661,6 +677,8 @@ フラッシュライトを点灯 フラッシュライトを消灯 フラッシュライトを切り替える + Turn On Low Power Mode + Turn Off Low Power Mode 暗い壁紙 このアクションでは、壁紙を暗くするために、ShizukuまたはRootが必要です。 トリガーを選択 @@ -801,6 +819,8 @@ 常にダークテーマ ブラックテーマにする クリップボード履歴 + Long press for symbols + Accented characters リスト ピッカー diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2adbf01c9..cd02e8208 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -137,6 +137,10 @@ 사용 통계 정지를 방지하기 위해 현재 포그라운드에 있는 앱을 감지하는 데 필요합니다. 정지를 방지하기 위해 재생 중인 미디어 및 활성 알림을 감지하는 데 필요합니다. + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. 화면이 꺼진 경우에만 표시 자동 알림 건너뛰기 @@ -204,7 +208,12 @@ ~에 끄다 맞춤형 프라이빗 DNS - 일반적인 DNS 사전 설정 + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. 공급자 호스트 이름 AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ 장치 잠금 해제 충전기 연결됨 충전기 연결 끊김 + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on 충전 중 화면 켜짐 떨리다 @@ -661,6 +677,8 @@ 손전등 켜기 손전등 끄기 손전등 전환 + Turn On Low Power Mode + Turn Off Low Power Mode 희미한 벽지 이 작업을 수행하려면 시스템 배경화면 밝기 조절을 조정하기 위해 Shizuku 또는 Root가 필요합니다. 트리거 선택 @@ -801,6 +819,8 @@ 항상 어두운 테마 피치 블랙 테마 클립보드 기록 + Long press for symbols + Accented characters 목록 소매치기 diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 7fc62e5bf..d5531b7bf 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -137,6 +137,10 @@ Gebruiksstatistieken Vereist om te detecteren welke apps momenteel in gebruik zijn om te voorkomen dat ze worden bevroren. Vereist om afspelende media en actieve meldingen te detecteren om te voorkomen dat ze worden bevroren. + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Alleen laten zien als het scherm uit is Stille meldingen overslaan @@ -204,7 +208,12 @@ Aan Uit Aangepaste Privé-DNS - Aangepaste DNS-voorinstellingen + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Hostnaam van provider AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Apparaat ontgrendelen Lader verbonden Lader losgekoppeld + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Opladen Scherm aan Trillen @@ -661,6 +677,8 @@ Zaklamp aanzetten Zaklamp uitzetten Zaklamp in-/uitschakelen + Turn On Low Power Mode + Turn Off Low Power Mode Achtergrond dimmen Deze actie vereist Shizuku of Root om de achtergrond te dimmen. Selecteer trigger @@ -801,6 +819,8 @@ Altijd donker thema Pikzwart thema Klembordgeschiedenis + Long press for symbols + Accented characters lijst kiezer diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 36eaa4e6d..77c89628d 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -137,6 +137,10 @@ Bruksstatistikk Nødvendig for å oppdage hvilke apper som er i forgrunnen for å unngå å fryse dem Nødvendig for å oppdage avspillingsmedier og aktive varsler for å unngå å fryse dem + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Vises bare når skjermen er av Hopp over stille varsler @@ -204,7 +208,12 @@ Av Egendefinert privat DNS - Vanlige DNS-forhåndsinnstillinger + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Leverandørens vertsnavn AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Lås opp enheten Lader tilkoblet Lader frakoblet + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Lader Skjerm på Vibrere @@ -661,6 +677,8 @@ Slå på lommelykt Slå av lommelykten Slå på lommelykt + Turn On Low Power Mode + Turn Off Low Power Mode Dim bakgrunn Denne handlingen krever Shizuku eller Root for å justere systembakgrunnsdimming. Velg Trigger @@ -801,6 +819,8 @@ Alltid mørkt tema Pitch black-tema Utklippstavlehistorikk + Long press for symbols + Accented characters liste plukker diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 13ee7a52f..fae3349f4 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -137,6 +137,10 @@ Statystyki użytkowania Wymagane do wykrycia, które aplikacje są aktualnie na pierwszym planie, aby uniknąć ich zablokowania Wymagane do wykrywania odtwarzanych multimediów i aktywnych powiadomień, aby uniknąć ich zablokowania + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Pokazuj tylko przy wyłączonym ekranie Pomiń ciche powiadomienia @@ -204,7 +208,12 @@ NA Wyłączony Niestandardowy prywatny DNS - Typowe ustawienia wstępne DNS + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Nazwa hosta dostawcy AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Odblokuj urządzenie Ładowarka podłączona Ładowarka odłączona + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Ładowanie Ekran włączony Wibrować @@ -661,6 +677,8 @@ Włącz latarkę Wyłącz latarkę Przełącz latarkę + Turn On Low Power Mode + Turn Off Low Power Mode Ciemna tapeta Ta czynność wymaga Shizuku lub Roota, aby dostosować przyciemnienie tapety systemowej. Wybierz opcję Wyzwalacz @@ -801,6 +819,8 @@ Zawsze ciemny motyw Czarny motyw Historia schowka + Long press for symbols + Accented characters lista zbieracz diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 0e7cd4d20..f34f09b0e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -3,1132 +3,1152 @@ Essentials BETA Essentials Accessibility Service\n\nThis service is required for the following advanced features:\n\n• Physical Button Remapping:\nDetects volume button presses even when the screen is off to trigger actions like the Flashlight.\n\n• Per-App Settings:\nMonitors the currently active app to apply specific profiles for Dynamic Night Light, Notification Lighting Colors, and App Lock.\n\n• Screen Control:\nAllows the app to lock the screen (e.g. via Double Tap or Widgets) and detect screen state changes.\n\n• Security:\nPrevents unauthorized changes by detecting window content when the device is locked.\n\nNo input text or sensitive user data is collected or transmitted. - Congelamento de aplicativos - Desative aplicativos que raramente são usados - Congelamento de aplicativos - Abra o congelamento de aplicativos - Aplicativo congelado - Tela vazia fora do widget - Congelamento de aplicativos - Pulso de lanterna - Confira os pré-lançamentos - Pode ser instável + App Freezing + Disable apps that are rarely used + App Freezing + Open App Freezing + Frozen App + Empty screen off widget + App Freezing + Flashlight Pulse + Check for pre-releases + Might be unstable - Segurança - Ativar bloqueio de aplicativo - Segurança de bloqueio de aplicativos - Autenticar para ativar o bloqueio de aplicativos - Autenticar para desativar o bloqueio de aplicativos - Selecione aplicativos bloqueados - Escolha quais aplicativos exigem autenticação - Proteja seus aplicativos com autenticação biométrica. Os aplicativos bloqueados exigirão autenticação ao serem iniciados. Permanecem desbloqueados até que a tela seja desligada. - Esteja ciente de que esta não é uma solução robusta, pois é apenas um aplicativo de terceiros. Se você precisar de segurança forte, considere usar o Private Space ou outros recursos semelhantes. - Outra observação: o prompt de autenticação biométrica só permite usar métodos de classe seguros FORTES. Os métodos de segurança de desbloqueio facial na classe FRACA em dispositivos como o Pixel 7 só poderão utilizar os outros métodos de autenticação STRONG disponíveis, como impressão digital ou PIN. + Security + Enable app lock + App Lock Security + Authenticate to enable app lock + Authenticate to disable app lock + Select locked apps + Choose which apps require authentication + Secure your apps with biometric authentication. Locked apps will require authentication when launching, Stays unlocked until the screen turns off. + Beware that this is not a robust solution as this is only a 3rd party application. If you need strong security, consider using Private Space or other such features. + Another note, the biometric authentication prompt only lets you use STRONG secure class methods. Face unlock security methods in WEAK class in devices such as Pixel 7 will only be able to utilize the available other STRONG auth methods such as fingerprint or pin. - Ativar remapeamento de botão - Use Shizuku ou Root ou Root - Funciona com a tela desligada (recomendado) - Shizuku não está correndo - Detectado %1$s + Enable Button Remap + Use Shizuku or Root or Root + Works with screen off (Recommended) + Shizuku is not running + Detected %1$s Status: %1$s - Abra Shizuku - Lanterna - Opções de lanterna - Ajustar o desbotamento e outras configurações - Tema preto como breu - Use fundo preto puro no modo escuro - Feedback tátil - Remapear toque longo - Tela desligada - Tela ativada - Aumentar o volume - Diminuir volume - Alternar lanterna - Reprodução/pausa de mídia - Próxima mídia - Mídia anterior - Alternar vibração - Alternar mudo - Assistente de IA - Faça uma captura de tela - Ciclo de modos de som - Curtir a música atual - Como configurações de música - Este recurso requer acesso à notificação para detectar a mídia atualmente sendo reproduzida e acionar a ação semelhante. Ative-o abaixo. - Mostrar mensagem de brinde - Mostrar sobreposição no AOD - Olhar de música ambiente - Dê uma olhada na mídia no AOD - Modo encaixado - Mantenha a sobreposição visível indefinidamente enquanto a música estiver tocando no AOD - Visão geral da notificação - Mantenha o AOD ativado enquanto as notificações estiverem pendentes - Os mesmos aplicativos da iluminação de notificação - Este recurso ativará dinamicamente o Always on Display quando uma notificação chegar de um aplicativo selecionado e o desativará quando todas as notificações correspondentes forem descartadas. Escolha aplicativos ou use a mesma seleção da iluminação de notificação. - Conceder acesso à notificação - Alternar volume de mídia - Quando a tela estiver desligada, mantenha pressionado o botão selecionado para acionar a ação atribuída. Em dispositivos Pixel, esta ação só é acionada se o AOD estiver ativado devido a limitações do sistema. - Quando a tela estiver ligada, mantenha pressionado o botão selecionado para acionar a ação atribuída. - Intensidade da lanterna - Aparecer e desaparecer gradualmente - Alternar suavemente a lanterna - Controles globais - Fade-in global da lanterna - Ajustar intensidade - Volume + - ajusta a intensidade da lanterna - Atualização ao vivo - Mostrar brilho na barra de status - Outro - Sempre desligue a lanterna - Mesmo quando a tela está ligada - Configurações + Open Shizuku + Flashlight + Flashlight options + Adjust fading and other settings + Pitch black theme + Use pure black background in dark mode + Haptic Feedback + Remap Long Press + Screen Off + Screen On + Volume Up + Volume Down + Toggle flashlight + Media play/pause + Media next + Media previous + Toggle vibrate + Toggle mute + AI assistant + Take screenshot + Cycle sound modes + Like current song + Like song settings + This feature requires notification access to detect the currently playing media and trigger the like action. Please enable it below. + Show toast message + Show overlay on AOD + Ambient music glance + Glance at media on AOD + Docked mode + Keep the overlay visible indefinitely while music is playing on AOD + Notification glance + Keep AOD on while notifications are pending + Same apps as notification lighting + This feature will dynamically enable Always on Display when a notification arrives from a selected app, and disable it once all matching notifications are dismissed. Pick apps or use the same selection as notification lighting. + Grant notification access + Toggle media volume + When the screen is off, long-press the selected button to trigger its assigned action. On Pixel devices, this action only gets triggered if the AOD is on due to system limitations. + When the screen is on, long-press the selected button to trigger its assigned action. + Flashlight Intensity + Fade in and out + Smoothly toggle flashlight + Global controls + Fade-in flashlight globally + Adjust intensity + Volume + - adjusts flashlight intensity + Live update + Show brightness in status bar + Other + Always turn off flashlight + Even while display is on + Settings - Mostrar notificação - Postar notificações - Permite que o aplicativo mostre notificações - Conceder permissão - Cafeína Ativa - Ativo - A tela está sendo mantida ativa - Ignorar a otimização da bateria - Abortar com tela desligada - Pular contagem regressiva - Comece com cafeína imediatamente. - Predefinições de tempo limite - Selecione as durações disponíveis para o bloco QS + Show Notification + Post Notifications + Allows the app to show notifications + Grant Permission + Caffeinate Active + Active + Screen is being kept awake + Ignore battery optimization + Abort with screen off + Skip countdown + Start Caffeinate immediately. + Timeout Presets + Select available durations for QS tile 5m 10m 30m - Acesso Não perturbe - Necessário para alternar entre os modos de som, vibração e mudo + Do Not Disturb access + Required to cycle between sound, vibrate and mute modes 1h - Começando em %1$ds… - %1$s restante - Notificação persistente para cafeína + Starting in %1$ds… + %1$s remaining + Persistent notification for Caffeinate - Ativar luz noturna dinâmica - Aplicativos que desligam a luz noturna - Selecione aplicativos + Enable Dynamic Night Light + Apps that toggle off night light + Select apps - Controle de aplicativos - Congelar - Descongelar - Mais opções - Congelar todos os aplicativos - Descongelar todos os aplicativos - Exportar lista de aplicativos congelados - Importar lista de aplicativos congelados - Escolha aplicativos para congelar - Escolha quais aplicativos podem ser congelados - Automação - Congelar quando bloqueado - Atraso de congelamento - Imediato + App Control + Freeze + Unfreeze + More options + Freeze all apps + Unfreeze all apps + Export frozen apps list + Import frozen apps list + Pick apps to freeze + Choose which apps can be frozen + Automation + Freeze when locked + Freeze delay + Immediate 1m 5m 15m Manual - Congelar aplicativos automaticamente - Congele aplicativos selecionados quando o dispositivo for bloqueado. Escolha um atraso para evitar o congelamento de aplicativos se você desbloquear a tela logo após desligá-la. - O congelamento de aplicativos do sistema pode ser perigoso e causar comportamento inesperado. - Ativar nas configurações - Não congele aplicativos ativos - Estatísticas de uso - Necessário para detectar quais aplicativos estão atualmente em primeiro plano para evitar congelá-los - Necessário para detectar mídia em reprodução e notificações ativas para evitar congelá-las + Auto freeze apps + Freeze selected apps when the device locks. Choose a delay to avoid freezing apps if you unlock the screen shortly after turning it off. + Freezing system apps might be dangerous and may cause unexpected behavior. + Enable in Settings + Don\'t freeze active apps + Usage Stats + Required to detect which apps are currently in the foreground to avoid freezing them + Required to detect playing media and active notifications to avoid freezing them + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. - Mostrar apenas quando a tela está desligada - Ignorar notificações silenciosas - Ignorar notificações persistentes - Pulso de lanterna - Pulso de lanterna - Somente enquanto estiver voltado para baixo - Os mesmos aplicativos da iluminação de notificação - Estilo - Ajuste de curso - Raio de canto - Espessura do traço - Ajuste de brilho - Propagação de brilho - Colocação - Posição horizontal - Posição vertical - Ajuste do indicador - Escala - Duração - Animação - Contagem de pulso - Duração do pulso - Modo de cor - Exibição ambiente - Exibição ambiente - Adequado se você não estiver usando AOD. - Acorde a tela e mostre a iluminação - Mostrar tela de bloqueio - Sem sobreposição preta + Only show when screen off + Skip silent notifications + Skip persistent notifications + Flashlight Pulse + Flashlight pulse + Only while facing down + Same apps as notification lighting + Style + Stroke adjustment + Corner radius + Stroke thickness + Glow adjustment + Glow spread + Placement + Horizontal position + Vertical position + Indicator adjustment + Scale + Duration + Animation + Pulse count + Pulse duration + Color Mode + Ambient display + Ambient display + Suitable if you are not using AOD. + Wake screen and show lighting + Show lock screen + No black overlay - Adicionar - Já adicionado - Requer Android 13+ - Desfoque da interface do usuário - Bolhas - Conteúdo Sensível - Toque para acordar + Add + Already added + Requires Android 13+ + UI Blur + Bubbles + Sensitive Content + Tap to Wake AOD - Cafeína - Modo de som - Iluminação de notificação - Luz noturna dinâmica - Segurança bloqueada - Bloqueio de aplicativo - Áudio Mono - Lanterna - Congelamento de aplicativos - Pulso de lanterna - Fique acordado - Teclado Essencial - Inglês (EUA) - Ativo - Inativo - Opções do desenvolvedor - Alterne facilmente as opções do desenvolvedor do sistema em um bloco QS. Isso pode redefinir algumas das configurações do desenvolvedor que você modificou. + Caffeinate + Sound Mode + Notification Lighting + Dynamic Night Light + Locked Security + App Lock + Mono Audio + Flashlight + App Freezing + Flashlight Pulse + Stay awake + Essentials Keyboard + English (US) + Active + Inactive + Developer Options + Toggle system Developer Options from a QS tile easily. This may reset some of the developer settings you have modified. NFC - DNS privado - Automático - Desligado - Depuração USB - Seletor de cores - Tem certeza de que\'está no Android 17? (╯°_°)╯ - Conta-gotas - Sobre - Desligado - DNS privado personalizado - Predefinições de DNS comuns - Nome do host do provedor - DNS do AdGuard + Private DNS + Auto + Off + USB Debugging + Color Picker + Are you sure you\'re on Android 17? (╯°_°)╯ + Eye Dropper + On + Off + Custom Private DNS + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. + Provider hostname + AdGuard DNS dns.adguard.com - DNS público do Google + Google Public DNS dns.google - DNS da Cloudflare + Cloudflare DNS 1dot1dot1dot1.cloudflare-dns.com - DNS Quad9 + Quad9 DNS dns.quad9.net - Navegação limpa + CleanBrowsing adult-filter-dns.cleanbrowsing.org - Carregando - Limite a 80% - Adaptativo - Não otimizado - Permissão ausente + Charging + Limit to 80% + Adaptive + Not optimized + Permission missing - Segurança bloqueada com tela - Segurança de tela bloqueada - Autenticar para ativar a segurança de bloqueio de tela - Autenticar para desativar a segurança de bloqueio de tela - ⚠️ AVISO - Este recurso não é infalível. Pode haver casos extremos em que alguém ainda consiga interagir com o bloco. \nLembre-se também de que o Android sempre permitirá uma reinicialização forçada e os Pixels sempre permitirão que o dispositivo seja desligado da tela de bloqueio também. - Certifique-se de remover o bloco do modo avião das configurações rápidas, pois isso não pode ser evitado porque não abre uma janela de diálogo. - Quando ativado, o painel Configurações rápidas será fechado imediatamente e o dispositivo será bloqueado se alguém tentar interagir com blocos da Internet enquanto o dispositivo estiver bloqueado. \n\nIsso também desativará o desbloqueio biométrico para evitar acesso não autorizado adicional. A escala da animação será reduzida para 0,1x enquanto estiver bloqueada para dificultar ainda mais a interação. + Screen locked security + Screen Locked Security + Authenticate to enable screen locked security + Authenticate to disable screen locked security + ⚠️ WARNING + This feature is not foolproof. There may be edge cases where someone still being able to interact with the tile. \nAlso keep in mind that Android will always allow to do a forced reboot and Pixels will always allow the device to be turned off from the lock screen as well. + Make sure to remove the airplane mode tile from quick settings as that is not preventable because it does not open a dialog window. + When enabled, the Quick Settings panel will be immediately closed and the device will be locked down if someone attempt to interact with Internet tiles while the device is locked. \n\nThis will also disable biometric unlock to prevent further unauthorized access. Animation scale will be reduced to 0.1x while locked to make it even harder to interact with. - Reordenar modos - Pressione e segure para alternar - Arraste para reordenar - Som - Vibrar - Silencioso + Re-order modes + Long press to toggle + Drag to reorder + Sound + Vibrate + Silent - Conectividade - Telefone e rede - Áudio e mídia - Status do sistema - Específico do OEM + Connectivity + Phone & Network + Audio & Media + System Status + OEM Specific WiFi Bluetooth - NFC / Félica + NFC / Felica VPN - Modo Avião - Ponto de acesso - Elenco - Dados móveis - Sinal de telefone - VoLTE/VoNR - Chamadas WiFi / VoWiFi - Status/sincronização da chamada + Airplane Mode + Hotspot + Cast + Mobile Data + Phone Signal + VoLTE / VoNR + WiFi Calling / VoWiFi + Call Status / Sync TTY Volume - Fone de ouvido - Viva-voz + Headset + Speakerphone DMB - Relógio - Método de entrada (IME) - Alarme - Bateria - Economia de energia - Economia de dados - Bloqueio de rotação - Localização/GPS - Sincronizar - Perfil gerenciado - Não incomodar - Privacidade e pasta segura - Status de segurança (SU) - Rato/Teclado OTG - Recursos inteligentes da Samsung - Serviços Samsung + Clock + Input Method (IME) + Alarm + Battery + Power Saving + Data Saver + Rotation Lock + Location / GPS + Sync + Managed Profile + Do Not Disturb + Privacy & Secure Folder + Security Status (SU) + OTG Mouse / Keyboard + Samsung Smart Features + Samsung Services Ethernet - Mostrar segundos no relógio - Porcentagem de bateria - Sempre - Carregando - Nunca - Câmera e microfone usam chips - Dados inteligentes - Ler estado do telefone - Necessário para detectar o tipo de rede para o recurso Smart Data - Necessário para detectar alterações no status da chamada para acionar o feedback tátil. - Visibilidade Inteligente - Wi-Fi inteligente - Ocultar dados móveis quando o WiFi estiver conectado - Ocultar dados móveis em determinados modos - Redefinir todos os ícones - Mais configurações - Observe que a implementação dessas opções pode depender do OEM e algumas podem não funcionar. + Show Seconds in Clock + Battery Percentage + Always + Charging + Never + Camera and Microphone use chips + Smart Data + Read Phone State + Required to detect network type for Smart Data feature + Required to detect call status changes to trigger haptic feedback. + Smart Visibility + Smart WiFi + Hide mobile data when WiFi is connected + Hide mobile data in certain modes + Reset All Icons + More Settings + Please note that the implementation of these options may depend on the OEM and some may not be functional at all. - Outro + Other - Segundos do relógio - Mostrar segundos no relógio da barra de status - Porcentagem de bateria - Configurar a visibilidade da porcentagem da bateria - Chips de privacidade - Mostrar indicador quando a câmera ou o microfone estão em uso - Alternar visibilidade para %1$s - Fixar nos favoritos - Liberar dos Favoritos + Clock Seconds + Show seconds in status bar clock + Battery Percentage + Configure battery percentage visibility + Privacy Chips + Show indicator when camera or mic is in use + Toggle visibility for %1$s + Pin to Favorites + Unpin from Favorites - Ferramentas - Visuais - Sistema + Tools + Visuals + System - Fundamentos de pesquisa - Nenhum resultado para \"%1$s\" - Resultados da pesquisa - %1$s requer as seguintes permissões + Search Essentials + No results for \"%1$s\" + Search Results + %1$s requires following permissions - Widget de tela desligada - Widget invisível para desligar a tela - Ícones da barra de status - Controlar a visibilidade dos ícones da barra de status - Cafeinar - Mantenha a tela ativa - Modo de economia de energia do Maps - Para qualquer dispositivo Android - Iluminação de notificação - Acenda para notificações - Pulse a lanterna para notificações - Bloco de modo de som - Vibrações de chamada - Vibrar para ações de chamada - Mostrar dispositivos Bluetooth - Exibir o nível da bateria dos dispositivos Bluetooth conectados - Limitar o máximo de dispositivos - Ajustar o máximo de dispositivos visíveis no widget - Plano de fundo do widget - Mostrar plano de fundo do widget + Screen off widget + Invisible widget to turn the screen off + Statusbar icons + Control statusbar icons visibility + Caffeinate + Keep the screen awake + Maps power saving mode + For any Android device + Notification lighting + Light up for notifications + Pulse the flashlight for notifications + Sound mode tile + Call vibrations + Vibrate for call actions + Show Bluetooth devices + Display battery level of connected Bluetooth devices + Limit max devices + Adjust max devices visible in widget + Widget background + Show widget background - Automação de gatilho - Agende uma ação para ser acionada em uma observação - Automação de Estado - Agende uma ação para execução com base no estado de uma condição de entrada e saída - Nova Automação - Editar automação - Ações de link - Lidar com links com vários aplicativos - Suspender notificações do sistema - Adiar notificações persistentes - Blocos de configurações rápidas - Ver tudo - Remapeamento de botão - Remapear ações de botão de hardware - Luz noturna dinâmica - Alternar luz noturna com base no aplicativo - Segurança bloqueada com tela - Impedir controles de rede - Bloqueio de aplicativo - Aplicativos seguros com biometria - Congelar - Desative aplicativos raramente usados - Marca d\'água - Adicione dados EXIF ​​e logotipos às fotos - Sempre em exibição - Mostrar hora e informações enquanto a tela está desligada - Sincronização de calendário - Sincronize eventos com seu relógio - Sobreposição - Quadro - Marca do dispositivo - Dados EXIF - Escolha a imagem - Imagem salva na galeria - Compartilhar - Configurações EXIF - Distância focal - Abertura + Trigger Automation + Schedule an action to trigger on an observation + State Automation + Schedule an action to execute based on the state of a condition in and out + New Automation + Edit Automation + Link actions + Handle links with multiple apps + Snooze system notifications + Snooze persistent notifications + Quick settings tiles + View all + Button remap + Remap hardware button actions + Dynamic night light + Toggle night light based on app + Screen locked security + Prevent network controls + App lock + Secure apps with biometrics + Freeze + Disable rarely used apps + Watermark + Add EXIF data and logos to photos + Always on Display + Show time and info while screen off + Calendar Sync + Sync events to your watch + Overlay + Frame + Device Brand + EXIF Data + Pick Image + Image saved to gallery + Share + EXIF Settings + Focal Length + Aperture ISO - Velocidade do obturador - Data e hora - Mover para o topo - Alinhar à esquerda - Tamanho da marca - Tamanho dos dados - Tamanho do texto - Tamanho da fonte - Texto personalizado - Digite seu texto... - Espaçamento - Largura da borda - Cantos Arredondados - Cor - Logotipo - Mostrar logotipo - Tamanho do logotipo - Editar textos de marca d\'água - Marca do dispositivo - Data e hora - Sem informações de data - Girar para a esquerda - Girar para a direita - Próximo + Shutter Speed + Date & Time + Move to Top + Align Left + Brand Size + Data Size + Text Size + Font Size + Custom Text + Enter your text... + Spacing + Border Width + Round Corners + Color + Logo + Show Logo + Logo Size + Edit Watermark Texts + Device brand + Date & Time + No date information + Rotate left + Rotate right + Next OK - Salvar alterações - Configurações de sincronização de calendário - Sincronize calendários específicos - Sincronização Periódica - Sincronize a cada 15 minutos se forem encontradas alterações - Sincronizar agora - Acione a sincronização imediata para assistir - Nenhuma agenda local encontrada - A sincronização do calendário foi iniciada + Save Changes + Calendar Sync Settings + Sync specific calendars + Periodic Sync + Sync every 15 minutes if changes found + Sync Now + Trigger immediate sync to watch + No local calendars found + Calendar sync started - Feedback tátil do widget - Escolha feedback tátil para toques em widgets - Wi-Fi inteligente - Ocultar dados móveis quando o WiFi estiver conectado - Dados inteligentes - Ocultar dados móveis em determinados modos - Redefinir todos os ícones - Redefinir a visibilidade do ícone da barra de status para o padrão - Abortar cafeína com a tela desligada - Desligue automaticamente o Caffeinate ao bloquear manualmente o dispositivo - Estilo de iluminação - Escolha entre Stroke, Glow, Spinner e muito mais - Raio de canto - Ajuste o raio do canto da iluminação de notificação - Ignorar notificações silenciosas - Não mostre iluminação para notificações silenciosas - Pulso de lanterna - Lanterna pulsa lentamente para novas notificações - Somente enquanto estiver voltado para baixo - Lanterna de pulso somente quando o dispositivo está voltado para baixo - Nenhum canal do sistema descoberto ainda. Eles aparecerão aqui assim que forem detectados. - Desfoque da interface do usuário - Alternar desfoque da IU em todo o sistema - Bolhas - Ativar bolhas flutuantes nas janelas - Conteúdo Sensível - Ocultar detalhes da notificação na tela de bloqueio - Toque para acordar - Toque duas vezes para ativar o controle + Widget Haptic feedback + Pick haptic feedback for widget taps + Smart WiFi + Hide mobile data when WiFi is connected + Smart Data + Hide mobile data in certain modes + Reset All Icons + Reset status bar icon visibility to default + Abort Caffeinate with screen off + Automatically turn off Caffeinate when manually locking the device + Lighting Style + Choose between Stroke, Glow, Spinner, and more + Corner radius + Adjust the corner radius of the notification lighting + Skip silent notifications + Do not show lighting for silent notifications + Flashlight pulse + Slowly pulse flashlight for new notifications + Only while facing down + Pulse flashlight only when device is face down + No system channels discovered yet. They will appear here once detected. + UI Blur + Toggle system-wide UI blur + Bubbles + Enable floating window bubbles + Sensitive Content + Hide notification details on lockscreen + Tap to Wake + Double tap to wake control AOD - Alternar sempre em exibição - Cafeína - Alternar manter a tela ativa - Modo de som - Ciclo de modos de som (Toque/Vibrar/Silencioso) - Iluminação de notificação - Alternar serviço de iluminação de notificação - Luz noturna dinâmica - Alternar automação de luz noturna - Segurança bloqueada - Segurança de rede na alternância da tela de bloqueio - Áudio Mono - Forçar alternância de saída de áudio mono - Lanterna - Alternar lanterna dedicada - Congelamento de aplicativos - Inicie a grade de congelamento do aplicativo - Pulso de lanterna - Alternar pulso da lanterna de notificação - Alternar opção de desenvolvedor para ficar acordado - DNS privado - Alternar entre os modos DNS privado (Desligado/Automático/Nome do host) - Depuração USB - Alternar opção de desenvolvedor de depuração USB - Ativar remapeamento de botão - Alternância mestre para remapeamento do botão de volume - Remapear feedback tátil - Feedback de vibração quando o botão remapeado é pressionado - Alternar lanterna - Alternar lanterna com botões de volume - Ativar luz noturna dinâmica - Interruptor mestre para luz noturna dinâmica - Ativar bloqueio de aplicativo - Alternância mestre para bloqueio de aplicativos - Selecione aplicativos bloqueados - Escolha quais aplicativos exigem autenticação - Escolha aplicativos para congelar - Escolha quais aplicativos podem ser congelados - Congelar todos os aplicativos - Congele imediatamente todos os aplicativos escolhidos - Congelar quando bloqueado - Congelar aplicativos selecionados quando o dispositivo for bloqueado - Atraso de congelamento - Atraso antes de congelar após o bloqueio + Always On Display toggle + Caffeinate + Keep screen awake toggle + Sound Mode + Cycle sound modes (Ring/Vibrate/Silent) + Notification Lighting + Toggle notification lighting service + Dynamic Night Light + Night light automation toggle + Locked Security + Network security on lockscreen toggle + Mono Audio + Force mono audio output toggle + Flashlight + Dedicated flashlight toggle + App Freezing + Launch app freezing grid + Flashlight Pulse + Toggle notification flashlight pulse + Toggle stay awake developer option + Private DNS + Cycle Private DNS modes (Off/Auto/Hostname) + USB Debugging + Toggle USB Debugging developer option + Enable Button Remap + Master toggle for volume button remapping + Remap Haptic Feedback + Vibration feedback when remapped button is pressed + Flashlight toggle + Toggle flashlight with volume buttons + Enable Dynamic Night Light + Master switch for dynamic night light + Enable app lock + Master toggle for app locking + Select locked apps + Choose which apps require authentication + Pick apps to freeze + Choose which apps can be frozen + Freeze all apps + Immediately freeze all picked apps + Freeze when locked + Freeze selected apps when device locks + Freeze delay + Delay before freezing after locking Shizuku - Necessário para comandos avançados. Instale o Shizuku da Play Store. - Instale Shizuku - Conceder permissão - Necessário para executar comandos de economia de energia enquanto os mapas estão navegando. - Requer Shizuku ou Root - Acesso à raiz - Permissões necessárias para ações do sistema usando privilégios Root. - Ouvinte de notificação - Requer acesso de ouvinte de notificação para monitorar o status de navegação do Google Maps e ativar a economia de energia quando não estiver navegando. - Requer acesso de ouvinte de notificação para detectar novas notificações e acionar iluminação de borda. - Requer acesso de ouvinte de notificação para monitorar e adiar notificações indesejadas do sistema. - Serviço de acessibilidade - Necessário para App Lock, widget Screen off e outros recursos para detectar interações - Necessário para acionar a iluminação de notificação em novas notificações - Navegador padrão - Necessário para lidar com links de forma eficiente - Necessário para interceptar eventos de botão de hardware - Necessário para interceptar eventos de teclas de volume enquanto a tela está desligada para acionar a sobreposição Ambient Glance. - Necessário para monitorar aplicativos em primeiro plano. - Gravar configurações seguras - Obrigatório para ícones da barra de status e segurança de tela bloqueada - Necessário para alternar a luz noturna. Conceda via ADB ou root. - Modificar configurações do sistema - Necessário para alternar o brilho adaptável e outras configurações do sistema - Permissão de sobreposição - Necessário para exibir a sobreposição de iluminação de notificação na tela - Administrador do dispositivo - Necessário para bloquear o dispositivo (desativando a biometria) em tentativas de acesso não autorizado - Conceder permissão - Copiar ADB - Verificar - Ativar nas configurações - Como conceder - Otimização da bateria - Certifique-se de que o serviço não seja eliminado pelo sistema para economizar energia. + Required for advanced commands. Install Shizuku from the Play Store. + Install Shizuku + Grant Permission + Required to run power-saving commands while maps is navigating. + Requires Shizuku or Root + Root Access + Permissions required for system actions using Root privileges. + Notification Listener + Requires notification listener access to monitor Google Maps navigation status and enable power saving when not navigatiing. + Requires notification listener access to detect new notifications and trigger edge lighting. + Requires notification listener access to monitor and snooze unwanted system notifications. + Accessibility Service + Required for App Lock, Screen off widget and other features to detect interactions + Required to trigger notification lighting on new notifications + Default Browser + Required to handle links efficiently + Required to intercept hardware button events + Required to intercept volume key events while the screen is off to trigger the Ambient Glance overlay. + Needed to monitor foreground applications. + Write Secure Settings + Required for Statusbar icons and Screen Locked Security + Needed to toggle Night Light. Grant via ADB or root. + Modify System Settings + Required to toggle Adaptive Brightness and other system settings + Overlay Permission + Required to display the notification lighting overlay on the screen + Device Administrator + Required to hard-lock the device (disabling biometrics) on unauthorized access attempts + Grant Permission + Copy ADB + Check + Enable in Settings + How to grant + Battery Optimization + Ensure the service is not killed by the system to save power. - Fundamentos - Congelar - Congelado - faça você mesmo - Aplicativos - Aplicativos desativados - Faça você mesmo - Encontre e gerencie aplicativos - Atualizações de aplicativos - Atualizações de aplicativos - Adicionar repositório - Editar repositório - Insira o URL do repositório GitHub ou proprietário/repo - Acompanhar - Nenhum APK encontrado na versão mais recente - Repositório não encontrado - Último lançamento - Ver LEIA-ME - %d Estrelas - Aplicativo instalado - Não instalado - Escolha o aplicativo - Selecione o aplicativo - Cancelar rastreamento - Pendente - Atualizado - Acompanhe e baixe os lançamentos mais recentes de seus aplicativos favoritos diretamente do GitHub. - Formato inválido. Use proprietário/repo ou URL do GitHub - Ocorreu um erro durante a pesquisa + Essentials + Freeze + Frozen + DIY + Apps + Disabled apps + Do It Yourself + Find and manage apps + App Updates + App Updates + Add Repository + Edit Repository + Enter GitHub Repository URL or owner/repo + Track + No APK found in the latest release + Repository not found + Latest Release + View README + %d Stars + Installed app + Not installed + Pick app + Select app + Untrack + Pending + Up-to-date + Track and download the latest releases for your favorite apps directly from GitHub. + Invalid format. Use owner/repo or GitHub URL + An error occurred during search Auto - Opções - Confira os pré-lançamentos - Notificações - Limite de taxa do GitHub excedido. Por favor, tente novamente mais tarde. + Options + Check for pre-releases + Notifications + GitHub rate limit exceeded. Please try again later. - Configuração do teclado - Ativar nas configurações - Mudar para o Essencial - Habilitado - Desabilitado - Brilho adaptativo - Economia de energia nos mapas - Procurar - Parar - Procurar + Keyboard Setup + Enable in settings + Switch to Essentials + Enabled + Disabled + Adaptive Brightness + Maps Power Saving + Search + Stop + Search - Voltar - Voltar - Configurações - Reportar um bug + Back + Back + Settings + Report a Bug Crash reporting Off Auto - O Essentials crashou, o relatório foi enviado + Essentials crashed, Report sent Simulate crash - Bem vindo ao Essentials + Welcome to Essentials A Toolbox for Android Nerds - por sameerasw.com - Vamos começar + by sameerasw.com + Let\'s Begin Acknowledgement - Este aplicativo é uma coleção de utilidades que podem interagir profundamente com o sistema do seu dispositivo. Usando algumas utilidades pode modificar seu sistema ou comportamento em formas inesperadas. \n\n Você só precisa conceder permissões necessárias, que são utilizadas por utilidades que você está usando, assim dando acesso completo ao comportamento do aplicativo. \n\nAlém disso, o aplicativo não rastreia dados nem os guarda, não preciso deles...(Você pode se referir ao código-fonte para mais informações). \n\nEste aplicativo Sempre vai ser gratuito e de código aberto. Nunca pague, ou instale o Essentials de fontes desconhecidas. + This app is a collection of utilities that can interact deeply with your device system. Using some features might modify system settings or behavior in unexpected ways. \n\nYou only need to grant necessary permissions which are required for selected features you are using giving you full control over the app\'s behavior. \n\nFurther more, the app does not track or store any of your personal data, I don\'t need them... Keep to yourself safe. You can refer to the source code for more information. \n\nThis app is fully open source and is and always will be free to use. Do not pay or install from unknown sources. WARNING: Proceed with caution. The developer takes no responsibility for any system instability, data loss, or other issues caused by the use of this app. By proceeding, you acknowledge these risks. I know you didn\'t even read this carefully but, in case you need any help, feel free to reach out the developer or the community. I Understand - Qualquer hora que estiver sem ideia do que alguma utilidade ou algum Quick Settings Tile faz, pressione longamente e selecione \"O quê é isso?\" para aprender mais. + Anytime you are clueless on a feature or a Quick Settings Tile on what it does and what permissions may necessary for it, just long press it and pick \'What is this?\' to learn more. You can report bugs or find helpful guides anytime in the app settings. - Me deixe entrar + Let Me in Already Preferences - Configure algumas coisas básicas para começar. - Configurações do aplicativo + Configure some basic settings to get started. + App Settings Haptic Feedback Updates - Procurar por atualizações automaticamente - Procurar por atualização no início do aplicativo + Auto check for updates + Check for updates at app launch All Set Check What\'s New? - Feito - Visualização - Guia de ajuda - O que é isso? - Atualização disponível + Done + Preview + Help Guide + What is this? + Update Available Glance at your device\'s hardware and software specifications in detail. This information is fetched from GSMArena and system properties to provide a comprehensive overview of your Android device. - Ambient Music Glance mostra uma sobreposição Now Playing na tela de bloqueio quando a música está tocando e a reprodução muda. \n\nSe o seu dispositivo não suporta sobreposições sobre AOD, você pode optar pelo protetor de tela Ambience adicionado nas configurações do Android como alternativa durante o carregamento. - A iluminação de notificação adiciona um belo efeito de iluminação de borda quando você recebe notificações.\n\nVocê pode personalizar o estilo, as cores e o comportamento da animação. Funciona mesmo quando a tela está desligada (depende do OEM) ou na parte superior do seu aplicativo atual. Escolha aplicativos, prioridade de notificação ou qual comportamento deve ser desencadeado a partir de determinados controles. Se o seu OEM não suportar sobreposições acima de AOD, use a opção de exibição ambiente encontrada abaixo. - Desligue facilmente a tela com um toque em um widget redimensionável transparente que não adiciona ícones ou qualquer confusão à sua tela inicial. - Assuma o controle total sobre os ícones da barra de status.\n\nOculte ícones específicos como WiFi, Bluetooth ou dados de celular para manter sua barra de status limpa. Você também pode personalizar o formato do relógio e o indicador de bateria com alguns controles inteligentes. Esta é a lista de controles AOSP disponíveis, portanto o sistema operacional do seu dispositivo pode não respeitar todos os controles. - A cafeína evita que sua tela desligue automaticamente.\n\nMantenha sua tela ativa por um período específico ou indefinidamente. Útil ao ler artigos longos ou fazer referência a uma receita. - Obtenha o modo de economia de energia do Google Maps exclusivo da série Pixel 10 com fundo preto mínimo para exibir na tela de bloqueio em qualquer dispositivo Android. Inicie uma sessão de navegação, desligue e ligue a tela novamente. - Pulse a lanterna ao receber uma notificação.\n\nCom os dispositivos com suporte de hardware para escurecimento da lanterna, o pulso será suavemente animado. - Adie notificações irritantes e persistentes do sistema que não podem ser modificadas por padrão. \n\nAguarde até que a notificação chegue e entre neste recurso onde o canal de notificação\'s será listado. Selecione para adiar na próxima vez.\n\nQualquer notificação adiada ainda pode ser acessada no seu histórico de notificações no Android. - Adicione blocos personalizados ao painel de configurações rápidas.\n\nPressione e segure qualquer um deles para saber o que eles fazem. - Remapeie seus botões de hardware para executar diferentes ações e atalhos.\n\nPersonalize o que acontece quando você pressiona longamente os botões de volume com certas condições. \n\nAlguns comportamentos, como o gatilho de desligamento da tela ou os controles da lanterna, podem depender do OEM de sua implementação e podem não funcionar em todos os dispositivos conforme o esperado. Alguns cenários podem ser contornados usando permissões Shizuku, mas podem não proporcionar a mesma experiência devido às implementações. - Alterna automaticamente o filtro de luz azul da tela com base no aplicativo em primeiro plano. - Aumente a segurança quando seu dispositivo estiver bloqueado.\n\nRestringir o acesso a alguns blocos QS sensíveis, evitando modificações não autorizadas na rede e evitando ainda mais tentativas de fazê-lo, aumentando a velocidade da animação para evitar spam de toque.\n\nEste recurso não é robusto e pode ter falhas, como alguns blocos que permitem alternar diretamente, como bluetooth ou modo de vôo, não podendo ser impedido. - Proteja seus aplicativos com uma camada de autenticação secundária.\n\nO método de autenticação da tela de bloqueio do seu dispositivo será usado desde que atenda ao nível de segurança biométrica classe 3 pelos padrões Android. - Seja notificado quando chegar mais perto do seu destino para garantir que você nunca perca a parada.\n\nVá para o Google Maps, mantenha pressionado um alfinete próximo ao seu destino e certifique-se de que diz \"Alfinete caído\" (caso contrário, o cálculo da distância pode não ser preciso) e, em seguida, compartilhe a localização com o aplicativo Essentials e comece a rastrear. - Congele aplicativos para impedir que sejam executados em segundo plano.\n\nEvite o consumo de bateria e o uso de dados congelando completamente os aplicativos quando não os estiver usando. Eles serão descongelados instantaneamente quando você os iniciar. Os aplicativos não aparecerão na gaveta de aplicativos e também não aparecerão para atualizações de aplicativos na Play Store enquanto estiverem congelados. - Um método de entrada personalizado que ninguém pediu.\n\nÉ apenas uma experiência. Vários idiomas podem não ter suporte, pois é uma implementação muito complexa e demorada. - Monitore os níveis de bateria de todos os seus dispositivos conectados.\n\nVeja o status da bateria de seus fones de ouvido Bluetooth, relógio e outros acessórios em um só lugar. Conecte-se ao aplicativo AirSync para exibir também o nível da bateria do seu Mac. - Adicione uma legenda/marca d\'água personalizada às suas fotos com dados EXIF ​​e informações do dispositivo.\n\nCompartilhe uma imagem diretamente de outro aplicativo no Essentials para adicionar facilmente uma marca d\'água. - Sincronize toda a sua próxima programação de calendário, independentemente das restrições das contas do Google que não permitem a adição de dispositivos wearOS devido a políticas profissionais ou escolares. \n\nCertifique-se de instalar o aplicativo complementar wearOS Essentials para exibir a programação no aplicativo, bem como em um bloco ou uma complicação. - Acompanhe as atualizações dos seus aplicativos instalados.\n\nReceba notificações sobre atualizações disponíveis, visualize registros de alterações e instale-as facilmente com um toque. - Adicione feedback tátil às suas chamadas.\n\nVibra quando uma chamada é conectada, desconectada ou aceita, fornecendo confirmação tátil sem olhar para a tela. - Alterne rapidamente entre os modos Som, Vibração e Silencioso.\n\nUm bloco conveniente para alterar o modo de campainha sem usar os botões ou configurações de volume. Você pode reordenar os modos ou desativar qualquer um, se não for necessário, para personalizar a alternância do bloco para o comportamento do ciclo. - Alterne facilmente o efeito de profundidade de desfoque no nível do sistema em todo o sistema operacional. - Ative ou desative balões de notificação flutuantes.\n\nAlterne rapidamente a configuração de todo o sistema para balões de conversa. - Oculte conteúdo confidencial na tela de bloqueio.\n\nAlterne se o conteúdo da notificação é mostrado ou oculto quando o dispositivo está bloqueado. - Alternar toque para ativar a funcionalidade.\n\nAtive ou desative a capacidade de ativar sua tela com um toque. - Ative o Always On Display.\n\nAtive ou desative rapidamente o Always On Display para visualizar informações rapidamente. - Controle automaticamente seu Always On Display com base em suas notificações. Quando uma mensagem ou alerta chega de um aplicativo selecionado, o AOD permanecerá ativado até você descartar a notificação, garantindo que você nunca perca informações importantes sem desperdiçar bateria quando nenhum alerta estiver presente. - Combine canais de áudio em mono.\n\nÚtil ao usar um único fone de ouvido ou para fins de acessibilidade. - Alterne a lanterna.\n\nUm toque longo abre os controles para ajuste de intensidade que pode precisar de implementação de hardware que alguns dispositivos podem não ter. - Mantenha a tela ativa durante o carregamento.\n\nEvita que a tela hiberne enquanto o dispositivo estiver conectado a uma fonte de energia adequada para desenvolvedores durante a depuração. - Alterne NFC.\n\nAtive ou desative rapidamente a Near Field Communication para pagamentos e emparelhamento. - Alternar brilho adaptável.\n\nAtiva ou desativa o ajuste automático de brilho da tela com base na luz ambiente. - Alternar DNS privado.\n\nPercorrer os modos de provedor de DNS desativado, automático e privado. - Alternar depuração USB.\n\nAtivar ou desativar o acesso à depuração ADB diretamente nas configurações rápidas. - Inicie a ferramenta conta-gotas para escolher as cores introduzidas no Android 17 BETA 2 - Otimize a vida útil da bateria limitando a carga máxima ou usando o carregamento adaptativo. Isso foi especialmente projetado para dispositivos Pixel para garantir longevidade e ciclos de carregamento saudáveis.\n\nCréditos: TebbeUbben/ChargeQuickTile + Ambient Music Glance shows a Now Playing overlay on your lock screen when music is playing and playback changes. \n\nIf your device does not support overlays over AOD, you can opt for the Ambience screensaver added in your Android settings as an alternative while charging. + Notification Lighting adds a beautiful edge lighting effect when you receive notifications.\n\nYou can customize the animation style, colors, and behavior. It works even when the screen is off (OEM dependent) or on top of your current app. Pick apps, notification priority or what behavior it should be triggering on from given controls. If your OEM does not support overlays above AOD, sue the Ambient display option found below. + Easily turn the screen off with a tap on a transparent resizable widget that does not add icons or any clutter to your home screen. + Take full control over your status bar icons.\n\nHide specific icons like WiFi, Bluetooth, or cellular data to keep your status bar clean. You can also customize the clock format and battery indicator with some smart controls as well. These are the list of available AOSP controls so your device OS might not respect all the controls. + Caffeinate prevents your screen from turning off automatically.\n\nKeep your screen awake for a specific duration or indefinitely. Useful when reading long articles or referencing a recipe. + Get the Pixel 10 series exclusive Google Maps Power Saving mode with the minimal pitch black background to display over your lock screen on any Android device. Start a navigation session, turn the screen off and back on. + Pulse the flashlight when you receive a notification.\n\nWith devices have hardware support for flashlight dimming, the pulse will be smoothly animated. + Snooze annoying persistent system notifications which can not be modified by default. \n\nPlease wait until the notification arrives and then go into this feature where it\'s notification channel will be listed. Select that to snooze from next time.\n\nAny snoozed notification can still be accessed from your notification history in Android. + Add custom tiles to your Quick Settings panel.\n\nLong press any of them to learn what they do. + Remap your hardware buttons to perform different actions and shortcuts.\n\nCustomize what happens when you long press volume buttons with certain conditions. \n\nSome behavior such as screen off trigger or flashlight controls might be OEM dependent on their implementation and may not work on all devices as expected. Some scenarios could be worked around using Shizuku permissions but may not give the same experience due to the implementations. + Automatically toggle your screen blue light filter based on the foreground app. + Enhance security when your device is locked.\n\nRestrict access to some sensitive QS tiles preventing unauthorized network modifications and further preventing them re-attempting to do so by increasing the animation speed to prevent touch spam.\n\nThis feature is not robust and may have flaws such as some tiles which allow toggling directly such as bluetooth or flight mode not being able to be prevented. + Secure your apps with a secondary authentication layer.\n\nYour device lock screen authentication method will be used as long as it meets the class 3 biometric security level by Android standards. + Get notified when you get closer to your destination to ensure you never miss the stop.\n\nGo to Google Maps, long press a pin nearby to your destination and make sure it says \"Dropped pin\" (Otherwise the distance calculation might not be accurate), And then share the location to the Essentials app and start tracking. + Freeze apps to stop them from running in the background.\n\nPrevent battery drain and data usage by completely freezing apps when you are not using them. They will be unfrozen instantly when you launch them. The apps will not show up in the app drawer and also will not show up for app updates in Play Store while frozen. + A custom input method no-one asked for.\n\nIt is just an experiment. Multiple languages may not get support as it is a very complex and time consuming implementation. + Monitor battery levels of all your connected devices.\n\nSee the battery status of your Bluetooth headphones, watch, and other accessories in one place. Connect with AirSync application to display your mac battery level as well. + Add a custom caption/ watermark to your photos with EXIF data and device information.\n\nShare an image directly from other app to Essentials to easily add a watermark. + Sync all your upcoming calendar schedule not matter the restrictions on Google accounts not letting to be added to wearOS devices due to work or school policies. \n\nMake sure to install the wearOS Essentials companion app to display the schedule in the app as well as in a tile or a complication. + Keep track of updates for your installed apps.\n\nGet notified about available updates, view changelogs and install them easily with a tap. + Add haptic feedback to your calls.\n\nVibrate when a call is connected, disconnected, or accepted, giving you tactile confirmation without looking at the screen. + Quickly toggle between Sound, Vibrate, and Silent modes.\n\nA convenient tile to change your ringer mode without using the volume buttons or settings. You can re-order the modes or disable any if not needed to customize the tile toggle to cycle behavior. + Easily toggle the system level blur depth effect across the OS. + Enable or disable floating notification bubbles.\n\nQuickly toggle the system-wide setting for conversation bubbles. + Hide sensitive content on the lock screen.\n\nToggle whether notification content is shown or hidden when your device is locked. + Toggle tap to wake functionality.\n\nEnable or disable the ability to wake your screen with a tap. + Toggle Always On Display.\n\nQuickly enable or disable the always-on display to view info at a glance. + Automatically control your Always On Display based on your notifications. When a message or alert arrives from a selected app, AOD will stay on until you dismiss the notification, ensuring you never miss important info without wasting battery when no alerts are present. + Combine audio channels into mono.\n\nUseful when using a single earbud or for accessibility purposes. + Toggle the flashlight.\n\nA Long pressing opens the controls for intensity adjustment which might need hardware implementation which some devices may lack. + Keep the screen awake while charging.\n\nPrevents the screen from sleeping as long as the device is connected to a power source which is suitable for developers during debugging. + Toggle NFC.\n\nQuickly enable or disable Near Field Communication for payments and pairing. + Toggle adaptive brightness.\n\nEnable or disable automatic screen brightness adjustment based on ambient light. + Toggle Private DNS.\n\nCycle through Off, Automatic, and Private DNS provider modes. + Toggle USB Debugging.\n\nEnable or disable ADB debugging access directly from the quick settings. + Launch the eye dropper tool to pick colors introduced in Android 17 BETA 2 + Optimize your battery life by limiting the maximum charge or using adaptive charging. This is specially designed for Pixel devices to ensure longevity and healthy charging cycles.\n\nCredits: TebbeUbben/ChargeQuickTile Download - Tela desligada - Tela ativada - Desbloqueio de dispositivo - Carregador conectado - Carregador desconectado - Carregando - Tela ativada - Vibrar - Mostrar notificação - Remover notificação - Ligue a lanterna - Desligue a lanterna - Alternar lanterna - Papel de parede escuro - Esta ação requer que Shizuku ou Root ajustem o escurecimento do papel de parede do sistema. - Selecione o gatilho - Aplicativo - Automatize com base em aplicativo aberto - Selecione o estado - Selecione Ação - Em ação - Fora de ação - Cancelar - Salvar - Editar - Excluir - Habilitar - Desativar - Serviço de automação - Automações ativas - Monitorando eventos do sistema para suas automações - Efeitos do dispositivo - Controle os efeitos no nível do sistema, como escala de cinza, supressão de AOD, escurecimento do papel de parede e modo noturno. - Tons de cinza - Suprimir exibição de ambiente - Papel de parede escuro - Modo noturno - Este recurso requer Android 15 ou superior. - Habilitado - Desabilitado - Modo de som - Esta ação permite alternar entre os modos Som, Vibração e Silêncio com base nos gatilhos. Requer acesso Não perturbe. + Screen Off + Screen On + Device Unlock + Charger Connected + Charger Disconnected + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on + Charging + Screen On + Vibrate + Show Notification + Remove Notification + Turn On Flashlight + Turn Off Flashlight + Toggle Flashlight + Turn On Low Power Mode + Turn Off Low Power Mode + Dim Wallpaper + This action requires Shizuku or Root to adjust system wallpaper dimming. + Select Trigger + App + Automate based on open app + Select State + Select Action + In Action + Out Action + Cancel + Save + Edit + Delete + Enable + Disable + Automation Service + Automations Active + Monitoring system events for your automations + Device Effects + Control system-level effects like grayscale, AOD suppression, wallpaper dimming, and night mode. + Grayscale + Suppress Ambient Display + Dim Wallpaper + Night Mode + This feature requires Android 15 or higher. + Enabled + Disabled + Sound Mode + This action allows switching between Sound, Vibrate, and Silent modes based on triggers. It requires Do Not Disturb access. Sameera Wijerathna - A caixa de ferramentas completa para Pixel e Androids + The all-in-one toolbox for your Pixel and Androids - Sistema - Personalizado - Específico do aplicativo + System + Custom + App specific - Falha na autenticação - Mantenha pressionado um aplicativo na grade para adicionar um atalho - Aplicativo não encontrado ou desinstalado + Authentication failed + Long press an app in the grid to add a shortcut + App not found or uninstalled - Atualizações de aplicativos - Notificações para novas atualizações de aplicativos - Atualização disponível - Nenhum dispositivo conectado - Desconhecido + App Updates + Notifications for new app updates + Update available + No devices connected + Unknown 5G 4G 3G - Shizuku (Rika) + Shizuku (Rikka) Shizuku (TuoZi) - Procurar - Necessário para bloquear o dispositivo quando são tentadas alterações de rede não autorizadas na tela de bloqueio. - Autenticar para acessar as configurações - %1$s Configurações - recurso - configurações - esconder - mostrar - visibilidade - Erro ao carregar aplicativos: %1$s + Search + Required to hard-lock the device when unauthorized network changes are attempted on lock screen. + Authenticate to access settings + %1$s Settings + feature + settings + hide + show + visibility + Error loading apps: %1$s - vibração - tocar - sentir + vibration + touch + feel - rede - visibilidade - automático - esconder + network + visibility + auto + hide - restaurar - padrão - ícone + restore + default + icon - teclado - altura - preenchimento - háptico - entrada + keyboard + height + padding + haptic + input - luz - tocha + light + torch - luz - tocha - pulso - notificação + light + torch + pulse + notification - acordado - desenvolvedor - poder - cobrar + awake + developer + power + charge - brilho - notificação - liderado + glow + notification + led - redondo - forma - borda + round + shape + edge - seguro - privacidade - biométrico + secure + privacy + biometric face - impressão digital + fingerprint - som - acessibilidade - ouvir + sound + accessibility + hear - ficar - sobre - tempo esgotado + stay + on + timeout - tocar - acordar - mostrar + touch + wake + display - temporizador - espere - tempo esgotado + timer + wait + timeout - Tema sempre escuro - Tema preto como breu - Histórico da área de transferência + Always dark theme + Pitch black theme + Clipboard History + Long press for symbols + Accented characters - lista - selecionador - seleção + list + picker + selection - animação + animation visual - olhar + look - quieto - ignorar - filtro + quiet + ignore + filter - automação + automation auto - trancar + lock adb - USB - depurar + usb + debug - borrão - vidro - vinheta + blur + glass + vignette - flutuador - janela - sobreposição + float + window + overlay - sempre - mostrar - relógio + always + display + clock - áudio - mudo + audio + mute volume - azul - filtro + blue + filter auto - congelar + freeze shizuku manual - agora + now shizuku - proximidade + proximity sensor face - abaixo + down - trocar - mestre + switch + master - vibração - sentir + vibration + feel - bateria - cobrar - otimização + battery + charge + optimization pixel - Inverter seleção - Mostrar aplicativos do sistema + Invert selection + Show system apps - Você está atualizado - Esta é uma versão de pré-lançamento e pode ser instável. - Notas de versão %1$s - Ver no GitHub - Baixar APK + You are up to date + This is a pre-release version and might be unstable. + Release Notes %1$s + View on GitHub + Download APK - Nenhum - Sutil - Dobro - Clique - Marcação + None + Subtle + Double + Click + Tick - Desligar - Brilho da lanterna + Turn Off + Flashlight Brightness - Desbloqueie o telefone para alterar as configurações de rede + Unlock phone to change network settings - Desenvolvido por %1$s\ncom ❤\uFE0F de \uD83C\uDDF1\uD83C\uDDF0 - Site - Contato - Telegrama - Apoiar - Outros aplicativos + Developed by %1$s\nwith ❤\uFE0F from \uD83C\uDDF1\uD83C\uDDF0 + Website + Contact + Telegram + Support + Other Apps AirSync ZenZero - Tela - Tarefas + Canvas + Tasks Zero - Ajuda e guias - Precisa de mais suporte? Estenda a mão, - Colapso - Expandir - Grupo de Apoio - E-mail - Enviar e-mail - Nenhum aplicativo de e-mail disponível - Passo %1$d Imagem + Help & Guides + Need more support? Reach out, + Collapse + Expand + Support Group + Email + Send email + No email app available + Step %1$d Image - Permissões de acessibilidade, notificação e sobreposição - Você pode receber esta mensagem de acesso negado se tentar conceder permissões confidenciais, como acessibilidade, ouvinte de notificação ou permissões de sobreposição. Para concedê-lo, verifique os passos abaixo. - 1. Vá para a página de informações do aplicativo Essentials. - 2. Abra o menu de 3 pontos e selecione \'Permitir configurações restritas\'. Talvez seja necessário autenticar com biometria. Uma vez feito isso, tente conceder a permissão novamente. + Accessibility, Notification and Overlay permissions + You may get this access denied message if you try to grant sensitive permissions such as accessibility, notification listener or overlay permissions. To grant it, check the steps below. + 1. Go to app info page of Essentials. + 2. Open the 3-dot menu and select \'Allow restricted settings\'. You may have to authenticate with biometrics. Once done, Try to grant the permission again. Shizuku - Shizuku é uma ferramenta poderosa que permite que aplicativos usem APIs do sistema diretamente com ADB ou permissões de root. É necessário para recursos como modo mínimo do Maps e App Freezer. E insistirá em conceder algumas permissões, como WRITE_SECURE_SETTINGS. \n\nMas a versão do Shizuku na Play Store pode estar desatualizada e provavelmente ficará inutilizável em versões recentes do Android, portanto, nesse caso, obtenha a versão mais recente no github ou um fork atualizado dele. - Modo de economia de energia do Maps - Este recurso aciona automaticamente o modo de economia de energia do Google Maps, que atualmente é exclusivo da série Pixel 10. Um membro da comunidade descobriu que ele ainda pode ser usado em qualquer dispositivo Android iniciando a atividade minMode dos mapas com privilégios de root. \n\nE então, eu o automatizei com Tasker para acionar automaticamente quando a tela desliga durante uma sessão de navegação e então consegui fazer o mesmo com apenas permissões de tempo de execução do Shizuku. \n\nEle deve ser mostrado no AOD da série Pixel 10, por causa disso, você poderá ver uma mensagem ocasional aparecendo na tela informando que ele não suporta o modo paisagem. Isso não pode ser evitado pelo aplicativo e você pode ignorar. - Modo de som silencioso - Você deve ter notado que o modo silencioso também aciona o DND. \n\nIsso se deve à forma como o Android o implementou, pois mesmo que usemos a mesma API para mudar para o modo vibratório, por algum motivo ele ativa o DND junto com o modo silencioso e isso não é evitável neste momento. :( - O que é congelar? - Faça uma pausa e fique longe das distrações dos aplicativos enquanto economiza um pouco de energia, evitando que os aplicativos sejam executados em segundo plano. Adequado para aplicativos raramente usados. \n\nNão recomendado para quaisquer serviços de comunicação, pois eles não irão notificá-lo em caso de emergência, a menos que você os descongele. \n\nAltamente recomendado não congelar aplicativos do sistema, pois eles podem levar à instabilidade do sistema. Prossiga com cautela, você foi avisado. \n\nInspirado por Hail <3 - O bloqueio de aplicativos e a segurança de tela bloqueada são realmente seguros? - Absolutamente não. \n\nQualquer aplicativo de terceiros não pode interferir 100% nas interações regulares do dispositivo e até mesmo o bloqueio do aplicativo é apenas uma sobreposição acima dos aplicativos selecionados para evitar a interação com eles. Existem soluções alternativas e não são infalíveis. \n\nO mesmo acontece com o recurso de segurança de tela bloqueada que detecta alguém tentando interagir com os blocos de rede que, por algum motivo, ainda estão acessíveis para qualquer pessoa em Pixels. Portanto, se eles se esforçarem o suficiente, ainda poderão alterá-los e, especialmente, se você tiver um bloco QS de modo de voo adicionado, este aplicativo não poderá impedir interações com ele. \n\nEsses recursos são feitos apenas como experimentos para uso leve e nunca seriam recomendados como soluções fortes de segurança e privacidade. \n\nSeguro alternativas:\n - Bloqueio de aplicativo: espaço privado e pasta segura em Pixels e Samsung\n - Impedir o acesso a redes móveis: certifique-se de que a proteção contra roubo e as configurações off-line/desligamento encontre meu dispositivo estejam ativadas. Você também pode pesquisar o Graphene OS. - Ícones da barra de status - Você pode notar que mesmo depois de redefinir os ícones da barra de status, alguns ícones, como rotação do dispositivo e ícones de fones de ouvido com fio, podem permanecer visíveis. Isso se deve à forma como a lista negra statubar é implementada no Android e como seu OEM pode tê-la personalizado. \nVocê pode precisar de mais ajustes. \n\nAlém disso, nem todas as opções de visibilidade de ícones podem funcionar, pois dependem das implementações e disponibilidade do OEM. - A iluminação de notificação não funciona - Depende do OEM. Alguns, como o OneUI, parecem não permitir sobreposições acima do AOD, impedindo a exibição dos efeitos de iluminação. Nesse caso, tente a exibição do ambiente como solução alternativa. - O remapeamento do botão não funciona enquanto a exibição está desligada - Alguns OEMs limitam os relatórios do serviço de acessibilidade quando a tela está realmente desligada, mas eles ainda podem funcionar enquanto o AOD está ligado. \nNesse caso, você pode usar remapeamentos de botão com o AOD ativado, mas não desativado. \n\nComo solução alternativa, você precisará usar as permissões do Shizuku e ativar o \'Use Shizuku ou Root\' toggle nas configurações de remapeamento do botão que identifica e ouve eventos de entrada de hardware.\nIsso não é garantido para funcionar em todos os dispositivos e necessidades testando.\n\nE mesmo que\'estive ativado, o método Shizuku só será usado quando for\'s necessário. Caso contrário, ele sempre retornará para Acessibilidade, que também controla o bloqueio da entrada real durante um toque longo. - O brilho da lanterna não funciona - Apenas um número limitado de dispositivos tem suporte de hardware e software para ajustar a intensidade da lanterna. \n\n\'A versão mínima do Android é 13 (SDK33).\nO controle de brilho da lanterna suporta apenas HAL versão 3.8 e superior, portanto, entre os dispositivos suportados, os mais recentes (por exemplo, Pixel 6/7, Samsung S23, etc.)\'\npolodarb/Lanterna-Tiramisu - Que diabos é esse aplicativo? - Boa pergunta,\n\nSempre quis extrair o máximo dos meus dispositivos à medida que\'Sou um usuário rooteado desde que comprei meu primeiro dispositivo Project Treble. E eu\'Tenho adorado o aplicativo Tasker, que é como um deus quando se trata de automação e utiliza todas as APIs e recursos internos possíveis do Android.\n\nPortanto, não estou desenraizado e de volta à experiência beta do Android e queria aproveitar ao máximo o que é possível com determinados privilégios. É melhor compartilhá-los. Então, com meu conhecimento iniciante em Kotlin Jetpack e com o apoio de muitas ferramentas de pesquisa e assistência e também da grande comunidade, construí um aplicativo completo contendo tudo o que eu queria que estivesse em meu Android com determinadas permissões. E aqui está.\n\nSolicitações de recursos são bem-vindas. Vou considerar e ver se elas são viáveis ​​com as permissões disponíveis e minhas habilidades. Hoje em dia o que não é possível. :)\n\nPor que não na Play Store?\nEu não\'Não quero arriscar que minha conta de desenvolvedor seja banida devido às permissões e APIs altamente confidenciais e internas usadas no aplicativo. Mas com a forma como o sideload do Android está indo, vamos\'vamos ver o que temos que fazer. Eu entendo as preocupações de aplicativos transferidos serem maliciosos.\nJá que estamos no assunto, confira meu outro aplicativo AirSync se você for um usuário Mac + Android. *plugue sem vergonha*\n\nAproveite, continue construindo! (っ◕‿◕)っ + Shizuku is a powerful tool that allows apps to use system APIs directly with ADB or root permissions. It is required for features like Maps min mode, App Freezer. And willa ssist granting some permissions such as WRITE_SECURE_SETTINGS. \n\nBut the Play Store version of Shizuku might be outdated and will probably be unusable on recent Android versions so in that case, please get the latest version from the github or an up-to-date fork of it. + Maps power saving mode + This feature automatically triggers Google Maps power saving mode which is currently exclusive to the Pixel 10 series. A community member discovered that it is still usable on any Android device by launching the maps minMode activity with root privileges. \n\nAnd then, I had it automated with Tasker to automatically trigger when the screen turns off during a navigation session and then was able to achieve the same with just runtime Shizuku permissions. \n\nIt is intended to be shown over the AOD of Pixel 10 series so because of that, you may see an occasional message popping up on the display that it does not support landscape mode. That is not avoidable by the app and you can ignore. + Silent sound mode + You may have noticed that the silent mode also triggers DND. \n\nThis is due to how the Android implemented it as even if we use the same API to switch to vibrate mode, it for some reason turns on DND along with the silent mode and this is not avoidable at this moment. :( + What is freeze? + Pause and stay away from app distractions while saving a little bit of power preventing apps running in the background. Suitable for rarely used apps. \n\nNot recommended for any communication services as they will not notify you in an emergency unless you unfreeze them. \n\nHighly advised to not freeze system apps as they can lead to system instability. Proceed with caution, You were warned. \n\nInspired by Hail <3 + Are app lock and screen locked security actually secure? + Absolutely not. \n\nAny 3rd party application can not 100% interfere with regular device interactions and even the app lock is only an overlay above selected apps to prevent interacting with them. There are workarounds and it is not foolproof. \n\nSame goes with the screen locked security feature which detects someone trying to interact with the network tiles which for some reason are still accessible for anyone on Pixels. So if they try hard enough they might still be able to change them and especially if you have a flight mode QS tile added, this app can not prevent interactions with it. \n\nThese features are made just as experiments for light usage and would never recommend as strong security and privacy solutions. \n\nSecure alternatives:\n - App lock: Private Space and Secure folder on Pixels and Samsung\n - Preventing mobile networks access: Make sure your theft protection and offline/ power off find my device settings are on. You may look into Graphene OS as well. + Statusbar icons + You may notice that even after resetting the statusbar icons, Some icons such as device rotation, wired headphone icons may stay visible. This is due to how the statubar blacklist is implemented in Android and how your OEM may have customized them. \nYou may need further adjustments. \n\nAlso not all icon visibility options may work as they depend on the OEM implementations and availability. + Notification lighting does not work + It depends on the OEM. Some like OneUI does not seem to allow overlays above the AOD preventing the lighting effects being shown. In this case, try the ambient display as a workaround. + Button remap does not work while display is off + Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku or Root\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. + Flashlight brightness does not work + Only a limited number of devices got hardware and software support adjusting the flashlight intensity. \n\n\'The minimum version of Android is 13 (SDK33).\nFlashlight brightness control only supports HAL version 3.8 and higher, so among the supported devices, the latest ones (For example, Pixel 6/7, Samsung S23, etc.)\'\npolodarb/Flashlight-Tiramisu + What the hell is this app? + Good question,\n\nI always wanted to extract the most out of my devices as I\'ve been a rooted user for ever since I got my first Project Treble device. And I\'ve been loving the Tasker app which is like the god when comes automation and utilizing every possible API and internal features of Android.\n\nSo I am not unrooted and back on stock Android beta experience and wanted to get the most out from what is possible with given privileges. Might as well share them. So with my beginner knowledge in Kotlin Jetpack and with the support of many research and assist tools and also the great community, I built an all-in-one app containing everything I wanted to be in my Android with given permissions. And here it is.\n\nFeature requests are welcome, I will consider and see if they are achievable with available permissions and my skills. Nowadays what is not possible. :)\n\nWhy not on Play Store?\nI don\'t wanna risk getting my Developer account banned due to the highly sensitive and internal permissions and APIs being used in the app. But with the way Android sideloading is headed, let\'s see what we have to do. I do understand the concerns of sideloaded apps being malicious.\nWhile we are at the topic, Checkout my other app AirSync if you are a mac + Android user. *shameless plug*\n\nEnjoy, Keep building! (っ◕‿◕)っ - Relatório de bug copiado para a área de transferência - Relatório de bug - Compartilhar registros - Incluir registros e detalhes - Informações do dispositivo - Relatório Bruto - Abrir problema do GitHub - Relatório por e-mail - Copiar para a área de transferência - Relatório de erros essenciais - Enviar por + Bug report copied to clipboard + Bug report + Share logs + Include logs and details + Device Info + Raw Report + Open GitHub Issue + Email Report + Copy to Clipboard + Essentials Bug Report + Please enable crash reporting as it will automatically report details that would help resolving issues. - Já chegamos? - Alertas de destino próximo - Abra o Google Maps, escolha um local e compartilhe-o no Essentials. - Raio de alerta: %d m - Localização - Usado para detectar a chegada ao seu destino. - Localização de fundo - Necessário para monitorar sua chegada enquanto o aplicativo está fechado ou a tela desligada. - Destino alcançado! - Você chegou ao seu destino. - Local de processamento… - DISTÂNCIA RESTANTE - Calculando… - Parar de rastrear - Destino pronto - Comece a rastrear - Ver mapa - Claro - Sem destino - Abrir mapas - Permissão de alarme em tela cheia - Necessário para ativar seu dispositivo na chegada. Toque para conceder. - %1$d eu - %1$,1f km - Alarme de viagem ativo - %1$s restante (%2$d%%) - Progresso da viagem - Mostra a distância em tempo real até o destino - Destino próximo - Prepare-se para descer - Liberar - Conjunto de destino: %1$.4f, %2$.4f - Usar raiz - Em vez de Shizuku - Acesso root não disponível. Por favor, verifique seu gerenciador root. + Are we there yet? + Destination nearby alerts + Open Google Maps, pick a location, and share it to Essentials. + Alert Radius: %d m + Location + Used to detect arrival at your destination. + Background Location + Required to monitor your arrival while the app is closed or the screen is off. + Destination Reached! + You have arrived at your destination. + Processing location… + DISTANCE REMAINING + Calculating… + Stop Tracking + Destination Ready + Start Tracking + View Map + Clear + No Destination + Open Maps + Full-Screen Alarm Permission + Required to wake your device upon arrival. Tap to grant. + %1$d m + %1$.1f km + Travel alarm active + %1$s remaining (%2$d%%) + Travel Progress + Shows real-time distance to destination + Destination Nearby + Prepare to get off + Dismiss + Destination set: %1$.4f, %2$.4f + Use Root + Instead of Shizuku + Root access not available. Please check your root manager. - Teclado - Chaves - Personalize o layout e o comportamento - Altura do teclado - Ajuste o tamanho vertical total do teclado - Preenchimento inferior - Adicione espaço abaixo do teclado - Feedback tátil - Vibrar ao pressionar a tecla - Teste o teclado - Altura do teclado - Preenchimento inferior - Feedback tátil - Redondeza chave - Mover funções para baixo - Funções de preenchimento lateral - Força do feedback tátil - Formato do teclado - Redondo - Plano - Inverso - Baterias - Monitore os níveis de bateria do seu dispositivo - Status da bateria - Conecte-se ao AirSync - Exibir a bateria do seu dispositivo Mac conectado no AirSync - Baixe o aplicativo AirSync - Necessário para sincronização de bateria do Mac - Notificação de bateria - Notificação persistente de status da bateria - Este aplicativo mostra a porcentagem da bateria dos seus dispositivos Mac e Bluetooth conectados. Você pode configurar quais dispositivos serão exibidos nas configurações do widget de bateria. - Replique a experiência do widget de bateria na sua aba de notificações. Ele mostrará os níveis de bateria de todos os seus dispositivos conectados em uma única notificação persistente, atualizada em tempo real. Isso inclui seu Mac (via AirSync) e acessórios Bluetooth. - Notificação de status da bateria - Notificação persistente mostrando os níveis de bateria dos dispositivos conectados - Dispositivos próximos - Necessário para detectar e recuperar informações da bateria de acessórios Bluetooth + Keyboard + Keys + Customize layout and behavior + Keyboard Height + Adjust the total vertical size of the keyboard + Bottom Padding + Add space below the keyboard + Haptic Feedback + Vibrate on key press + Test the keyboard + Keyboard Height + Bottom Padding + Haptic Feedback + Key Roundness + Move functions to bottom + Functions side padding + Haptic feedback strength + Keyboard shape + Round + Flat + Inverse + Batteries + Monitor your device battery levels + Battery Status + Connect to AirSync + Display battery from your connected mac device in AirSync + Download AirSync App + Required for Mac battery sync + Battery notification + Persistent battery status notification + This notification displays battery levels for your connected Mac and Bluetooth devices. You can configure which devices to show in the Battery Widget settings. + Replicate the battery widget experience in your notification shade. It will show the battery levels of all your connected devices in a single persistent notification, updated in real-time. This includes your Mac (via AirSync) and Bluetooth accessories. + Battery Status Notification + Persistent notification showing connected devices battery levels + Nearby Devices + Required to detect and retrieve battery information from Bluetooth accessories - Copiar código - Abrir página de login - Faça login para estender os limites de chamadas de API - Aguardando autorização... - Faça login com GitHub - sair - Perfil + Copy code + Open login page + Sign in to extend API call limits + Waiting for authorization... + Sign in with GitHub + Sign out + Profile - Notas de versão - Nenhum repositório rastreado ainda - Nenhum aplicativo vinculado - Atualizado %1$s + Release Notes + No repositories tracked yet + No app linked + Updated %1$s - agora mesmo - %1$dhá muito tempo - %1$dh atrás - %1$dd atrás - %1$dhá cerca de um mês - %1$dvocê atrás - Tentar novamente - Iniciar login - Solicitando código do dispositivo... - 1. Copie seu código: - 2. Cole o código no GitHub: - APKs encontrados - LEIA-ME - Atualizar + just now + %1$dm ago + %1$dh ago + %1$dd ago + %1$dmo ago + %1$dy ago + Retry + Start Sign In + Requesting device code... + 1. Copy your code: + 2. Paste the code on GitHub: + Found APKs + README + Refresh - Bloco de modo de som - Bloco QS para alternar o modo de som - Mostrar controle deslizante - Mostrar controle deslizante de volume no bloco - Comportamento do Ciclo - Escolha os modos para percorrer - Olhar de música ambiente - Dê uma olhada na mídia no AOD - Som e sensação tátil - Volume e recursos táteis - Segurança e privacidade - Proteja e proteja seu dispositivo - Notificações e Alertas - Nunca perca suas prioridades - Entrada e ações - Controle seu dispositivo com facilidade + Sound mode tile + QS tile to toggle sound mode + Show slider + Show volume slider in tile + Cycle Behavior + Choose modes to cycle through + Ambient music glance + Glance at media on AOD + Sound and Haptics + Volume and haptic features + Security and Privacy + Protect and secure your device + Notifications and Alerts + Never miss your priorities + Input and Actions + Control your device with ease Widgets - De relance na tela inicial - Mostrar - Recursos visuais para aprimorar sua experiência - Assistir - Integrações com WearOS - Nenhum relógio detectado - Parece que você não tem o aplicativo complementar Essentials Wear instalado no seu relógio. - Instalar companheiro - Interação + At a glance on your home screen + Display + Visuals to enhance your experience + Watch + Integrations with WearOS + No Watch detected + It looks like you do not have the Essentials Wear companion app installed on your watch. + Install Companion + Interaction Interface - Mostrar - Proteção - abc + Display + Protection + ABC \?#/ - Ei! Você pode verificar as atualizações nas configurações do aplicativo, não há necessidade de adicionar aqui XD - Exportar - Importar - Repositórios exportados com sucesso - Falha ao exportar repositórios - Repositórios importados com sucesso - Falha ao importar repositórios - Aplicativos - Escala e animações - Ajuste a escala e as animações do sistema - Texto - Escala de fonte - Peso da fonte - Reiniciar - Escala - Menor largura - É necessária permissão de Shizuku para ajustar a escala - Conceder permissão - Animações - Escala de duração do animador - Escala de animação de transição - Escala de animação de janela - Ajuste a escala da fonte, o peso e as velocidades de animação em todo o sistema. Observe que algumas configurações podem exigir permissões avançadas ou a reinicialização do dispositivo para determinados aplicativos para refletir as alterações. \n\nShizuku adicional ou permissão de root podem ser necessárias para ajustes de escala - Forçar desligamento do AOD - Forçar o desligamento do AOD quando não houver notificações. Requer permissão de acessibilidade. - Acessibilidade automática - Concede automaticamente a permissão de acessibilidade na inicialização do aplicativo, caso esteja faltando, usando WRITE_SECURE_SETTINGS. - Ajuda e guias - Seu Android - Armazenar - Memória - Usar desfoque - Habilite elementos de desfoque progressivo na IU - O desfoque está desativado neste dispositivo para evitar um bug de exibição conhecido em dispositivos Samsung com Android 15 ou inferior. + Oi! You can check updates in app settings, No need to add here XD + Export + Import + Repositories exported successfully + Failed to export repositories + Repositories imported successfully + Failed to import repositories + Apps + Scale and Animations + Adjust system scale and animations + Text + Font Scale + Font Weight + Reset + Scale + Smallest Width + Shizuku permission required to adjust scale + Grant Permission + Animations + Animator duration scale + Transition animation scale + Window animation scale + Adjust system-wide font scale, weight, and animation speeds. Note that some settings may require advanced permissions or a device reboot for certain apps to reflect changes. \n\nAdditional shizuku or root permission may be necessary for scale adjustments + Force turn off AOD + Force turn off the AOD when no notifications. Requires accessibility permission. + Auto accessibility + Automatically grants the accessibility permission on app launch if missing using WRITE_SECURE_SETTINGS. + Help and Guides + Your Android + Storage + Memory + Use blur + Enable progressive blur elements across the UI + Blur is disabled on this device to prevent a known display bug on Samsung devices with Android 15 or below. - Nenhum aplicativo selecionado para congelar. - Comece - Nova Automação - Adicionar repositório + No apps selected to freeze. + Get Started + New Automation + Add Repository diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index caf6ac750..3c51ac12e 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -137,6 +137,10 @@ Statistici de utilizare Necesar pentru a detecta ce aplicații sunt în prim-plan pentru a evita înghețarea lor Necesar pentru a detecta redarea media și notificările active pentru a evita înghețarea acestora + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Afișează numai când ecranul este oprit Omite notificările silențioase @@ -204,7 +208,12 @@ Pe Oprit DNS privat personalizat - Presetări DNS comune + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Numele de gazdă al furnizorului AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Deblocare dispozitiv Încărcătorul conectat Încărcătorul deconectat + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Încărcare Ecranul pornit Vibrați @@ -661,6 +677,8 @@ Porniți lanterna Opriți lanterna Comutați lanterna + Turn On Low Power Mode + Turn Off Low Power Mode Dim Wallpaper Această acțiune necesită Shizuku sau Root pentru a regla estomparea tapetului sistemului. Selectați Declanșare @@ -801,6 +819,8 @@ Temă mereu întunecată Tema pitch black Istoricul clipboard-ului + Long press for symbols + Accented characters listă culegător diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9e49a8e37..b70912997 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,6 +1,6 @@ - Основы + Essentials БЕТА Служба специальных возможностей Essentials\n\nЭта служба необходима для следующих функций:\n\n• Переназначение физических кнопок:\nОбнаруживает нажатие кнопок громкости, даже если экран выключен, чтобы вызывать такие действия, как фонарик.\n\n• Индивидуальные настройки приложений:\nОтслеживает текущее активное приложение для применения конкретных профилей для Динамической ночной подсветки, Цвета подсветки уведомлений и блокировку приложений.\n\n• Контроль экрана:\nПозволяет приложению блокировать экран (например, через двойное нажатие или виджет) и обнаруживать изменения состояния экрана.\n\n• Безопасность:\nПредотвращает несанкционированные изменения, обнаруживая содержимое окна, когда устройство заблокировано.\n\nНикакой вводимый текст или конфиденциальные данные не собираются и не передаются. Заморозка приложений @@ -10,205 +10,214 @@ Замороженные приложения Пустой виджет для отключения экрана Заморозка приложений - Фонарик Импульс - Проверьте наличие предварительных выпусков - Может быть нестабильно + Мигание фонариком + Проверять наличие pre-releases + Могут быть нестабильны Безопасность Включить блокировку приложения Безопасность блокировки приложений - Аутентификация, чтобы включить блокировку приложения + Пройдите аутентификацию, чтобы включить блокировку приложения Пройдите аутентификацию, чтобы отключить блокировку приложения Выбрать заблокированные приложения Выберите, какие приложения требуют аутентификации - Защитите свои приложения с помощью биометрической аутентификации. Заблокированные приложения потребуют аутентификации при запуске. Остаются разблокированными, пока экран не выключится. - Имейте в виду, что это ненадежное решение, поскольку это только стороннее приложение. Если вам нужна надежная безопасность, рассмотрите возможность использования Private Space или других подобных функций. - Еще одно замечание: запрос биометрической аутентификации позволяет использовать только СИЛЬНЫЕ методы класса безопасности. Методы безопасности разблокировки по лицу в классе WEAK на таких устройствах, как Pixel 7, смогут использовать только другие доступные методы аутентификации STRONG, такие как отпечаток пальца или PIN-код. + Защитите свои приложения с помощью биометрической аутентификации. Заблокированные приложения требуют аутентификации при запуске и остаются разблокированными до тех пор, пока экран не погаснет. + Имейте в виду, что это не надежное решение, поскольку это всего лишь стороннее приложение. Если вам нужна надежная защита, рассмотрите возможность использования личного пространства или других подобных функций. + Обратите внимание, что запрос на биометрическую аутентификацию позволяет использовать только методы класса STRONG secure. Методы разблокировки по лицу, относящиеся к классу WEAK, на устройствах, таких как Pixel 7, смогут использовать только другие доступные методы надежной аутентификации, такие как отпечаток пальца или PIN-код. Включить переназначение кнопок - Используйте Шизуку или Root или Root + Использовать Shizuku или Root-права Работает с выключенным экраном (рекомендуется) Shizuku не запущен Обнаружено %1$s Статус: %1$s - Открыть Шизуку + Открыть Shizuku Фонарик - Варианты фонарика + Настройки фонарика Отрегулируйте затухание и другие настройки Темная тема Использовать черный фон в темном режиме - Тактильная обратная связь + Тактильная отдача Переназначить долгое нажатие Экран выключен Экран включен Увеличение громкости Уменьшить громкость Переключить фонарик - Медиа-воспроизведение/пауза - СМИ дальше + Воспроизведение/пауза + Следующее медиа Предыдущее медиа - Переключить вибрацию - Отключить звук - ИИ-помощник - Сделать скриншот - Циклически переключать звуковые режимы - Нравится текущая песня - Нравятся настройки песни + Вибрация + Без звука + AI ассистент + Сделать снимок экрана + Переключение звуковых режимов + Лайкнуть песню + Лайкнуть настройки песни Для этой функции требуется доступ к уведомлениям для обнаружения воспроизводимого в данный момент мультимедиа и запуска аналогичного действия. Пожалуйста, включите его ниже. Показать всплывающее сообщение - Показать наложение на AOD - I dont know, pls help - Взгляните на СМИ на AOD + Показывать наложение на AOD + Фоновый обзор музыки + Обзор медиа в режиме AOD Закрепленный режим - Сохраняйте наложение видимым на неопределенный срок во время воспроизведения музыки на AOD. - Уведомление - Не отключать AOD, пока ожидаются уведомления. + Сохраняйте наложение видимым на неопределенный срок во время воспроизведения музыки на AOD + Обзор уведомлений + Не отключать AOD, пока ожидаются уведомления Те же приложения, что и подсветка уведомлений - Эта функция будет динамически включать функцию «Всегда на дисплее» при поступлении уведомления от выбранного приложения и отключать ее после закрытия всех соответствующих уведомлений. Выбирайте приложения или используйте тот же выбор, что и для подсветки уведомлений. + Эта функция будет динамически включать \"Всегда на дисплее\" при поступлении уведомления из выбранного приложения и отключать его, как только все соответствующие уведомления будут отклонены. Выберите приложения или используйте тот же параметр, что и подсветка уведомлений. Предоставить доступ к уведомлениям - Переключить громкость мультимедиа - Когда экран выключен, нажмите и удерживайте выбранную кнопку, чтобы вызвать назначенное ей действие. На устройствах Pixel это действие срабатывает только в том случае, если AOD включен из-за системных ограничений. - Когда экран включен, нажмите и удерживайте выбранную кнопку, чтобы вызвать назначенное ей действие. + Переключить громкость медиа + Когда экран выключен, нажмите и удерживайте выбранную кнопку, чтобы активировать назначенное действие. На устройствах Pixel это действие запускается только при включенном AOD из-за системных ограничений. + Когда экран включен, нажмите и удерживайте выбранную кнопку, чтобы активировать назначенное ей действие. Яркость фонарика Постепенное появление и исчезновение Плавное переключение фонарика - Глобальный контроль - Светящийся фонарик по всему миру + Глобальное управление + Глобальное затухание фонарика Отрегулируйте интенсивность - Громкость + - регулирует интенсивность фонарика. - Живое обновление + Громкость + - регулирует интенсивность фонаря + Индикатор яркости Показывать яркость в строке состояния - Другой - Всегда выключайте фонарик - Даже когда дисплей включен + Другое + Всегда выключать фонарик + Даже при включенном экране Настройки - Показать уведомление - Опубликовать уведомления - Позволяет приложению показывать уведомления - Предоставить разрешение + Показывать уведомления + Отправка уведомлений + Разрешить приложению отправку уведомлений + Выдать разрешение Caffeinate активен - Активный - Экран не активируется + Активен + Экран остаётся включенным Игнорировать оптимизацию батареи - Прервать с выключением экрана + Прервать, если экран выключен Пропустить обратный отсчет - Немедленно начните принимать кофеин. - Предустановки тайм-аута - Выберите доступную продолжительность для плитки QS + Запустить Caffeinate прямо сейчас. + Пресеты ожидания + Выберите доступные интервалы для плитки быстрого доступа 10м - 30 м + 30м Доступ к режиму «Не беспокоить» - Требуется для переключения между режимами звука, вибрации и отключения звука. - 1 час + Требуется для переключения между режимами звука, вибрации и отключения звука + - Начнём через %1$dс… - %1$s оставшийся - Постоянное уведомление о кофеинате + Запуск через %1$dс… + %1$s осталось + Постоянное уведомление о Caffeinate - Включить динамический ночной свет - Приложения, которые отключают ночник - Выберите приложения + Включает динамическую ночную подсветку + Приложения, отключающие ночную подсветку + Выбрать приложения Управление приложением Заморозить Разморозить - Больше возможностей + Больше настроек Заморозить все приложения Разморозить все приложения Экспортировать список замороженных приложений Импортировать список замороженных приложений - Выберите приложения, которые нужно заморозить - Выберите, какие приложения можно заморозить + Выбор приложений для заморозки + Выберите, какие приложения будут заморожены Автоматизация Заморозить при блокировке Задержка заморозки - Немедленный + Немедленно - 5 м - 15 м - Руководство + + 15м + Вручную Автоматическое замораживание приложений Заморозить выбранные приложения при блокировке устройства. Выберите задержку, чтобы избежать зависания приложений, если вы разблокируете экран вскоре после его выключения. - Замораживание системных приложений может быть опасным и может привести к неожиданному поведению. + Замораживание системных приложений опасно и может привести к неожиданным ошибкам. Включить в настройках - Не блокируйте активные приложения + Не замораживать активные приложения Статистика использования - Требуется для определения того, какие приложения в данный момент находятся на переднем плане, чтобы избежать их зависания. - Требуется для обнаружения воспроизводимого мультимедиа и активных уведомлений, чтобы избежать их зависания. + Необходимо для определения, какие приложения в данный момент отображаются, чтобы избежать зависания + Требуется для определения медиа и активных уведомлений, чтобы избежать их зависания + Режим заморозки + Заморожено + Приложение приостановлено + Невозможно переключить режим, пока приложения заморожены. Пожалуйста, разморозьте их и повторите попытку. - Показывать только при выключенном экране - Пропустить беззвучные уведомления - Пропустить постоянные уведомления - Фонарик Импульс - Импульс фонарика - Только лицом вниз + Показывать только если экран выключен + Пропускать уведомления без звука + Пропускать постоянные уведомления + Мигание фонариком + Мигание фонариком + Только экраном вниз Те же приложения, что и подсветка уведомлений Стиль Регулировка хода - Угловой радиус - Толщина штриха + Радиус закругления + Толщина обводки Регулировка свечения Распространение свечения Размещение - Горизонтальное положение - Вертикальное положение + Горизонтальная позиция + Вертикальная позиция Регулировка индикатора - Шкала + Размер Продолжительность Анимация - Подсчет пульса - Длительность импульса - Цветовой режим - Окружающий дисплей - Окружающий дисплей + Количество миганий + Длительность мигания + Цвет + Подсветка экрана + Подсветка экрана Подходит, если вы не используете AOD. - Включить экран и показать подсветку + Включает экран и подсветку Показать экран блокировки - Нет черного наложения + Без чёрного фона - Добавлять + Добавить Уже добавлено - Требуется Android 13+. - Размытие пользовательского интерфейса + Требуется Android 13+ + Размытие интерфейса Пузыри - Деликатный контент - Нажмите, чтобы разбудить - АОД + Чувствительный контент + Нажать для пробуждения + AOD Caffeinate - Звуковой режим - Освещение уведомлений - Динамический ночник - Заблокированная безопасность + Режим звука + Подсветка уведомлений + Динамический ночной свет + Заблокировать безопасность Блокировка приложения - Моно Аудио + Моно звук Фонарик - Зависание приложения - Фонарик Импульс - Бодрствуйте + Заморозка приложений + Мигание фонариком + Оставаться включенным Клавиатура Essentials Английский (США) Активный Неактивный - Параметры разработчика - Легко переключайте параметры разработчика системы с плитки QS. Это может привести к сбросу некоторых измененных вами настроек разработчика. + Настройки разработчика + Легко переключайте параметры разработчика системы с помощью плитки быстрых настроек. Это может привести к сбросу некоторых измененных вами настроек разработчика. NFC Частный DNS Авто - Выключенный - USB-отладка + Выключено + Отладка по USB Выбор цвета Ты уверен, что у тебя Android 17? (╯°_°)╯ - Глазная пипетка + Пипетка Включено Выключено Пользовательский частный DNS - Общие настройки DNS - Имя хоста поставщика + DNS пресет + Добавить DNS пресет + Имя пресета + Сбросить + Удалить пресет + Вы уверены, что хотите сбросить все DNS пресеты до значений по умолчанию? Это приведет к удалению всех ваших пользовательских настроек. + Имя хоста провайдера AdGuard DNS dns.adguard.com - Публичный DNS Google + Google Public DNS dns.google Cloudflare DNS 1dot1dot1dot1.cloudflare-dns.com @@ -218,25 +227,25 @@ adult-filter-dns.cleanbrowsing.org Зарядка Ограничить до 80% - Адаптивный - Не оптимизировано + Адаптивная + Без оптимизации Разрешение отсутствует Безопасность при заблокированном экране - Безопасность с заблокированным экраном + Безопасность при заблокированном экране Аутентификация для включения блокировки экрана Аутентификация для отключения блокировки экрана - ⚠️ ПРЕДУПРЕЖДЕНИЕ - Эта функция не является надежной. Могут быть крайние случаи, когда кто-то все еще сможет взаимодействовать с плиткой. \nТакже имейте в виду, что Android всегда позволяет выполнить принудительную перезагрузку, а Pixels также всегда позволяет выключать устройство с экрана блокировки. - Обязательно удалите плитку режима полета из быстрых настроек, поскольку это невозможно предотвратить, поскольку при этом не открывается диалоговое окно. - Если этот параметр включен, панель быстрых настроек будет немедленно закрыта, а устройство будет заблокировано, если кто-то попытается взаимодействовать с плитками Интернета, пока устройство заблокировано. \n\nЭто также отключит биометрическую разблокировку, чтобы предотвратить дальнейший несанкционированный доступ. Масштаб анимации будет уменьшен до 0,1x при блокировке, чтобы с ней было еще труднее взаимодействовать. + ⚠️ ВНИМАНИЕ + Эта функция не является надежной. В некоторых случаях пользователь все еще может взаимодействовать с плиткой. \nТакже имейте в виду, что Android всегда разрешает принудительную перезагрузку, а телефоны Pixel всегда позволяют отключить устройство с экрана блокировки. + Обязательно удалите плитку \"Режим полета\" из быстрых настроек, так как это невозможно предотвратить, поскольку она не открывает диалоговое окно. + Когда включено, панель быстрых настроек будет немедленно закрываться, а устройство будет блокироваться, если кто-то попытается взаимодействовать с интернет-плитками, пока устройство заблокировано. \n\nПри этом будет отключена биометрическая разблокировка, чтобы предотвратить дальнейший несанкционированный доступ. Масштаб анимации при блокировке будет уменьшен до 0,1x, чтобы сделать взаимодействие с устройством еще более затруднительным. - Изменение порядка режимов - Длительное нажатие для переключения - Перетащите, чтобы изменить порядок + Изменить порядок режимов + Зажмите для переключения + Перетащите для изменения порядка Звук Вибрация - Тихий + Без звука Возможности подключения Телефон и сеть @@ -246,60 +255,60 @@ Wi-Fi Bluetooth - NFC / Фелика + NFC / Felica VPN Режим полета Точка доступа - Бросать - Мобильные данные - Телефонный сигнал + Трансляция + Мобильный интернет + Звук телефона VoLTE / VoNR - Звонки по Wi-Fi / VoWiFi + Wi-Fi звонки / VoWiFi Статус вызова/синхронизация - телетайп - Объем - Гарнитура + TTY + Громкость + Наушники Громкая связь - ДМБ + DMB Часы Метод ввода (IME) Тревога Батарея Энергосбережение Экономия данных - Блокировка вращения - Местоположение / GPS - Синхронизировать + Блокировка поворота экрана + Геолокация + Синхронизация Управляемый профиль - Просьба не беспокоить - Конфиденциальность и безопасная папка + Не беспокоить + Конфиденциальность и защищенная папка Статус безопасности (SU) - OTG мышь/клавиатура - Умные функции Samsung - Сервисы Самсунг + OTG Мышь / Клавиатура + Интеллектуальные функции Samsung + Сервисы Samsung Ethernet - Показать секунды на часах + Показывать секунды в часах Процент заряда батареи Всегда - На зарядке + Зарядка Никогда Камера и микрофон используют чипы - Умные данные - Чтение состояния телефона - Требуется для определения типа сети для функции Smart Data. - Требуется для обнаружения изменений статуса вызова для запуска тактильной обратной связи. + Смарт-данные + Считывание состояния телефона + Требуется для определения типа сети для использования функции смарт-передачи данных + Требуется для обнаружения изменений статуса вызова для запуска тактильной отдачи. Умная видимость Умный Wi-Fi Скрыть мобильные данные при подключении Wi-Fi Скрыть мобильные данные в определенных режимах Сбросить все значки Дополнительные настройки - Обратите внимание, что реализация этих опций может зависеть от OEM-производителя, а некоторые могут вообще не работать. + Обратите внимание, что реализация этих функций может зависеть от производителя оригинального оборудования, а некоторые из них могут быть вообще недоступны. - Другой + Другое - Часы Секунды + Секунды в часах Показывать секунды в часах строки состояния Процент заряда батареи Настройка видимости процента заряда батареи @@ -317,36 +326,36 @@ Поиск Essentials Нет результатов для \"%1$s\" Результаты поиска - %1$s требуются следующие разрешения + %1$s требует следующие разрешения Виджет выключения экрана Невидимый виджет для выключения экрана Значки в строке состояния Управление видимостью значков в строке состояния Caffeinate - Держите экран включенным - Режим энергосбережения карт + Оставлять экран включенным + Режим энергосбережения карты Для любого устройства Android Подсветка уведомлений Свет для уведомлений - Импульсный фонарик для уведомлений + Мигание фонарика от уведомлений Плитка звукового режима Вибрация звонка - Вибрация для действий по вызову - Показать устройства Bluetooth - Отображение уровня заряда батареи подключенных устройств Bluetooth + Вибрация во время звонка + Показать Bluetooth устройства + Отображение уровня заряда батареи подключенных Bluetooth устройств Ограничить максимальное количество устройств Отрегулируйте максимальное количество устройств, видимых в виджете Фон виджета Показать фон виджета - Триггерная автоматизация - Запланируйте действие, которое будет запускаться по наблюдению - Государственная автоматизация - Запланируйте действие для выполнения в зависимости от состояния входного и выходного условия. + Автоматизация по триггеру + Запланируйте действие, которое будет запускаться по триггеру + Автоматизация по состоянию + Запланируйте действие, которое будет запускаться в зависимости от состояния входного и выходного условия Новая автоматизация Редактировать автоматизацию - Связать действия + Действия для ссылок Обработка ссылок с несколькими приложениями Отложить системные уведомления Отложить постоянные уведомления @@ -354,8 +363,8 @@ Посмотреть все Переназначение кнопок Переназначение действий аппаратных кнопок - Динамический ночник - Переключить ночник на основе приложения + Динамический ночной свет + Ночной свет по приложениям Безопасность при заблокированном экране Запретить сетевой контроль Блокировка приложения @@ -363,7 +372,7 @@ Заморозить Отключите редко используемые приложения Водяной знак - Добавляйте данные EXIF ​​и логотипы к фотографиям + Добавить EXIF данные ​​и логотипы к фотографиям Всегда на дисплее Показывать время и информацию при выключенном экране Синхронизация календаря @@ -373,15 +382,15 @@ Марка устройства EXIF-данные Выберите изображение - Изображение сохранено в галерее. - Делиться + Изображение сохранено в галерее + Поделиться Настройки EXIF Фокусное расстояние Диафрагма - ИСО + ISO Скорость затвора Дата и время - Перейти наверх + Переместить наверх Выровнять по левому краю Размер бренда Размер данных @@ -390,32 +399,32 @@ Пользовательский текст Введите текст... Расстояние - Ширина границы - Круглые углы + Ширина обводки + Закругление углов Цвет Логотип Показать логотип Размер логотипа - Редактировать тексты водяных знаков - Марка устройства + Редактировать текст водяного знака + Бренд устройства Дата и время Нет информации о дате Повернуть влево Повернуть вправо Следующий - ХОРОШО + ОК Сохранить изменения Настройки синхронизации календаря Синхронизировать определенные календари Периодическая синхронизация - Синхронизируйте каждые 15 минут, если обнаружены изменения. + Синхронизируйте каждые 15 минут, если обнаружены изменения Синхронизировать сейчас Запустить немедленную синхронизацию для просмотра - Местные календари не найдены + Локальные календари не найдены Синхронизация календаря началась - Виджет Тактильная обратная связь - Выберите тактильную обратную связь для нажатий виджета + Виджет тактильной отдачи + Выберите тактильную отдачу для нажатий виджета Умный Wi-Fi Скрыть мобильные данные при подключении Wi-Fi Умные данные @@ -425,56 +434,56 @@ Отменить запуск Caffeinate при выключенном экране Автоматически отключать Caffeinate при ручной блокировке устройства Стиль освещения - Выбирайте между «Обводкой», «Свечением», «Спиннером» и т. д. - Угловой радиус + Выбирайте между «Обводкой», «Свечением», «Спиннером» и другое + Радиус закругления Отрегулируйте угловой радиус подсветки уведомлений Пропустить беззвучные уведомления Не показывать подсветку для беззвучных уведомлений - Импульс фонарика + Мигание фонариком Медленное мигание фонарика при появлении новых уведомлений - Только лицом вниз - Импульсный фонарик только тогда, когда устройство находится лицевой стороной вниз + Только экраном вниз + Мигать фонариком только тогда, когда устройство находится экраном вниз Системные каналы пока не обнаружены. Они появятся здесь после обнаружения. - Размытие пользовательского интерфейса - Включить общесистемное размытие пользовательского интерфейса + Размытие интерфейса + Переключить системное размытие интерфейса Пузыри Включить всплывающие пузыри в окнах - Деликатный контент + Чувствительный контент Скрыть детали уведомлений на экране блокировки - Нажмите, чтобы разбудить - Нажмите дважды, чтобы активировать контроль - АОД + Нажать для пробуждения + Нажать дважды, чтобы активировать контроль + AOD Переключатель «Всегда на дисплее» Caffeinate - Переключить экран в активный режим - Звуковой режим + Оставлять экран включенным + Режим звука Переключение режимов звука (Звонок/Вибрация/Без звука) - Освещение уведомлений + Подсветка уведомлений Переключить службу подсветки уведомлений - Динамический ночник - Переключатель автоматизации ночного освещения + Динамический ночной свет + Переключатель автоматизации ночного света Заблокированная безопасность Сетевая безопасность при переключении экрана блокировки - Моно Аудио - Принудительное переключение вывода монофонического звука + Моно звук + Принудительное переключение вывода моно звука Фонарик Специальный переключатель фонарика - Зависание приложения - Запустить сетку блокировки приложения - Фонарик Импульс - Переключить пульс фонарика уведомлений - Переключить опцию разработчика «Не спать» + Заморозка приложения + Запустить сетку заморозки приложения + Мигание фонарика + Переключить мигание фонарика уведомлений + Переключить опцию разработчика «Не отключать экран» Частный DNS Циклическое переключение режимов частного DNS (Выкл./Авто/Имя хоста) - USB-отладка + Отладка по USB Переключить опцию разработчика отладки по USB Включить переназначение кнопок Главный переключатель для переназначения кнопки громкости - Переназначение тактильной обратной связи - Вибрационная обратная связь при нажатии переназначенной кнопки + Переназначение тактильной отдачи + Вибрационная отдача при нажатии переназначенной кнопки Переключение фонарика Переключение фонарика кнопками громкости - Включить динамический ночной свет + Включить динамическую ночную подсветку Главный выключатель динамического ночного освещения Включить блокировку приложения Главный переключатель для блокировки приложений @@ -494,30 +503,30 @@ Установить Shizuku Предоставить разрешение Требуется для запуска команд энергосбережения во время навигации по картам. - Требуется Shizuku или Root-доступ + Требуется Shizuku или Root-права Корневой доступ Разрешения, необходимые для системных действий с использованием root-прав. Прослушиватель уведомлений - Требуется доступ к прослушивателю уведомлений для отслеживания состояния навигации на Картах Google и включения энергосбережения, когда навигация не осуществляется. + Требуется доступ к прослушивателю уведомлений для отслеживания состояния навигации на Google Картах и включения энергосбережения, когда навигация не осуществляется. Требуется доступ к прослушивателю уведомлений для обнаружения новых уведомлений и включения боковой подсветки. Требуется доступ к прослушивателю уведомлений для отслеживания и откладывания нежелательных системных уведомлений. Служба доступности - Требуется для блокировки приложений, виджета отключения экрана и других функций для обнаружения взаимодействий. - Требуется для включения подсветки уведомлений о новых уведомлениях. + Требуется для блокировки приложений, виджета отключения экрана и других функций обнаружения взаимодействий + Требуется для включения подсветки уведомлений на новые уведомления Браузер по умолчанию Требуется для эффективной обработки ссылок Требуется для перехвата событий аппаратных кнопок - Требуется для перехвата событий клавиши регулировки громкости при выключенном экране для запуска наложения Ambient Glance. + Требуется для перехвата событий клавиши регулировки громкости при выключенном экране для запуска наложения фонового обзора. Требуется для мониторинга приоритетных приложений. Запись настроек безопасности - Требуется для значков в строке состояния и защиты при блокировке экрана. - Требуется для переключения ночного света. Предоставить через ADB или root. + Требуется для значков в строке состояния и защиты при блокировке экрана + Требуется для переключения ночного света. Предоставить через ADB или Root-права. Изменить настройки системы - Требуется для переключения адаптивной яркости и других системных настроек. + Требуется для переключения адаптивной яркости и других системных настроек Разрешение наложения - Требуется для отображения наложения подсветки уведомлений на экране. + Требуется для отображения наложения подсветки уведомлений на экране Администратор устройства - Требуется жесткая блокировка устройства (отключение биометрии) при попытках несанкционированного доступа. + Требуется жесткая блокировка устройства (отключение биометрии) при попытках несанкционированного доступа Предоставить разрешение Копировать ADB Проверить @@ -528,8 +537,8 @@ Essentials Заморозить - Замороженный - Сделай сам + Заморожен + DIY Приложения Отключенные приложения Сделай это сам @@ -542,24 +551,24 @@ Отслеживать В последней версии APK не найден. Репозиторий не найден - Последний выпуск + Последний релиз Посмотреть README %d Звезды Установленное приложение Не установлено Выбрать приложение Выберите приложение - Отследить + Не отслеживать В ожидании - Актуальные + Актуально Отслеживайте и загружайте последние версии ваших любимых приложений прямо с GitHub. - Неверный формат. Используйте URL-адрес владельца/репозитория или GitHub. + Неверный формат. Используйте URL-адрес owner/repo или GitHub Во время поиска произошла ошибка Авто Параметры - Проверьте наличие предварительных выпусков + Проверьте наличие pre-releases Уведомления - Превышен лимит скорости GitHub. Пожалуйста, повторите попытку позже. + Превышен лимит запросов GitHub. Пожалуйста, повторите попытку позже. Настройка клавиатуры Включить в настройках @@ -567,7 +576,7 @@ Включено Выключено Адаптивная яркость - Карты Энергосбережение + Энергосбережение на карте Поиск Стоп Поиск @@ -581,19 +590,19 @@ Выкл Авто Сбой Essentials, Отчёт отправлен - Simulate crash + Имитировать краш Добро пожаловать в Essentials Набор инструментов для фанатов Android сделано sameerasw.com Давайте начнём - Acknowledgement + Подтверждение Это приложение представляет собой набор утилит, которые могут глубоко взаимодействовать с системой вашего устройства. Использование некоторых функций может привести к неожиданным результатам. \n\nВам нужно предоставить приложению только необходимые разрешения для выбранных функций, которые вы используете, это обеспечит наибольшую стабильность и полный контроль над приложением. \n\nБолее того, приложение не отслеживает и не хранит ваши личные данные, они мне не нужны... Берегите себя. Вы можете обратиться к исходному коду для получения дополнительной информации. \n\nЭто приложение имеет полностью открытый исходный код и всегда будет бесплатным. Не покупайте и не устанавливайте его из неизвестных источников. ВНИМАНИЕ: Используйте на свой страх и риск. Разработчик не отвечает за возникновение нестабильности системы, потери данных и прочих ошибок во время использования приложения. Продолжая, вы подтверждаете свое согласие с данными рисками. - Я знаю что ты даже не вчитывался, но в случае если тебе нужна помощь, не стесняйся обратиться к разработчику и сообществу. + Я знаю что ты даже не вчитывался, но в случае, если тебе нужна помощь, не стесняйся обратиться к разработчику и сообществу. Я понимаю - Anytime you are clueless on a feature or a Quick Settings Tile on what it does and what permissions may necessary for it, just long press it and pick \'What is this?\' to learn more. + Если вы не знаете, что делает функция или панель быстрых настроек и какие разрешения для нее могут потребоваться, просто нажмите на нее и выберите \"Что это?\", чтобы узнать больше. Вы можете сообщить об ошибке или найти полезные руководства в настройках приложения в любой момент. - Впустите меня уже + Ну давай же, впусти меня Свойства Выберите некоторые базовые настройки, чтобы начать. Настройки приложения @@ -601,51 +610,51 @@ Обновления Автоматическая проверка обновлений Проверять наличие обновлений при запуске приложения - All Set - Проверьте Что нового? - Сделанный - Предварительный просмотр - Справочное руководство + Отметить всё + Проверьте \"Что нового?\" + Готово + Просмотр + Руководство Что это? Доступно обновление - Glance at your device\'s hardware and software specifications in detail. This information is fetched from GSMArena and system properties to provide a comprehensive overview of your Android device. - Ambient Music Glance отображает наложение «Сейчас исполняется» на экране блокировки, когда воспроизводится музыка и изменяется воспроизведение. \n\nЕсли ваше устройство не поддерживает наложение поверх AOD, вы можете выбрать заставку Ambience, добавленную в настройки Android, в качестве альтернативы во время зарядки. + Подробно ознакомьтесь со спецификациями аппаратного и программного обеспечения вашего устройства. Эта информация взята из GSMArena и системных свойств, чтобы предоставить полный обзор вашего Android-устройства. + Фоновый обзор музыки включает «Сейчас исполняется» на экране блокировки, когда воспроизводится музыка и изменяется воспроизведение. \n\nЕсли ваше устройство не поддерживает наложение поверх AOD, вы можете выбрать фоновую заставку, добавленную в настройки Android, в качестве альтернативы во время зарядки. Подсветка уведомлений добавляет красивый эффект боковой подсветки при получении уведомлений.\n\nВы можете настроить стиль, цвета и поведение анимации. Он работает, даже когда экран выключен (зависит от OEM) или находится поверх текущего приложения. Выберите приложения, приоритет уведомлений или поведение, при котором оно должно запускаться, с помощью заданных элементов управления. Если ваш OEM-производитель не поддерживает наложения выше AOD, подайте в суд на опцию Ambient display, указанную ниже. Легко выключите экран, коснувшись прозрачного виджета с изменяемым размером, который не добавляет значков или беспорядка на главный экран. Получите полный контроль над значками строки состояния.\n\nСкрывайте определенные значки, например Wi-Fi, Bluetooth или данные сотовой связи, чтобы строка состояния оставалась чистой. Вы также можете настроить формат часов и индикатор заряда батареи с помощью интеллектуальных элементов управления. Это список доступных элементов управления AOSP, поэтому ОС вашего устройства может не поддерживать все элементы управления. - Кофеинат предотвращает автоматическое выключение экрана.\n\nНе допускайте отключения экрана в течение определенного времени или на неопределенный срок. Полезно при чтении длинных статей или ссылок на рецепты. + Caffeinate предотвращает автоматическое выключение экрана.\n\nНе допускайте отключения экрана в течение определенного времени или на неопределенный срок. Полезно при чтении длинных статей или ссылок на рецепты. Получите эксклюзивный режим энергосбережения Google Maps для серии Pixel 10 с минимальным черным фоном для отображения на экране блокировки на любом устройстве Android. Запустите сеанс навигации, выключите и снова включите экран. - Включите импульсный свет фонарика при получении уведомления.\n\nЕсли устройства имеют аппаратную поддержку затемнения фонарика, импульс будет плавно анимироваться. - Отложить раздражающие постоянные системные уведомления, которые по умолчанию нельзя изменить. \n\nПодождите, пока не придет уведомление, а затем перейдите к этой функции, где будет указан канал уведомлений\'. Выберите это значение, чтобы отложить уведомление в следующий раз.\n\nЛюбое отложенное уведомление по-прежнему можно просмотреть в истории уведомлений в Android. + Включите мигание фонарика при получении уведомления.\n\nЕсли устройства имеют аппаратную поддержку затемнения фонарика, мигание будет плавно анимироваться. + Отложите постоянные раздражающие системные уведомления, которые по умолчанию нельзя отключить. \n\nПодождите, пока не придет уведомление, а затем перейдите к этой функции, где будет указан канал уведомлений. Выберите этот канал, чтобы отложить уведомление в следующий раз.\n\nЛюбое отложенное уведомление по-прежнему можно просмотреть в истории уведомлений в Android. Добавьте пользовательские плитки на панель быстрых настроек.\n\nНажмите и удерживайте любой из них, чтобы узнать, что они делают. Переназначьте аппаратные кнопки для выполнения различных действий и ярлыков.\n\nНастройте то, что происходит при длительном нажатии кнопок громкости при определенных условиях. \n\nНекоторые функции, такие как триггер выключения экрана или элементы управления фонариком, могут зависеть от OEM-производителя и работать не на всех устройствах должным образом. Некоторые сценарии можно обойти, используя разрешения Shizuku, но из-за реализаций они могут не дать такого же результата. Автоматически переключайте фильтр синего света экрана на основе приложения на переднем плане. - Повысьте безопасность, когда ваше устройство заблокировано.\n\nОграничьте доступ к некоторым конфиденциальным плиткам QS, предотвращая несанкционированные изменения в сети и дополнительно предотвращая их повторные попытки сделать это, увеличивая скорость анимации для предотвращения сенсорного спама.\n\nЭта функция не является надежной и может иметь недостатки, например, некоторые плитки, которые позволяют напрямую переключаться, например Bluetooth или Режим полета невозможно предотвратить. + Повысьте безопасность, когда ваше устройство заблокировано.\n\nОграничьте доступ к некоторым конфиденциальным плиткам быстрых настроек, предотвращая несанкционированные изменения в сети и дополнительно предотвращая их повторные попытки сделать это, увеличивая скорость анимации для предотвращения сенсорного спама.\n\nЭта функция не является надежной и может иметь недостатки. Hапример, некоторые плитки, которые позволяют переключаться напрямую и которые невозможно предотвратить (Bluetooth или Режим полета). Защитите свои приложения с помощью вторичного уровня аутентификации.\n\nМетод аутентификации на экране блокировки вашего устройства будет использоваться, если он соответствует уровню биометрической безопасности класса 3 по стандартам Android. - Получите уведомление, когда вы приблизитесь к пункту назначения, чтобы не пропустить остановку.\n\nПерейдите на Карты Google, нажмите и удерживайте булавку рядом с пунктом назначения и убедитесь, что там написано «Упала булавка» (в противном случае расчет расстояния может быть неточным), а затем поделитесь местоположением с приложением Essentials и начните отслеживать. + Получите уведомление, когда вы приблизитесь к пункту назначения, чтобы не пропустить остановку.\n\nПерейдите на Google Карты, нажмите и удерживайте рядом с пунктом назначения и убедитесь, что открылось меню с описанием места, а затем поделитесь местоположением с приложением Essentials и начните отслеживать. Заморозьте приложения, чтобы они не работали в фоновом режиме.\n\nПредотвратите разрядку аккумулятора и использование данных, полностью заморозив приложения, когда вы ими не пользуетесь. Они будут разморожены мгновенно при запуске. Приложения не будут отображаться в панели приложений, а также не будут отображаться в обновлениях приложений в Play Store, если они заморожены. Пользовательский метод ввода, о котором никто не просил.\n\nЭто всего лишь эксперимент. Несколько языков могут не поддерживаться, поскольку это очень сложная и трудоемкая реализация. Контролируйте уровень заряда батареи всех подключенных устройств.\n\nПросматривайте состояние батареи наушников, часов и других аксессуаров Bluetooth в одном месте. Подключитесь к приложению AirSync, чтобы отобразить уровень заряда батареи вашего Mac. Добавьте к своим фотографиям собственную подпись/водяной знак с данными EXIF ​​и информацией об устройстве.\n\nОтправьте изображение прямо из другого приложения в Essentials, чтобы легко добавить водяной знак. Синхронизируйте все предстоящее расписание календаря, независимо от ограничений учетных записей Google, которые не позволяют добавлять его на устройства WearOS из-за рабочих или учебных правил. \n\nОбязательно установите сопутствующее приложение WearOS Essentials, чтобы расписание отображалось в приложении, а также на плитке или в расширении. - Следите за обновлениями установленных приложений.\n\nПолучайте уведомления о доступных обновлениях, просматривайте журналы изменений и легко устанавливайте их одним касанием. - Добавьте к своим звонкам тактильную обратную связь.\n\nВибрация при подключении, отключении или принятии вызова, давая вам тактильное подтверждение, не глядя на экран. + Следите за обновлениями установленных приложений.\n\nПолучайте уведомления о доступных обновлениях, просматривайте журналы изменений и легко устанавливайте их одним нажатием. + Добавьте к своим звонкам тактильную отдачу.\n\nВибрация при подключении, отключении или принятии вызова, давая вам тактильное подтверждение, не глядя на экран. Быстро переключайтесь между режимами «Звук», «Вибрация» и «Без звука».\n\nУдобная плитка для изменения режима звонка без использования кнопок громкости или настроек. Вы можете изменить порядок режимов или отключить любой из них, если это не требуется, чтобы настроить переключение плиток для циклического поведения. Легко переключайте эффект глубины размытия на системном уровне в ОС. Включите или отключите плавающие всплывающие уведомления.\n\nБыстро переключайте общесистемные настройки для всплывающих окон разговоров. Скрыть конфиденциальное содержимое на экране блокировки.\n\nПереключите, будет ли отображаться или скрываться содержимое уведомлений, когда ваше устройство заблокировано. - Переключите функцию пробуждения касанием.\n\nВключите или отключите возможность пробуждения экрана касанием. + Переключите функцию пробуждения нажатием.\n\nВключите или отключите возможность пробуждения экрана нажатием. Переключить всегда включенный дисплей.\n\nБыстро включите или отключите всегда включенный дисплей, чтобы сразу просмотреть информацию. - Автоматически управляйте функцией Always On Display на основе ваших уведомлений. Когда сообщение или оповещение поступает из выбранного приложения, AOD будет оставаться включенным до тех пор, пока вы не закроете уведомление, гарантируя, что вы никогда не пропустите важную информацию, не тратя заряд батареи при отсутствии оповещений. - Объедините аудиоканалы в моно.\n\nПолезно при использовании одного наушника или в целях доступности. + Автоматически управляйте функцией Всегда на дисплее на основе ваших уведомлений. Когда сообщение или оповещение поступает из выбранного приложения, AOD будет оставаться включенным до тех пор, пока вы не закроете уведомление, гарантируя, что вы никогда не пропустите важную информацию, не тратя заряд батареи при отсутствии оповещений. + Объедините аудио в моно.\n\nПолезно при использовании одного наушника или в целях доступности. Включите фонарик.\n\nДлительное нажатие открывает элементы управления для регулировки интенсивности, для чего может потребоваться аппаратная реализация, которой может не хватать на некоторых устройствах. Не отключайте экран во время зарядки.\n\nПредотвращает переход экрана в спящий режим, пока устройство подключено к источнику питания, подходящему для разработчиков во время отладки. - Переключите NFC.\n\nБыстро включите или отключите Near Field Communication для платежей и сопряжения. + Переключите NFC.\n\nБыстро включите или отключите NFC для платежей и сопряжения. Переключить адаптивную яркость.\n\nВключите или отключите автоматическую регулировку яркости экрана в зависимости от окружающего освещения. - Переключите частный DNS.\n\nПереключайте режимы провайдера «Выкл.», «Автоматический» и «Частный DNS». + Переключите частный DNS.\n\nПереключайте режимы провайдера «Выкл», «Автоматический» и «Частный DNS». Переключить отладку по USB.\n\nВключите или отключите доступ к отладке ADB непосредственно из быстрых настроек. - Запустите инструмент «Пипетка», чтобы выбирать цвета, представленные в Android 17 BETA 2. - Оптимизируйте срок службы аккумулятора, ограничив максимальный заряд или используя адаптивную зарядку. Он специально разработан для устройств Pixel, чтобы обеспечить долговечность и бесперебойность циклов зарядки.\n\nИсточники: TebbeUbben/ChargeQuickTile. + Запустите инструмент «Пипетка», чтобы выбирать цвета, представленные в Android 17 BETA 2 + Оптимизируйте срок службы аккумулятора, ограничив максимальный заряд или используя адаптивную зарядку. Он специально разработан для устройств Pixel, чтобы обеспечить долговечность и бесперебойность циклов зарядки.\n\nИсточники: TebbeUbben/ChargeQuickTile Скачать Экран выключен @@ -653,6 +662,13 @@ Разблокировка устройства Зарядное устройство подключено Зарядное устройство отключено + Планировщик + Временной период + Выбрать время + Выбрать отрезок времени + Время старта + Время окончания + Повторять Зарядка Экран включен Вибрация @@ -661,15 +677,17 @@ Включить фонарик Выключить фонарик Переключить фонарик - Тусклые обои - Для этого действия требуется Shizuku или Root для настройки затемнения системных обоев. + Включить режим энергосбережения + Выключить режим энергосбережения + Затемнённые обои + Для этого действия требуется Shizuku или Root-права для настройки затемнения системных обоев. Выберите триггер Приложение Автоматизация на основе открытого приложения - Выберите штат + Выберите состояние Выберите действие - В действии - Выходное действие + На включение + На выключение Отмена Сохранять Редактировать @@ -683,26 +701,26 @@ Управляйте эффектами на уровне системы, такими как оттенки серого, подавление AOD, затемнение обоев и ночной режим. Оттенки серого Подавить окружающее отображение - Тусклые обои + Затемнённые обои Ночной режим Для этой функции требуется Android 15 или более поздняя версия. Включено - Неполноценный + Выключено Звуковой режим Это действие позволяет переключаться между режимами «Звук», «Вибрация» и «Без звука» на основе триггеров. Требуется доступ к режиму «Не беспокоить». - Самира Виджератна - Универсальный набор инструментов для вашего Pixel и Android. + Sameera Wijerathna + Универсальный набор инструментов для вашего Pixel и Android Система Обычай Специально для приложения - Аутентификация не удалась - Нажмите и удерживайте приложение в сетке, чтобы добавить ярлык. + Не удалось выполнить аутентификацию + Нажмите и удерживайте приложение в сетке, чтобы добавить ярлык Приложение не найдено или удалено - Обновления приложений + Обновления приложения Уведомления о новых обновлениях приложения Доступно обновление Нет подключенных устройств @@ -710,28 +728,28 @@ 5G 4G 3G - Шизуку (Рикка) - Шизуку (ТуоЗи) + Shizuku (Rikka) + Shizuku (TuoZi) Поиск Требуется для жесткой блокировки устройства при попытке несанкционированного изменения сети на экране блокировки. Аутентификация для доступа к настройкам %1$s Настройки особенность настройки - скрывать - показывать + скрыть + показать видимость Ошибка загрузки приложений: %1$s вибрация - трогать + нажать чувствовать сеть видимость авто - скрывать + скрыть восстановить @@ -741,7 +759,7 @@ клавиатура высота - прокладка + отступ тактильный вход @@ -752,13 +770,13 @@ свет факел - пульс + мигание уведомление бодрствующий разработчик - власть + питание заряжать @@ -789,9 +807,9 @@ тайм-аут - трогать - будить - отображать + нажать + включить + экран таймер @@ -801,6 +819,8 @@ Всегда темная тема Темная тема История буфера обмена + Длительное нажатие на символы + Символы с ударением список сборщик @@ -822,8 +842,8 @@ замок - ADB - USB + adb + usb отладка @@ -884,13 +904,13 @@ Инвертировать выделение Показать системные приложения - Вы в курсе + Последняя версия Это предварительная версия, которая может работать нестабильно. Примечания к выпуску %1$s Посмотреть на GitHub Скачать APK - Никто + Нет Тонкий Двойной Нажмите @@ -908,45 +928,45 @@ Поддерживать Другие приложения AirSync - ДзенЗеро - Холст - Задачи - Ноль + ZenZero + Canvas + Tasks + Zero Помощь и руководства Нужна дополнительная помощь? Обратитесь к нам, Свернуть - Расширять - Группа поддержки + Развернуть + Группа с поддержкой Электронная почта Отправить письмо Приложение электронной почты недоступно - Step %1$d Изображение + Шаг %1$d Изображение Разрешения на доступность, уведомления и наложение Вы можете получить это сообщение об отказе в доступе, если попытаетесь предоставить конфиденциальные разрешения, такие как специальные возможности, прослушиватель уведомлений или разрешения наложения. Чтобы предоставить его, выполните следующие действия. 1. Перейдите на страницу информации о приложении Essentials. 2. Откройте трехточечное меню и выберите \'Разрешить ограниченные настройки\'. Возможно, вам придется пройти аутентификацию с помощью биометрии. После этого попробуйте предоставить разрешение еще раз. - Шизуку - Shizuku — это мощный инструмент, который позволяет приложениям использовать системные API напрямую с правами ADB или root. Он необходим для таких функций, как минимальный режим Карт и App Freezer. И мы постараемся предоставить некоторые разрешения, такие как WRITE_SECURE_SETTINGS. \n\nНо версия Shizuku в Play Store может быть устаревшей и, вероятно, будет непригодна для использования в последних версиях Android, поэтому в этом случае получите последнюю версию с github или ее обновленную вилку. + Shizuku + Shizuku — это мощный инструмент, который позволяет приложениям использовать системные API напрямую с правами ADB или Root-правами. Он необходим для таких функций, как минимальный режим карты и Заморозка приложений. И мы постараемся предоставить некоторые разрешения, такие как WRITE_SECURE_SETTINGS. \n\nНо версия Shizuku в Play Store может быть устаревшей и, вероятно, будет непригодна для использования в последних версиях Android, поэтому в этом случае получите последнюю версию с GitHub или его обновленный форк. Режим энергосбережения карт Эта функция автоматически запускает режим энергосбережения Google Maps, который в настоящее время доступен только для серии Pixel 10. Член сообщества обнаружил, что его по-прежнему можно использовать на любом устройстве Android, запустив действие minMode карт с правами root. \n\nА затем я автоматизировал его с помощью Tasker для автоматического запуска при выключении экрана во время сеанса навигации, а затем смог добиться того же, используя только разрешения Shizuku во время выполнения. \n\nЭто предназначено для показа над AOD серии Pixel 10, поэтому на дисплее может время от времени появляться сообщение о том, что он не поддерживает ландшафтный режим. Приложение не может этого избежать, и вы можете игнорировать это. - Тихий звуковой режим - Возможно, вы заметили, что беззвучный режим также запускает режим «Не беспокоить». \n\nЭто связано с тем, как это реализовано в Android: даже если мы используем тот же API для переключения в режим вибрации, он по какой-то причине включает «Не беспокоить» вместе с беззвучным режимом, и в данный момент этого невозможно избежать. :( + Режим \"Без звука\" + Возможно, вы заметили, что режим \"Без звука\" также запускает режим «Не беспокоить». \n\nЭто связано с тем, как это реализовано в Android: даже если мы используем тот же API для переключения в режим вибрации, он по какой-то причине включает «Не беспокоить» вместе с режимом \"Без звука\", и в данный момент этого невозможно избежать. :( Что такое заморозка? Делайте паузу и не отвлекайтесь на приложения, сохраняя при этом немного энергии, предотвращая работу приложений в фоновом режиме. Подходит для редко используемых приложений. \n\nНе рекомендуется для любых служб связи, поскольку они не уведомят вас в чрезвычайной ситуации, пока вы их не разморозите. \n\nНастоятельно рекомендуется не замораживать системные приложения, поскольку они могут привести к нестабильности системы. Действуйте осторожно, вас предупредили. \n\nВдохновлено Hail <3 Действительно ли безопасна блокировка приложений и блокировка экрана? - Абсолютно нет. \n\nЛюбое стороннее приложение не может на 100 % мешать обычному взаимодействию с устройством, и даже блокировка приложений — это всего лишь наложение над выбранными приложениями, предотвращающее взаимодействие с ними. Существуют обходные пути, но они не являются надежными. \n\nТо же самое касается функции безопасности блокировки экрана, которая обнаруживает, что кто-то пытается взаимодействовать с сетевыми плитками, которые по какой-то причине все еще доступны для всех на Pixels. Поэтому, если они приложат все усилия, они все равно смогут их изменить, особенно если у вас добавлена плитка QS режима полета, это приложение не сможет предотвратить взаимодействие с ним. \n\nЭти функции созданы в качестве экспериментов для легкого использования и никогда не рекомендуются в качестве надежных решений для обеспечения безопасности и конфиденциальности. \n\nSecure Альтернативы:\n - Блокировка приложения: личное пространство и безопасная папка на Pixels и Samsung\n - Предотвращение доступа к мобильным сетям: убедитесь, что ваша защита от кражи и автономный режим/отключение питания обнаруживают, что настройки моего устройства включены. Вы также можете изучить Graphene OS. + Точно нет. \n\nНи одно стороннее приложение не может на 100% вмешиваться в обычные взаимодействия с устройствами, и даже блокировка приложений - это всего лишь наложение поверх выбранных приложений для предотвращения взаимодействия с ними. Существуют обходные пути, и они не являются надежными. \n\nТо же самое относится и к функции безопасности screen locked, которая обнаруживает, что кто-то пытается взаимодействовать с сетевыми плитками, которые по какой-то причине доступны для всех Pixels. Так что, если они приложат достаточно усилий, они все равно смогут изменить их, и особенно если у вас добавлена плитка быстрых настроек в режиме полета, это приложение не сможет предотвратить взаимодействие с ней. \n\nЭти функции созданы в качестве экспериментов для легкого использования и никогда не рекомендуются в качестве надежных решений для обеспечения безопасности и конфиденциальности. \n\nБезопасные льтернативы:\n - Блокировка приложения: личное пространство и безопасная папка на Pixels и Samsung\n - Предотвращение доступа к мобильным сетям: убедитесь, что ваша защита от кражи и автономный режим/отключение питания обнаруживают, что настройки моего устройства включены. Вы также можете изучить Graphene OS. Значки в строке состояния Вы можете заметить, что даже после сброса значков строки состояния некоторые значки, такие как вращение устройства и значки проводных наушников, могут оставаться видимыми. Это связано с тем, как черный список statubar реализован в Android и с тем, как ваш OEM-производитель мог настроить их. \nВам могут потребоваться дополнительные настройки. \n\nКроме того, не все параметры видимости значков могут работать, поскольку они зависят от реализаций и доступности OEM. Подсветка уведомлений не работает - Это зависит от OEM. Некоторые из них, например OneUI, похоже, не допускают наложения над AOD, предотвращая отображение световых эффектов. В этом случае попробуйте внешний дисплей в качестве обходного пути. + Это зависит от OEM. Некоторые из них, например OneUI, похоже, не допускают наложения над AOD, предотвращая отображение световых эффектов. В этом случае попробуйте внешний дисплей в качестве обходного пути. Переназначение кнопок не работает, пока дисплей выключен - Некоторые OEM-производители ограничивают отчеты службы специальных возможностей, когда дисплей фактически выключен, но они все равно могут работать, пока AOD включен. \nВ этом случае вы можете использовать переназначение кнопок с включенным AOD, но не с выключенным. \n\nВ качестве обходного пути вам нужно будет использовать разрешения Shizuku и включить его. \'Используйте Shizuku или Root\' переключите настройки переназначения кнопок, которые идентифицируют и прослушивают события ввода оборудования.\nНе гарантируется, что это будет работать на всех устройствах и при любых обстоятельствах. тестирование.\n\nИ даже если он\' включен, метод Сидзуку будет использоваться только тогда, когда он\' необходим. В противном случае он всегда будет переключаться на доступность, которая также блокирует фактический ввод при длительном нажатии. + Некоторые OEM-производители ограничивают отчеты службы специальных возможностей, когда дисплей фактически выключен, но они все равно могут работать, пока AOD включен. \nВ этом случае вы можете использовать переназначение кнопок с включенным AOD, но не с выключенным. \n\nВ качестве обходного пути вам нужно будет использовать разрешения Shizuku и включить его. \'Используйте Shizuku или Root-права\' переключите настройки переназначения кнопок, которые идентифицируют и прослушивают события ввода оборудования.\nНе гарантируется, что это будет работать на всех устройствах и при любых обстоятельствах. тестирование.\n\nИ даже если он включен, метод Shizuku будет использоваться только тогда, когда он необходим. В противном случае он всегда будет переключаться на доступность, которая также блокирует фактический ввод при длительном нажатии. Яркость фонарика не работает - Лишь ограниченное количество устройств получило аппаратную и программную поддержку регулировки интенсивности фонарика. \n\n\'Минимальная версия Android — 13 (SDK33).\nРегулировка яркости фонарика поддерживает только HAL версии 3.8 и выше, поэтому среди поддерживаемых устройств самые последние (например, Pixel 6/7, Samsung S23, и т. д.)\'\nполодарб/Фонарик-Тирамису + Лишь ограниченное количество устройств получило аппаратную и программную поддержку регулировки интенсивности фонарика. \n\n\'Минимальная версия Android — 13 (SDK33).\nРегулировка яркости фонарика поддерживает только HAL версии 3.8 и выше, поэтому среди поддерживаемых устройств самые последние (например, Pixel 6/7, Samsung S23, и т. д.)\'\npolodarb/Flashlight-Tiramisu Что это за приложение, черт возьми? - Хороший вопрос,\n\nЯ всегда хотел извлечь максимальную пользу из своих устройств, поскольку я\'был пользователем root с тех пор, как получил свое первое устройство Project Treble. И мне\'мне нравится приложение Tasker, которое похоже на бога, когда приходит автоматизация и использует все возможные API и внутренние функции Android.\n\nТак что я не рутирован и вернулся к стандартной бета-версии Android, и хотел получить максимальную отдачу от того, что возможно с заданными привилегиями. Могли бы и поделиться ими. Итак, используя мои начальные познания в Kotlin Jetpack, а также при поддержке многих исследовательских и вспомогательных инструментов, а также большого сообщества, я создал приложение «все в одном», содержащее все, что я хотел, чтобы было в моем Android, с заданными разрешениями. И вот оно.\n\nЗапросы на функции приветствуются, я рассмотрю и посмотрю, достижимы ли они с имеющимися разрешениями и моими навыками. В наше время то, что невозможно. :)\n\nПочему не в Play Store?\nЯ не\'не хочу рисковать, что моя учетная запись разработчика будет заблокирована из-за очень конфиденциальных внутренних разрешений и API, используемых в приложении. Но учитывая то, как развивается неопубликованная загрузка Android, давайте\' посмотрим, что нам нужно делать. Я понимаю опасения, что загруженные неопубликованные приложения являются вредоносными.\nРаз уж мы затронули эту тему, ознакомьтесь с другим моим приложением AirSync, если вы являетесь пользователем Mac + Android. *бесстыдный плагин*\n\nНаслаждайтесь, продолжайте строить! (っ◕‿◕)っ + Хороший вопрос,\n\nЯ всегда хотел извлечь максимум пользы из своих устройств, поскольку я был постоянным пользователем с тех пор, как у меня появилось мое первое устройство Project Treble. И мне очень понравилось приложение Tasker, которое просто божественно, когда речь заходит об автоматизации и использовании всех возможных API и внутренних функций Android.\n\nИтак, я не лишен прав и вернулся к стандартному бета-тестированию Android и хотел получить максимальную отдачу от того, что возможно с предоставленными привилегиями. С таким же успехом я мог бы поделиться ими. Итак, благодаря моим начальным знаниям в Kotlin Jetpack и поддержке множества исследовательских и вспомогательных инструментов, а также замечательного сообщества, я создал приложение \"все в одном\", содержащее все, что я хотел бы видеть в своем Android, с соответствующими разрешениями. И вот оно.\n\nЗапросы на дополнительные функции приветствуются, я рассмотрю их и посмотрю, возможно ли это с имеющимися разрешениями и моими навыками. В наши дни нет ничего невозможного. :)\n\nПочему не в Play Store?\nЯ не хочу рисковать тем, что моя учетная запись разработчика будет заблокирована из-за очень важных внутренних разрешений и API, используемых в приложении. Но, учитывая то, как продвигается сторонняя загрузка на Android, давайте посмотрим, что нам нужно сделать. Я действительно понимаю опасения по поводу того, что загруженные в стороннем режиме приложения могут быть вредоносными.\nРаз уж мы затронули эту тему, ознакомьтесь с другим моим приложением AirSync, если вы являетесь пользователем Mac + Android. *бесстыжая реклама*\n\nПолучайте удовольствие и продолжайте творить! (っ◕‿◕)っ Отчет об ошибке скопирован в буфер обмена. Отчет об ошибке @@ -958,15 +978,15 @@ Отчет по электронной почте Копировать в буфер обмена Отчет об ошибке Essentials - Отправить через + Пожалуйста, включите функцию создания отчетов о сбоях, так как она автоматически сообщит подробности, которые помогут устранить неполадки. Мы уже приехали? Оповещения о местах назначения поблизости - Откройте Карты Google, выберите местоположение и поделитесь им с Essentials. + Откройте Google Карты, выберите местоположение и поделитесь им с Essentials. Радиус оповещения: %d м - Расположение + Местоположение Используется для определения прибытия в пункт назначения. - Фоновое расположение + Фоновое местоположение Требуется для отслеживания вашего прибытия, пока приложение закрыто или экран выключен. Пункт назначения достигнут! Вы прибыли в пункт назначения. @@ -977,7 +997,7 @@ Пункт назначения готов Начать отслеживание Посмотреть карту - Прозрачный + Очистить Нет пункта назначения Открыть карты Разрешение на отображение полноэкранных уведомлений @@ -992,7 +1012,7 @@ Приготовьтесь выйти Отклонить Набор назначения: %1$.4f, %2$.4f - Использовать Root + Использовать Root-права Вместо Shizuku Доступ к root-правам отсутствует. Проверьте настройки менеджера root-прав. @@ -1000,19 +1020,19 @@ Ключи Настройте макет и поведение Высота клавиатуры - Отрегулируйте общий вертикальный размер клавиатуры + Отрегулировать вертикальный размер клавиатуры Нижняя прокладка Добавьте пространство под клавиатурой - Тактильная обратная связь + Тактильная отдача Вибрация при нажатии клавиши - Проверьте клавиатуру + Проверить клавиатуру Высота клавиатуры Нижняя прокладка - Тактильная обратная связь + Тактильная отдача Ключевая округлость Переместить функции вниз Функции бокового заполнения - Сила тактильной обратной связи + Сила тактильной отдачи Форма клавиатуры Круглый Плоский @@ -1029,13 +1049,13 @@ Это уведомление отображает уровень батареи для вашего подключённого Mac и Bluetooth устройств. Вы можете настроить какие устройства отображаются в настройках Состояния Батареи. Воспроизведите виджет батареи в панели уведомлений. Он покажет уровень заряда батареи всех ваших подключенных устройств в одном постоянном уведомлении, обновляемом в режиме реального времени. Сюда входит ваш Mac (через AirSync) и аксессуары Bluetooth. Уведомление о состоянии батареи - Постоянное уведомление, показывающее уровень заряда батареи подключенных устройств. + Постоянное уведомление, показывающее уровень заряда батареи подключенных устройств Устройства поблизости - Требуется для обнаружения и получения информации о батарее от аксессуаров Bluetooth. + Требуется для обнаружения и получения информации о батарее от аксессуаров Bluetooth Скопировать код Открыть страницу входа - Войдите, чтобы расширить лимиты вызовов API. + Войдите, чтобы расширить лимиты вызовов API Ожидание авторизации... Войдите с помощью GitHub выход @@ -1058,17 +1078,17 @@ 1. Скопируйте свой код: 2. Вставьте код на GitHub: Найденные APK-файлы - ЧИТАЙТЕ + ПРОЧТИТЕ Обновить - Плитка звукового режима - Плитка QS для переключения режима звука - Показать слайдер + Плитка режима звука + Плитка быстрых настроек для переключения режима звука + Показывать ползунок Показать ползунок громкости на плитке Поведение цикла Выбирайте режимы для переключения - Эмбиентный музыкальный взгляд - Взгляните на СМИ на AOD + Фоновый обзор музыки + Обзор медиа в режиме AOD Звук и тактильный отклик Объем и тактильные особенности Безопасность и конфиденциальность diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index 36ac27097..f438d4ee7 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -137,6 +137,10 @@ භාවිත සංඛ්යාලේඛන ඒවා කැටි කිරීම වළක්වා ගැනීම සඳහා දැනට පෙරබිමෙහි ඇති යෙදුම් හඳුනා ගැනීමට අවශ්‍ය වේ වාදනය කරන මාධ්‍ය සහ සක්‍රිය දැනුම්දීම් කැටි කිරීම වළක්වා ගැනීම සඳහා අනාවරණය කර ගැනීමට අවශ්‍ය වේ + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. තිරය ​​අක්‍රිය වූ විට පමණක් පෙන්වන්න නිහඬ දැනුම්දීම් මඟ හරින්න @@ -204,7 +208,12 @@ ක්‍රියාත්මකයි අක්රියයි අභිරුචි පුද්ගලික DNS - පොදු DNS පෙරසිටුවීම් + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. සපයන්නාගේ සත්කාරක නාමය AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ උපාංගය අගුළු හැරීම චාජර් සම්බන්ධ කර ඇත ආරෝපණය විසන්ධි විය + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on අයකිරීම තිරය ​​ක්‍රියාත්මකයි කම්පනය කරන්න @@ -661,6 +677,8 @@ ෆ්ලෑෂ් ලයිට් සක්රිය කරන්න ෆ්ලෑෂ් ලයිට් අක්රිය කරන්න ෆ්ලෑෂ් ලයිට් ටොගල් කරන්න + Turn On Low Power Mode + Turn Off Low Power Mode අඳුරු බිතුපත මෙම ක්‍රියාවට පද්ධති බිතුපත අඳුරු වීම සීරුමාරු කිරීමට Shizuku හෝ Root අවශ්‍ය වේ. Trigger තෝරන්න @@ -801,6 +819,8 @@ සෑම විටම අඳුරු තේමාව තාර කළු තේමාව ක්ලිප්බෝඩ් ඉතිහාසය + Long press for symbols + Accented characters ලැයිස්තුව පිකර් diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 42520a8fe..37cbae2b1 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -137,6 +137,10 @@ Усаге Статс Потребно је да открије које су апликације тренутно у првом плану како би се избегло њихово замрзавање Потребно за откривање медија који се репродукују и активних обавештења како би се избегло њихово замрзавање + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Прикажи само када је екран искључен Прескочи тиха обавештења @@ -204,7 +208,12 @@ Он Офф Прилагођени приватни ДНС - Уобичајена ДНС унапред подешена подешавања + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Име хоста добављача АдГуард ДНС днс.адгуард.цом @@ -653,6 +662,13 @@ Откључавање уређаја Пуњач повезан Пуњач је искључен + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Пуњење Сцреен Он Вибрирај @@ -661,6 +677,8 @@ Укључите лампу Искључите лампу Укључи батеријску лампу + Turn On Low Power Mode + Turn Off Low Power Mode Дим Валлпапер Ова радња захтева Шизуку или Роот да подесе затамњење системске позадине. Изаберите Триггер @@ -801,6 +819,8 @@ Увек мрачна тема Питцх блацк тема Цлипбоард Хистори + Long press for symbols + Accented characters листа берач diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index dba51d608..3daaab0a2 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -137,6 +137,10 @@ Användningsstatistik Krävs för att upptäcka vilka appar som för närvarande är i förgrunden för att undvika att frysa dem Krävs för att upptäcka spelande media och aktiva aviseringar för att undvika att frysa dem + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Visas endast när skärmen är avstängd Hoppa över tysta aviseringar @@ -204,7 +208,12 @@ Av Anpassad privat DNS - Vanliga DNS-förinställningar + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Leverantörens värdnamn AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Lås upp enheten Laddare ansluten Laddare frånkopplad + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Laddar Skärm på Vibrera @@ -661,6 +677,8 @@ Slå på ficklampan Stäng av ficklampan Växla ficklampa + Turn On Low Power Mode + Turn Off Low Power Mode Dimmig tapet Den här åtgärden kräver Shizuku eller Root för att justera systemets tapetdämpning. Välj Trigger @@ -801,6 +819,8 @@ Alltid mörkt tema Kolsvart tema Urklippshistorik + Long press for symbols + Accented characters lista plockare diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index e41dde807..3dd803624 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -137,6 +137,10 @@ Kullanım İstatistikleri Donmasını önlemek amacıyla hangi uygulamaların ön planda olduğunu tespit etmek için gereklidir Dondurulmalarını önlemek amacıyla oynatılan medyayı ve aktif bildirimleri tespit etmek için gereklidir + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Yalnızca ekran kapalıyken göster Sessiz bildirimleri atla @@ -204,7 +208,12 @@ Açık Kapalı Özel Özel DNS - Ortak DNS Ön Ayarları + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Sağlayıcı ana bilgisayar adı AdGuard DNs\'i dns.adguard.com @@ -653,6 +662,13 @@ Cihaz Kilidini Açma Şarj Cihazı Bağlı Şarj Cihazı Bağlantısı Kesildi + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Şarj etme Ekran Açık Titreşim @@ -661,6 +677,8 @@ El Fenerini Aç El Fenerini Kapat El Fenerini Aç/Kapat + Turn On Low Power Mode + Turn Off Low Power Mode Loş Duvar Kağıdı Bu eylem, sistem duvar kağıdı karartmasını ayarlamak için Shizuku veya Root\'u gerektirir. Tetikleyiciyi Seçin @@ -801,6 +819,8 @@ Her zaman karanlık tema Zifiri siyah tema Pano Geçmişi + Long press for symbols + Accented characters liste seçici diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 05a355ae5..dd81e1f86 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -137,6 +137,10 @@ Статистика використання Потрібен для виявлення програм, які зараз працюють на передньому плані, щоб уникнути їх зависання Необхідний для виявлення відтворюваних медіафайлів і активних сповіщень, щоб уникнути їх зависання + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Показувати, лише коли екран вимкнено Пропустити тихі сповіщення @@ -204,7 +208,12 @@ Увімкнено Вимкнено Спеціальний приватний DNS - Загальні налаштування DNS + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Ім\'я хоста постачальника DNS AdGuard dns.adguard.com @@ -653,6 +662,13 @@ Розблокування пристрою Зарядний пристрій підключено Зарядний пристрій відключено + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Зарядка Екран увімкнено Вібрувати @@ -661,6 +677,8 @@ Увімкніть ліхтарик Вимкніть ліхтарик Перемкнути ліхтарик + Turn On Low Power Mode + Turn Off Low Power Mode Тьмяні шпалери Для виконання цієї дії потрібен Shizuku або Root, щоб відрегулювати затемнення шпалер системи. Виберіть Тригер @@ -801,6 +819,8 @@ Завжди темна тема Безпросвітна темна тема Історія буфера обміну + Long press for symbols + Accented characters список збирач diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 610d7e6ec..58cae29a2 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -137,6 +137,10 @@ Thống kê sử dụng Cần thiết để phát hiện ứng dụng nào hiện đang ở nền trước để tránh đóng băng chúng Cần thiết để phát hiện phương tiện đang phát và thông báo đang hoạt động để tránh đóng băng chúng + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Chỉ hiển thị khi tắt màn hình Bỏ qua thông báo im lặng @@ -204,7 +208,12 @@ TRÊN Tắt DNS riêng tùy chỉnh - Cài đặt trước DNS phổ biến + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Tên máy chủ của nhà cung cấp DNS AdGuard dns.adguard.com @@ -653,6 +662,13 @@ Mở khóa thiết bị Đã kết nối bộ sạc Đã ngắt kết nối bộ sạc + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Sạc Bật màn hình Rung @@ -661,6 +677,8 @@ Bật đèn pin Tắt đèn pin Chuyển đổi đèn pin + Turn On Low Power Mode + Turn Off Low Power Mode Hình nền mờ Hành động này yêu cầu Shizuku hoặc Root điều chỉnh độ mờ của hình nền hệ thống. Chọn kích hoạt @@ -801,6 +819,8 @@ Chủ đề luôn tối Chủ đề đen tuyền Lịch sử bảng nhớ tạm + Long press for symbols + Accented characters danh sách người nhặt đồ diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c28976533..d152a6502 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -137,6 +137,10 @@ Usage Stats Required to detect which apps are currently in the foreground to avoid freezing them Required to detect playing media and active notifications to avoid freezing them + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Only show when screen off Skip silent notifications @@ -204,7 +208,12 @@ 开启 关闭 自定义私有DNS - 常见预设DNS + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. 供应商主机名 AdGuard DNS dns.adguard.com @@ -653,6 +662,13 @@ Device Unlock Charger Connected Charger Disconnected + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Charging Screen On Vibrate @@ -661,6 +677,8 @@ Turn On Flashlight Turn Off Flashlight Toggle Flashlight + Turn On Low Power Mode + Turn Off Low Power Mode Dim Wallpaper This action requires Shizuku or Root to adjust system wallpaper dimming. Select Trigger @@ -801,6 +819,8 @@ Always dark theme Pitch black theme Clipboard History + Long press for symbols + Accented characters list picker diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml index a6b3daec9..3089b92ef 100644 --- a/app/src/main/res/values/ic_launcher_background.xml +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -1,2 +1,4 @@ - \ No newline at end of file + + #1D9452 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f77789388..5fa06f2c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,6 +141,10 @@ Usage Stats Required to detect which apps are currently in the foreground to avoid freezing them Required to detect playing media and active notifications to avoid freezing them + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. Only show when screen off @@ -210,7 +214,12 @@ On Off Custom Private DNS - Common DNS Presets + DNS Presets + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. Provider hostname AdGuard DNS dns.adguard.com @@ -683,6 +692,13 @@ Device Unlock Charger Connected Charger Disconnected + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on Charging Screen On @@ -693,6 +709,8 @@ Turn On Flashlight Turn Off Flashlight Toggle Flashlight + Turn On Low Power Mode + Turn Off Low Power Mode Dim Wallpaper This action requires Shizuku or Root to adjust system wallpaper dimming. Select Trigger @@ -845,6 +863,8 @@ Always dark theme Pitch black theme Clipboard History + Long press for symbols + Accented characters list diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 000000000..af21007da --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,12 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/14666a19c360bb62ffe5941534b1af7c/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/454b2f35f06b635aa4a2871eddd1e32e/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/14666a19c360bb62ffe5941534b1af7c/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/454b2f35f06b635aa4a2871eddd1e32e/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/b30866b18394d55c95dc662594302c0a/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/88dd3a3df8e2336eb284d6b9b6b98d9c/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec393a8d1f984107f9ae6ce8f45a661f/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/454b2f35f06b635aa4a2871eddd1e32e/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/e24f7511bc753a0499e443f100864e16/redirect +toolchainVendor=AMAZON +toolchainVersion=21 diff --git a/settings.gradle.kts b/settings.gradle.kts index 38d8580a2..55aed1854 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,9 @@ pluginManagement { gradlePluginPortal() } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories {