diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 16ad43dd5..198adeff5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -128,6 +128,7 @@ dependencies { // Google Maps & Location implementation(libs.play.services.location) implementation(libs.play.services.wearable) + implementation(libs.androidx.wear.remote.interactions) implementation(libs.androidx.work.runtime.ktx) implementation(libs.gson) @@ -153,4 +154,10 @@ dependencies { implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.common) + // RemoteIntent support + implementation("androidx.wear:wear-remote-interactions:1.1.0-alpha02") + + // tandard wearable library + implementation("com.google.android.gms:play-services-wearable:19.0.0") + } \ No newline at end of file diff --git a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt index de548187e..b5f463c26 100644 --- a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt @@ -225,6 +225,7 @@ class FeatureSettingsActivity : AppCompatActivity() { // Help Sheet State var showHelpSheet by remember { mutableStateOf(false) } var showInstructionsSheet by remember { mutableStateOf(false) } + var showWatchInstallHelpSheet by remember { mutableStateOf(false) } var selectedHelpFeature by remember { mutableStateOf( null @@ -270,6 +271,7 @@ class FeatureSettingsActivity : AppCompatActivity() { "Battery notification" -> !viewModel.isPostNotificationsEnabled.value || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !viewModel.isBluetoothPermissionGranted.value) "Text and animations" -> !viewModel.isWriteSettingsEnabled.value || !isWriteSecureSettingsEnabled "Always on Display" -> !isWriteSecureSettingsEnabled + "Lock screen clock" -> !isWriteSecureSettingsEnabled "Other customizations" -> !com.sameerasw.essentials.utils.ShellUtils.hasPermission(context) "Shut-Up!" -> !isWriteSecureSettingsEnabled || !viewModel.isUsageStatsPermissionGranted.value else -> false @@ -326,6 +328,12 @@ class FeatureSettingsActivity : AppCompatActivity() { ) } + if (showWatchInstallHelpSheet) { + com.sameerasw.essentials.ui.components.sheets.WatchInstallHelpBottomSheet( + onDismissRequest = { showWatchInstallHelpSheet = false } + ) + } + val pageTitle = if (featureObj != null) stringResource(featureObj.title) else featureId val hasMenu = featureObj != null && featureObj.aboutDescription != null @@ -413,6 +421,7 @@ class FeatureSettingsActivity : AppCompatActivity() { "Caffeinate" -> !viewModel.isPostNotificationsEnabled.value "Battery notification" -> !viewModel.isPostNotificationsEnabled.value || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !viewModel.isBluetoothPermissionGranted.value) "Text and animations" -> !viewModel.isWriteSettingsEnabled.value || !isWriteSecureSettingsEnabled + "Lock screen clock" -> !isWriteSecureSettingsEnabled "Screen refresh rate" -> !viewModel.isShizukuPermissionGranted.value "Shut-Up!" -> !isWriteSecureSettingsEnabled || !viewModel.isUsageStatsPermissionGranted.value else -> false @@ -682,6 +691,14 @@ class FeatureSettingsActivity : AppCompatActivity() { ) } + "Lock screen clock" -> { + com.sameerasw.essentials.ui.composables.configs.LockScreenClockSettingsUI( + viewModel = viewModel, + modifier = Modifier.padding(top = 16.dp), + highlightSetting = highlightSetting + ) + } + "Shut-Up!" -> { ShutUpSettingsUI( viewModel = viewModel, @@ -700,12 +717,15 @@ class FeatureSettingsActivity : AppCompatActivity() { EssentialsFloatingToolbar( title = pageTitle, + isBeta = featureObj?.isBeta ?: false, onBackClick = { finish() }, modifier = Modifier .align(Alignment.BottomCenter) .zIndex(1f), onHelpClick = { - if (hasMenu) { + if (featureId == "Watch") { + showWatchInstallHelpSheet = true + } else if (hasMenu) { selectedHelpFeature = featureObj showHelpSheet = true } else { diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/LocationReachedRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/LocationReachedRepository.kt index 69368108f..8cce39280 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/LocationReachedRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/LocationReachedRepository.kt @@ -150,4 +150,19 @@ class LocationReachedRepository(context: Context) { saveAlarms(alarms) } } + + fun updatePausedState(alarmId: String, isPaused: Boolean) { + val alarms = getAlarms().toMutableList() + val index = alarms.indexOfFirst { it.id == alarmId } + if (index != -1) { + alarms[index] = alarms[index].copy(isPaused = isPaused) + saveAlarms(alarms) + } + + // Also update temp alarm if it matches + val currentTemp = _tempAlarm.value + if (currentTemp?.id == alarmId) { + _tempAlarm.value = currentTemp.copy(isPaused = isPaused) + } + } } 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 36bc75def..afb0cdf33 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 @@ -225,6 +225,16 @@ class SettingsRepository(private val context: Context) { const val KEY_SHUT_UP_SELECTED_APPS = "shut_up_selected_apps" const val KEY_SHUT_UP_ORIGINAL_SETTINGS = "shut_up_original_settings" + const val KEY_SHUT_UP_ATTEMPT_SHIZUKU_RESTART = "shut_up_attempt_shizuku_restart" + const val KEY_DISABLE_ROTATION_SUGGESTION = "disable_rotation_suggestion" + + const val KEY_LOCK_SCREEN_CLOCK_WEIGHT = "lock_screen_clock_weight" + const val KEY_LOCK_SCREEN_CLOCK_WIDTH = "lock_screen_clock_width" + const val KEY_LOCK_SCREEN_CLOCK_GRADE = "lock_screen_clock_grade" + const val KEY_LOCK_SCREEN_CLOCK_ROUNDNESS = "lock_screen_clock_roundness" + const val KEY_LOCK_SCREEN_CLOCK_COLOR_TONE = "lock_screen_clock_color_tone" + const val KEY_LOCK_SCREEN_CLOCK_SELECTED_COLOR_ID = "lock_screen_clock_selected_color_id" + const val KEY_LOCK_SCREEN_CLOCK_SEED_COLOR = "lock_screen_clock_seed_color" } // Observe changes @@ -800,6 +810,9 @@ class SettingsRepository(private val context: Context) { saveTrackedRepos(current) } + fun isShutUpAttemptShizukuRestartEnabled(): Boolean = getBoolean(KEY_SHUT_UP_ATTEMPT_SHIZUKU_RESTART, true) + fun setShutUpAttemptShizukuRestartEnabled(enabled: Boolean) = putBoolean(KEY_SHUT_UP_ATTEMPT_SHIZUKU_RESTART, enabled) + fun removeTrackedRepo(fullName: String) { val current = getTrackedRepos().toMutableList() current.removeAll { it.fullName == fullName } @@ -1209,5 +1222,25 @@ class SettingsRepository(private val context: Context) { } } + fun getLockScreenClockWeight(): Int = getInt(KEY_LOCK_SCREEN_CLOCK_WEIGHT, 300) + fun setLockScreenClockWeight(value: Int) = putInt(KEY_LOCK_SCREEN_CLOCK_WEIGHT, value) + + fun getLockScreenClockWidth(): Int = getInt(KEY_LOCK_SCREEN_CLOCK_WIDTH, 116) + fun setLockScreenClockWidth(value: Int) = putInt(KEY_LOCK_SCREEN_CLOCK_WIDTH, value) + + fun getLockScreenClockGrade(): Int = getInt(KEY_LOCK_SCREEN_CLOCK_GRADE, 0) + fun setLockScreenClockGrade(value: Int) = putInt(KEY_LOCK_SCREEN_CLOCK_GRADE, value) + + fun getLockScreenClockRoundness(): Int = getInt(KEY_LOCK_SCREEN_CLOCK_ROUNDNESS, 100) + fun setLockScreenClockRoundness(value: Int) = putInt(KEY_LOCK_SCREEN_CLOCK_ROUNDNESS, value) + + fun getLockScreenClockColorTone(): Int = getInt(KEY_LOCK_SCREEN_CLOCK_COLOR_TONE, 75) + fun setLockScreenClockColorTone(value: Int) = putInt(KEY_LOCK_SCREEN_CLOCK_COLOR_TONE, value) + + fun getLockScreenClockSelectedColorId(): String = getString(KEY_LOCK_SCREEN_CLOCK_SELECTED_COLOR_ID, "DEFAULT") ?: "DEFAULT" + fun setLockScreenClockSelectedColorId(value: String) = putString(KEY_LOCK_SCREEN_CLOCK_SELECTED_COLOR_ID, value) + + fun getLockScreenClockSeedColor(): Int = getInt(KEY_LOCK_SCREEN_CLOCK_SEED_COLOR, 0) + fun setLockScreenClockSeedColor(value: Int) = putInt(KEY_LOCK_SCREEN_CLOCK_SEED_COLOR, value) } diff --git a/app/src/main/java/com/sameerasw/essentials/domain/model/LocationAlarm.kt b/app/src/main/java/com/sameerasw/essentials/domain/model/LocationAlarm.kt index bfe149fc1..a0e52e0f3 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/model/LocationAlarm.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/model/LocationAlarm.kt @@ -12,6 +12,7 @@ data class LocationAlarm( @SerializedName("radius") val radius: Int = 1000, // in meters @SerializedName("isEnabled") val isEnabled: Boolean = false, @SerializedName("lastTravelled") val lastTravelled: Long? = null, + @SerializedName("isPaused") val isPaused: Boolean = false, @SerializedName("iconResName") val iconResName: String = "round_navigation_24", @SerializedName("createdAt") val createdAt: Long = System.currentTimeMillis() ) diff --git a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt index 6e6cea7fd..0532e5e02 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt @@ -105,21 +105,6 @@ object FeatureRegistry { override fun isEnabled(viewModel: MainViewModel) = true override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {} }, - object : Feature( - id = "Shut-Up!", - title = R.string.feat_shut_up_title, - iconRes = R.drawable.rounded_domino_mask_24, - category = R.string.cat_system, - description = R.string.feat_shut_up_desc, - aboutDescription = R.string.shut_up_description, - permissionKeys = listOf("WRITE_SECURE_SETTINGS", "USAGE_STATS"), - showToggle = false, - hasMoreSettings = true, - parentFeatureId = "Security" - ) { - override fun isEnabled(viewModel: MainViewModel) = true - override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {} - }, object : Feature( id = "Notifications", title = R.string.feat_notifications_alerts_title, @@ -234,6 +219,20 @@ object FeatureRegistry { override fun isEnabled(viewModel: MainViewModel) = true override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {} }, + object : Feature( + id = "Lock screen clock", + title = R.string.feat_lock_screen_clock_title, + iconRes = R.drawable.rounded_nest_clock_farsight_analog_24, + category = R.string.cat_interface, + description = R.string.feat_lock_screen_clock_desc, + aboutDescription = R.string.about_desc_lock_screen_clock, + permissionKeys = listOf("WRITE_SECURE_SETTINGS"), + showToggle = false, + parentFeatureId = "Display" + ) { + override fun isEnabled(viewModel: MainViewModel) = true + override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {} + }, object : Feature( id = "Watch", title = R.string.feat_watch_title, @@ -826,14 +825,30 @@ object FeatureRegistry { override fun isEnabled(viewModel: MainViewModel) = viewModel.isAppLockEnabled.value override fun isToggleEnabled(viewModel: MainViewModel, context: Context) = - if (viewModel.isUseUsageAccess.value) + (if (viewModel.isUseUsageAccess.value) viewModel.isUsageStatsPermissionGranted.value else - viewModel.isAccessibilityEnabled.value + viewModel.isAccessibilityEnabled.value) override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) = viewModel.setAppLockEnabled(enabled, context) }, + object : Feature( + id = "Shut-Up!", + title = R.string.feat_shut_up_title, + iconRes = R.drawable.rounded_domino_mask_24, + category = R.string.cat_system, + description = R.string.feat_shut_up_desc, + aboutDescription = R.string.shut_up_description, + permissionKeys = listOf("WRITE_SECURE_SETTINGS", "USAGE_STATS"), + showToggle = false, + hasMoreSettings = true, + isBeta = true, + parentFeatureId = "Security" + ) { + override fun isEnabled(viewModel: MainViewModel) = true + override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {} + }, object : Feature( id = "Location reached", diff --git a/app/src/main/java/com/sameerasw/essentials/domain/registry/PermissionRegistry.kt b/app/src/main/java/com/sameerasw/essentials/domain/registry/PermissionRegistry.kt index 0feac26fb..a1f982d43 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/registry/PermissionRegistry.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/registry/PermissionRegistry.kt @@ -29,6 +29,7 @@ fun initPermissionRegistry() { PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_dynamic_night_light_title) PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.tile_developer_options) PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.tile_charge_optimization) + PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_lock_screen_clock_title) // Shizuku permission PermissionRegistry.register("SHIZUKU", R.string.feat_freeze_title) diff --git a/app/src/main/java/com/sameerasw/essentials/services/LocationReachedService.kt b/app/src/main/java/com/sameerasw/essentials/services/LocationReachedService.kt index e0df7a315..1e2c4d2f9 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/LocationReachedService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/LocationReachedService.kt @@ -43,7 +43,9 @@ class LocationReachedService : Service() { private const val NOTIFICATION_ID = 2001 private const val ALARM_NOTIFICATION_ID = 1001 private const val CHANNEL_ID = "location_reached_live" - private const val ACTION_STOP = "com.sameerasw.essentials.STOP_LOCATION_REACHED" + const val ACTION_STOP = "com.sameerasw.essentials.STOP_LOCATION_REACHED" + const val ACTION_PAUSE = "com.sameerasw.essentials.PAUSE_LOCATION_REACHED" + const val ACTION_RESUME = "com.sameerasw.essentials.RESUME_LOCATION_REACHED" fun start(context: Context) { val intent = Intent(context, LocationReachedService::class.java) @@ -67,6 +69,14 @@ class LocationReachedService : Service() { stopTracking() return START_NOT_STICKY } + if (intent?.action == ACTION_PAUSE) { + pauseTracking() + return START_STICKY + } + if (intent?.action == ACTION_RESUME) { + resumeTracking() + return START_STICKY + } isAlarmTriggered = false createNotificationChannel() @@ -102,14 +112,45 @@ class LocationReachedService : Service() { if (alarm != null) { repository.saveLastTrip(alarm) + repository.updatePausedState(alarm.id, false) } repository.saveActiveAlarmId(null) stopSelf() } + private fun pauseTracking() { + val activeId = repository.getActiveAlarmId() ?: return + repository.updatePausedState(activeId, true) + // Force an update to show paused state in notification + val alarms = repository.getAlarms() + val alarm = alarms.find { it.id == activeId } + if (alarm != null) { + updateNotification(null) + } + } + + private fun resumeTracking() { + val activeId = repository.getActiveAlarmId() ?: return + repository.updatePausedState(activeId, false) + // Force an update to refresh notification buttons immediately + updateNotification(null) + + // Then try to get location + val alarms = repository.getAlarms() + val alarm = alarms.find { it.id == activeId } + if (alarm != null) { + updateProgress(alarm) + } + } + @android.annotation.SuppressLint("MissingPermission") private fun updateProgress(alarm: LocationAlarm) { + if (alarm.isPaused) { + updateNotification(null) + return + } + fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, null) .addOnSuccessListener { location -> location?.let { @@ -172,15 +213,15 @@ class LocationReachedService : Service() { notificationManager.notify(ALARM_NOTIFICATION_ID, notification) } - private fun updateNotification(distanceKm: Float) { + private fun updateNotification(distanceKm: Float?) { val startDist = repository.getStartDistance() val startTime = repository.getStartTime() - val progressPercent = if (startDist > 0) { + val progressPercent = if (startDist > 0 && distanceKm != null) { ((1.0f - (distanceKm * 1000f / startDist)) * 100).toInt().coerceIn(0, 100) } else 0 var etaText: String? = null - if (startDist > 0 && startTime > 0) { + if (startDist > 0 && startTime > 0 && distanceKm != null) { val elapsed = System.currentTimeMillis() - startTime val currentDistMeters = distanceKm * 1000f val distanceTravelled = startDist - currentDistMeters @@ -238,21 +279,50 @@ class LocationReachedService : Service() { getString(R.string.location_reached_service_remaining, distanceText, progress) } - if (Build.VERSION.SDK_INT >= 35) { - val activeId = repository.getActiveAlarmId() - val alarm = repository.getAlarms().find { it.id == activeId } - val iconResName = alarm?.iconResName ?: "round_navigation_24" - val iconResId = resources.getIdentifier(iconResName, "drawable", packageName) - val finalIconId = if (iconResId != 0) iconResId else R.drawable.round_navigation_24 + val pauseIntent = Intent(this, LocationReachedService::class.java).apply { + action = ACTION_PAUSE + } + val pausePendingIntent = PendingIntent.getService( + this, 1, pauseIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val resumeIntent = Intent(this, LocationReachedService::class.java).apply { + action = ACTION_RESUME + } + val resumePendingIntent = PendingIntent.getService( + this, 2, resumeIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + val activeId = repository.getActiveAlarmId() + val alarm = repository.getAlarms().find { it.id == activeId } + val isPaused = alarm?.isPaused == true + val iconResName = alarm?.iconResName ?: "round_navigation_24" + val iconResId = resources.getIdentifier(iconResName, "drawable", packageName) + val finalIconId = if (iconResId != 0) iconResId else R.drawable.round_navigation_24 + + if (Build.VERSION.SDK_INT >= 35) { + val destinationName = alarm?.name?.ifEmpty { "Destination" } ?: "Destination" val builder = Notification.Builder(this, CHANNEL_ID) .setSmallIcon(finalIconId) - .setContentTitle(getString(R.string.location_reached_service_title)) - .setContentText(contentText) + .setContentTitle(getString(R.string.location_reached_service_title, destinationName)) + .setContentText(if (isPaused) getString(R.string.location_reached_pause) else contentText) .setOngoing(true) .setOnlyAlertOnce(true) .setCategory(Notification.CATEGORY_SERVICE) .setContentIntent(mainPendingIntent) + .addAction( + if (isPaused) { + Notification.Action.Builder( + Icon.createWithResource(this, R.drawable.round_play_arrow_24), + getString(R.string.location_reached_resume), resumePendingIntent + ).build() + } else { + Notification.Action.Builder( + Icon.createWithResource(this, R.drawable.rounded_pause_24), + getString(R.string.location_reached_pause), pausePendingIntent + ).build() + } + ) .addAction( Notification.Action.Builder( Icon.createWithResource(this, R.drawable.rounded_power_settings_new_24), @@ -268,7 +338,7 @@ class LocationReachedService : Service() { .setProgressTrackerIcon( Icon.createWithResource( this, - R.drawable.round_play_arrow_24 + if (isPaused) R.drawable.rounded_pause_24 else R.drawable.round_play_arrow_24 ).setTint(getColor(android.R.color.system_accent1_300)) ) builder.style = progressStyle @@ -283,7 +353,11 @@ class LocationReachedService : Service() { val extras = android.os.Bundle() extras.putBoolean("android.requestPromotedOngoing", true) extras.putBoolean("android.substituteContextualActions", true) - distanceKm?.let { extras.putString("android.shortCriticalText", distanceText) } + if (isPaused) { + extras.putString("android.shortCriticalText", "Paused") + } else { + distanceKm?.let { extras.putString("android.shortCriticalText", distanceText) } + } builder.addExtras(extras) builder.javaClass.getMethod( @@ -292,9 +366,14 @@ class LocationReachedService : Service() { ) .invoke(builder, true) - distanceKm?.let { + if (isPaused) { builder.javaClass.getMethod("setShortCriticalText", CharSequence::class.java) - .invoke(builder, distanceText) + .invoke(builder, "Paused") + } else { + distanceKm?.let { + builder.javaClass.getMethod("setShortCriticalText", CharSequence::class.java) + .invoke(builder, distanceText) + } } } catch (_: Throwable) { } @@ -302,16 +381,11 @@ class LocationReachedService : Service() { return builder.build() } - val activeId = repository.getActiveAlarmId() - val alarm = repository.getAlarms().find { it.id == activeId } - val iconResName = alarm?.iconResName ?: "round_navigation_24" - val iconResId = resources.getIdentifier(iconResName, "drawable", packageName) - val finalIconId = if (iconResId != 0) iconResId else R.drawable.round_navigation_24 - + val destinationName = alarm?.name?.ifEmpty { "Destination" } ?: "Destination" val builder = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(finalIconId) - .setContentTitle(getString(R.string.location_reached_service_title)) - .setContentText(contentText) + .setContentTitle(getString(R.string.location_reached_service_title, destinationName)) + .setContentText(if (isPaused) getString(R.string.location_reached_pause) else contentText) .setOngoing(true) .setOnlyAlertOnce(true) .setPriority(NotificationCompat.PRIORITY_MAX) @@ -319,6 +393,11 @@ class LocationReachedService : Service() { .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setContentIntent(mainPendingIntent) .setProgress(100, progress, false) + .addAction( + if (isPaused) R.drawable.round_play_arrow_24 else R.drawable.rounded_pause_24, + if (isPaused) getString(R.string.location_reached_resume) else getString(R.string.location_reached_pause), + if (isPaused) resumePendingIntent else pausePendingIntent + ) .addAction( R.drawable.rounded_power_settings_new_24, getString(R.string.location_reached_stop_tracking), @@ -327,7 +406,11 @@ class LocationReachedService : Service() { val extras = android.os.Bundle() extras.putBoolean("android.requestPromotedOngoing", true) - distanceKm?.let { extras.putString("android.shortCriticalText", distanceText) } + if (isPaused) { + extras.putString("android.shortCriticalText", "Paused") + } else { + distanceKm?.let { extras.putString("android.shortCriticalText", distanceText) } + } builder.addExtras(extras) return builder.build() diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/StatusBarIconHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/StatusBarIconHandler.kt new file mode 100644 index 000000000..8206ca534 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/StatusBarIconHandler.kt @@ -0,0 +1,213 @@ +package com.sameerasw.essentials.services.handlers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.os.BatteryManager +import android.provider.Settings +import android.telephony.TelephonyManager +import com.sameerasw.essentials.domain.StatusBarIconRegistry +import com.sameerasw.essentials.ui.components.pickers.NetworkType +import com.sameerasw.essentials.utils.updateIconBlacklistSetting +import com.sameerasw.essentials.viewmodels.StatusBarIconViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +/** + * Handler for dynamic status bar icon management in the background. + */ +class StatusBarIconHandler(private val context: Context) { + private val scope = CoroutineScope(Dispatchers.Main + Job()) + private var smartDataJob: Job? = null + + private val connectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + private val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + updateAll() + } + + override fun onLost(network: Network) { + updateAll() + } + + override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + updateAll() + } + } + + private val batteryReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + updateBatteryPercentage() + } + } + + fun register() { + try { + connectivityManager.registerDefaultNetworkCallback(networkCallback) + } catch (e: Exception) { + // Fallback for older versions or issues + startPollingFallback() + } + + val filter = IntentFilter().apply { + addAction(Intent.ACTION_POWER_CONNECTED) + addAction(Intent.ACTION_POWER_DISCONNECTED) + addAction(Intent.ACTION_BATTERY_CHANGED) + } + context.registerReceiver(batteryReceiver, filter) + + updateAll() + } + + fun unregister() { + try { + connectivityManager.unregisterNetworkCallback(networkCallback) + } catch (e: Exception) {} + + try { + context.unregisterReceiver(batteryReceiver) + } catch (e: Exception) {} + + smartDataJob?.cancel() + } + + fun updateAll() { + scope.launch { + val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) + val isSmartWiFiEnabled = prefs.getBoolean(StatusBarIconViewModel.PREF_SMART_WIFI_ENABLED, false) + val isSmartDataEnabled = prefs.getBoolean(StatusBarIconViewModel.PREF_SMART_DATA_ENABLED, false) + + if (isSmartWiFiEnabled || isSmartDataEnabled) { + updateNetworkIcons(isSmartWiFiEnabled, isSmartDataEnabled) + } + + updateBatteryPercentage() + } + } + + private fun updateNetworkIcons(isSmartWiFiEnabled: Boolean, isSmartDataEnabled: Boolean) { + val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) + + // 1. Get current states + val isWifiConnected = isWifiConnected() + val networkType = getCurrentNetworkType() + + // 2. Load user preferences for all icons + val visibilities = StatusBarIconRegistry.ALL_ICONS.associate { icon -> + icon.id to prefs.getBoolean(icon.preferencesKey, icon.defaultVisible) + }.toMutableMap() + + // 3. Apply Smart WiFi logic + if (isSmartWiFiEnabled) { + val mobileDataVisible = visibilities["mobile_data"] ?: true + visibilities["mobile_data"] = mobileDataVisible && !isWifiConnected + } + + // 4. Apply Smart Data logic + if (isSmartDataEnabled && !(isSmartWiFiEnabled && isWifiConnected)) { + val selectedNetworkTypes = prefs.getStringSet( + StatusBarIconViewModel.PREF_SELECTED_NETWORK_TYPES, + setOf(NetworkType.NETWORK_4G.name, NetworkType.NETWORK_5G.name) + )?.map { NetworkType.valueOf(it) }?.toSet() ?: emptySet() + + val shouldHideMobileData = selectedNetworkTypes.contains(networkType) || + (selectedNetworkTypes.contains(NetworkType.NETWORK_OTHER) && + !setOf( + NetworkType.NETWORK_5G, + NetworkType.NETWORK_4G, + NetworkType.NETWORK_3G + ).contains(networkType)) + + visibilities["mobile_data"] = visibilities["mobile_data"] == true && !shouldHideMobileData + } + + // 5. Update system settings + val blacklistNames = StatusBarIconRegistry.getBlacklistNames(visibilities) + updateIconBlacklistSetting(context, blacklistNames) + } + + private fun updateBatteryPercentage() { + val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) + val mode = prefs.getInt(StatusBarIconViewModel.PREF_BATTERY_PERCENT_MODE, 0) + + if (mode != 2) return // Only handle "Charging Only" mode here + + val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { ifilter -> + context.registerReceiver(null, ifilter) + } + val status: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1 + val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || + status == BatteryManager.BATTERY_STATUS_FULL + + updateSettingsValue("status_bar_show_battery_percent", if (isCharging) 1 else 0) + } + + private fun updateSettingsValue(key: String, value: Int) { + try { + Settings.System.putInt(context.contentResolver, key, value) + } catch (e: Exception) { + try { + Settings.Secure.putInt(context.contentResolver, key, value) + } catch (e2: Exception) { + // Fallback to Shizuku/Root if available (omitted for brevity in service, assuming permissions handled) + } + } + } + + private fun isWifiConnected(): Boolean { + return try { + val network = connectivityManager.activeNetwork ?: return false + val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + } catch (e: Exception) { + false + } + } + + private fun getCurrentNetworkType(): NetworkType { + return try { + val network = connectivityManager.activeNetwork ?: return NetworkType.NETWORK_OTHER + val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return NetworkType.NETWORK_OTHER + + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return NetworkType.NETWORK_OTHER + } + + val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager + val networkType = telephonyManager.networkType + + when (networkType) { + TelephonyManager.NETWORK_TYPE_NR -> NetworkType.NETWORK_5G + TelephonyManager.NETWORK_TYPE_LTE, + TelephonyManager.NETWORK_TYPE_HSPAP -> NetworkType.NETWORK_4G + TelephonyManager.NETWORK_TYPE_HSDPA, + TelephonyManager.NETWORK_TYPE_HSUPA, + TelephonyManager.NETWORK_TYPE_HSPA, + TelephonyManager.NETWORK_TYPE_UMTS, + TelephonyManager.NETWORK_TYPE_TD_SCDMA -> NetworkType.NETWORK_3G + else -> NetworkType.NETWORK_OTHER + } + } catch (e: Exception) { + NetworkType.NETWORK_OTHER + } + } + + private fun startPollingFallback() { + smartDataJob?.cancel() + smartDataJob = scope.launch { + while (true) { + updateAll() + delay(10000) + } + } + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt index 3e699069b..b5ff6b367 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt @@ -39,6 +39,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene private lateinit var ambientGlanceHandler: AmbientGlanceHandler private lateinit var aodForceTurnOffHandler: AodForceTurnOffHandler private lateinit var omniGestureOverlayHandler: OmniGestureOverlayHandler + private lateinit var statusBarIconHandler: StatusBarIconHandler private var screenReceiver: BroadcastReceiver? = null @@ -58,6 +59,8 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene key == "circle_to_search_gesture_height" || key == "circle_to_search_preview_enabled") { updateOmniOverlay() + } else if (key == "smart_wifi_enabled" || key == "smart_data_enabled" || key == "battery_percent_mode" || key?.startsWith("icon_") == true) { + statusBarIconHandler.updateAll() } } @@ -72,8 +75,10 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene ambientGlanceHandler = AmbientGlanceHandler(this) aodForceTurnOffHandler = AodForceTurnOffHandler(this) omniGestureOverlayHandler = OmniGestureOverlayHandler(this) + statusBarIconHandler = StatusBarIconHandler(this) flashlightHandler.register() + statusBarIconHandler.register() // Screen Receiver screenReceiver = object : BroadcastReceiver() { @@ -194,6 +199,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene ambientGlanceHandler.removeOverlay() aodForceTurnOffHandler.removeOverlay() omniGestureOverlayHandler.removeOverlay() + statusBarIconHandler.unregister() stopInputEventListener() serviceScope.cancel() getSharedPreferences("essentials_prefs", MODE_PRIVATE) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/EssentialsFloatingToolbar.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/EssentialsFloatingToolbar.kt index 5331d4e6b..290507e07 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/EssentialsFloatingToolbar.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/EssentialsFloatingToolbar.kt @@ -4,6 +4,7 @@ import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring import androidx.compose.foundation.Canvas +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* @@ -38,6 +39,7 @@ fun EssentialsFloatingToolbar( selectedIndex: Int = -1, // Standard Mode title: String? = null, + isBeta: Boolean = false, onBackClick: (() -> Unit)? = null, onHelpClick: (() -> Unit)? = null, // FAB / Action @@ -135,17 +137,37 @@ fun EssentialsFloatingToolbar( if (title != null) { Spacer(modifier = Modifier.width(8.dp)) - Text( - text = title, - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.background, - maxLines = 1, - overflow = TextOverflow.Ellipsis, + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier .widthIn(min = 100.dp, max = 250.dp) .padding(horizontal = 8.dp) .align(Alignment.CenterVertically) - ) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.background, + maxLines = 1, + modifier = Modifier.basicMarquee().weight(1f, fill = false) + ) + if (isBeta) { + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.background + ), + shape = MaterialTheme.shapes.extraSmall + ) { + Text( + text = stringResource(R.string.label_beta), + modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.primary + ) + } + } + } } } else { // TABBED MODE - Expanding labels diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/FavoriteCarousel.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/FavoriteCarousel.kt index b61f16dae..4e75d6125 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/FavoriteCarousel.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/FavoriteCarousel.kt @@ -4,6 +4,7 @@ import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -184,8 +185,7 @@ fun FavoriteCarousel( style = MaterialTheme.typography.labelSmall, textAlign = TextAlign.Center, maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().basicMarquee() ) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/FeatureCard.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/FeatureCard.kt index b3e87d16f..649be25e3 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/FeatureCard.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/FeatureCard.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.draw.blur import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.sameerasw.essentials.R import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu @@ -218,12 +219,15 @@ fun FeatureCard( ) { Text( text = resolvedTitle, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(1f, fill = false), + maxLines = 1, + overflow = TextOverflow.Ellipsis ) if (isBeta) { Card( colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.background + containerColor = MaterialTheme.colorScheme.primary ), shape = MaterialTheme.shapes.extraSmall ) { @@ -231,7 +235,7 @@ fun FeatureCard( text = stringResource(R.string.label_beta), modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.primary + color = MaterialTheme.colorScheme.onPrimary ) } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/LocationAlarmCard.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/LocationAlarmCard.kt index 1094169ff..c0cfef4b6 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/LocationAlarmCard.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/LocationAlarmCard.kt @@ -22,6 +22,8 @@ fun LocationAlarmCard( isAnyTracking: Boolean, onStart: () -> Unit, onStop: () -> Unit, + onPause: () -> Unit, + onResume: () -> Unit, onClick: () -> Unit, modifier: Modifier = Modifier ) { @@ -67,20 +69,38 @@ fun LocationAlarmCard( }, trailingContent = { if (isActive) { - IconButton( - onClick = { - com.sameerasw.essentials.utils.HapticUtil.performVirtualKeyHaptic(view) - onStop() - }, - colors = IconButtonDefaults.iconButtonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.error - ) - ) { - Icon( - painter = painterResource(R.drawable.rounded_close_24), - contentDescription = "Stop" - ) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + IconButton( + onClick = { + com.sameerasw.essentials.utils.HapticUtil.performVirtualKeyHaptic(view) + if (alarm.isPaused) onResume() else onPause() + }, + colors = IconButtonDefaults.iconButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + ) { + Icon( + painter = painterResource(if (alarm.isPaused) R.drawable.round_play_arrow_24 else R.drawable.rounded_pause_24), + contentDescription = if (alarm.isPaused) "Resume" else "Pause" + ) + } + + IconButton( + onClick = { + com.sameerasw.essentials.utils.HapticUtil.performVirtualKeyHaptic(view) + onStop() + }, + colors = IconButtonDefaults.iconButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.error + ) + ) { + Icon( + painter = painterResource(R.drawable.rounded_close_24), + contentDescription = "Stop" + ) + } } } else if (!isAnyTracking) { IconButton( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/SegmentedPicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/SegmentedPicker.kt index 80d029ef7..bb2800268 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/SegmentedPicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/SegmentedPicker.kt @@ -1,6 +1,7 @@ package com.sameerasw.essentials.ui.components.pickers import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -66,7 +67,7 @@ fun SegmentedPicker( iconProvider(item) androidx.compose.foundation.layout.Spacer(Modifier.padding(end = 8.dp)) } - Text(labelProvider(item), fontSize = dimensionResource(R.dimen.font_small).value.sp) + Text(labelProvider(item), fontSize = dimensionResource(R.dimen.font_small).value.sp, modifier= Modifier.basicMarquee(), maxLines = 1) } } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ShutUpPerAppSettingsSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ShutUpPerAppSettingsSheet.kt index f5c61c9a7..bbdc449f8 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ShutUpPerAppSettingsSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ShutUpPerAppSettingsSheet.kt @@ -1,16 +1,20 @@ package com.sameerasw.essentials.ui.components.sheets import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import com.sameerasw.essentials.R import com.sameerasw.essentials.domain.model.ShutUpAppConfig import com.sameerasw.essentials.ui.components.cards.IconToggleItem import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer +import com.sameerasw.essentials.viewmodels.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -19,11 +23,38 @@ fun ShutUpPerAppSettingsSheet( config: ShutUpAppConfig, onConfigChanged: (ShutUpAppConfig) -> Unit, onCreateShortcut: (ShutUpAppConfig) -> Unit, - isFrozen: Boolean + isFrozen: Boolean, + viewModel: MainViewModel = viewModel() ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) var currentConfig by remember(config) { mutableStateOf(config) } - + var showShizukuRestartWarning by remember { mutableStateOf(false) } + + val isAttemptShizukuRestart by viewModel.isShutUpAttemptShizukuRestart + + if (showShizukuRestartWarning) { + AlertDialog( + onDismissRequest = { showShizukuRestartWarning = false }, + title = { Text(stringResource(R.string.shut_up_shizuku_restart_warning_title)) }, + text = { Text(stringResource(R.string.shut_up_shizuku_restart_warning_message)) }, + confirmButton = { + TextButton(onClick = { + showShizukuRestartWarning = false + val newConfig = currentConfig.copy(autoArchive = true) + currentConfig = newConfig + onConfigChanged(newConfig) + }) { + Text(stringResource(R.string.action_enable)) + } + }, + dismissButton = { + TextButton(onClick = { showShizukuRestartWarning = false }) { + Text(stringResource(R.string.action_cancel)) + } + } + ) + } + ModalBottomSheet( onDismissRequest = onDismissRequest, sheetState = sheetState, @@ -32,7 +63,8 @@ fun ShutUpPerAppSettingsSheet( Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp), + .padding(16.dp) + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( @@ -50,7 +82,7 @@ fun ShutUpPerAppSettingsSheet( iconRes = R.drawable.rounded_settings_24, title = stringResource(R.string.shut_up_disable_dev_options), isChecked = currentConfig.disableDevOptions, - onCheckedChange = { + onCheckedChange = { val newConfig = currentConfig.copy(disableDevOptions = it) currentConfig = newConfig onConfigChanged(newConfig) @@ -60,7 +92,7 @@ fun ShutUpPerAppSettingsSheet( iconRes = R.drawable.rounded_adb_24, title = stringResource(R.string.shut_up_disable_usb_debugging), isChecked = currentConfig.disableUsbDebugging, - onCheckedChange = { + onCheckedChange = { val newConfig = currentConfig.copy(disableUsbDebugging = it) currentConfig = newConfig onConfigChanged(newConfig) @@ -70,30 +102,51 @@ fun ShutUpPerAppSettingsSheet( iconRes = R.drawable.rounded_android_wifi_4_bar_plus_24, title = stringResource(R.string.shut_up_disable_wireless_debugging), isChecked = currentConfig.disableWirelessDebugging, - onCheckedChange = { + onCheckedChange = { val newConfig = currentConfig.copy(disableWirelessDebugging = it) currentConfig = newConfig onConfigChanged(newConfig) } ) + if (currentConfig.disableWirelessDebugging) { + IconToggleItem( + iconRes = R.drawable.rounded_adb_24, + title = stringResource(R.string.shut_up_attempt_shizuku_restart), + isChecked = isAttemptShizukuRestart, + onCheckedChange = { + viewModel.setShutUpAttemptShizukuRestartEnabled(it) + } + ) + } IconToggleItem( iconRes = R.drawable.rounded_settings_accessibility_24, title = stringResource(R.string.shut_up_disable_accessibility), isChecked = currentConfig.disableAccessibility, - onCheckedChange = { + onCheckedChange = { val newConfig = currentConfig.copy(disableAccessibility = it) currentConfig = newConfig onConfigChanged(newConfig) } ) + } + + RoundedCardContainer( + modifier = Modifier, + spacing = 2.dp, + cornerRadius = 24.dp + ) { IconToggleItem( iconRes = R.drawable.rounded_snowflake_24, title = stringResource(R.string.shut_up_auto_archive_notif_title), isChecked = currentConfig.autoArchive, - onCheckedChange = { - val newConfig = currentConfig.copy(autoArchive = it) - currentConfig = newConfig - onConfigChanged(newConfig) + onCheckedChange = { + if (it && !isAttemptShizukuRestart && currentConfig.disableWirelessDebugging) { + showShizukuRestartWarning = true + } else { + val newConfig = currentConfig.copy(autoArchive = it) + currentConfig = newConfig + onConfigChanged(newConfig) + } } ) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/WatchInstallHelpBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/WatchInstallHelpBottomSheet.kt new file mode 100644 index 000000000..6769f62e3 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/WatchInstallHelpBottomSheet.kt @@ -0,0 +1,113 @@ +package com.sameerasw.essentials.ui.components.sheets + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +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.ui.components.containers.RoundedCardContainer + +import androidx.lifecycle.viewmodel.compose.viewModel +import com.sameerasw.essentials.viewmodels.WatchViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun WatchInstallHelpBottomSheet( + onDismissRequest: () -> Unit, + viewModel: WatchViewModel = viewModel() +) { + val context = LocalContext.current + val uriHandler = LocalUriHandler.current + + ModalBottomSheet( + onDismissRequest = onDismissRequest, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 32.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Header + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Box( + modifier = Modifier + .size(40.dp) + .background( + color = MaterialTheme.colorScheme.primaryContainer, + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_watch_24), + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.primary + ) + } + + Text( + text = stringResource(R.string.watch_help_title), + style = MaterialTheme.typography.titleMedium + ) + } + + // Description + RoundedCardContainer( + containerColor = MaterialTheme.colorScheme.surfaceBright + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(R.string.watch_help_description), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + + // Action Button + Button( + onClick = { + viewModel.openPlayStoreOnWatch(context) + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = MaterialTheme.shapes.extraLarge, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_watch_24), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.watch_help_open_play_store), + style = MaterialTheme.typography.labelLarge + ) + } + } + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LocationReachedSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LocationReachedSettingsUI.kt index fc5d83680..7e00444fd 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LocationReachedSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LocationReachedSettingsUI.kt @@ -82,6 +82,8 @@ fun LocationReachedSettingsUI( remainingTimeMinutes = remainingTimeMinutes, startDistance = startDistance, onStop = { locationViewModel.stopTracking() }, + onPause = { locationViewModel.pauseTracking() }, + onResume = { locationViewModel.resumeTracking() }, onStart = { HapticUtil.performVirtualKeyHaptic(view) locationViewModel.startTracking(it) @@ -121,6 +123,14 @@ fun LocationReachedSettingsUI( HapticUtil.performVirtualKeyHaptic(view) locationViewModel.stopTracking() }, + onPause = { + HapticUtil.performVirtualKeyHaptic(view) + locationViewModel.pauseTracking() + }, + onResume = { + HapticUtil.performVirtualKeyHaptic(view) + locationViewModel.resumeTracking() + }, onClick = { HapticUtil.performVirtualKeyHaptic(view) locationViewModel.setTempAlarm(alarm) @@ -206,9 +216,12 @@ fun TopStatusCard( remainingTimeMinutes: Int?, startDistance: Float, onStop: () -> Unit, + onPause: () -> Unit, + onResume: () -> Unit, onStart: (String) -> Unit ) { val isTracking = activeAlarm != null + val isPaused = activeAlarm?.isPaused == true val displayAlarm = activeAlarm ?: lastTrip RoundedCardContainer( @@ -298,26 +311,50 @@ fun TopStatusCard( color = MaterialTheme.colorScheme.primary, trackColor = MaterialTheme.colorScheme.primaryContainer, wavelength = 20.dp, - amplitude = { 1.0f } + amplitude = { if (isPaused) 0f else 1.0f } ) } Spacer(modifier = Modifier.height(32.dp)) - Button( - onClick = onStop, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.error - ), - shape = androidx.compose.foundation.shape.CircleShape + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - Icon(painterResource(R.drawable.rounded_close_24), contentDescription = null) - Spacer(modifier = Modifier.width(8.dp)) - Text(stringResource(R.string.action_stop)) + Button( + onClick = { if (isPaused) onResume() else onPause() }, + modifier = Modifier + .weight(1f) + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ), + shape = androidx.compose.foundation.shape.CircleShape + ) { + Icon( + painterResource(if (isPaused) R.drawable.round_play_arrow_24 else R.drawable.rounded_pause_24), + contentDescription = null + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(if (isPaused) stringResource(R.string.location_reached_resume) else stringResource(R.string.location_reached_pause)) + } + + Button( + onClick = onStop, + modifier = Modifier + .weight(1f) + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.error + ), + shape = androidx.compose.foundation.shape.CircleShape + ) { + Icon(painterResource(R.drawable.rounded_close_24), contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text(stringResource(R.string.action_stop)) + } } } else if (lastTrip != null) { val context = LocalContext.current diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LockScreenClockSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LockScreenClockSettingsUI.kt new file mode 100644 index 000000000..c4a198858 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LockScreenClockSettingsUI.kt @@ -0,0 +1,334 @@ +package com.sameerasw.essentials.ui.composables.configs + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.material3.carousel.HorizontalMultiBrowseCarousel +import androidx.compose.material3.carousel.rememberCarouselState +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.platform.LocalContext +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.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.sameerasw.essentials.R +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.sliders.ConfigSliderItem +import com.sameerasw.essentials.ui.theme.Shapes +import com.sameerasw.essentials.utils.HapticUtil +import com.sameerasw.essentials.viewmodels.MainViewModel@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun LockScreenClockSettingsUI( + viewModel: MainViewModel, + modifier: Modifier = Modifier, + highlightSetting: String? = null +) { + val context = LocalContext.current + val view = LocalView.current + val currentClockId by viewModel.lockScreenClockId + val isDark = isSystemInDarkTheme() + + val inversionMatrix = remember { + ColorMatrix( + floatArrayOf( + -1f, 0f, 0f, 0f, 255f, + 0f, -1f, 0f, 0f, 255f, + 0f, 0f, -1f, 0f, 255f, + 0f, 0f, 0f, 1f, 0f + ) + ) + } + + val clockOptions = remember { + listOf( + ClockOption("DEFAULT", R.string.lock_screen_clock_default, R.drawable.clock_flex), + ClockOption("ANALOG_CLOCK_BIGNUM", R.string.lock_screen_clock_bignum, R.drawable.clock_bignum), + ClockOption("DIGITAL_CLOCK_CALLIGRAPHY", R.string.lock_screen_clock_calligraphy, R.drawable.clock_calligraphy), + ClockOption("DIGITAL_CLOCK_GROWTH", R.string.lock_screen_clock_growth, R.drawable.clock_growth), + ClockOption("DIGITAL_CLOCK_HANDWRITTEN", R.string.lock_screen_clock_handwritten, R.drawable.clock_handwritten), + ClockOption("DIGITAL_CLOCK_INFLATE", R.string.lock_screen_clock_inflate, R.drawable.clock_inflate), + ClockOption("DIGITAL_CLOCK_METRO", R.string.lock_screen_clock_metro, R.drawable.clock_metro), + ClockOption("DIGITAL_CLOCK_NUMBEROVERLAP", R.string.lock_screen_clock_numoverlap, R.drawable.clock_overlap), + ClockOption("DIGITAL_CLOCK_WEATHER", R.string.lock_screen_clock_weather, R.drawable.clock_weather) + ) + } + + val colorOptions = remember { + listOf( + ClockColorOption("DEFAULT", Color.Transparent, 0, R.string.color_default), + ClockColorOption("RED", Color(0xFFE57373), -23641, R.string.color_red), + ClockColorOption("GREEN", Color(0xFF81C784), -14057967, R.string.color_green), + ClockColorOption("BLUE", Color(0xFF64B5F6), -14575885, R.string.color_blue), + ClockColorOption("YELLOW", Color(0xFFFFF176), -5317, R.string.color_yellow), + ClockColorOption("ORANGE", Color(0xFFFFB74D), -18611, R.string.color_orange), + ClockColorOption("PURPLE", Color(0xFFBA68C8), -4560702, R.string.color_purple), + ClockColorOption("PINK", Color(0xFFF06292), -1023342, R.string.color_pink), + ClockColorOption("TEAL", Color(0xFF4DB6AC), -11684180, R.string.color_teal) + ) + } + + val isDefaultStyleSelected = currentClockId == "DEFAULT" || currentClockId == "DIGITAL_CLOCK_FLEX" + + val carouselState = rememberCarouselState { clockOptions.size } + + LaunchedEffect(carouselState) { + var isFirst = true + snapshotFlow { carouselState.currentItem } + .collect { + if (isFirst) { + isFirst = false + } else { + HapticUtil.performSliderHaptic(view) + } + } + } + + Column( + modifier = modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = stringResource(R.string.lock_screen_clock_select_label), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + HorizontalMultiBrowseCarousel( + state = carouselState, + preferredItemWidth = 180.dp, + itemSpacing = 2.dp, + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + ) { index -> + val option = clockOptions[index] + val isSelected = if (option.id == "DEFAULT") isDefaultStyleSelected else currentClockId == option.id + + Box( + modifier = Modifier + .fillMaxSize() + .padding(vertical = 4.dp) + .maskClip(MaterialTheme.shapes.large) + .background(if (isDark) Color.White else MaterialTheme.colorScheme.surfaceBright) + .pointerInput(option) { + detectTapGestures { + HapticUtil.performUIHaptic(view) + if (option.id == "DEFAULT") { + if (!isDefaultStyleSelected) { + viewModel.setLockScreenClockId("DEFAULT", context) + } + } else { + viewModel.setLockScreenClockId(option.id, context) + } + } + } + ) { + androidx.compose.foundation.Image( + painter = painterResource(id = option.imageRes), + contentDescription = stringResource(option.nameRes), + modifier = Modifier.fillMaxSize(), + contentScale = androidx.compose.ui.layout.ContentScale.Crop, + colorFilter = if (isDark) ColorFilter.colorMatrix(inversionMatrix) else null + ) + + if (isSelected) { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)) + ) + } + } + } + + // Color & Tone Section + Text( + text = stringResource(R.string.label_color), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(start = 16.dp, top = 8.dp, bottom = 4.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + RoundedCardContainer { + Column( + modifier = Modifier.fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.surfaceBright, + shape = RoundedCornerShape(MaterialTheme.shapes.extraSmall.bottomEnd) + ).padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Multi-row Color Picker + val rows = colorOptions.chunked(5) + rows.forEach { rowOptions -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + rowOptions.forEach { colorOption -> + ColorCircle( + colorOption = colorOption, + isSelected = viewModel.lockScreenClockSelectedColorId.value == colorOption.id, + onClick = { + HapticUtil.performUIHaptic(view) + viewModel.setLockScreenClockColor(colorOption.id, colorOption.seedColor, context) + } + ) + } + } + } + } + + ConfigSliderItem( + title = stringResource(R.string.label_color_tone), + value = viewModel.lockScreenClockColorTone.intValue.toFloat(), + onValueChange = { viewModel.setLockScreenClockColorTone(it.toInt(), context) }, + valueRange = 0f..100f, + valueFormatter = { "${it.toInt()}%" }, + iconRes = R.drawable.rounded_palette_24, + enabled = viewModel.lockScreenClockSelectedColorId.value != "DEFAULT" + ) + } + + if (isDefaultStyleSelected) { + Text( + text = stringResource(R.string.label_style_font), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(start = 16.dp, top = 8.dp, bottom = 4.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + RoundedCardContainer { + SegmentedPicker( + items = listOf("DEFAULT", "DIGITAL_CLOCK_FLEX"), + selectedItem = currentClockId ?: "DEFAULT", + onItemSelected = { viewModel.setLockScreenClockId(it, context) }, + labelProvider = { if (it == "DEFAULT") "Default" else "Flex" } + ) + + ConfigSliderItem( + title = stringResource(R.string.label_weight), + value = viewModel.lockScreenClockWeight.intValue.toFloat(), + onValueChange = { viewModel.setLockScreenClockWeight(it.toInt(), context) }, + valueRange = 100f..1000f, + increment = 10f, + valueFormatter = { it.toInt().toString() }, + iconRes = R.drawable.rounded_line_weight_24 + ) + + ConfigSliderItem( + title = stringResource(R.string.label_width), + value = viewModel.lockScreenClockWidth.intValue.toFloat(), + onValueChange = { viewModel.setLockScreenClockWidth(it.toInt(), context) }, + valueRange = 25f..200f, + increment = 5f, + valueFormatter = { it.toInt().toString() }, + iconRes = R.drawable.rounded_arrows_outward_24 + ) + + ConfigSliderItem( + title = stringResource(R.string.label_roundness), + value = viewModel.lockScreenClockRoundness.intValue.toFloat(), + onValueChange = { viewModel.setLockScreenClockRoundness(it.toInt(), context) }, + valueRange = 0f..100f, + increment = 5f, + valueFormatter = { it.toInt().toString() }, + iconRes = R.drawable.rounded_rounded_corner_24 + ) + } + } + + // About Section + RoundedCardContainer { + IconToggleItem( + iconRes = R.drawable.rounded_info_24, + title = stringResource(R.string.about_title), + description = stringResource(R.string.about_desc_lock_screen_clock), + isChecked = false, + onCheckedChange = {}, + showToggle = false + ) + } + + Spacer(modifier = Modifier.height(32.dp)) + } +} + +@Composable +fun ColorCircle( + colorOption: ClockColorOption, + isSelected: Boolean, + onClick: () -> Unit +) { + Box( + modifier = Modifier + .size(44.dp) + .clip(CircleShape) + .background( + if (colorOption.id == "DEFAULT") MaterialTheme.colorScheme.surfaceVariant else colorOption.color + ) + .border( + width = if (isSelected) 3.dp else 1.dp, + color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline.copy(alpha = 0.2f), + shape = CircleShape + ) + .pointerInput(colorOption.id) { + detectTapGestures { + onClick() + } + }, + contentAlignment = Alignment.Center + ) { + if (colorOption.id == "DEFAULT") { + Icon( + painter = painterResource(id = R.drawable.rounded_palette_24), + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + if (isSelected) { + Box( + modifier = Modifier + .size(8.dp) + .clip(CircleShape) + .background(if (colorOption.id == "DEFAULT") MaterialTheme.colorScheme.primary else Color.White) + ) + } + } +} + +data class ClockOption( + val id: String, + val nameRes: Int, + val imageRes: Int +) + +data class ClockColorOption( + val id: String, + val color: Color, + val seedColor: Int, + val nameRes: Int +) + diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/OtherCustomizationsSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/OtherCustomizationsSettingsUI.kt index ac1391140..625e490b4 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/OtherCustomizationsSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/OtherCustomizationsSettingsUI.kt @@ -37,6 +37,7 @@ enum class PermissionModule { HIDE_GESTURE_BAR, SHOW_ON_LAUNCHER, CIRCLE_TO_SEARCH, + DISABLE_ROTATION_SUGGESTION, NONE } @@ -111,6 +112,7 @@ fun OtherCustomizationsSettingsUI( listOf(shizukuPermission, appDetectionPermission) } PermissionModule.CIRCLE_TO_SEARCH -> listOf(shizukuPermission, accessibilityPermission) + PermissionModule.DISABLE_ROTATION_SUGGESTION -> listOf(shizukuPermission) else -> emptyList() } @@ -219,6 +221,27 @@ fun OtherCustomizationsSettingsUI( modifier = Modifier.highlight(highlightSetting == "circle_to_search_gesture_toggle") ) + IconToggleItem( + title = stringResource(R.string.feat_disable_rotation_suggestion_title), + description = stringResource(R.string.feat_disable_rotation_suggestion_desc), + isChecked = viewModel.isDisableRotationSuggestionEnabled.value, + onCheckedChange = { enabled -> + if (viewModel.isWriteSecureSettingsEnabled.value || viewModel.isShizukuPermissionGranted.value || viewModel.isRootPermissionGranted.value) { + viewModel.setDisableRotationSuggestionEnabled(enabled, context) + } else { + requestingPermissionFor = PermissionModule.DISABLE_ROTATION_SUGGESTION + } + }, + enabled = true, + onDisabledClick = { + if (!viewModel.isWriteSecureSettingsEnabled.value && !viewModel.isShizukuPermissionGranted.value && !viewModel.isRootPermissionGranted.value) { + requestingPermissionFor = PermissionModule.DISABLE_ROTATION_SUGGESTION + } + }, + iconRes = R.drawable.rounded_mobile_rotate_24, + modifier = Modifier.highlight(highlightSetting == "disable_rotation_suggestion_toggle") + ) + AnimatedVisibility( visible = viewModel.isCircleToSearchGestureEnabled.value, enter = expandVertically(), diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt index e8e9a11c9..352be8299 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt @@ -10,6 +10,7 @@ import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -626,7 +627,7 @@ fun QSTileCard( style = MaterialTheme.typography.titleMedium, color = contentColor, maxLines = 1, - overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis + modifier = Modifier.basicMarquee() ) Text( text = if (isMissingPermissions) "Grant permission" diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/ShutUpSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/ShutUpSettingsUI.kt index 2d02a6f6b..6aceba2a0 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/ShutUpSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/ShutUpSettingsUI.kt @@ -152,7 +152,8 @@ fun ShutUpSettingsUI( config = configs.find { it.packageName == selectedConfigForEditing?.packageName } ?: selectedConfigForEditing!!, onConfigChanged = { viewModel.updateShutUpConfig(it) }, onCreateShortcut = { viewModel.createShutUpShortcut(context, it) }, - isFrozen = isFrozen + isFrozen = isFrozen, + viewModel = viewModel ) } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/StatusBarIconSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/StatusBarIconSettingsUI.kt index f4a6547de..0698357ec 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/StatusBarIconSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/StatusBarIconSettingsUI.kt @@ -328,13 +328,12 @@ fun StatusBarIconSettingsUI( color = MaterialTheme.colorScheme.surfaceBright, shape = androidx.compose.foundation.shape.RoundedCornerShape(MaterialTheme.shapes.extraSmall.bottomEnd) ) - .padding(16.dp) .highlight(highlightSetting == "battery_percentage") ) { Row( modifier = Modifier .fillMaxWidth() - .padding(bottom = 8.dp), + .padding(top = 16.dp, start = 20.dp, end = 16.dp, bottom = 2.dp), verticalAlignment = androidx.compose.ui.Alignment.CenterVertically ) { Text( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/WatchSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/WatchSettingsUI.kt index 3f8204972..779c17caa 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/WatchSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/WatchSettingsUI.kt @@ -100,8 +100,9 @@ fun WatchSettingsUI( color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f), modifier = Modifier.padding(top = 4.dp, bottom = 12.dp) ) + val context = LocalContext.current Button( - onClick = { uriHandler.openUri("https://github.com/sameerasw/essentials-wear") }, + onClick = { viewModel.openPlayStoreOnWatch(context) }, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.onPrimary, diff --git a/app/src/main/java/com/sameerasw/essentials/utils/AppUtil.kt b/app/src/main/java/com/sameerasw/essentials/utils/AppUtil.kt index 5d0b62928..175a0debe 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/AppUtil.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/AppUtil.kt @@ -26,6 +26,7 @@ object AppUtil { // Target size for app icons to balance quality and performance private const val ICON_SIZE = 64 + private const val SHORTCUT_ICON_SIZE = 192 /** * Get all installed apps (not just launcher apps) @@ -197,22 +198,37 @@ object AppUtil { return bitmap } - fun drawableToBitmap(drawable: android.graphics.drawable.Drawable): Bitmap { - if (drawable is BitmapDrawable) { + fun drawableToBitmap(drawable: android.graphics.drawable.Drawable, size: Int? = null): Bitmap { + if (drawable is BitmapDrawable && size == null) { return drawable.bitmap } + val width = size ?: drawable.intrinsicWidth.coerceAtLeast(1) + val height = size ?: drawable.intrinsicHeight.coerceAtLeast(1) + val bitmap = Bitmap.createBitmap( - drawable.intrinsicWidth.coerceAtLeast(1), - drawable.intrinsicHeight.coerceAtLeast(1), + width, + height, Bitmap.Config.ARGB_8888 ) val canvas = Canvas(bitmap) - drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.setBounds(0, 0, width, height) drawable.draw(canvas) return bitmap } + /** + * Get a properly sized icon for shortcuts + */ + fun getShortcutIcon(context: Context, packageName: String): Bitmap { + val drawable = try { + context.packageManager.getApplicationIcon(packageName) + } catch (e: Exception) { + context.packageManager.defaultActivityIcon + } + return drawableToBitmap(drawable, SHORTCUT_ICON_SIZE) + } + fun getAppVersion(context: Context, packageName: String): String? { return try { val pInfo = context.packageManager.getPackageInfo(packageName, 0) diff --git a/app/src/main/java/com/sameerasw/essentials/utils/ShortcutUtil.kt b/app/src/main/java/com/sameerasw/essentials/utils/ShortcutUtil.kt index ea8ebb3db..3509448a9 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/ShortcutUtil.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/ShortcutUtil.kt @@ -27,7 +27,7 @@ object ShortcutUtil { val shortcut = ShortcutInfo.Builder(context, app.packageName) .setShortLabel(app.appName) .setLongLabel(app.appName) - .setIcon(Icon.createWithBitmap(app.icon.asAndroidBitmap())) + .setIcon(Icon.createWithBitmap(AppUtil.getShortcutIcon(context, app.packageName))) .setIntent(intent) .build() diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/LocationReachedViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/LocationReachedViewModel.kt index c261fc2b2..997bbf44a 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/LocationReachedViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/LocationReachedViewModel.kt @@ -169,6 +169,7 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl // Save as last trip lastTrip.value = alarm repository.saveLastTrip(alarm) + repository.updatePausedState(id, false) } repository.saveActiveAlarmId(null) @@ -181,6 +182,25 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl startTime.value = 0L } + fun pauseTracking() { + val id = activeAlarmId.value ?: return + val intent = Intent(getApplication(), LocationReachedService::class.java).apply { + action = LocationReachedService.ACTION_PAUSE + } + getApplication().startService(intent) + repository.updatePausedState(id, true) + } + + fun resumeTracking() { + val id = activeAlarmId.value ?: return + val intent = Intent(getApplication(), LocationReachedService::class.java).apply { + action = LocationReachedService.ACTION_RESUME + } + getApplication().startService(intent) + repository.updatePausedState(id, false) + updateCurrentDistance() + } + private var distanceTrackingJob: kotlinx.coroutines.Job? = null fun startUiTracking() { @@ -213,6 +233,8 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl val id = activeAlarmId.value val activeAlarm = savedAlarms.value.find { it.id == id } ?: tempAlarm.value ?: return + if (activeAlarm.isPaused) return + fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, null) .addOnSuccessListener { location -> location?.let { 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 2a549799a..dd40ac4ed 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -139,6 +139,15 @@ class MainViewModel : ViewModel() { val isCircleToSearchGestureEnabled = mutableStateOf(false) val circleToSearchGestureHeight = mutableFloatStateOf(48f) val isCircleToSearchPreviewEnabled = mutableStateOf(false) + val isDisableRotationSuggestionEnabled = mutableStateOf(false) + val lockScreenClockId = mutableStateOf(null) + val lockScreenClockWeight = mutableIntStateOf(300) + val lockScreenClockWidth = mutableIntStateOf(116) + val lockScreenClockGrade = mutableIntStateOf(0) + val lockScreenClockRoundness = mutableIntStateOf(100) + val lockScreenClockColorTone = mutableIntStateOf(75) + val lockScreenClockSelectedColorId = mutableStateOf("DEFAULT") + val lockScreenClockSeedColor = mutableIntStateOf(0) // Live Wallpaper val liveWallpaperSelectedVideo = mutableStateOf(SettingsRepository.LIVE_WALLPAPER_DEFAULT_VIDEO) @@ -147,6 +156,7 @@ class MainViewModel : ViewModel() { val shutUpConfigs = mutableStateOf>(emptyList()) val isShutUpLoading = mutableStateOf(false) + val isShutUpAttemptShizukuRestart = mutableStateOf(true) @@ -545,6 +555,14 @@ class MainViewModel : ViewModel() { liveWallpaperCustomVideos.clear() liveWallpaperCustomVideos.addAll(settingsRepository.getLiveWallpaperCustomVideos()) } + + SettingsRepository.KEY_SHUT_UP_ATTEMPT_SHIZUKU_RESTART -> { + isShutUpAttemptShizukuRestart.value = settingsRepository.isShutUpAttemptShizukuRestartEnabled() + } + SettingsRepository.KEY_DISABLE_ROTATION_SUGGESTION -> { + isDisableRotationSuggestionEnabled.value = settingsRepository.getBoolean(key) + appContext?.let { applyDisableRotationSuggestion(it, isDisableRotationSuggestionEnabled.value) } + } } } } @@ -576,6 +594,11 @@ class MainViewModel : ViewModel() { loadShutUpConfigs() } + fun setShutUpAttemptShizukuRestartEnabled(enabled: Boolean) { + isShutUpAttemptShizukuRestart.value = enabled + settingsRepository.setShutUpAttemptShizukuRestartEnabled(enabled) + } + fun saveShutUpSelectedApps(context: Context, apps: List) { val currentConfigs = settingsRepository.loadShutUpConfigs().associateBy { it.packageName } val newConfigs = apps.filter { it.isEnabled }.map { @@ -600,16 +623,11 @@ class MainViewModel : ViewModel() { } if (androidx.core.content.pm.ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { - val appIcon = try { - val drawable = context.packageManager.getApplicationIcon(config.packageName) - AppUtil.drawableToBitmap(drawable) - } catch (e: Exception) { - null - } + val appIcon = AppUtil.getShortcutIcon(context, config.packageName) val pinShortcutInfo = androidx.core.content.pm.ShortcutInfoCompat.Builder(context, config.packageName) .setShortLabel(appName) - .setIcon(if (appIcon != null) androidx.core.graphics.drawable.IconCompat.createWithBitmap(appIcon) else androidx.core.graphics.drawable.IconCompat.createWithResource(context, R.drawable.rounded_volume_off_24)) + .setIcon(androidx.core.graphics.drawable.IconCompat.createWithBitmap(appIcon)) .setIntent(intent) .build() @@ -643,6 +661,16 @@ class MainViewModel : ViewModel() { isHideGestureBarOnLauncherEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_HIDE_GESTURE_BAR_ON_LAUNCHER_ENABLED, false) notificationLightingSystemMode.intValue = settingsRepository.getNotificationLightingSystemMode() + isShutUpAttemptShizukuRestart.value = settingsRepository.isShutUpAttemptShizukuRestartEnabled() + isDisableRotationSuggestionEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_DISABLE_ROTATION_SUGGESTION, false) + lockScreenClockId.value = readCurrentLockScreenClockId(context) + lockScreenClockWeight.intValue = settingsRepository.getLockScreenClockWeight() + lockScreenClockWidth.intValue = settingsRepository.getLockScreenClockWidth() + lockScreenClockGrade.intValue = settingsRepository.getLockScreenClockGrade() + lockScreenClockRoundness.intValue = settingsRepository.getLockScreenClockRoundness() + lockScreenClockColorTone.intValue = settingsRepository.getLockScreenClockColorTone() + lockScreenClockSelectedColorId.value = settingsRepository.getLockScreenClockSelectedColorId() + lockScreenClockSeedColor.intValue = settingsRepository.getLockScreenClockSeedColor() loadShutUpConfigs() if (isHideGestureBarEnabled.value) { @@ -1429,6 +1457,157 @@ class MainViewModel : ViewModel() { updateAppDetectionService(context) } + fun setDisableRotationSuggestionEnabled(enabled: Boolean, context: Context) { + isDisableRotationSuggestionEnabled.value = enabled + settingsRepository.putBoolean(SettingsRepository.KEY_DISABLE_ROTATION_SUGGESTION, enabled) + applyDisableRotationSuggestion(context, enabled) + } + + private fun applyDisableRotationSuggestion(context: Context, enabled: Boolean) { + val value = if (enabled) 0 else 1 + val key = "show_rotation_suggestions" + + var success = false + if (PermissionUtils.canWriteSecureSettings(context)) { + try { + success = Settings.Secure.putInt(context.contentResolver, key, value) + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (!success) { + val command = "settings put secure $key $value" + if (ShizukuUtils.hasPermission()) { + ShizukuUtils.runCommand(command) + } else if (RootUtils.isRootPermissionGranted()) { + RootUtils.runCommand(command) + } + } + } + + fun setLockScreenClockId(clockId: String, context: Context) { + val timestamp = System.currentTimeMillis() + val json = if (lockScreenClockSelectedColorId.value == "DEFAULT") { + "{\"clockId\":\"$clockId\",\"metadata\":{\"metadataSelectedColorId\":\"DEFAULT\",\"metadataColorToneProgress\":${lockScreenClockColorTone.intValue},\"appliedTimestamp\":$timestamp},\"axes\":[{\"key\":\"wght\",\"value\":${lockScreenClockWeight.intValue}},{\"key\":\"wdth\",\"value\":${lockScreenClockWidth.intValue}},{\"key\":\"ROND\",\"value\":${lockScreenClockRoundness.intValue}}]}" + } else { + "{\"clockId\":\"$clockId\",\"seedColor\":${lockScreenClockSeedColor.intValue},\"metadata\":{\"metadataSelectedColorId\":\"${lockScreenClockSelectedColorId.value}\",\"metadataColorToneProgress\":${lockScreenClockColorTone.intValue},\"appliedTimestamp\":$timestamp},\"axes\":[{\"key\":\"wght\",\"value\":${lockScreenClockWeight.intValue}},{\"key\":\"wdth\",\"value\":${lockScreenClockWidth.intValue}},{\"key\":\"ROND\",\"value\":${lockScreenClockRoundness.intValue}}]}" + } + val command = "settings put secure lock_screen_custom_clock_face '$json'" + var success = false + + if (PermissionUtils.canWriteSecureSettings(context)) { + try { + success = Settings.Secure.putString( + context.contentResolver, + "lock_screen_custom_clock_face", + json + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (!success) { + if (ShizukuUtils.hasPermission()) { + ShizukuUtils.runCommand(command) + success = true + } else if (RootUtils.isRootPermissionGranted()) { + RootUtils.runCommand(command) + success = true + } + } + + if (success) { + lockScreenClockId.value = clockId + } + } + + fun setLockScreenClockWeight(value: Int, context: Context) { + lockScreenClockWeight.intValue = value + settingsRepository.setLockScreenClockWeight(value) + lockScreenClockId.value?.let { setLockScreenClockId(it, context) } + } + + fun setLockScreenClockWidth(value: Int, context: Context) { + lockScreenClockWidth.intValue = value + settingsRepository.setLockScreenClockWidth(value) + lockScreenClockId.value?.let { setLockScreenClockId(it, context) } + } + + fun setLockScreenClockGrade(value: Int, context: Context) { + lockScreenClockGrade.intValue = value + settingsRepository.setLockScreenClockGrade(value) + lockScreenClockId.value?.let { setLockScreenClockId(it, context) } + } + + fun setLockScreenClockRoundness(value: Int, context: Context) { + lockScreenClockRoundness.intValue = value + settingsRepository.setLockScreenClockRoundness(value) + lockScreenClockId.value?.let { setLockScreenClockId(it, context) } + } + + fun setLockScreenClockColorTone(value: Int, context: Context) { + lockScreenClockColorTone.intValue = value + settingsRepository.setLockScreenClockColorTone(value) + + // Update effective seed color based on new tone + if (lockScreenClockSelectedColorId.value != "DEFAULT") { + val effectiveSeed = calculateEffectiveSeedColor(lockScreenClockSelectedColorId.value, value) + lockScreenClockSeedColor.intValue = effectiveSeed + settingsRepository.setLockScreenClockSeedColor(effectiveSeed) + } + + lockScreenClockId.value?.let { setLockScreenClockId(it, context) } + } + + fun setLockScreenClockColor(id: String, seed: Int, context: Context) { + lockScreenClockSelectedColorId.value = id + val effectiveSeed = if (id == "DEFAULT") 0 else calculateEffectiveSeedColor(id, lockScreenClockColorTone.intValue) + lockScreenClockSeedColor.intValue = effectiveSeed + settingsRepository.setLockScreenClockSelectedColorId(id) + settingsRepository.setLockScreenClockSeedColor(effectiveSeed) + lockScreenClockId.value?.let { setLockScreenClockId(it, context) } + } + + private fun calculateEffectiveSeedColor(colorId: String, tone: Int): Int { + val baseColor = when (colorId) { + "RED" -> android.graphics.Color.parseColor("#E57373") + "GREEN" -> android.graphics.Color.parseColor("#81C784") + "BLUE" -> android.graphics.Color.parseColor("#64B5F6") + "YELLOW" -> android.graphics.Color.parseColor("#FFF176") + "ORANGE" -> android.graphics.Color.parseColor("#FFB74D") + "PURPLE" -> android.graphics.Color.parseColor("#BA68C8") + "PINK" -> android.graphics.Color.parseColor("#F06292") + "TEAL" -> android.graphics.Color.parseColor("#4DB6AC") + else -> return 0 + } + val hsv = FloatArray(3) + android.graphics.Color.colorToHSV(baseColor, hsv) + + // Calibrated HSV mapping to match user examples: + // Tone 0 -> Saturation ~0.8, Value ~0.35 (Dark, saturated) + // Tone 100 -> Saturation ~0.2, Value ~1.0 (Light, pastel) + hsv[1] = (0.8f - (tone / 100f) * 0.6f).coerceIn(0f, 1f) + hsv[2] = (0.35f + (tone / 100f) * 0.65f).coerceIn(0f, 1f) + + return android.graphics.Color.HSVToColor(hsv) + } + + private fun readCurrentLockScreenClockId(context: Context): String? { + return try { + val raw = Settings.Secure.getString( + context.contentResolver, + "lock_screen_custom_clock_face" + ) ?: return null + // Extract clockId from JSON string like {"clockId":"DIGITAL_CLOCK_WEATHER"} + val match = Regex(""""clockId":\s*"([^"]+)"""").find(raw) + match?.groupValues?.getOrNull(1) + } catch (e: Exception) { + null + } + } + private fun applyHideGestureBar(context: Context, enabled: Boolean) { if (enabled) { com.sameerasw.essentials.utils.StatusBarManager.requestDisable( diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/StatusBarIconViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/StatusBarIconViewModel.kt index 81c011d26..077226507 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/StatusBarIconViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/StatusBarIconViewModel.kt @@ -116,50 +116,11 @@ class StatusBarIconViewModel : ViewModel() { loadStatusBarSettings(context) loadAdvancedFlags(context) - if (isSmartWiFiEnabled.value && isWriteSecureSettingsEnabled.value) { - startSmartWiFiUpdates(context) - } - - if (isSmartDataEnabled.value && isWriteSecureSettingsEnabled.value) { - startSmartDataUpdates(context) - } - - if (batteryPercentageMode.value == 2) { - startBatteryStatusListener(context) - } - } - - private fun startBatteryStatusListener(context: Context) { - if (batteryReceiver != null) return - - batteryReceiver = object : android.content.BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (batteryPercentageMode.value == 2) { - val isCharging = intent.action == Intent.ACTION_POWER_CONNECTED - updateSettingsValue( - context, - "status_bar_show_battery_percent", - if (isCharging) 1 else 0 - ) - } - } - } - - val filter = IntentFilter().apply { - addAction(Intent.ACTION_POWER_CONNECTED) - addAction(Intent.ACTION_POWER_DISCONNECTED) - } - context.registerReceiver(batteryReceiver, filter) - } + loadStatusBarSettings(context) + loadAdvancedFlags(context) - private fun stopBatteryStatusListener(context: Context) { - batteryReceiver?.let { - try { - context.unregisterReceiver(it) - } catch (e: Exception) { - } - batteryReceiver = null - } + // Initial update for UI consistency + updateIconBlacklist(context) } /** @@ -239,13 +200,7 @@ class StatusBarIconViewModel : ViewModel() { context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE).edit { putBoolean(PREF_SMART_WIFI_ENABLED, enabled) } - - if (enabled && isWriteSecureSettingsEnabled.value) { - startSmartWiFiUpdates(context) - } else { - smartWifiJob?.cancel() - updateIconBlacklist(context) - } + updateIconBlacklist(context) } fun setSmartDataEnabled(enabled: Boolean, context: Context) { @@ -253,14 +208,7 @@ class StatusBarIconViewModel : ViewModel() { context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE).edit { putBoolean(PREF_SMART_DATA_ENABLED, enabled) } - - if (enabled && isWriteSecureSettingsEnabled.value) { - startSmartDataUpdates(context) - } else { - smartDataJob?.cancel() - updateIconBlacklist(context) - } - + updateIconBlacklist(context) updateSelectedNetworkTypes(context, enabled) } @@ -297,117 +245,6 @@ class StatusBarIconViewModel : ViewModel() { updateIconBlacklistSetting(context, blacklistNames) } - private fun startSmartWiFiUpdates(context: Context) { - smartWifiJob?.cancel() - smartWifiJob = scope.launch { - while (true) { - val isWifiConnected = isWifiConnected(context) - updateSmartWiFiBlacklist(context, isWifiConnected) - delay(1000) - } - } - } - - /** - * Update blacklist for Smart WiFi feature - * Uses registry-based approach for clean code - */ - private fun updateSmartWiFiBlacklist(context: Context, wifiConnected: Boolean) { - if (!isSmartWiFiEnabled.value || !isWriteSecureSettingsEnabled.value) return - - val visibilities = getIconVisibilities().toMutableMap() - - // Smart WiFi logic: hide mobile data when WiFi is connected - val mobileDataVisible = visibilities["mobile_data"] ?: true - visibilities["mobile_data"] = mobileDataVisible && !wifiConnected - - val blacklistNames = StatusBarIconRegistry.getBlacklistNames(visibilities) - updateIconBlacklistSetting(context, blacklistNames) - } - - private fun startSmartDataUpdates(context: Context) { - smartDataJob?.cancel() - smartDataJob = scope.launch { - while (true) { - val currentNetworkType = getCurrentNetworkType(context) - updateSmartDataBlacklist(context, currentNetworkType) - delay(10000) - } - } - } - - /** - * Update blacklist for Smart Data feature - */ - private fun updateSmartDataBlacklist(context: Context, networkType: NetworkType) { - if (!isSmartDataEnabled.value || !isWriteSecureSettingsEnabled.value) { - return - } - - if (isSmartWiFiEnabled.value && isWifiConnected(context)) { - return - } - - val visibilities = getIconVisibilities().toMutableMap() - - val shouldHideMobileData = selectedNetworkTypes.value.contains(networkType) || - (selectedNetworkTypes.value.contains(NetworkType.NETWORK_OTHER) && - !setOf( - NetworkType.NETWORK_5G, - NetworkType.NETWORK_4G, - NetworkType.NETWORK_3G - ).contains(networkType)) - - visibilities["mobile_data"] = !shouldHideMobileData - - val blacklistNames = StatusBarIconRegistry.getBlacklistNames(visibilities) - updateIconBlacklistSetting(context, blacklistNames) - } - - private fun getCurrentNetworkType(context: Context): NetworkType { - return try { - if (ContextCompat.checkSelfPermission( - context, - Manifest.permission.READ_PHONE_STATE - ) != PackageManager.PERMISSION_GRANTED - ) { - return NetworkType.NETWORK_OTHER - } - - val connectivityManager = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val network = connectivityManager.activeNetwork ?: return NetworkType.NETWORK_OTHER - val capabilities = connectivityManager.getNetworkCapabilities(network) - ?: return NetworkType.NETWORK_OTHER - - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - return NetworkType.NETWORK_OTHER - } - - val telephonyManager = - context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - - @Suppress("DEPRECATION") - val networkType = telephonyManager.networkType - - when (networkType) { - TelephonyManager.NETWORK_TYPE_NR -> NetworkType.NETWORK_5G - TelephonyManager.NETWORK_TYPE_LTE, - TelephonyManager.NETWORK_TYPE_HSPAP -> NetworkType.NETWORK_4G - - TelephonyManager.NETWORK_TYPE_HSDPA, - TelephonyManager.NETWORK_TYPE_HSUPA, - TelephonyManager.NETWORK_TYPE_HSPA, - TelephonyManager.NETWORK_TYPE_UMTS, - TelephonyManager.NETWORK_TYPE_TD_SCDMA -> NetworkType.NETWORK_3G - - else -> NetworkType.NETWORK_OTHER - } - } catch (@Suppress("UNUSED_PARAMETER") e: Exception) { - NetworkType.NETWORK_OTHER - } - } - private fun isWifiConnected(context: Context): Boolean { return try { val connectivityManager = @@ -535,12 +372,6 @@ class StatusBarIconViewModel : ViewModel() { putInt(PREF_BATTERY_PERCENT_MODE, mode) } - if (mode == 2) { - startBatteryStatusListener(context) - } else { - stopBatteryStatusListener(context) - } - val systemValue = when (mode) { 1 -> 1 // Always 2 -> { // Charging diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/WatchViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/WatchViewModel.kt index 1878d448e..944e89e2a 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/WatchViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/WatchViewModel.kt @@ -31,4 +31,13 @@ class WatchViewModel : ViewModel() { connectedWatchName.value = null } } + + fun openPlayStoreOnWatch(context: Context) { + val intent = android.content.Intent(android.content.Intent.ACTION_VIEW) + .setData(android.net.Uri.parse("market://details?id=com.sameerasw.essentials")) + .addCategory(android.content.Intent.CATEGORY_BROWSABLE) + + val remoteActivityHelper = androidx.wear.remote.interactions.RemoteActivityHelper(context) + remoteActivityHelper.startRemoteActivity(intent) + } } diff --git a/app/src/main/res/drawable/clock_bignum.jpg b/app/src/main/res/drawable/clock_bignum.jpg new file mode 100644 index 000000000..e6096439b Binary files /dev/null and b/app/src/main/res/drawable/clock_bignum.jpg differ diff --git a/app/src/main/res/drawable/clock_calligraphy.jpg b/app/src/main/res/drawable/clock_calligraphy.jpg new file mode 100644 index 000000000..4a854385f Binary files /dev/null and b/app/src/main/res/drawable/clock_calligraphy.jpg differ diff --git a/app/src/main/res/drawable/clock_flex.jpg b/app/src/main/res/drawable/clock_flex.jpg new file mode 100644 index 000000000..6d28646cb Binary files /dev/null and b/app/src/main/res/drawable/clock_flex.jpg differ diff --git a/app/src/main/res/drawable/clock_growth.jpg b/app/src/main/res/drawable/clock_growth.jpg new file mode 100644 index 000000000..b7cbb1c5c Binary files /dev/null and b/app/src/main/res/drawable/clock_growth.jpg differ diff --git a/app/src/main/res/drawable/clock_handwritten.jpg b/app/src/main/res/drawable/clock_handwritten.jpg new file mode 100644 index 000000000..9b75a556a Binary files /dev/null and b/app/src/main/res/drawable/clock_handwritten.jpg differ diff --git a/app/src/main/res/drawable/clock_inflate.jpg b/app/src/main/res/drawable/clock_inflate.jpg new file mode 100644 index 000000000..e556407e3 Binary files /dev/null and b/app/src/main/res/drawable/clock_inflate.jpg differ diff --git a/app/src/main/res/drawable/clock_metro.jpg b/app/src/main/res/drawable/clock_metro.jpg new file mode 100644 index 000000000..4beec0c5b Binary files /dev/null and b/app/src/main/res/drawable/clock_metro.jpg differ diff --git a/app/src/main/res/drawable/clock_overlap.jpg b/app/src/main/res/drawable/clock_overlap.jpg new file mode 100644 index 000000000..376d3c235 Binary files /dev/null and b/app/src/main/res/drawable/clock_overlap.jpg differ diff --git a/app/src/main/res/drawable/clock_weather.jpg b/app/src/main/res/drawable/clock_weather.jpg new file mode 100644 index 000000000..7dbc2ed9a Binary files /dev/null and b/app/src/main/res/drawable/clock_weather.jpg differ diff --git a/app/src/main/res/drawable/rounded_arrows_outward_24.xml b/app/src/main/res/drawable/rounded_arrows_outward_24.xml new file mode 100644 index 000000000..b8aeb3703 --- /dev/null +++ b/app/src/main/res/drawable/rounded_arrows_outward_24.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/rounded_line_weight_24.xml b/app/src/main/res/drawable/rounded_line_weight_24.xml new file mode 100644 index 000000000..f3bfcf2d9 --- /dev/null +++ b/app/src/main/res/drawable/rounded_line_weight_24.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/values-ach/strings.xml b/app/src/main/res/values-ach/strings.xml index 6dcf5eb30..a6a5eadef 100644 --- a/app/src/main/res/values-ach/strings.xml +++ b/app/src/main/res/values-ach/strings.xml @@ -1114,7 +1114,7 @@ Mitte me juku cim ni ka oo. Dii me miini. %1$d m %1$.1f km - Alarm me wot tye ka tic + Travelling to %1$s %1$s ma odong (%2$d%%) Wot tye ka mede anyim Nyutu bor ma tye i kare kikome me oo kama omyera icit iye diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index f2587a674..9071fe95e 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -1114,7 +1114,7 @@ Vereis om jou toestel wakker te maak met aankoms. Tik om toe te staan. %1$d m %1$.1f km - Reisalarm aktief + Travelling to %1$s %1$s oorblywende (%2$d%%) Reisvordering Toon intydse afstand na bestemming diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index b2aadf5b6..10d0498a1 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -1114,7 +1114,7 @@ مطلوب لتنبيه جهازك عند الوصول. انقر للمنح. %1$d م %1$.1f كم - إنذار السفر نشط + Travelling to %1$s %1$s المتبقي (%2$d%%) تقدم السفر يظهر المسافة في الوقت الحقيقي إلى الوجهة diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index e21d727dd..7a83e89f1 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -1114,7 +1114,7 @@ Necessari per activar el dispositiu en arribar. Toca per concedir. %1$d m %1$,1f km - Alarma de viatge activa + Travelling to %1$s %1$s restant (%2$d%%) Progrés de viatge Mostra la distància en temps real fins a la destinació diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 465872c35..5a65e334e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1114,7 +1114,7 @@ Vyžadováno k probuzení zařízení při příjezdu. Klepnutím udělíte. %1$d m %1$.1f km - Aktivní cestovní alarm + Travelling to %1$s %1$s zbývající (%2$d%%) Cestovní pokrok Zobrazuje vzdálenost do cíle v reálném čase diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index e69f496d6..17d3dd852 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -1114,7 +1114,7 @@ Påkrævet for at vække din enhed ved ankomst. Tryk for at give. %1$d m %1$.1f km - Rejsealarm aktiv + Travelling to %1$s %1$s resterende (%2$d%%) Rejsefremskridt Viser afstand til destination i realtid diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ddc6e162c..6978f5ae3 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1114,7 +1114,7 @@ Benötigt, um dein Gerät bei der Ankunft zu wecken. Tippe zum Gewähren. %1$d m %1$.1f km - Reisealarm aktiv + Travelling to %1$s %1$s verbleibend (%2$d%%) Reisefortschritt Zeigt die Entfernung zum Zielort in Echtzeit an diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index ce0f8a5f8..45c087c3d 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1114,7 +1114,7 @@ Απαιτείται η αφύπνιση της συσκευής σας κατά την άφιξη. Πατήστε για παραχώρηση. %1$d m %1$.1f χλμ - Ενεργός συναγερμός ταξιδιού + Travelling to %1$s %1$s υπόλοιπο (%2$d%%) Ταξιδιωτική Πρόοδος Εμφανίζει την απόσταση από τον προορισμό σε πραγματικό χρόνο diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index c567cf212..09d7e1582 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -1,6 +1,9 @@ - + BETA + Attempt Shizuku restart + Shizuku Restart Warning + Since wireless debugging was toggled, Shizuku services might not have started automatically causing freezing to not function. Consider turning on attempt shizuku restart as well 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. App Freezing Disable apps that are rarely used @@ -1103,7 +1106,7 @@ Processing location… DISTANCE REMAINING Calculating… - Stop Tracking + Stop Destination Ready Start Tracking View Map @@ -1114,7 +1117,7 @@ Required to wake your device upon arrival. Tap to grant. %1$d m %1$.1f km - Travel Alarm active + Travelling to %1$s %1$s remaining (%2$d%%) Travel Progress Shows real-time distance to destination @@ -1221,6 +1224,9 @@ No Watch detected It looks like you do not have the Essentials Wear companion app installed on your watch. Install Companion + Install on Watch + To use Watch features, you need to install the Essentials companion app on your Wear OS watch. Click the button below to open the Play Store directly on your watch. + Open on Watch Interaction Interface Display @@ -1383,11 +1389,31 @@ Disable USB Debugging Disable Wireless Debugging Disable Accessibility Services - Shortcut created for %s + Shortcut for %1$s created on home screen + Disable rotation suggestion + Do not show the rotation button when auto-rotate is off App got shut up Shut up features returned Auto Freeze %1$s will be archived in %2$d seconds Freeze now Abort + + Lock screen clock + Customize lock screen clock on Pixels + Change the lock screen clock style on Pixels. Select from the available clock faces provided by the System UI. Some old styles might not be available. + Big Number + Calligraphy + Flex + Growth + Handwritten + Inflate + Metro + Number Overlap + Weather + Select Clock Style + Current + Apply + Clock style applied + WRITE_SECURE_SETTINGS permission required diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2fcf8ce36..2d118884d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1116,7 +1116,7 @@ Cuando llegue un mensaje o alerta de una aplicación seleccionada, la Pantalla s Necesario para activar tu dispositivo al llegar. Toca para concederlo. %1$d m %1$.1f km - Alarma de viaje activa + Travelling to %1$s %1$s restante (%2$d%%) Progreso del viaje Muestra la distancia al destino en tiempo real diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 0bc1132d9..2f47f305e 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -1114,7 +1114,7 @@ Pakollinen herättämään laitteesi saavuttaessa. Myönnä napauttamalla. %1$d m %1$.1f km - Matkahälytys aktiivinen + Travelling to %1$s %1$s jäljellä (%2$d%%) Matkan edistyminen Näyttää reaaliaikaisen etäisyyden määränpäähän diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5594e1e5f..eaa4a8433 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1114,7 +1114,7 @@ Requise pour réveiller votre appareil à l\'arrivée. Appuyez pour autoriser. %1$d m %1$.1f km - Alarme de trajet active + Travelling to %1$s %1$s restant (%2$d%) Progrès du trajet Affiche la distance de la destination en temps réel diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index a58b1cb1e..f934e695b 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1103,7 +1103,7 @@ Processing location… DISTANCE REMAINING Calculating… - Stop Tracking + Stop Destination Ready Start Tracking View Map @@ -1114,7 +1114,7 @@ Required to wake your device upon arrival. Tap to grant. %1$d m %1$.1f km - Travel Alarm active + Travelling to %1$s %1$s remaining (%2$d%%) Travel Progress Shows real-time distance to destination diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index ffa17225e..674b3d35a 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1114,7 +1114,7 @@ Az eszköz felébresztéséhez érkezéskor szükséges. Koppintson az engedélyezéshez. %1$d m %1$.1f km - Utazási riasztó aktív + Travelling to %1$s %1$s fennmaradó (%2$d%%) Az utazás előrehaladása Valós idejű távolságot mutat a céltól diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 1f6c8e48b..e5bc606c8 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1114,7 +1114,7 @@ Necessario per riattivare il dispositivo all\'arrivo. Tocca per concedere. %1$d M %1$.1f km - Allarme di viaggio attivo + Travelling to %1$s %1$s rimanente (%2$d%%) Progresso del viaggio Mostra la distanza in tempo reale dalla destinazione diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 89e33788e..9ddf1d9d3 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1114,7 +1114,7 @@ 到着時にデバイスを起動するのに必要です。許可するにはタップしてください。 %1$dm %1$.1fkm - Travel Alarm active + Travelling to %1$s 残り%1$s (%2$d%%) お出かけの進行状況 目的地までの距離をリアルタイムで表示 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index fad9750ca..87d320763 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1114,7 +1114,7 @@ 도착 시 기기를 깨워야 합니다. 부여하려면 탭하세요. %1$d 중 %1$.1fkm - 여행 알람 활성화 + Travelling to %1$s %1$s 남음 (%2$d%%) 여행 진행 목적지까지의 거리를 실시간으로 표시 diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index e364458ee..b1f12bed2 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1114,7 +1114,7 @@ Vereist voor het wekken van je apparaat bij aankomst. Klikken om toestemmingen te geven. %1$d m %1$.1f km - Travel Alarm active + Travelling to %1$s %1$s resterend (%2$d%%) Reisvoortgang Laat de realtime afstand tot de bestemming zien diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index b1e533bab..0828b4111 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -1114,7 +1114,7 @@ Nødvendig for å vekke enheten ved ankomst. Trykk for å gi. %1$d m %1$.1f km - Reisealarm aktiv + Travelling to %1$s %1$s gjenværende (%2$d%%) Reisefremgang Viser avstand til destinasjon i sanntid diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 13b4fbed5..f290460d2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1114,7 +1114,7 @@ Wymagane, aby obudzić urządzenie po przyjeździe. Kliknij, aby przyznać. %1$d M %1$.1f km - Aktywny alarm podróżny + Travelling to %1$s %1$s pozostałe (%2$d%%) Postęp podróży Pokazuje w czasie rzeczywistym odległość do celu diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d27f25106..fc3f43066 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1103,7 +1103,7 @@ Processing location… DISTANCE REMAINING Calculating… - Stop Tracking + Stop Destination Ready Start Tracking View Map @@ -1114,7 +1114,7 @@ Required to wake your device upon arrival. Tap to grant. %1$d m %1$.1f km - Travel alarm active + Travelling to %1$s %1$s remaining (%2$d%%) Travel Progress Shows real-time distance to destination diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 05e35d608..58b3b81a3 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1114,7 +1114,7 @@ Este necesar să vă treziți dispozitivul la sosire. Atingeți pentru a acorda. %1$d m %1$.1f km - Alarma de călătorie activă + Travelling to %1$s %1$s rămas (%2$d%%) Progresul călătoriei Afișează distanța în timp real până la destinație diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d1ad6a0bc..fbcdeabb1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1114,7 +1114,7 @@ Требуется разрешение на пробуждение устройства по прибытии. Нажмите, чтобы разрешить. %1$d м %1$.1f км - Сигнализация движения активна + Travelling to %1$s %1$s осталось (%2$d%%) Прогресс путешествия Показывает расстояние до пункта назначения в режиме реального времени diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index 9acaad479..ce72041fb 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -1,6 +1,9 @@ බීටා + Shizuku නැවත ආරම්භ කිරීමට උත්සාහ කරන්න + Shizuku නැවත ආරම්භ කිරීමේ අනතුරු ඇඟවීම + Wireless debugging ටොගල් කර ඇති බැවින්, Shizuku සේවාවන් ස්වයංක්‍රීයව ආරම්භ නොවී තිබිය හැකි අතර එමඟින් යෙදුම් කැටි කිරීම ක්‍රියාත්මක නොවේ. Shizuku නැවත ආරම්භ කිරීමට උත්සාහ කරන්න විකල්පයද ක්‍රියාත්මක කිරීම සලකා බලන්න 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. යෙදුම් කැටි කිරීම කලාතුරකින් භාවිතා කරන යෙදුම් අක්රිය කරන්න @@ -1114,7 +1117,7 @@ පැමිණීමෙන් පසු ඔබගේ උපාංගය අවදි කිරීමට අවශ්‍ය වේ. ප්‍රදානය කිරීමට තට්ටු කරන්න. %1$d මීටර් කිලෝමීටර් %1$.1f - සංචාරක අනතුරු ඇඟවීම සක්‍රීයයි + Travelling to %1$s %1$s ඉතිරි (%2$d%%) ගමන් ප්‍රගතිය ගමනාන්තයට තත්‍ය කාලීන දුර පෙන්වයි @@ -1390,4 +1393,6 @@ %1$s will be archived in %2$d seconds Freeze now Abort + භ්‍රමණ යෝජනා අබල කරන්න + ස්වයංක්‍රීය භ්‍රමණය අක්‍රිය වූ විට භ්‍රමණ බොත්තම පෙන්වන්න එපා diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index eae1822f2..b3ac02ce5 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -1114,7 +1114,7 @@ Неопходно је да пробудите уређај по доласку. Додирните да бисте одобрили. %1$d м %1$.1ф км - Путни аларм активан + Travelling to %1$s %1$s преостали (%2$d%%) Травел Прогресс Приказује удаљеност до одредишта у реалном времену diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 44408ec00..b9866376b 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1114,7 +1114,7 @@ Krävs för att väcka din enhet vid ankomst. Tryck för att bevilja. %1$d m %1$.1f km - Resalarm aktivt + Travelling to %1$s %1$s återstående (%2$d%%) Resans framsteg Visar avstånd till destination i realtid diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 35ec7f7d4..33769d568 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1115,7 +1115,7 @@ Varışta cihazınızı uyandırmak için gereklidir. Vermek için dokunun. %1$d M %1$.1f km - Seyahat alarmı etkin + Travelling to %1$s %1$s kalan (%2$d%%) Seyahat İlerlemesi Hedefe olan gerçek zamanlı mesafeyi gösterir diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 270a52649..67b28be58 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1114,7 +1114,7 @@ Потрібно, щоб вивести пристрій із режиму сну після прибуття. Торкніться, щоб надати. %1$d м %1$.1f км - Будильник подорожі активний + Travelling to %1$s %1$s залишилося (%2$d%%) Прогрес подорожі Показує відстань до місця призначення в реальному часі diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 03cc46eeb..84c7f8ccd 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1114,7 +1114,7 @@ Cần thiết để đánh thức thiết bị của bạn khi đến nơi. Nhấn để cấp. %1$d tôi %1$.1f km - Cảnh báo hành trình đang hoạt động + Travelling to %1$s %1$s còn lại (%2$d%%) Tiến độ du lịch Shows real-time distance to destination diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index d18998cb2..b8d3b0f2f 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1114,7 +1114,7 @@ 需要在到达时唤醒您的设备。点击授予。 %1$d 米 %1$.1f 公里 - 旅行闹钟活跃 + Travelling to %1$s 剩余 %1$s 行程进度 显示到目的地的实时距离 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 36d9e984e..20c4c5789 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,8 @@ BETA + Attempt Shizuku restart + Shizuku Restart Warning + Since wireless debugging was toggled, Shizuku services might not have started automatically causing freezing to not function. Consider turning on attempt shizuku restart as well 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. App Freezing Disable apps that are rarely used @@ -761,6 +764,8 @@ Re-Start Share coordinates (Dropped pin) from Google Maps to Essentials to save as a destination.\n\nThe distance shown is the direct distance to the destination, not the distance along the roads.\n\nTake all calculations of time and distance with a grain of salt as they are not always accurate. Are we there yet? + Pause + Resume Radius: %1$d m Distance to target: %1$s Last: %1$s @@ -1168,7 +1173,7 @@ Processing location… DISTANCE REMAINING Calculating… - Stop Tracking + Stop Destination Ready Start Tracking View Map @@ -1179,7 +1184,7 @@ Required to wake your device upon arrival. Tap to grant. %1$d m %1$.1f km - Travel alarm active + Travelling to %1$s %1$s remaining (%2$d%%) Travel Progress Shows real-time distance to destination @@ -1302,6 +1307,9 @@ No Watch detected It looks like you do not have the Essentials Wear companion app installed on your watch. Install Companion + Install on Watch + To use Watch features, you need to install the Essentials companion app on your Wear OS watch. Click the button below to open the Play Store directly on your watch. + Open on Watch Interaction @@ -1472,10 +1480,49 @@ Disable Wireless Debugging Disable Accessibility Services Shortcut created for %s + Disable rotation suggestion + Do not show the rotation button when auto-rotate is off App got shut up Shut up features returned Auto Freeze %1$s will be archived in %2$d seconds Freeze now Abort + + Lock screen clock + Customize lock screen clock on Pixels + Change the lock screen clock style on Pixels. Select from the available clock faces provided by the System UI. Some old styles might not be available. + Big Number + Calligraphy + Flex + Growth + Handwritten + Inflate + Metro + Number Overlap + Weather + Select Clock Style + Current + Apply + Clock style applied + WRITE_SECURE_SETTINGS permission required + Default + Weight + Width + Grade + Roundness + Lightness + Variation + Color + Style & Font + About + Default + Red + Green + Blue + Yellow + Orange + Purple + Pink + Teal diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 184cf0853..94ec054b7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ work = "2.9.1" sentry = "8.14.0" graphicsShapes = "1.0.1" media3 = "1.5.1" +wearRemoteInteractions = "1.1.0" [libraries] @@ -60,6 +61,7 @@ sentry-android = { group = "io.sentry", name = "sentry-android", version.ref = " androidx-graphics-shapes = { group = "androidx.graphics", name = "graphics-shapes", version.ref = "graphicsShapes" } androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" } androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3" } +androidx-wear-remote-interactions = { group = "androidx.wear", name = "wear-remote-interactions", version.ref = "wearRemoteInteractions" } [plugins]