diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 09ac4550c..fbf167a9e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,19 +8,24 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget kotlin { compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) + jvmTarget.set(JvmTarget.JVM_21) freeCompilerArgs.add("-Xannotation-default-target=param-property") + optIn.addAll( + "androidx.compose.material3.ExperimentalMaterial3Api", + "androidx.compose.material3.ExperimentalMaterial3ExpressiveApi", + "androidx.compose.foundation.ExperimentalFoundationApi" + ) } } android { namespace = "com.sameerasw.essentials" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "com.sameerasw.essentials" minSdk = 26 - targetSdk = 36 + targetSdk = 37 versionCode = 43 versionName = "14.0" @@ -58,8 +63,8 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildFeatures { compose = true @@ -83,7 +88,7 @@ dependencies { implementation("androidx.core:core-splashscreen:1.0.1") // Force latest Material3 1.5.0-alpha17 for new MaterialShapes - implementation("androidx.compose.material3:material3:1.5.0-alpha17") + implementation("androidx.compose.material3:material3:1.5.0-alpha19") implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.graphics) diff --git a/app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt b/app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt index 4533e2448..dd9eac455 100644 --- a/app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt @@ -7,26 +7,30 @@ import android.os.Bundle import android.util.Log import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricPrompt -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage import androidx.core.content.ContextCompat -import androidx.appcompat.app.AppCompatActivity +import coil.compose.AsyncImage import com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService import com.sameerasw.essentials.ui.theme.EssentialsTheme import java.util.concurrent.Executor @@ -159,7 +163,7 @@ class AppLockActivity : AppCompatActivity() { putExtra("package_name", packageToLock) } sendBroadcast(intent) - + val accessibilityIntent = Intent(this, ScreenOffAccessibilityService::class.java).apply { action = "APP_AUTHENTICATED" putExtra("package_name", packageToLock) diff --git a/app/src/main/java/com/sameerasw/essentials/AppUpdatesActivity.kt b/app/src/main/java/com/sameerasw/essentials/AppUpdatesActivity.kt index d6672d4ec..20c31e37b 100644 --- a/app/src/main/java/com/sameerasw/essentials/AppUpdatesActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/AppUpdatesActivity.kt @@ -8,6 +8,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -46,7 +47,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.viewmodel.compose.viewModel import com.sameerasw.essentials.ui.components.ReusableTopAppBar import com.sameerasw.essentials.ui.components.cards.TrackedRepoCard @@ -260,7 +260,12 @@ class AppUpdatesActivity : AppCompatActivity() { modifier = Modifier.padding(bottom = 16.dp, end = 16.dp), containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - elevation = androidx.compose.material3.FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp) + elevation = androidx.compose.material3.FloatingActionButtonDefaults.elevation( + 0.dp, + 0.dp, + 0.dp, + 0.dp + ) ) { androidx.compose.material3.Icon( painter = painterResource(id = R.drawable.rounded_add_24), @@ -299,7 +304,7 @@ class AppUpdatesActivity : AppCompatActivity() { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - androidx.compose.material3.Text( + Text( text = stringResource(R.string.msg_no_repos_tracked), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant @@ -307,7 +312,7 @@ class AppUpdatesActivity : AppCompatActivity() { Spacer(modifier = Modifier.height(32.dp)) - androidx.compose.material3.Text( + Text( text = stringResource(R.string.label_apps), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(bottom = 12.dp), @@ -334,7 +339,7 @@ class AppUpdatesActivity : AppCompatActivity() { // Pending Section if (pending.isNotEmpty()) { item { - androidx.compose.material3.Text( + Text( text = "${stringResource(R.string.label_pending)} (${pending.size})", style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(start = 16.dp, bottom = 8.dp), @@ -383,7 +388,7 @@ class AppUpdatesActivity : AppCompatActivity() { // Up-to-date Section if (upToDate.isNotEmpty()) { item { - androidx.compose.material3.Text( + Text( text = "${stringResource(R.string.label_up_to_date)} (${upToDate.size})", style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(start = 16.dp, bottom = 8.dp), @@ -441,7 +446,7 @@ class AppUpdatesActivity : AppCompatActivity() { // Not Installed Section if (notInstalled.isNotEmpty()) { item { - androidx.compose.material3.Text( + Text( text = "${stringResource(R.string.label_not_installed)} (${notInstalled.size})", style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(start = 16.dp, bottom = 8.dp), @@ -489,7 +494,7 @@ class AppUpdatesActivity : AppCompatActivity() { // Apps Section item { - androidx.compose.material3.Text( + Text( text = stringResource(R.string.label_apps), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(start = 16.dp, bottom = 8.dp), diff --git a/app/src/main/java/com/sameerasw/essentials/EssentialsApp.kt b/app/src/main/java/com/sameerasw/essentials/EssentialsApp.kt index ad5982dd7..4680ef977 100644 --- a/app/src/main/java/com/sameerasw/essentials/EssentialsApp.kt +++ b/app/src/main/java/com/sameerasw/essentials/EssentialsApp.kt @@ -4,16 +4,12 @@ import android.app.Application import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.os.Build import android.os.Handler import android.os.Looper import android.widget.Toast import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.utils.ShizukuUtils -import io.sentry.Sentry import io.sentry.android.core.SentryAndroid class EssentialsApp : Application() { @@ -27,11 +23,11 @@ class EssentialsApp : Application() { override fun onCreate() { super.onCreate() context = applicationContext - + try { resources?.configuration } catch (e: Exception) { - + } ShizukuUtils.initialize() @@ -70,12 +66,17 @@ class EssentialsApp : Application() { if (mode == "off") return SentryAndroid.init(this) { options -> - options.dsn = "https://e105699467efe3a43a16bfbad3a63b33@o4510996760887296.ingest.de.sentry.io/4510996763312208" + options.dsn = + "https://e105699467efe3a43a16bfbad3a63b33@o4510996760887296.ingest.de.sentry.io/4510996763312208" options.isEnabled = true - + options.setBeforeSend { event, _ -> Handler(Looper.getMainLooper()).post { - Toast.makeText(this@EssentialsApp, R.string.sentry_crash_toast, Toast.LENGTH_LONG).show() + Toast.makeText( + this@EssentialsApp, + R.string.sentry_crash_toast, + Toast.LENGTH_LONG + ).show() } event } diff --git a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt index b5f463c26..142a05367 100644 --- a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt @@ -7,6 +7,7 @@ import android.os.VibratorManager import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,7 +15,6 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars @@ -22,11 +22,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -38,12 +34,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.core.net.toUri -import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel @@ -56,7 +50,6 @@ import com.sameerasw.essentials.ui.components.linkActions.LinkPickerScreen import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet import com.sameerasw.essentials.ui.composables.configs.AlwaysOnDisplaySettingsUI import com.sameerasw.essentials.ui.composables.configs.AmbientMusicGlanceSettingsUI - import com.sameerasw.essentials.ui.composables.configs.AppLockSettingsUI import com.sameerasw.essentials.ui.composables.configs.BatteriesSettingsUI import com.sameerasw.essentials.ui.composables.configs.BatteryNotificationSettingsUI @@ -69,11 +62,11 @@ import com.sameerasw.essentials.ui.composables.configs.LocationReachedSettingsUI import com.sameerasw.essentials.ui.composables.configs.MapsPowerSavingSettingsUI import com.sameerasw.essentials.ui.composables.configs.NotificationLightingSettingsUI import com.sameerasw.essentials.ui.composables.configs.OtherCustomizationsSettingsUI -import com.sameerasw.essentials.ui.composables.configs.ShutUpSettingsUI import com.sameerasw.essentials.ui.composables.configs.QuickSettingsTilesSettingsUI import com.sameerasw.essentials.ui.composables.configs.RefreshRateSettingsUI import com.sameerasw.essentials.ui.composables.configs.ScreenLockedSecuritySettingsUI import com.sameerasw.essentials.ui.composables.configs.ScreenOffWidgetSettingsUI +import com.sameerasw.essentials.ui.composables.configs.ShutUpSettingsUI import com.sameerasw.essentials.ui.composables.configs.SnoozeNotificationsSettingsUI import com.sameerasw.essentials.ui.composables.configs.SoundModeTileSettingsUI import com.sameerasw.essentials.ui.composables.configs.StatusBarIconSettingsUI @@ -83,7 +76,6 @@ import com.sameerasw.essentials.ui.modifiers.BlurDirection import com.sameerasw.essentials.ui.modifiers.progressiveBlur import com.sameerasw.essentials.ui.theme.EssentialsTheme import com.sameerasw.essentials.utils.BiometricSecurityHelper -import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.viewmodels.CaffeinateViewModel import com.sameerasw.essentials.viewmodels.MainViewModel import com.sameerasw.essentials.viewmodels.StatusBarIconViewModel @@ -109,9 +101,10 @@ class FeatureSettingsActivity : AppCompatActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false } - - val isDarkMode = (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == - android.content.res.Configuration.UI_MODE_NIGHT_YES + + val isDarkMode = + (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == + android.content.res.Configuration.UI_MODE_NIGHT_YES window.setBackgroundDrawableResource(if (isDarkMode) android.R.color.black else R.color.app_window_background) val featureId = intent.getStringExtra("feature") ?: "" val featureObj = FeatureRegistry.ALL_FEATURES.find { it.id == featureId } @@ -178,7 +171,7 @@ class FeatureSettingsActivity : AppCompatActivity() { androidx.compose.runtime.CompositionLocalProvider( com.sameerasw.essentials.ui.state.LocalMenuStateManager provides remember { com.sameerasw.essentials.ui.state.MenuStateManager() } ) { - val view = LocalView.current + LocalView.current val prefs = context.getSharedPreferences("essentials_prefs", MODE_PRIVATE) val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -272,7 +265,10 @@ class FeatureSettingsActivity : AppCompatActivity() { "Text and animations" -> !viewModel.isWriteSettingsEnabled.value || !isWriteSecureSettingsEnabled "Always on Display" -> !isWriteSecureSettingsEnabled "Lock screen clock" -> !isWriteSecureSettingsEnabled - "Other customizations" -> !com.sameerasw.essentials.utils.ShellUtils.hasPermission(context) + "Other customizations" -> !com.sameerasw.essentials.utils.ShellUtils.hasPermission( + context + ) + "Shut-Up!" -> !isWriteSecureSettingsEnabled || !viewModel.isUsageStatsPermissionGranted.value else -> false } @@ -334,13 +330,15 @@ class FeatureSettingsActivity : AppCompatActivity() { ) } - val pageTitle = if (featureObj != null) stringResource(featureObj.title) else featureId + val pageTitle = + if (featureObj != null) stringResource(featureObj.title) else featureId val hasMenu = featureObj != null && featureObj.aboutDescription != null val statusBarHeightPx = with(LocalDensity.current) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() } - val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val statusBarHeight = + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() Box( modifier = Modifier @@ -352,7 +350,8 @@ class FeatureSettingsActivity : AppCompatActivity() { direction = BlurDirection.TOP ) ) { - val hasScroll = featureId != "Sound mode tile" && featureId != "Quick settings tiles" && featureId != "Location reached" + val hasScroll = + featureId != "Sound mode tile" && featureId != "Quick settings tiles" && featureId != "Location reached" Column( modifier = Modifier .fillMaxSize() @@ -365,11 +364,15 @@ class FeatureSettingsActivity : AppCompatActivity() { ) { // Top padding for status bar if (featureId != "Quick settings tiles" && featureId != "Location reached") { - androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(statusBarHeight)) + androidx.compose.foundation.layout.Spacer( + modifier = Modifier.height( + statusBarHeight + ) + ) } if (featureId == "Watch") { - val context = androidx.compose.ui.platform.LocalContext.current + val context = LocalContext.current LaunchedEffect(Unit) { watchViewModel.check(context) } @@ -711,7 +714,11 @@ class FeatureSettingsActivity : AppCompatActivity() { } // Bottom padding for toolbar if (featureId != "Quick settings tiles" && featureId != "Location reached") { - androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(150.dp)) + androidx.compose.foundation.layout.Spacer( + modifier = Modifier.height( + 150.dp + ) + ) } } diff --git a/app/src/main/java/com/sameerasw/essentials/LinkPickerActivity.kt b/app/src/main/java/com/sameerasw/essentials/LinkPickerActivity.kt index 199d26b7e..cb2a00503 100644 --- a/app/src/main/java/com/sameerasw/essentials/LinkPickerActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/LinkPickerActivity.kt @@ -3,9 +3,9 @@ package com.sameerasw.essentials import android.content.Intent import android.net.Uri import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -16,7 +16,8 @@ class LinkPickerActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val locationViewModel = com.sameerasw.essentials.viewmodels.LocationReachedViewModel(application) + val locationViewModel = + com.sameerasw.essentials.viewmodels.LocationReachedViewModel(application) if (locationViewModel.handleIntent(intent)) { val settingsIntent = Intent(this, FeatureSettingsActivity::class.java).apply { putExtra("feature", "Location reached") diff --git a/app/src/main/java/com/sameerasw/essentials/MainActivity.kt b/app/src/main/java/com/sameerasw/essentials/MainActivity.kt index 9bfc03064..53fb66798 100644 --- a/app/src/main/java/com/sameerasw/essentials/MainActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/MainActivity.kt @@ -6,40 +6,45 @@ import android.os.Build import android.os.Bundle import android.util.Log import android.view.animation.AnticipateInterpolator +import android.widget.Toast +import androidx.activity.compose.PredictiveBackHandler +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.compose.animation.core.animateFloatAsState +import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button import androidx.compose.material3.CircularWavyProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingToolbarDefaults -import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset import androidx.compose.material3.FloatingToolbarExitDirection.Companion.Bottom import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -55,56 +60,38 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale +import androidx.compose.ui.layout.ContentScale 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.unit.dp import androidx.compose.ui.zIndex -import androidx.compose.ui.draw.scale -import androidx.compose.ui.draw.alpha -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import androidx.activity.compose.PredictiveBackHandler import androidx.core.animation.doOnEnd import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat -import androidx.appcompat.app.AppCompatActivity -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale import coil.compose.AsyncImage -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale +import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.domain.DIYTabs import com.sameerasw.essentials.domain.registry.initPermissionRegistry import com.sameerasw.essentials.ui.components.EssentialsFloatingToolbar import com.sameerasw.essentials.ui.components.ToolbarItem import com.sameerasw.essentials.ui.components.cards.TrackedRepoCard -import androidx.compose.foundation.layout.statusBarsPadding -import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer +import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu +import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem import com.sameerasw.essentials.ui.components.sheets.AddRepoBottomSheet import com.sameerasw.essentials.ui.components.sheets.GitHubAuthSheet import com.sameerasw.essentials.ui.components.sheets.InstructionsBottomSheet import com.sameerasw.essentials.ui.components.sheets.PrankBottomSheet import com.sameerasw.essentials.ui.components.sheets.UpdateBottomSheet -import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu -import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem -import com.sameerasw.essentials.ui.composables.WelcomeScreen import com.sameerasw.essentials.ui.composables.DIYScreen import com.sameerasw.essentials.ui.composables.FreezeGridUI import com.sameerasw.essentials.ui.composables.SetupFeatures +import com.sameerasw.essentials.ui.composables.WelcomeScreen import com.sameerasw.essentials.ui.modifiers.BlurDirection import com.sameerasw.essentials.ui.modifiers.progressiveBlur import com.sameerasw.essentials.ui.theme.EssentialsTheme @@ -114,8 +101,10 @@ import com.sameerasw.essentials.viewmodels.GitHubAuthViewModel import com.sameerasw.essentials.viewmodels.LocationReachedViewModel import com.sameerasw.essentials.viewmodels.MainViewModel import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) class MainActivity : AppCompatActivity() { val viewModel: MainViewModel by viewModels() val updatesViewModel: AppUpdatesViewModel by viewModels() @@ -202,7 +191,8 @@ class MainActivity : AppCompatActivity() { } // Animate the branding icon if it exists - val brandingViewId = resources.getIdentifier("splashscreen_branding_view", "id", "android") + val brandingViewId = + resources.getIdentifier("splashscreen_branding_view", "id", "android") val brandingView = if (brandingViewId != 0) { splashScreenView.findViewById(brandingViewId) } else { @@ -260,7 +250,6 @@ class MainActivity : AppCompatActivity() { var showUpdateSheet by remember { mutableStateOf(false) } var showInstructionsSheet by remember { mutableStateOf(false) } - val isUpdateAvailable by viewModel.isUpdateAvailable val updateInfo by viewModel.updateInfo var showGitHubAuthSheet by remember { mutableStateOf(false) } @@ -299,7 +288,11 @@ class MainActivity : AppCompatActivity() { val index = tabs.indexOf(defaultTab) if (index != -1) index else 0 } - var currentPage by remember { androidx.compose.runtime.mutableIntStateOf(initialPage) } + var currentPage by remember { + androidx.compose.runtime.mutableIntStateOf( + initialPage + ) + } val backProgress = remember { Animatable(0f) } val scope = rememberCoroutineScope() @@ -360,7 +353,7 @@ class MainActivity : AppCompatActivity() { val prankSheetState = androidx.compose.material3.rememberModalBottomSheetState( skipPartiallyExpanded = true ) - + if (isAprilFoolsSheetVisible) { PrankBottomSheet( viewModel = viewModel, @@ -461,13 +454,20 @@ class MainActivity : AppCompatActivity() { } Box(modifier = Modifier.fillMaxSize()) { Scaffold( - contentWindowInsets = androidx.compose.foundation.layout.WindowInsets(0, 0, 0, 0), + contentWindowInsets = WindowInsets( + 0, + 0, + 0, + 0 + ), containerColor = MaterialTheme.colorScheme.surfaceContainer, topBar = {} ) { innerPadding -> - val statusBarHeightPx = with(androidx.compose.ui.platform.LocalDensity.current) { - WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() - } + val statusBarHeightPx = + with(androidx.compose.ui.platform.LocalDensity.current) { + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + .toPx() + } Box( modifier = Modifier @@ -479,7 +479,8 @@ class MainActivity : AppCompatActivity() { ) ) { val currentTab = remember(tabs, currentPage) { - tabs.getOrNull(currentPage) ?: tabs.firstOrNull() ?: DIYTabs.ESSENTIALS + tabs.getOrNull(currentPage) ?: tabs.firstOrNull() + ?: DIYTabs.ESSENTIALS } EssentialsFloatingToolbar( @@ -506,16 +507,28 @@ class MainActivity : AppCompatActivity() { HapticUtil.performVirtualKeyHaptic(view) when (currentTab) { DIYTabs.ESSENTIALS -> { - startActivity(Intent(context, SettingsActivity::class.java)) + startActivity( + Intent( + context, + SettingsActivity::class.java + ) + ) } + DIYTabs.FREEZE -> { - startActivity(Intent(context, FeatureSettingsActivity::class.java).apply { - putExtra("feature", "Freeze") - }) + startActivity( + Intent( + context, + FeatureSettingsActivity::class.java + ).apply { + putExtra("feature", "Freeze") + }) } + DIYTabs.DIY -> { showNewAutomationSheet = true } + DIYTabs.APPS -> { val user = gitHubUser if (user != null) { @@ -529,7 +542,12 @@ class MainActivity : AppCompatActivity() { containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer, shape = MaterialTheme.shapes.large, - elevation = androidx.compose.material3.FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp) + elevation = androidx.compose.material3.FloatingActionButtonDefaults.elevation( + 0.dp, + 0.dp, + 0.dp, + 0.dp + ) ) { when (currentTab) { DIYTabs.ESSENTIALS -> { @@ -538,24 +556,29 @@ class MainActivity : AppCompatActivity() { contentDescription = stringResource(R.string.content_desc_settings) ) } + DIYTabs.FREEZE -> { Icon( painter = painterResource(id = R.drawable.rounded_settings_heart_24), contentDescription = stringResource(R.string.content_desc_settings) ) } + DIYTabs.DIY -> { Icon( painter = painterResource(id = R.drawable.rounded_add_24), contentDescription = stringResource(R.string.diy_editor_new_title) ) } + DIYTabs.APPS -> { val user = gitHubUser if (user != null) { AsyncImage( model = user.avatarUrl, - contentDescription = stringResource(R.string.action_profile), + contentDescription = stringResource( + R.string.action_profile + ), contentScale = ContentScale.Crop, modifier = Modifier .size(24.dp) @@ -566,7 +589,9 @@ class MainActivity : AppCompatActivity() { } else { Icon( painter = painterResource(id = R.drawable.brand_github), - contentDescription = stringResource(R.string.action_sign_in_github) + contentDescription = stringResource( + R.string.action_sign_in_github + ) ) } } @@ -578,11 +603,19 @@ class MainActivity : AppCompatActivity() { if (user != null) { SegmentedDropdownMenu( expanded = showFabProfileMenu, - onDismissRequest = { showFabProfileMenu = false } + onDismissRequest = { + showFabProfileMenu = false + } ) { SegmentedDropdownMenuItem( - text = { Text(user.name ?: user.login) }, - onClick = { showFabProfileMenu = false }, + text = { + Text( + user.name ?: user.login + ) + }, + onClick = { + showFabProfileMenu = false + }, leadingIcon = { Icon( painter = painterResource(id = R.drawable.brand_github), @@ -636,7 +669,8 @@ class MainActivity : AppCompatActivity() { ), label = "Tab Transition" ) { targetPage -> - val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val statusBarHeight = WindowInsets.statusBars.asPaddingValues() + .calculateTopPadding() val topContentPadding = statusBarHeight val bottomToolbarPadding = 150.dp val contentPadding = PaddingValues( @@ -662,14 +696,22 @@ class MainActivity : AppCompatActivity() { modifier = Modifier.fillMaxSize(), contentPadding = contentPadding, onGetStartedClick = { - startActivity(Intent(context, FeatureSettingsActivity::class.java).apply { - putExtra("feature", "Freeze") - }) + startActivity( + Intent( + context, + FeatureSettingsActivity::class.java + ).apply { + putExtra("feature", "Freeze") + }) }, onSettingsClick = { - startActivity(Intent(context, FeatureSettingsActivity::class.java).apply { - putExtra("feature", "Freeze") - }) + startActivity( + Intent( + context, + FeatureSettingsActivity::class.java + ).apply { + putExtra("feature", "Freeze") + }) } ) } @@ -679,8 +721,12 @@ class MainActivity : AppCompatActivity() { modifier = Modifier.fillMaxSize(), contentPadding = contentPadding, showNewAutomationSheet = showNewAutomationSheet, - onDismissNewAutomationSheet = { showNewAutomationSheet = false }, - onNewAutomationClick = { showNewAutomationSheet = true } + onDismissNewAutomationSheet = { + showNewAutomationSheet = false + }, + onNewAutomationClick = { + showNewAutomationSheet = true + } ) } @@ -692,7 +738,11 @@ class MainActivity : AppCompatActivity() { Column( modifier = Modifier .fillMaxSize() - .padding(bottom = bottomToolbarPadding, start = 16.dp, end = 16.dp), + .padding( + bottom = bottomToolbarPadding, + start = 16.dp, + end = 16.dp + ), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { @@ -702,180 +752,274 @@ class MainActivity : AppCompatActivity() { Column( modifier = Modifier .fillMaxSize() - .padding(bottom = bottomToolbarPadding, start = 16.dp, end = 16.dp), + .padding( + bottom = bottomToolbarPadding, + start = 16.dp, + end = 16.dp + ), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - Text( - text = stringResource(R.string.msg_no_repos_tracked), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = { - HapticUtil.performVirtualKeyHaptic(view) - showAddRepoSheet = true - } - ) { - Text(stringResource(R.string.action_add_repository)) - } + Text( + text = stringResource(R.string.msg_no_repos_tracked), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) - Spacer(modifier = Modifier.height(32.dp)) - - Text( - text = stringResource(R.string.label_apps), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(bottom = 12.dp), - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - - ImportExportButtons( - view = view, - exportLauncher = exportLauncher, - importLauncher = importLauncher, - showExport = false - ) - } - } else { - val pending = - trackedRepos.filter { it.isUpdateAvailable && it.mappedPackageName != null } - .sortedByDescending { it.publishedAt } - val upToDate = - trackedRepos.filter { !it.isUpdateAvailable && it.mappedPackageName != null } - .sortedByDescending { it.publishedAt } - val notInstalled = - trackedRepos.filter { it.mappedPackageName == null } - - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues( - bottom = bottomToolbarPadding, - start = 16.dp, - end = 16.dp - ), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - item { - Spacer(modifier = Modifier.height(topContentPadding)) - } - item { - RoundedCardContainer { - AppsActionButtons( - view = view, - onAddClick = { - HapticUtil.performUIHaptic(view) - showAddRepoSheet = true - }, - onRefreshAllClick = { - HapticUtil.performUIHaptic(view) - updatesViewModel.checkForUpdates(context) - }, - isRefreshing = refreshingRepoIds.isNotEmpty(), - progress = { animatedProgress } - ) + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { + HapticUtil.performVirtualKeyHaptic( + view + ) + showAddRepoSheet = true + } + ) { + Text(stringResource(R.string.action_add_repository)) } - } - // Pending Section - if (pending.isNotEmpty()) { + Spacer(modifier = Modifier.height(32.dp)) + + Text( + text = stringResource(R.string.label_apps), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(bottom = 12.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + ImportExportButtons( + view = view, + exportLauncher = exportLauncher, + importLauncher = importLauncher, + showExport = false + ) + } + } else { + val pending = + trackedRepos.filter { it.isUpdateAvailable && it.mappedPackageName != null } + .sortedByDescending { it.publishedAt } + val upToDate = + trackedRepos.filter { !it.isUpdateAvailable && it.mappedPackageName != null } + .sortedByDescending { it.publishedAt } + val notInstalled = + trackedRepos.filter { it.mappedPackageName == null } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues( + bottom = bottomToolbarPadding, + start = 16.dp, + end = 16.dp + ), + verticalArrangement = Arrangement.spacedBy( + 16.dp + ) + ) { item { - Text( - text = "${stringResource(R.string.label_pending)} (${pending.size})", - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding( - start = 16.dp, - bottom = 8.dp - ), - color = MaterialTheme.colorScheme.onSurfaceVariant + Spacer( + modifier = Modifier.height( + topContentPadding + ) ) } item { RoundedCardContainer { - pending.forEach { repo -> - val isInstalling = - updatesViewModel.installingRepoId.value == repo.fullName - TrackedRepoCard( - repo = repo, - isLoading = refreshingRepoIds.contains( - repo.fullName - ), - installStatus = if (isInstalling) updatesViewModel.installStatus.value else null, - downloadProgress = if (isInstalling) updatesViewModel.updateProgress.value else 0f, - onClick = { - updatesViewModel.prepareEdit( - context, - repo - ) - showAddRepoSheet = true - }, - onActionClick = { - updatesViewModel.downloadAndInstall( - context, - repo - ) - }, - onDeleteClick = { - updatesViewModel.untrackRepo( - context, - repo.fullName - ) - }, - onShowReleaseNotes = { - repoToShowReleaseNotesFullName = + AppsActionButtons( + view = view, + onAddClick = { + HapticUtil.performUIHaptic( + view + ) + showAddRepoSheet = true + }, + onRefreshAllClick = { + HapticUtil.performUIHaptic( + view + ) + updatesViewModel.checkForUpdates( + context + ) + }, + isRefreshing = refreshingRepoIds.isNotEmpty(), + progress = { animatedProgress } + ) + } + } + + // Pending Section + if (pending.isNotEmpty()) { + item { + Text( + text = "${stringResource(R.string.label_pending)} (${pending.size})", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding( + start = 16.dp, + bottom = 8.dp + ), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + item { + RoundedCardContainer { + pending.forEach { repo -> + val isInstalling = + updatesViewModel.installingRepoId.value == repo.fullName + TrackedRepoCard( + repo = repo, + isLoading = refreshingRepoIds.contains( repo.fullName - updatesViewModel.fetchReleaseNotesIfNeeded( - context, - repo - ) - } - ) + ), + installStatus = if (isInstalling) updatesViewModel.installStatus.value else null, + downloadProgress = if (isInstalling) updatesViewModel.updateProgress.value else 0f, + onClick = { + updatesViewModel.prepareEdit( + context, + repo + ) + showAddRepoSheet = + true + }, + onActionClick = { + updatesViewModel.downloadAndInstall( + context, + repo + ) + }, + onDeleteClick = { + updatesViewModel.untrackRepo( + context, + repo.fullName + ) + }, + onShowReleaseNotes = { + repoToShowReleaseNotesFullName = + repo.fullName + updatesViewModel.fetchReleaseNotesIfNeeded( + context, + repo + ) + } + ) + } } } } - } - // Up-to-date Section - if (upToDate.isNotEmpty()) { - item { - Text( - text = "${stringResource(R.string.label_up_to_date)} (${upToDate.size})", - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding( - start = 16.dp, - bottom = 8.dp - ), - color = MaterialTheme.colorScheme.onSurfaceVariant - ) + // Up-to-date Section + if (upToDate.isNotEmpty()) { + item { + Text( + text = "${stringResource(R.string.label_up_to_date)} (${upToDate.size})", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding( + start = 16.dp, + bottom = 8.dp + ), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + item { + RoundedCardContainer { + upToDate.forEach { repo -> + val isInstalling = + updatesViewModel.installingRepoId.value == repo.fullName + TrackedRepoCard( + repo = repo, + isLoading = refreshingRepoIds.contains( + repo.fullName + ), + installStatus = if (isInstalling) updatesViewModel.installStatus.value else null, + downloadProgress = if (isInstalling) updatesViewModel.updateProgress.value else 0f, + onClick = { + updatesViewModel.prepareEdit( + context, + repo + ) + showAddRepoSheet = + true + }, + onActionClick = { + if (repo.isUpdateAvailable) { + updatesViewModel.downloadAndInstall( + context, + repo + ) + } else { + repoToShowReleaseNotesFullName = + repo.fullName + updatesViewModel.fetchReleaseNotesIfNeeded( + context, + repo + ) + } + }, + onDeleteClick = { + updatesViewModel.untrackRepo( + context, + repo.fullName + ) + }, + onShowReleaseNotes = { + repoToShowReleaseNotesFullName = + repo.fullName + updatesViewModel.fetchReleaseNotesIfNeeded( + context, + repo + ) + } + ) + } + } + } } - item { - RoundedCardContainer { - upToDate.forEach { repo -> - val isInstalling = - updatesViewModel.installingRepoId.value == repo.fullName - TrackedRepoCard( - repo = repo, - isLoading = refreshingRepoIds.contains( - repo.fullName - ), - installStatus = if (isInstalling) updatesViewModel.installStatus.value else null, - downloadProgress = if (isInstalling) updatesViewModel.updateProgress.value else 0f, - onClick = { - updatesViewModel.prepareEdit( - context, - repo - ) - showAddRepoSheet = true - }, - onActionClick = { - if (repo.isUpdateAvailable) { + + // Not Installed Section + if (notInstalled.isNotEmpty()) { + item { + Text( + text = "${stringResource(R.string.label_not_installed)} (${notInstalled.size})", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding( + start = 16.dp, + bottom = 8.dp + ), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + item { + RoundedCardContainer { + notInstalled.forEach { repo -> + val isInstalling = + updatesViewModel.installingRepoId.value == repo.fullName + TrackedRepoCard( + repo = repo, + isLoading = refreshingRepoIds.contains( + repo.fullName + ), + installStatus = if (isInstalling) updatesViewModel.installStatus.value else null, + downloadProgress = if (isInstalling) updatesViewModel.updateProgress.value else 0f, + onClick = { + updatesViewModel.prepareEdit( + context, + repo + ) + showAddRepoSheet = + true + }, + onActionClick = { updatesViewModel.downloadAndInstall( context, repo ) - } else { + }, + onDeleteClick = { + updatesViewModel.untrackRepo( + context, + repo.fullName + ) + }, + onShowReleaseNotes = { repoToShowReleaseNotesFullName = repo.fullName updatesViewModel.fetchReleaseNotesIfNeeded( @@ -883,32 +1027,16 @@ class MainActivity : AppCompatActivity() { repo ) } - }, - onDeleteClick = { - updatesViewModel.untrackRepo( - context, - repo.fullName - ) - }, - onShowReleaseNotes = { - repoToShowReleaseNotesFullName = - repo.fullName - updatesViewModel.fetchReleaseNotesIfNeeded( - context, - repo - ) - } - ) + ) + } } } } - } - // Not Installed Section - if (notInstalled.isNotEmpty()) { + // Apps Section item { Text( - text = "${stringResource(R.string.label_not_installed)} (${notInstalled.size})", + text = stringResource(R.string.label_apps), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding( start = 16.dp, @@ -916,72 +1044,15 @@ class MainActivity : AppCompatActivity() { ), color = MaterialTheme.colorScheme.onSurfaceVariant ) - } - item { - RoundedCardContainer { - notInstalled.forEach { repo -> - val isInstalling = - updatesViewModel.installingRepoId.value == repo.fullName - TrackedRepoCard( - repo = repo, - isLoading = refreshingRepoIds.contains( - repo.fullName - ), - installStatus = if (isInstalling) updatesViewModel.installStatus.value else null, - downloadProgress = if (isInstalling) updatesViewModel.updateProgress.value else 0f, - onClick = { - updatesViewModel.prepareEdit( - context, - repo - ) - showAddRepoSheet = true - }, - onActionClick = { - updatesViewModel.downloadAndInstall( - context, - repo - ) - }, - onDeleteClick = { - updatesViewModel.untrackRepo( - context, - repo.fullName - ) - }, - onShowReleaseNotes = { - repoToShowReleaseNotesFullName = - repo.fullName - updatesViewModel.fetchReleaseNotesIfNeeded( - context, - repo - ) - } - ) - } - } + Spacer(modifier = Modifier.height(8.dp)) + ImportExportButtons( + view = view, + exportLauncher = exportLauncher, + importLauncher = importLauncher + ) } } - - // Apps Section - item { - Text( - text = stringResource(R.string.label_apps), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding( - start = 16.dp, - bottom = 8.dp - ), - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.height(8.dp)) - ImportExportButtons( - view = view, - exportLauncher = exportLauncher, - importLauncher = importLauncher - ) - } } - } } } @@ -992,8 +1063,8 @@ class MainActivity : AppCompatActivity() { androidx.compose.animation.AnimatedVisibility( visible = !isOnboardingCompleted || isWhatsNewVisible, - enter = androidx.compose.animation.fadeIn() + androidx.compose.animation.slideInVertically { it }, - exit = androidx.compose.animation.fadeOut() + androidx.compose.animation.slideOutVertically { it } + enter = fadeIn() + slideInVertically { it }, + exit = fadeOut() + slideOutVertically { it } ) { WelcomeScreen( viewModel = viewModel, @@ -1017,7 +1088,7 @@ class MainActivity : AppCompatActivity() { } } } - + override fun onResume() { super.onResume() viewModel.check(this) @@ -1042,7 +1113,6 @@ class MainActivity : AppCompatActivity() { } } -@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun AppsActionButtons( view: android.view.View, diff --git a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt index 90bf93ec5..013598631 100644 --- a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt @@ -10,12 +10,12 @@ import android.os.Bundle import android.provider.Settings import android.widget.Toast import androidx.activity.ComponentActivity -import androidx.appcompat.app.AppCompatActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn @@ -23,10 +23,10 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -39,17 +39,13 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -64,7 +60,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.core.app.ActivityCompat @@ -85,8 +80,8 @@ import com.sameerasw.essentials.ui.components.sheets.UpdateBottomSheet import com.sameerasw.essentials.ui.modifiers.BlurDirection import com.sameerasw.essentials.ui.modifiers.progressiveBlur import com.sameerasw.essentials.ui.theme.EssentialsTheme -import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.utils.DeviceUtils +import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.utils.PermissionUtils import com.sameerasw.essentials.viewmodels.MainViewModel import rikka.shizuku.Shizuku @@ -110,9 +105,10 @@ class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - - val isDarkMode = (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == - android.content.res.Configuration.UI_MODE_NIGHT_YES + + val isDarkMode = + (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == + android.content.res.Configuration.UI_MODE_NIGHT_YES window.setBackgroundDrawableResource(if (isDarkMode) android.R.color.black else R.color.app_window_background) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false @@ -123,7 +119,7 @@ class SettingsActivity : AppCompatActivity() { val isPitchBlackThemeEnabled by viewModel.isPitchBlackThemeEnabled EssentialsTheme(pitchBlackTheme = isPitchBlackThemeEnabled) { val context = LocalContext.current - val view = LocalView.current + LocalView.current var showBugReportSheet by remember { mutableStateOf(false) } @@ -141,7 +137,8 @@ class SettingsActivity : AppCompatActivity() { val statusBarHeightPx = with(LocalDensity.current) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() } - val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val statusBarHeight = + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() val isBlurEnabled by viewModel.isBlurEnabled @@ -155,7 +152,7 @@ class SettingsActivity : AppCompatActivity() { direction = BlurDirection.TOP ) ) { - val contentPadding = androidx.compose.foundation.layout.PaddingValues( + val contentPadding = PaddingValues( top = statusBarHeight, bottom = 150.dp, start = 16.dp, @@ -215,7 +212,7 @@ class SettingsActivity : AppCompatActivity() { @Composable fun SettingsContent( viewModel: MainViewModel, - contentPadding: androidx.compose.foundation.layout.PaddingValues, + contentPadding: PaddingValues, modifier: Modifier = Modifier ) { val isAccessibilityEnabled by viewModel.isAccessibilityEnabled @@ -320,7 +317,9 @@ fun SettingsContent( showInstructionsSheet = true }, showToggle = false, - modifier = Modifier.fillMaxWidth().height(72.dp) + modifier = Modifier + .fillMaxWidth() + .height(72.dp) ) } diff --git a/app/src/main/java/com/sameerasw/essentials/ShutUpShortcutActivity.kt b/app/src/main/java/com/sameerasw/essentials/ShutUpShortcutActivity.kt index 9646236b0..93c7d74c1 100644 --- a/app/src/main/java/com/sameerasw/essentials/ShutUpShortcutActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ShutUpShortcutActivity.kt @@ -1,6 +1,5 @@ package com.sameerasw.essentials -import android.content.Context import android.content.Intent import android.os.Bundle import android.provider.Settings @@ -62,8 +61,15 @@ class ShutUpShortcutActivity : ComponentActivity() { lifecycleScope.launch { // Unfreeze first while Shizuku/Root is still functional - if (com.sameerasw.essentials.utils.FreezeManager.isAppFrozen(this@ShutUpShortcutActivity, packageName)) { - com.sameerasw.essentials.utils.FreezeManager.unfreezeApp(this@ShutUpShortcutActivity, packageName) + if (com.sameerasw.essentials.utils.FreezeManager.isAppFrozen( + this@ShutUpShortcutActivity, + packageName + ) + ) { + com.sameerasw.essentials.utils.FreezeManager.unfreezeApp( + this@ShutUpShortcutActivity, + packageName + ) delay(200) // Small extra delay for system to register unfreeze } @@ -71,7 +77,11 @@ class ShutUpShortcutActivity : ComponentActivity() { if (PermissionUtils.canWriteSecureSettings(this@ShutUpShortcutActivity)) { applyShutUpSettings(config, settingsRepository) withContext(Dispatchers.Main) { - Toast.makeText(this@ShutUpShortcutActivity, getString(R.string.shut_up_toast_active), Toast.LENGTH_SHORT).show() + Toast.makeText( + this@ShutUpShortcutActivity, + getString(R.string.shut_up_toast_active), + Toast.LENGTH_SHORT + ).show() } } } @@ -84,46 +94,76 @@ class ShutUpShortcutActivity : ComponentActivity() { } } - private suspend fun applyShutUpSettings(config: ShutUpAppConfig, repository: SettingsRepository) { + private suspend fun applyShutUpSettings( + config: ShutUpAppConfig, + repository: SettingsRepository + ) { withContext(Dispatchers.IO) { val originalSettings = mutableMapOf() if (config.disableDevOptions) { // Backup all relevant dev settings because disabling the main toggle might reset them val secureSettings = listOf( - "anr_show_background", "bugreport_in_power_menu", "display_density_forced", - "mock_location", "secure_overlay_settings", "usb_audio_automatic_routing_disabled" + "anr_show_background", + "bugreport_in_power_menu", + "display_density_forced", + "mock_location", + "secure_overlay_settings", + "usb_audio_automatic_routing_disabled" ) val systemSettings = listOf("show_touches", "show_key_presses") val globalSettings = listOf( - "adb_allowed_connection_time", "adb_enabled", "adb_wifi_enabled", - "always_finish_activities", "animator_duration_scale", "app_standby_enabled", - "cached_apps_freezer", "default_install_location", "development_settings_enabled", - "disable_window_blurs", "enable_freeform_support", "enable_non_resizable_multi_window", - "force_allow_on_external", "force_desktop_mode_on_external_displays", "force_resizable_activities", - "mobile_data_always_on", "stay_on_while_plugged_in", "usb_mass_storage_enabled", - "wait_for_debugger", "wifi_display_certification_on", "wifi_display_on", - "wifi_scan_always_enabled", "window_animation_scale" + "adb_allowed_connection_time", + "adb_enabled", + "adb_wifi_enabled", + "always_finish_activities", + "animator_duration_scale", + "app_standby_enabled", + "cached_apps_freezer", + "default_install_location", + "development_settings_enabled", + "disable_window_blurs", + "enable_freeform_support", + "enable_non_resizable_multi_window", + "force_allow_on_external", + "force_desktop_mode_on_external_displays", + "force_resizable_activities", + "mobile_data_always_on", + "stay_on_while_plugged_in", + "usb_mass_storage_enabled", + "wait_for_debugger", + "wifi_display_certification_on", + "wifi_display_on", + "wifi_scan_always_enabled", + "window_animation_scale" ) secureSettings.forEach { key -> - Settings.Secure.getString(contentResolver, key)?.let { originalSettings["secure:$key"] = it } + Settings.Secure.getString(contentResolver, key) + ?.let { originalSettings["secure:$key"] = it } } systemSettings.forEach { key -> - Settings.System.getString(contentResolver, key)?.let { originalSettings["system:$key"] = it } + Settings.System.getString(contentResolver, key) + ?.let { originalSettings["system:$key"] = it } } globalSettings.forEach { key -> - Settings.Global.getString(contentResolver, key)?.let { originalSettings["global:$key"] = it } + Settings.Global.getString(contentResolver, key) + ?.let { originalSettings["global:$key"] = it } } // Disable dev options - Settings.Global.putString(contentResolver, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, "0") + Settings.Global.putString( + contentResolver, + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, + "0" + ) } // Always explicitly disable USB debugging if requested, even if dev options were already disabled // as some apps check this specific setting directly. if (config.disableUsbDebugging) { - val current = Settings.Global.getString(contentResolver, Settings.Global.ADB_ENABLED) ?: "0" + val current = + Settings.Global.getString(contentResolver, Settings.Global.ADB_ENABLED) ?: "0" if (current == "1") { if (!originalSettings.containsKey("global:${Settings.Global.ADB_ENABLED}")) { originalSettings["global:${Settings.Global.ADB_ENABLED}"] = "1" @@ -143,10 +183,18 @@ class ShutUpShortcutActivity : ComponentActivity() { } if (config.disableAccessibility) { - val current = Settings.Secure.getString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) + val current = Settings.Secure.getString( + contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + ) if (!current.isNullOrEmpty()) { - originalSettings["secure:${Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES}"] = current - Settings.Secure.putString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "") + originalSettings["secure:${Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES}"] = + current + Settings.Secure.putString( + contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + "" + ) } } 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 8cce39280..524eac83f 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 @@ -67,7 +67,7 @@ class LocationReachedRepository(context: Context) { saveActiveAlarmId(migratedAlarm.id) } } - + // Clear old prefs prefs.edit().apply { remove("location_reached_lat") @@ -158,7 +158,7 @@ class LocationReachedRepository(context: Context) { alarms[index] = alarms[index].copy(isPaused = isPaused) saveAlarms(alarms) } - + // Also update temp alarm if it matches val currentTemp = _tempAlarm.value if (currentTemp?.id == alarmId) { 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 afb0cdf33..5f17600d7 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 @@ -5,13 +5,13 @@ import android.content.SharedPreferences import com.google.gson.Gson import com.sameerasw.essentials.domain.HapticFeedbackType import com.sameerasw.essentials.domain.model.AppSelection +import com.sameerasw.essentials.domain.model.DnsPreset import com.sameerasw.essentials.domain.model.NotificationLightingColorMode import com.sameerasw.essentials.domain.model.NotificationLightingSide import com.sameerasw.essentials.domain.model.NotificationLightingStyle import com.sameerasw.essentials.domain.model.NotificationLightingSweepPosition -import com.sameerasw.essentials.domain.model.DnsPreset -import com.sameerasw.essentials.domain.model.TrackedRepo import com.sameerasw.essentials.domain.model.ScaleAnimationsProfile +import com.sameerasw.essentials.domain.model.TrackedRepo import com.sameerasw.essentials.domain.model.github.GitHubUser import com.sameerasw.essentials.utils.RootUtils import com.sameerasw.essentials.utils.ShizukuUtils @@ -24,7 +24,7 @@ class SettingsRepository(private val context: Context) { private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) private val gson = Gson() - + init { migrateUsageAccessKey() } @@ -485,7 +485,8 @@ class SettingsRepository(private val context: Context) { fun updateFlashlightPulseAppSelection(packageName: String, enabled: Boolean) = updateAppSelection(KEY_FLASHLIGHT_PULSE_SELECTED_APPS, packageName, enabled) - fun loadNotificationGlanceSelectedApps() = loadAppSelection(KEY_NOTIFICATION_GLANCE_SELECTED_APPS) + fun loadNotificationGlanceSelectedApps() = + loadAppSelection(KEY_NOTIFICATION_GLANCE_SELECTED_APPS) fun saveNotificationGlanceSelectedApps(apps: List) = saveAppSelection(KEY_NOTIFICATION_GLANCE_SELECTED_APPS, apps) @@ -497,7 +498,10 @@ class SettingsRepository(private val context: Context) { val json = prefs.getString(KEY_SHUT_UP_SELECTED_APPS, null) return if (json != null) { try { - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() } catch (e: Exception) { emptyList() } @@ -565,7 +569,10 @@ class SettingsRepository(private val context: Context) { val json = prefs.getString(KEY_SNOOZE_DISCOVERED_CHANNELS, null) return if (json != null) { try { - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() } catch (e: Exception) { emptyList() } @@ -602,7 +609,10 @@ class SettingsRepository(private val context: Context) { val json = prefs.getString(KEY_MAPS_DISCOVERED_CHANNELS, null) return if (json != null) { try { - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() } catch (e: Exception) { emptyList() } @@ -700,7 +710,8 @@ class SettingsRepository(private val context: Context) { fun importConfigs(inputStream: java.io.InputStream): Boolean { return try { val json = inputStream.bufferedReader().use { it.readText() } - val allConfigs: Map>> = gson.fromJson(json, Map::class.java) as Map>> + val allConfigs: Map>> = + gson.fromJson(json, Map::class.java) as Map>> allConfigs.forEach { (fileName, prefWrapper) -> val p = context.getSharedPreferences(fileName, Context.MODE_PRIVATE) @@ -745,7 +756,10 @@ class SettingsRepository(private val context: Context) { fun getBluetoothDevicesBattery(): List { val json = prefs.getString(KEY_BLUETOOTH_DEVICES_BATTERY, null) ?: return emptyList() return try { - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() } catch (e: Exception) { emptyList() } @@ -810,8 +824,11 @@ 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 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() @@ -858,13 +875,18 @@ class SettingsRepository(private val context: Context) { } fun isUserDictionaryEnabled(): Boolean = getBoolean(KEY_USER_DICTIONARY_ENABLED, false) - fun setUserDictionaryEnabled(enabled: Boolean) = putBoolean(KEY_USER_DICTIONARY_ENABLED, enabled) + fun setUserDictionaryEnabled(enabled: Boolean) = + putBoolean(KEY_USER_DICTIONARY_ENABLED, enabled) fun isAccentedCharactersEnabled(): Boolean = getBoolean(KEY_KEYBOARD_ACCENTED_CHARACTERS, false) - fun setAccentedCharactersEnabled(enabled: Boolean) = putBoolean(KEY_KEYBOARD_ACCENTED_CHARACTERS, enabled) + fun setAccentedCharactersEnabled(enabled: Boolean) = + putBoolean(KEY_KEYBOARD_ACCENTED_CHARACTERS, enabled) - fun isBatteryNotificationEnabled(): Boolean = getBoolean(KEY_BATTERY_NOTIFICATION_ENABLED, false) - fun setBatteryNotificationEnabled(enabled: Boolean) = putBoolean(KEY_BATTERY_NOTIFICATION_ENABLED, enabled) + fun isBatteryNotificationEnabled(): Boolean = + getBoolean(KEY_BATTERY_NOTIFICATION_ENABLED, false) + + fun setBatteryNotificationEnabled(enabled: Boolean) = + putBoolean(KEY_BATTERY_NOTIFICATION_ENABLED, enabled) // Live Wallpaper Helpers private val liveWallpaperPrefs: SharedPreferences by lazy { @@ -872,25 +894,33 @@ class SettingsRepository(private val context: Context) { } fun getLiveWallpaperSelectedVideo(): String = - liveWallpaperPrefs.getString(KEY_LIVE_WALLPAPER_SELECTED_VIDEO, LIVE_WALLPAPER_DEFAULT_VIDEO) + liveWallpaperPrefs.getString( + KEY_LIVE_WALLPAPER_SELECTED_VIDEO, + LIVE_WALLPAPER_DEFAULT_VIDEO + ) ?: LIVE_WALLPAPER_DEFAULT_VIDEO fun saveLiveWallpaperSelectedVideo(video: String) = liveWallpaperPrefs.edit().putString(KEY_LIVE_WALLPAPER_SELECTED_VIDEO, video).apply() fun getLiveWallpaperPlaybackTrigger(): String = - liveWallpaperPrefs.getString(KEY_LIVE_WALLPAPER_PLAYBACK_TRIGGER, LIVE_WALLPAPER_TRIGGER_UNLOCK) + liveWallpaperPrefs.getString( + KEY_LIVE_WALLPAPER_PLAYBACK_TRIGGER, + LIVE_WALLPAPER_TRIGGER_UNLOCK + ) ?: LIVE_WALLPAPER_TRIGGER_UNLOCK fun saveLiveWallpaperPlaybackTrigger(trigger: String) = liveWallpaperPrefs.edit().putString(KEY_LIVE_WALLPAPER_PLAYBACK_TRIGGER, trigger).apply() fun getLiveWallpaperCustomVideos(): List = - liveWallpaperPrefs.getString(KEY_LIVE_WALLPAPER_CUSTOM_VIDEOS, "")?.split(",")?.filter { it.isNotEmpty() } + liveWallpaperPrefs.getString(KEY_LIVE_WALLPAPER_CUSTOM_VIDEOS, "")?.split(",") + ?.filter { it.isNotEmpty() } ?: emptyList() fun saveLiveWallpaperCustomVideos(videos: List) = - liveWallpaperPrefs.edit().putString(KEY_LIVE_WALLPAPER_CUSTOM_VIDEOS, videos.joinToString(",")).apply() + liveWallpaperPrefs.edit() + .putString(KEY_LIVE_WALLPAPER_CUSTOM_VIDEOS, videos.joinToString(",")).apply() fun addLiveWallpaperCustomVideo(uri: String) { val current = getLiveWallpaperCustomVideos().toMutableList() @@ -903,9 +933,11 @@ class SettingsRepository(private val context: Context) { fun getLiveWallpaperAvailableVideos(): List { val raws = com.sameerasw.essentials.R.raw::class.java.fields.mapNotNull { field -> - try { - if (field.name == "keep") null else field.name - } catch (e: Exception) { null } + try { + if (field.name == "keep") null else field.name + } catch (e: Exception) { + null + } } return raws + getLiveWallpaperCustomVideos() } @@ -923,7 +955,10 @@ class SettingsRepository(private val context: Context) { fun getFontScale(): Float { return try { - android.provider.Settings.System.getFloat(context.contentResolver, android.provider.Settings.System.FONT_SCALE) + android.provider.Settings.System.getFloat( + context.contentResolver, + android.provider.Settings.System.FONT_SCALE + ) } catch (e: Exception) { 1.0f } @@ -932,7 +967,11 @@ class SettingsRepository(private val context: Context) { fun setFontScale(scale: Float) { putFloat(KEY_FONT_SCALE, scale) try { - android.provider.Settings.System.putFloat(context.contentResolver, android.provider.Settings.System.FONT_SCALE, scale) + android.provider.Settings.System.putFloat( + context.contentResolver, + android.provider.Settings.System.FONT_SCALE, + scale + ) } catch (e: Exception) { e.printStackTrace() } @@ -940,7 +979,10 @@ class SettingsRepository(private val context: Context) { fun getFontWeight(): Int { return try { - android.provider.Settings.Secure.getInt(context.contentResolver, "font_weight_adjustment") + android.provider.Settings.Secure.getInt( + context.contentResolver, + "font_weight_adjustment" + ) } catch (e: Exception) { 0 } @@ -949,7 +991,11 @@ class SettingsRepository(private val context: Context) { fun setFontWeight(weight: Int) { putInt(KEY_FONT_WEIGHT, weight) try { - android.provider.Settings.Secure.putInt(context.contentResolver, "font_weight_adjustment", weight) + android.provider.Settings.Secure.putInt( + context.contentResolver, + "font_weight_adjustment", + weight + ) } catch (e: Exception) { e.printStackTrace() } @@ -957,7 +1003,10 @@ class SettingsRepository(private val context: Context) { fun getSmallestWidth(): Int { val forcedDensity = try { - android.provider.Settings.Secure.getInt(context.contentResolver, "display_density_forced") + android.provider.Settings.Secure.getInt( + context.contentResolver, + "display_density_forced" + ) } catch (e: Exception) { 0 } @@ -974,7 +1023,7 @@ class SettingsRepository(private val context: Context) { val metrics = context.resources.displayMetrics val widthPx = Math.min(metrics.widthPixels, metrics.heightPixels) val density = (widthPx * 160) / widthDp - + val command = "wm density $density" if (ShizukuUtils.isShizukuAvailable() && ShizukuUtils.hasPermission()) { ShizukuUtils.runCommand(command) @@ -982,7 +1031,11 @@ class SettingsRepository(private val context: Context) { RootUtils.runCommand(command) } else { try { - android.provider.Settings.Secure.putInt(context.contentResolver, "display_density_forced", density) + android.provider.Settings.Secure.putInt( + context.contentResolver, + "display_density_forced", + density + ) } catch (e: Exception) { e.printStackTrace() } @@ -997,7 +1050,11 @@ class SettingsRepository(private val context: Context) { RootUtils.runCommand(command) } else { try { - android.provider.Settings.Secure.putString(context.contentResolver, "display_density_forced", null) + android.provider.Settings.Secure.putString( + context.contentResolver, + "display_density_forced", + null + ) } catch (e: Exception) { e.printStackTrace() } @@ -1015,9 +1072,20 @@ class SettingsRepository(private val context: Context) { fun setAnimationScale(key: String, scale: Float) { when (key) { - android.provider.Settings.Global.ANIMATOR_DURATION_SCALE -> putFloat(KEY_ANIMATOR_DURATION_SCALE, scale) - android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE -> putFloat(KEY_TRANSITION_ANIMATION_SCALE, scale) - android.provider.Settings.Global.WINDOW_ANIMATION_SCALE -> putFloat(KEY_WINDOW_ANIMATION_SCALE, scale) + android.provider.Settings.Global.ANIMATOR_DURATION_SCALE -> putFloat( + KEY_ANIMATOR_DURATION_SCALE, + scale + ) + + android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE -> putFloat( + KEY_TRANSITION_ANIMATION_SCALE, + scale + ) + + android.provider.Settings.Global.WINDOW_ANIMATION_SCALE -> putFloat( + KEY_WINDOW_ANIMATION_SCALE, + scale + ) } try { android.provider.Settings.Global.putFloat(context.contentResolver, key, scale) @@ -1035,15 +1103,27 @@ class SettingsRepository(private val context: Context) { setFontWeight(getInt(KEY_FONT_WEIGHT, 0)) } if (contains(KEY_ANIMATOR_DURATION_SCALE)) { - setAnimationScale(android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, getFloat(KEY_ANIMATOR_DURATION_SCALE, 1.0f)) + setAnimationScale( + android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, + getFloat(KEY_ANIMATOR_DURATION_SCALE, 1.0f) + ) } if (contains(KEY_TRANSITION_ANIMATION_SCALE)) { - setAnimationScale(android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, getFloat(KEY_TRANSITION_ANIMATION_SCALE, 1.0f)) + setAnimationScale( + android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, + getFloat(KEY_TRANSITION_ANIMATION_SCALE, 1.0f) + ) } if (contains(KEY_WINDOW_ANIMATION_SCALE)) { - setAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE, getFloat(KEY_WINDOW_ANIMATION_SCALE, 1.0f)) + setAnimationScale( + android.provider.Settings.Global.WINDOW_ANIMATION_SCALE, + getFloat(KEY_WINDOW_ANIMATION_SCALE, 1.0f) + ) } - if (contains(KEY_REFRESH_RATE_FIXED) || contains(KEY_REFRESH_RATE_MIN) || contains(KEY_REFRESH_RATE_PEAK)) { + if (contains(KEY_REFRESH_RATE_FIXED) || contains(KEY_REFRESH_RATE_MIN) || contains( + KEY_REFRESH_RATE_PEAK + ) + ) { val mode = getRefreshRateMode() val fixed = getFloat(KEY_REFRESH_RATE_FIXED, 0f) val min = getFloat(KEY_REFRESH_RATE_MIN, 0f) @@ -1101,11 +1181,31 @@ class SettingsRepository(private val context: Context) { private fun getDefaultDnsPresets(): List { return listOf( - DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_adguard), hostname = "dns.adguard.com", isDefault = true), - DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_google), hostname = "dns.google", isDefault = true), - DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_cloudflare), hostname = "1dot1dot1dot1.cloudflare-dns.com", isDefault = true), - DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_quad9), hostname = "dns.quad9.net", isDefault = true), - DnsPreset(name = context.getString(com.sameerasw.essentials.R.string.dns_preset_cleanbrowsing), hostname = "adult-filter-dns.cleanbrowsing.org", isDefault = true) + DnsPreset( + name = context.getString(com.sameerasw.essentials.R.string.dns_preset_adguard), + hostname = "dns.adguard.com", + isDefault = true + ), + DnsPreset( + name = context.getString(com.sameerasw.essentials.R.string.dns_preset_google), + hostname = "dns.google", + isDefault = true + ), + DnsPreset( + name = context.getString(com.sameerasw.essentials.R.string.dns_preset_cloudflare), + hostname = "1dot1dot1dot1.cloudflare-dns.com", + isDefault = true + ), + DnsPreset( + name = context.getString(com.sameerasw.essentials.R.string.dns_preset_quad9), + hostname = "dns.quad9.net", + isDefault = true + ), + DnsPreset( + name = context.getString(com.sameerasw.essentials.R.string.dns_preset_cleanbrowsing), + hostname = "adult-filter-dns.cleanbrowsing.org", + isDefault = true + ) ) } @@ -1118,7 +1218,9 @@ class SettingsRepository(private val context: Context) { savePrivateDnsPresets(getDefaultDnsPresets()) } - fun getScaleAnimationsMode(): String = getString(KEY_SCALE_ANIMATIONS_MODE, "default") ?: "default" + fun getScaleAnimationsMode(): String = + getString(KEY_SCALE_ANIMATIONS_MODE, "default") ?: "default" + fun setScaleAnimationsMode(mode: String) = putString(KEY_SCALE_ANIMATIONS_MODE, mode) fun getRefreshRateMode(): String = @@ -1141,7 +1243,8 @@ class SettingsRepository(private val context: Context) { putBoolean(KEY_REFRESH_RATE_DEFAULT_PEAK_INFINITY, enabled) fun getScaleAnimationsProfile(mode: String): ScaleAnimationsProfile { - val key = if (mode == "glove") KEY_SCALE_ANIMATIONS_GLOVE_PROFILE else KEY_SCALE_ANIMATIONS_DEFAULT_PROFILE + val key = + if (mode == "glove") KEY_SCALE_ANIMATIONS_GLOVE_PROFILE else KEY_SCALE_ANIMATIONS_DEFAULT_PROFILE val json = prefs.getString(key, null) return if (json != null) { try { @@ -1169,14 +1272,19 @@ class SettingsRepository(private val context: Context) { } fun saveScaleAnimationsProfile(mode: String, profile: ScaleAnimationsProfile) { - val key = if (mode == "glove") KEY_SCALE_ANIMATIONS_GLOVE_PROFILE else KEY_SCALE_ANIMATIONS_DEFAULT_PROFILE + val key = + if (mode == "glove") KEY_SCALE_ANIMATIONS_GLOVE_PROFILE else KEY_SCALE_ANIMATIONS_DEFAULT_PROFILE val json = gson.toJson(profile) putString(key, json) } fun getTouchSensitivityEnabled(): Boolean { return try { - android.provider.Settings.Secure.getInt(context.contentResolver, "touch_sensitivity_enabled", 0) == 1 + android.provider.Settings.Secure.getInt( + context.contentResolver, + "touch_sensitivity_enabled", + 0 + ) == 1 } catch (e: Exception) { false } @@ -1184,7 +1292,11 @@ class SettingsRepository(private val context: Context) { fun setTouchSensitivityEnabled(enabled: Boolean) { try { - android.provider.Settings.Secure.putInt(context.contentResolver, "touch_sensitivity_enabled", if (enabled) 1 else 0) + android.provider.Settings.Secure.putInt( + context.contentResolver, + "touch_sensitivity_enabled", + if (enabled) 1 else 0 + ) } catch (e: Exception) { e.printStackTrace() } @@ -1192,7 +1304,11 @@ class SettingsRepository(private val context: Context) { fun getAutoRotateEnabled(): Boolean { return try { - android.provider.Settings.System.getInt(context.contentResolver, android.provider.Settings.System.ACCELEROMETER_ROTATION, 0) == 1 + android.provider.Settings.System.getInt( + context.contentResolver, + android.provider.Settings.System.ACCELEROMETER_ROTATION, + 0 + ) == 1 } catch (e: Exception) { false } @@ -1200,7 +1316,11 @@ class SettingsRepository(private val context: Context) { fun setAutoRotateEnabled(enabled: Boolean) { try { - android.provider.Settings.System.putInt(context.contentResolver, android.provider.Settings.System.ACCELEROMETER_ROTATION, if (enabled) 1 else 0) + android.provider.Settings.System.putInt( + context.contentResolver, + android.provider.Settings.System.ACCELEROMETER_ROTATION, + if (enabled) 1 else 0 + ) } catch (e: Exception) { e.printStackTrace() } @@ -1208,7 +1328,11 @@ class SettingsRepository(private val context: Context) { fun getScreenTimeout(): Long { return try { - android.provider.Settings.System.getLong(context.contentResolver, android.provider.Settings.System.SCREEN_OFF_TIMEOUT, 30000L) + android.provider.Settings.System.getLong( + context.contentResolver, + android.provider.Settings.System.SCREEN_OFF_TIMEOUT, + 30000L + ) } catch (e: Exception) { 30000L } @@ -1216,7 +1340,11 @@ class SettingsRepository(private val context: Context) { fun setScreenTimeout(timeoutMs: Long) { try { - android.provider.Settings.System.putLong(context.contentResolver, android.provider.Settings.System.SCREEN_OFF_TIMEOUT, timeoutMs) + android.provider.Settings.System.putLong( + context.contentResolver, + android.provider.Settings.System.SCREEN_OFF_TIMEOUT, + timeoutMs + ) } catch (e: Exception) { e.printStackTrace() } @@ -1237,8 +1365,11 @@ class SettingsRepository(private val context: Context) { 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 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/DIYTabs.kt b/app/src/main/java/com/sameerasw/essentials/domain/DIYTabs.kt index 9f26741d2..0b2ef6ecd 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/DIYTabs.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/DIYTabs.kt @@ -4,7 +4,11 @@ import androidx.annotation.StringRes import com.sameerasw.essentials.R enum class DIYTabs(@StringRes val title: Int, val subtitle: Any, val iconRes: Int) { - ESSENTIALS(R.string.tab_essentials, if (com.sameerasw.essentials.BuildConfig.DEBUG) "=^._.^= ∫ Debug" else "=^..^=", R.drawable.ic_stat_name), + ESSENTIALS( + R.string.tab_essentials, + if (com.sameerasw.essentials.BuildConfig.DEBUG) "=^._.^= ∫ Debug" else "=^..^=", + R.drawable.ic_stat_name + ), FREEZE(R.string.tab_freeze, R.string.tab_freeze_subtitle, R.drawable.rounded_mode_cool_24), DIY(R.string.tab_diy, R.string.tab_diy_subtitle, R.drawable.rounded_experiment_24), APPS(R.string.tab_apps, R.string.tab_apps_subtitle, R.drawable.rounded_apps_24) diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt index c376b13ae..6c1fe5e73 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt @@ -80,9 +80,12 @@ sealed interface Action { @Keep enum class SoundModeType { - @SerializedName("SOUND") SOUND, - @SerializedName("VIBRATE") VIBRATE, - @SerializedName("SILENT") SILENT + @SerializedName("SOUND") + SOUND, + @SerializedName("VIBRATE") + VIBRATE, + @SerializedName("SILENT") + SILENT } @Keep @@ -90,11 +93,12 @@ sealed interface Action { @SerializedName("mode") val mode: SoundModeType = SoundModeType.SOUND ) : Action { override val title: Int get() = R.string.diy_action_sound_mode - override val icon: Int get() = when (mode) { - SoundModeType.SOUND -> R.drawable.rounded_volume_up_24 - SoundModeType.VIBRATE -> R.drawable.rounded_mobile_vibrate_24 - SoundModeType.SILENT -> R.drawable.rounded_volume_off_24 - } + override val icon: Int + get() = when (mode) { + SoundModeType.SOUND -> R.drawable.rounded_volume_up_24 + SoundModeType.VIBRATE -> R.drawable.rounded_mobile_vibrate_24 + SoundModeType.SILENT -> R.drawable.rounded_volume_off_24 + } override val permissions: List = listOf("notification_policy") override val isConfigurable: Boolean = true } diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/Automation.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/Automation.kt index f120491aa..1f95ff6b7 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/Automation.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/Automation.kt @@ -17,8 +17,11 @@ data class Automation( ) { @Keep enum class Type { - @SerializedName("TRIGGER") TRIGGER, - @SerializedName("STATE") STATE, - @SerializedName("APP") APP + @SerializedName("TRIGGER") + TRIGGER, + @SerializedName("STATE") + STATE, + @SerializedName("APP") + APP } } diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/DIYRepository.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/DIYRepository.kt index 2eeffcce9..643549862 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/DIYRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/DIYRepository.kt @@ -8,7 +8,6 @@ import com.google.gson.JsonDeserializer import com.google.gson.JsonElement import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer -import com.google.gson.reflect.TypeToken import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlin.reflect.KClass diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/State.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/State.kt index 431e9cc1d..0ca9d06cc 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/State.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/State.kt @@ -25,7 +25,7 @@ sealed interface State { override val title: Int = R.string.diy_state_screen_on override val icon: Int = R.drawable.rounded_mobile_text_2_24 } - + @Keep data class TimePeriod( @SerializedName("startHour") val startHour: Int = 0, diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt index 68200c0c2..f138ff9c6 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt @@ -42,7 +42,7 @@ sealed interface Trigger { override val title: Int = R.string.diy_trigger_charger_disconnected override val icon: Int = R.drawable.rounded_battery_android_frame_3_24 } - + @Keep data class Schedule( @SerializedName("hour") val hour: Int = 0, 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 0532e5e02..76d79a08e 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 @@ -728,9 +728,14 @@ object FeatureRegistry { parentFeatureId = "Display" ) { override val permissionKeys: List - get() = if (com.sameerasw.essentials.data.repository.SettingsRepository(com.sameerasw.essentials.EssentialsApp.context) - .getBoolean(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_USE_USAGE_ACCESS)) - listOf("USAGE_STATS", "WRITE_SECURE_SETTINGS") else listOf("ACCESSIBILITY", "WRITE_SECURE_SETTINGS") + get() = if (com.sameerasw.essentials.data.repository.SettingsRepository( + EssentialsApp.context) + .getBoolean(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_USE_USAGE_ACCESS) + ) + listOf("USAGE_STATS", "WRITE_SECURE_SETTINGS") else listOf( + "ACCESSIBILITY", + "WRITE_SECURE_SETTINGS" + ) override fun isEnabled(viewModel: MainViewModel) = viewModel.isDynamicNightLightEnabled.value @@ -760,7 +765,6 @@ object FeatureRegistry { }, - object : Feature( id = "Other customizations", title = R.string.feat_other_customizations_title, @@ -782,7 +786,9 @@ object FeatureRegistry { category = R.string.cat_protection, description = R.string.screen_locked_security_desc, aboutDescription = R.string.about_desc_screen_locked_security, - permissionKeys = if (ShellUtils.isRootEnabled(com.sameerasw.essentials.EssentialsApp.context)) listOf("ROOT") else listOf("SHIZUKU"), + permissionKeys = if (ShellUtils.isRootEnabled(EssentialsApp.context)) listOf( + "ROOT" + ) else listOf("SHIZUKU"), parentFeatureId = "Security" ) { override fun isEnabled(viewModel: MainViewModel) = @@ -819,8 +825,10 @@ object FeatureRegistry { parentFeatureId = "Security" ) { override val permissionKeys: List - get() = if (com.sameerasw.essentials.data.repository.SettingsRepository(com.sameerasw.essentials.EssentialsApp.context) - .getBoolean(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_USE_USAGE_ACCESS)) + get() = if (com.sameerasw.essentials.data.repository.SettingsRepository( + EssentialsApp.context) + .getBoolean(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_USE_USAGE_ACCESS) + ) listOf("USAGE_STATS") else listOf("ACCESSIBILITY") override fun isEnabled(viewModel: MainViewModel) = viewModel.isAppLockEnabled.value @@ -994,7 +1002,7 @@ object FeatureRegistry { override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) = viewModel.setCalendarSyncEnabled(enabled, context) }, - + object : Feature( id = "Lock from Watch", title = R.string.feat_lock_from_watch_title, diff --git a/app/src/main/java/com/sameerasw/essentials/domain/watermark/WatermarkEngine.kt b/app/src/main/java/com/sameerasw/essentials/domain/watermark/WatermarkEngine.kt index 3e31116c2..bb2c00477 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/watermark/WatermarkEngine.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/watermark/WatermarkEngine.kt @@ -274,7 +274,7 @@ class WatermarkEngine( val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { colorFilter = android.graphics.PorterDuffColorFilter( - shadowColor!!, + shadowColor, PorterDuff.Mode.SRC_IN ) alpha = 128 diff --git a/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt b/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt index 472fc6702..5e9cbc93e 100644 --- a/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt +++ b/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt @@ -1,7 +1,6 @@ package com.sameerasw.essentials.ime import android.content.ClipboardManager -import android.content.Context import android.inputmethodservice.InputMethodService import android.util.Log import android.view.KeyEvent @@ -375,17 +374,20 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM false ) } + SettingsRepository.KEY_USER_DICT_LAST_UPDATE -> { lifecycleScope.launch(kotlinx.coroutines.Dispatchers.IO) { suggestionEngine.loadUserDictionary() } } + SettingsRepository.KEY_KEYBOARD_LONG_PRESS_SYMBOLS -> { isLongPressSymbolsEnabled = sharedPreferences.getBoolean( SettingsRepository.KEY_KEYBOARD_LONG_PRESS_SYMBOLS, false ) } + SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS -> { isAccentedCharactersEnabled = sharedPreferences.getBoolean( SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS, @@ -447,7 +449,8 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM val textBefore = ic.getTextBeforeCursor(50, 0)?.toString() if (!textBefore.isNullOrEmpty()) { val content = textBefore.dropLast(1) - val lastWord = content.split(Regex("[^a-zA-Z0-9']")).lastOrNull() ?: "" + val lastWord = + content.split(Regex("[^a-zA-Z0-9']")).lastOrNull() ?: "" if (lastWord.isNotEmpty()) { lifecycleScope.launch(kotlinx.coroutines.Dispatchers.Default) { suggestionEngine.learnWord(lastWord) @@ -468,42 +471,94 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM val ic = currentInputConnection undoRedoManager.undo(ic) }, - onKeyPress = { keyCode -> - handleKeyPress(keyCode) - }, - canDelete = { - currentInputConnection?.getTextBeforeCursor(1, 0)?.isNotEmpty() == true - }, + onKeyPress = { keyCode -> + handleKeyPress(keyCode) + }, + canDelete = { + currentInputConnection?.getTextBeforeCursor(1, 0)?.isNotEmpty() == true + }, onCursorMove = { keyCode, isSelection, isWordJump -> val ic = currentInputConnection if (ic != null) { if (isSelection || isWordJump) { val eventTime = System.currentTimeMillis() var metaState = 0 - + // Press Modifiers if (isSelection) { - ic.sendKeyEvent(KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0)) - metaState = metaState or KeyEvent.META_SHIFT_ON or KeyEvent.META_SHIFT_LEFT_ON + ic.sendKeyEvent( + KeyEvent( + eventTime, + eventTime, + KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_SHIFT_LEFT, + 0, + 0 + ) + ) + metaState = + metaState or KeyEvent.META_SHIFT_ON or KeyEvent.META_SHIFT_LEFT_ON } if (isWordJump) { - ic.sendKeyEvent(KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CTRL_LEFT, 0, 0)) - metaState = metaState or KeyEvent.META_CTRL_ON or KeyEvent.META_CTRL_LEFT_ON + ic.sendKeyEvent( + KeyEvent( + eventTime, + eventTime, + KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_CTRL_LEFT, + 0, + 0 + ) + ) + metaState = + metaState or KeyEvent.META_CTRL_ON or KeyEvent.META_CTRL_LEFT_ON } // The Arrow Key ic.sendKeyEvent( - KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyCode, 0, metaState) + KeyEvent( + eventTime, + eventTime, + KeyEvent.ACTION_DOWN, + keyCode, + 0, + metaState + ) ) ic.sendKeyEvent( - KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyCode, 0, metaState) + KeyEvent( + eventTime, + eventTime, + KeyEvent.ACTION_UP, + keyCode, + 0, + metaState + ) ) if (isWordJump) { - ic.sendKeyEvent(KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_CTRL_LEFT, 0, 0)) + ic.sendKeyEvent( + KeyEvent( + eventTime, + eventTime, + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_CTRL_LEFT, + 0, + 0 + ) + ) } if (isSelection) { - ic.sendKeyEvent(KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0)) + ic.sendKeyEvent( + KeyEvent( + eventTime, + eventTime, + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SHIFT_LEFT, + 0, + 0 + ) + ) } } else { handleKeyPress(keyCode) @@ -571,10 +626,14 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM if (!before.isNullOrEmpty()) { var deleteCount = 1 val len = before.length - if (len >= 2 && Character.isSurrogatePair(before[len - 2], before[len - 1])) { + if (len >= 2 && Character.isSurrogatePair( + before[len - 2], + before[len - 1] + ) + ) { deleteCount = 2 } - + val charToDelete = before.subSequence(len - deleteCount, len).toString() undoRedoManager.recordDelete(charToDelete) inputConnection.deleteSurroundingText(deleteCount, 0) @@ -655,9 +714,10 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM // Lookup suggestion for current word if (newSelStart == newSelEnd) { - updateSuggestions() + updateSuggestions() } } + fun deleteClipboardItem(text: String) { val current = _clipboardHistory.value.toMutableList() if (current.remove(text)) { diff --git a/app/src/main/java/com/sameerasw/essentials/ime/SuggestionEngine.kt b/app/src/main/java/com/sameerasw/essentials/ime/SuggestionEngine.kt index 6a7117d4e..f323056a3 100644 --- a/app/src/main/java/com/sameerasw/essentials/ime/SuggestionEngine.kt +++ b/app/src/main/java/com/sameerasw/essentials/ime/SuggestionEngine.kt @@ -107,7 +107,7 @@ class SuggestionEngine(private val context: Context) : Log.e(TAG, "Error init SymSpell", e) } } - + // Init User Dictionary (Background) scope.launch(Dispatchers.IO) { loadUserDictionary() @@ -129,9 +129,10 @@ class SuggestionEngine(private val context: Context) : // Inject into SymSpell if ready if (isSymSpellReady && symSpell != null) { userWords.forEach { (word, freq) -> - try { - symSpell?.createDictionaryEntry(word, freq.toDouble() + 10000.0) - } catch (e: Exception) { } + try { + symSpell?.createDictionaryEntry(word, freq.toDouble() + 10000.0) + } catch (e: Exception) { + } } } } catch (e: Exception) { @@ -141,23 +142,24 @@ class SuggestionEngine(private val context: Context) : fun learnWord(word: String) { if (word.length < 2) return - if (word.any { it.isDigit() }) return - + if (word.any { it.isDigit() }) return + val currentFreq = userWords.getOrDefault(word, 0L) val newFreq = currentFreq + 1 userWords[word] = newFreq - + if (isSymSpellReady) { try { - symSpell?.createDictionaryEntry(word, newFreq.toDouble() + 10000.0) - } catch (e: Exception) { } + symSpell?.createDictionaryEntry(word, newFreq.toDouble() + 10000.0) + } catch (e: Exception) { + } } CoroutineScope(Dispatchers.IO).launch { saveUserDictionary() } } - + private fun saveUserDictionary() { try { FileOutputStream(userDictFile).use { fos -> @@ -187,7 +189,8 @@ class SuggestionEngine(private val context: Context) : symSpell?.lookup(word, Verbosity.Closest, 2.0) ?.map { item -> val term = item.term - val type = if (userWords.containsKey(term)) SuggestionType.Learned else SuggestionType.Prediction + val type = + if (userWords.containsKey(term)) SuggestionType.Learned else SuggestionType.Prediction Suggestion(term, type) } ?.distinctBy { it.text } @@ -237,7 +240,8 @@ class SuggestionEngine(private val context: Context) : // Merge: SymSpell (Prediction) + Android (Correction) // Deduplicate - val androidSuggestionsList = androidSuggestions.map { Suggestion(it, SuggestionType.Correction) } + val androidSuggestionsList = + androidSuggestions.map { Suggestion(it, SuggestionType.Correction) } val merged = (currentSymSpellSuggestions + androidSuggestionsList) .distinctBy { it.text } .take(8) @@ -252,14 +256,14 @@ class SuggestionEngine(private val context: Context) : currentSymSpellSuggestions = emptyList() } - fun getUserWords() = userWords.toMap() - + fun getUserWords() = userWords.toMap() + fun removeUserWord(word: String) { if (userWords.containsKey(word)) { userWords.remove(word) - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.IO).launch { saveUserDictionary() - } + } } } } diff --git a/app/src/main/java/com/sameerasw/essentials/ime/UndoRedoManager.kt b/app/src/main/java/com/sameerasw/essentials/ime/UndoRedoManager.kt index 2ba57f833..11dfd0429 100644 --- a/app/src/main/java/com/sameerasw/essentials/ime/UndoRedoManager.kt +++ b/app/src/main/java/com/sameerasw/essentials/ime/UndoRedoManager.kt @@ -29,7 +29,7 @@ class UndoRedoManager { if (undoStack.isNotEmpty()) { val top = undoStack.peek() val isRecent = (now - top.timestamp) < BATCH_TIMEOUT_MS - + if (top.type == ActionType.INSERT && isRecent) { top.text += text val updated = top.copy(text = top.text, timestamp = now) @@ -38,7 +38,7 @@ class UndoRedoManager { return } } - + undoStack.push(HistoryAction(ActionType.INSERT, text, now)) } @@ -46,11 +46,11 @@ class UndoRedoManager { if (text.isEmpty()) return val now = System.currentTimeMillis() redoStack.clear() - + if (undoStack.isNotEmpty()) { val top = undoStack.peek() val isRecent = (now - top.timestamp) < BATCH_TIMEOUT_MS - + if (top.type == ActionType.DELETE && isRecent) { top.text = text + top.text val updated = top.copy(text = top.text, timestamp = now) @@ -62,13 +62,13 @@ class UndoRedoManager { undoStack.push(HistoryAction(ActionType.DELETE, text, now)) } - + fun canUndo(): Boolean = undoStack.isNotEmpty() fun canRedo(): Boolean = redoStack.isNotEmpty() fun undo(ic: InputConnection?) { if (ic == null || undoStack.isEmpty()) return - + val action = undoStack.pop() redoStack.push(action) @@ -76,6 +76,7 @@ class UndoRedoManager { ActionType.INSERT -> { ic.deleteSurroundingText(action.text.length, 0) } + ActionType.DELETE -> { ic.commitText(action.text, 1) } @@ -84,7 +85,7 @@ class UndoRedoManager { fun redo(ic: InputConnection?) { if (ic == null || redoStack.isEmpty()) return - + val action = redoStack.pop() undoStack.push(action) @@ -92,6 +93,7 @@ class UndoRedoManager { ActionType.INSERT -> { ic.commitText(action.text, 1) } + ActionType.DELETE -> { ic.deleteSurroundingText(action.text.length, 0) } diff --git a/app/src/main/java/com/sameerasw/essentials/services/AppDetectionService.kt b/app/src/main/java/com/sameerasw/essentials/services/AppDetectionService.kt index e8ad3ec1b..0447434dc 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/AppDetectionService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/AppDetectionService.kt @@ -1,6 +1,9 @@ package com.sameerasw.essentials.services -import android.app.* +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service import android.app.usage.UsageStats import android.app.usage.UsageStatsManager import android.content.BroadcastReceiver @@ -12,12 +15,9 @@ import android.os.Build import android.os.Handler import android.os.IBinder import android.os.Looper -import android.util.Log import androidx.core.app.NotificationCompat -import com.sameerasw.essentials.AppLockActivity import com.sameerasw.essentials.R import com.sameerasw.essentials.services.handlers.AppFlowHandler -import java.util.* class AppDetectionService : Service() { @@ -42,6 +42,7 @@ class AppDetectionService : Service() { appFlowHandler.onAuthenticated(packageName) } } + "APP_AUTHENTICATION_FAILED" -> { goHome() } @@ -54,7 +55,7 @@ class AppDetectionService : Service() { isRunning = true appFlowHandler = AppFlowHandler(this) createNotificationChannel() - + val filter = IntentFilter().apply { addAction("APP_AUTHENTICATED") addAction("APP_AUTHENTICATION_FAILED") @@ -68,16 +69,16 @@ class AppDetectionService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { startForeground( - NOTIFICATION_ID, + NOTIFICATION_ID, createNotification(), if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE else 0 ) - + if (!isPolling) { isPolling = true startPolling() } - + return START_STICKY } @@ -85,32 +86,36 @@ class AppDetectionService : Service() { handler.postDelayed(object : Runnable { override fun run() { if (!isPolling) return - + val currentPackage = getForegroundPackage() if (currentPackage != null && currentPackage != lastPackageName) { lastPackageName = currentPackage appFlowHandler.onPackageChanged(currentPackage, isFromUsageStats = true) } - + handler.postDelayed(this, POLL_INTERVAL) } }, POLL_INTERVAL) } private fun getForegroundPackage(): String? { - val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager + val usageStatsManager = getSystemService(USAGE_STATS_SERVICE) as UsageStatsManager val time = System.currentTimeMillis() - val stats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 10, time) - + val stats = usageStatsManager.queryUsageStats( + UsageStatsManager.INTERVAL_DAILY, + time - 1000 * 10, + time + ) + if (stats == null || stats.isEmpty()) return null - + var recentStats: UsageStats? = null for (usageStats in stats) { if (recentStats == null || usageStats.lastTimeUsed > recentStats.lastTimeUsed) { recentStats = usageStats } } - + return recentStats?.packageName } @@ -127,7 +132,8 @@ class AppDetectionService : Service() { handler.removeCallbacksAndMessages(null) try { unregisterReceiver(authReceiver) - } catch (_: Exception) {} + } catch (_: Exception) { + } super.onDestroy() } diff --git a/app/src/main/java/com/sameerasw/essentials/services/BatteryNotificationService.kt b/app/src/main/java/com/sameerasw/essentials/services/BatteryNotificationService.kt index 50d5a43e4..5ff46f618 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/BatteryNotificationService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/BatteryNotificationService.kt @@ -1,6 +1,9 @@ package com.sameerasw.essentials.services -import android.app.* +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -12,11 +15,11 @@ import android.os.IBinder import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils +import com.google.gson.Gson import com.sameerasw.essentials.R import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.utils.BatteryRingDrawer import com.sameerasw.essentials.utils.BluetoothBatteryUtils -import com.google.gson.Gson class BatteryNotificationService : Service() { @@ -24,33 +27,43 @@ class BatteryNotificationService : Service() { private val NOTIF_ID = 8822 private val CHANNEL_ID = "battery_notification_channel" - private val preferenceChangeListener = object : SharedPreferences.OnSharedPreferenceChangeListener { - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - if (key == SettingsRepository.KEY_AIRSYNC_MAC_CONNECTED || - key == SettingsRepository.KEY_MAC_BATTERY_LEVEL || - key == SettingsRepository.KEY_MAC_BATTERY_IS_CHARGING || - key == SettingsRepository.KEY_BLUETOOTH_DEVICES_BATTERY || - key == SettingsRepository.KEY_SHOW_BLUETOOTH_DEVICES + private val preferenceChangeListener = + object : SharedPreferences.OnSharedPreferenceChangeListener { + override fun onSharedPreferenceChanged( + sharedPreferences: SharedPreferences?, + key: String? ) { - updateNotification() + if (key == SettingsRepository.KEY_AIRSYNC_MAC_CONNECTED || + key == SettingsRepository.KEY_MAC_BATTERY_LEVEL || + key == SettingsRepository.KEY_MAC_BATTERY_IS_CHARGING || + key == SettingsRepository.KEY_BLUETOOTH_DEVICES_BATTERY || + key == SettingsRepository.KEY_SHOW_BLUETOOTH_DEVICES + ) { + updateNotification() + } } } - } override fun onCreate() { super.onCreate() settingsRepository = SettingsRepository(this) createNotificationChannel() settingsRepository.registerOnSharedPreferenceChangeListener(preferenceChangeListener) - - startForeground(NOTIF_ID, buildBaseNotification(getString(R.string.feat_batteries_title), "")) - + + startForeground( + NOTIF_ID, + buildBaseNotification(getString(R.string.feat_batteries_title), "") + ) + updateNotification() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForeground(NOTIF_ID, buildBaseNotification(getString(R.string.feat_batteries_title), "")) + startForeground( + NOTIF_ID, + buildBaseNotification(getString(R.string.feat_batteries_title), "") + ) } updateNotification() return START_STICKY @@ -59,7 +72,8 @@ class BatteryNotificationService : Service() { override fun onDestroy() { try { settingsRepository.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) - } catch (_: Exception) {} + } catch (_: Exception) { + } super.onDestroy() } @@ -75,14 +89,14 @@ class BatteryNotificationService : Service() { setShowBadge(false) } val notificationManager: NotificationManager = - getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } } private fun updateNotification() { val batteryItems = fetchBatteryData() - + val notification = if (batteryItems.isEmpty()) { buildBaseNotification( getString(R.string.feat_batteries_title), @@ -93,9 +107,11 @@ class BatteryNotificationService : Service() { NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.rounded_battery_charging_60_24) .setLargeIcon(bitmap) - .setStyle(NotificationCompat.BigPictureStyle() - .bigPicture(bitmap) - .bigLargeIcon(null as Bitmap?)) + .setStyle( + NotificationCompat.BigPictureStyle() + .bigPicture(bitmap) + .bigLargeIcon(null as Bitmap?) + ) .setContentTitle(getString(R.string.feat_batteries_title)) .setPriority(NotificationCompat.PRIORITY_LOW) .setOngoing(true) @@ -122,35 +138,63 @@ class BatteryNotificationService : Service() { val maxDevices = settingsRepository.getBatteryWidgetMaxDevices() // Mac - val isAirSyncEnabled = settingsRepository.getBoolean(SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED) + val isAirSyncEnabled = + settingsRepository.getBoolean(SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED) val macLevel = settingsRepository.getInt(SettingsRepository.KEY_MAC_BATTERY_LEVEL, -1) - val isMacConnected = settingsRepository.getBoolean(SettingsRepository.KEY_AIRSYNC_MAC_CONNECTED) - val macIsCharging = settingsRepository.getBoolean(SettingsRepository.KEY_MAC_BATTERY_IS_CHARGING) + val isMacConnected = + settingsRepository.getBoolean(SettingsRepository.KEY_AIRSYNC_MAC_CONNECTED) + val macIsCharging = + settingsRepository.getBoolean(SettingsRepository.KEY_MAC_BATTERY_IS_CHARGING) if (isAirSyncEnabled && macLevel != -1 && isMacConnected) { val statusIcon = if (macIsCharging) R.drawable.rounded_flash_on_24 else if (macLevel <= 15) R.drawable.rounded_battery_android_frame_alert_24 else null - items.add(BatteryItemData(macLevel, R.drawable.rounded_laptop_mac_24, "Mac", statusIcon)) + items.add( + BatteryItemData( + macLevel, + R.drawable.rounded_laptop_mac_24, + "Mac", + statusIcon + ) + ) } // Bluetooth - val isShowBluetoothEnabled = settingsRepository.getBoolean(SettingsRepository.KEY_SHOW_BLUETOOTH_DEVICES) - val bluetoothJson = settingsRepository.getString(SettingsRepository.KEY_BLUETOOTH_DEVICES_BATTERY) + val isShowBluetoothEnabled = + settingsRepository.getBoolean(SettingsRepository.KEY_SHOW_BLUETOOTH_DEVICES) + val bluetoothJson = + settingsRepository.getString(SettingsRepository.KEY_BLUETOOTH_DEVICES_BATTERY) if (isShowBluetoothEnabled && !bluetoothJson.isNullOrEmpty() && bluetoothJson != "[]") { try { - val devices: List = Gson().fromJson(bluetoothJson, Array::class.java).toList() + val devices: List = Gson().fromJson( + bluetoothJson, + Array::class.java + ).toList() devices.forEach { device -> val iconRes = when { - device.name.contains("watch", true) || device.name.contains("gear", true) || device.name.contains("fit", true) -> R.drawable.rounded_watch_24 - device.name.contains("bud", true) || device.name.contains("pod", true) || device.name.contains("head", true) || device.name.contains("audio", true) || device.name.contains("sound", true) -> R.drawable.rounded_headphones_24 + device.name.contains("watch", true) || device.name.contains( + "gear", + true + ) || device.name.contains("fit", true) -> R.drawable.rounded_watch_24 + + device.name.contains("bud", true) || device.name.contains( + "pod", + true + ) || device.name.contains("head", true) || device.name.contains( + "audio", + true + ) || device.name.contains("sound", true) -> R.drawable.rounded_headphones_24 + else -> R.drawable.rounded_bluetooth_24 } - val statusIcon = if (device.level <= 15) R.drawable.rounded_battery_android_frame_alert_24 else null + val statusIcon = + if (device.level <= 15) R.drawable.rounded_battery_android_frame_alert_24 else null items.add(BatteryItemData(device.level, iconRes, device.name, statusIcon)) } - } catch (_: Exception) {} + } catch (_: Exception) { + } } return items.take(maxDevices) @@ -159,9 +203,9 @@ class BatteryNotificationService : Service() { private fun createCompositeBitmap(items: List): Bitmap { val itemSize = 256 val spacing = 48 - + val actualContentWidth = items.size * itemSize + (items.size - 1).coerceAtLeast(0) * spacing - + val minWideItems = 3 val minWideWidth = minWideItems * itemSize + (minWideItems - 1) * spacing val totalWidth = actualContentWidth.coerceAtLeast(minWideWidth) @@ -180,7 +224,7 @@ class BatteryNotificationService : Service() { val onSurface = Color.WHITE val trackColor = ColorUtils.setAlphaComponent(onSurface, 40) - val surfaceColor = Color.parseColor("#99000000") + val surfaceColor = Color.parseColor("#99000000") items.forEachIndexed { index, item -> val ringColor = when { @@ -194,7 +238,12 @@ class BatteryNotificationService : Service() { item.statusIconRes?.let { ContextCompat.getDrawable(this, it) }, itemSize, itemSize ) - canvas.drawBitmap(itemBitmap, startX + (index * (itemSize + spacing)).toFloat(), 0f, null) + canvas.drawBitmap( + itemBitmap, + startX + (index * (itemSize + spacing)).toFloat(), + 0f, + null + ) } return composite diff --git a/app/src/main/java/com/sameerasw/essentials/services/CalendarSyncManager.kt b/app/src/main/java/com/sameerasw/essentials/services/CalendarSyncManager.kt index 133a0fb1a..9687b80a3 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/CalendarSyncManager.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/CalendarSyncManager.kt @@ -33,8 +33,12 @@ object CalendarSyncManager { } // Listen for preference changes to start/stop sync - repo.registerOnSharedPreferenceChangeListener(object : SharedPreferences.OnSharedPreferenceChangeListener { - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + repo.registerOnSharedPreferenceChangeListener(object : + SharedPreferences.OnSharedPreferenceChangeListener { + override fun onSharedPreferenceChanged( + sharedPreferences: SharedPreferences?, + key: String? + ) { if (key == SettingsRepository.KEY_CALENDAR_SYNC_ENABLED) { val enabled = repo.getBoolean(key, false) if (enabled != isSyncEnabled) { diff --git a/app/src/main/java/com/sameerasw/essentials/services/DeviceInfoSyncManager.kt b/app/src/main/java/com/sameerasw/essentials/services/DeviceInfoSyncManager.kt index c08591e8c..5dc4656d1 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/DeviceInfoSyncManager.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/DeviceInfoSyncManager.kt @@ -3,21 +3,20 @@ package com.sameerasw.essentials.services import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.hardware.camera2.CameraManager import android.os.BatteryManager import android.os.Handler import android.os.Looper -import android.util.Log -import android.hardware.camera2.CameraManager -import com.sameerasw.essentials.utils.FlashlightUtil import com.google.android.gms.wearable.PutDataMapRequest import com.google.android.gms.wearable.Wearable +import com.sameerasw.essentials.utils.FlashlightUtil object DeviceInfoSyncManager { private const val TAG = "DeviceInfoSyncManager" private const val SYNC_PATH = "/device_info" private val handler = Handler(Looper.getMainLooper()) private var isInitialized = false - + private var isTorchOn = false private var torchLevel = 1 private var maxTorchLevel = 1 @@ -29,14 +28,15 @@ object DeviceInfoSyncManager { val primaryId = FlashlightUtil.getCameraId(context) if (cameraId == primaryId) { isTorchOn = enabled - + var level = FlashlightUtil.getCurrentLevel(context, cameraId) // Fallback to last known intensity if system returns default level 1 if (enabled && level <= 1) { - val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) + val prefs = + context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) level = prefs.getInt("flashlight_last_intensity", level) } - + torchLevel = level maxTorchLevel = FlashlightUtil.getMaxLevel(context, cameraId) isIntensitySupported = FlashlightUtil.isIntensitySupported(context, cameraId) @@ -67,10 +67,10 @@ object DeviceInfoSyncManager { if (isInitialized) return currentContext = context.applicationContext isInitialized = true - + // Initial sync syncDeviceInfo(context) - + // Start periodic sync handler.postDelayed(syncRunnable, 5 * 60 * 1000) @@ -91,7 +91,7 @@ object DeviceInfoSyncManager { // Sync on flashlight change val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager cameraManager.registerTorchCallback(torchCallback, handler) - + // Get initial flashlight state val id = FlashlightUtil.getCameraId(context) if (id != null) { @@ -122,8 +122,9 @@ object DeviceInfoSyncManager { val level: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1 val scale: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1 - val batteryPct = if (level != -1 && scale != -1) (level / scale.toFloat() * 100).toInt() else -1 - + val batteryPct = + if (level != -1 && scale != -1) (level / scale.toFloat() * 100).toInt() else -1 + val status: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1 val plugged: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1 val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || @@ -133,8 +134,13 @@ object DeviceInfoSyncManager { plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS val putDataMapReq = PutDataMapRequest.create(SYNC_PATH) - val ringerMode = (context.getSystemService(Context.AUDIO_SERVICE) as? android.media.AudioManager)?.ringerMode ?: 2 - val deviceName = android.provider.Settings.Global.getString(context.contentResolver, android.provider.Settings.Global.DEVICE_NAME) + val ringerMode = + (context.getSystemService(Context.AUDIO_SERVICE) as? android.media.AudioManager)?.ringerMode + ?: 2 + val deviceName = android.provider.Settings.Global.getString( + context.contentResolver, + android.provider.Settings.Global.DEVICE_NAME + ) ?: android.os.Build.MODEL val dataMap = putDataMapReq.dataMap diff --git a/app/src/main/java/com/sameerasw/essentials/services/EssentialsConditionProvider.kt b/app/src/main/java/com/sameerasw/essentials/services/EssentialsConditionProvider.kt index b701d6bc3..abb678a55 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/EssentialsConditionProvider.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/EssentialsConditionProvider.kt @@ -31,6 +31,7 @@ class EssentialsConditionProvider : ConditionProviderService() { } } + @Deprecated("Deprecated in Java") override fun onConnected() { Log.d(TAG, "onConnected") @@ -43,11 +44,13 @@ class EssentialsConditionProvider : ConditionProviderService() { instance = null } + @Deprecated("Deprecated in Java") override fun onSubscribe(conditionId: Uri?) { Log.d(TAG, "onSubscribe: $conditionId") notifyChange(false) } + @Deprecated("Deprecated in Java") override fun onUnsubscribe(conditionId: Uri?) { Log.d(TAG, "onUnsubscribe: $conditionId") } diff --git a/app/src/main/java/com/sameerasw/essentials/services/EssentialsWearableListenerService.kt b/app/src/main/java/com/sameerasw/essentials/services/EssentialsWearableListenerService.kt index 65d13dace..1919318ce 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/EssentialsWearableListenerService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/EssentialsWearableListenerService.kt @@ -1,6 +1,5 @@ package com.sameerasw.essentials.services -import android.util.Log import com.google.android.gms.wearable.MessageEvent import com.google.android.gms.wearable.WearableListenerService @@ -12,46 +11,71 @@ class EssentialsWearableListenerService : WearableListenerService() { override fun onMessageReceived(messageEvent: MessageEvent) { super.onMessageReceived(messageEvent) - + when (messageEvent.path) { PATH_REQUEST_SYNC -> { DeviceInfoSyncManager.forceSync(this) } + "/toggle_flashlight" -> { - val intent = android.content.Intent(this, com.sameerasw.essentials.services.receivers.FlashlightActionReceiver::class.java).apply { - action = com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.ACTION_TOGGLE + val intent = android.content.Intent( + this, + com.sameerasw.essentials.services.receivers.FlashlightActionReceiver::class.java + ).apply { + action = + com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.ACTION_TOGGLE } sendBroadcast(intent) } + "/set_flashlight_intensity" -> { val intensity = try { String(messageEvent.data).toInt() } catch (e: Exception) { 1 } - val intent = android.content.Intent(this, com.sameerasw.essentials.services.receivers.FlashlightActionReceiver::class.java).apply { - action = com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.ACTION_SET_INTENSITY - putExtra(com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.EXTRA_INTENSITY, intensity) + val intent = android.content.Intent( + this, + com.sameerasw.essentials.services.receivers.FlashlightActionReceiver::class.java + ).apply { + action = + com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.ACTION_SET_INTENSITY + putExtra( + com.sameerasw.essentials.services.receivers.FlashlightActionReceiver.EXTRA_INTENSITY, + intensity + ) } sendBroadcast(intent) } + "/toggle_sound_mode" -> { com.sameerasw.essentials.services.handlers.SoundModeHandler(this).cycleNextMode() } + "/lock_device" -> { val repository = com.sameerasw.essentials.data.repository.SettingsRepository(this) - val mode = repository.getInt(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_REMOTE_LOCK_MODE, 0) - + val mode = repository.getInt( + com.sameerasw.essentials.data.repository.SettingsRepository.KEY_REMOTE_LOCK_MODE, + 0 + ) + if (mode == 1) { // Device Admin Lock - val dpm = getSystemService(android.content.Context.DEVICE_POLICY_SERVICE) as android.app.admin.DevicePolicyManager - val adminComponent = android.content.ComponentName(this, com.sameerasw.essentials.services.receivers.SecurityDeviceAdminReceiver::class.java) + val dpm = + getSystemService(DEVICE_POLICY_SERVICE) as android.app.admin.DevicePolicyManager + val adminComponent = android.content.ComponentName( + this, + com.sameerasw.essentials.services.receivers.SecurityDeviceAdminReceiver::class.java + ) if (dpm.isAdminActive(adminComponent)) { dpm.lockNow() } } else { // Accessibility Lock - val intent = android.content.Intent(this, com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService::class.java).apply { + val intent = android.content.Intent( + this, + com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService::class.java + ).apply { action = "LOCK_SCREEN" } startService(intent) diff --git a/app/src/main/java/com/sameerasw/essentials/services/LiveWallpaperService.kt b/app/src/main/java/com/sameerasw/essentials/services/LiveWallpaperService.kt index 8855c319a..e552b441d 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/LiveWallpaperService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/LiveWallpaperService.kt @@ -1,12 +1,12 @@ package com.sameerasw.essentials.services import android.app.KeyguardManager -import android.net.Uri import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.SharedPreferences +import android.net.Uri import android.service.wallpaper.WallpaperService import android.view.SurfaceHolder import androidx.annotation.OptIn @@ -25,7 +25,7 @@ class LiveWallpaperService : WallpaperService() { private var exoPlayer: ExoPlayer? = null private lateinit var repository: SettingsRepository private val executor = android.os.Handler(android.os.Looper.getMainLooper()) - private val keyguardManager by lazy { getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager } + private val keyguardManager by lazy { getSystemService(KEYGUARD_SERVICE) as KeyguardManager } private val prefsListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> when (key) { @@ -52,11 +52,12 @@ class LiveWallpaperService : WallpaperService() { exoPlayer?.seekTo(0) }, 500) } + Intent.ACTION_SCREEN_ON -> { - val shouldPlay = isPreview || - !keyguardManager.isKeyguardLocked || - repository.getLiveWallpaperPlaybackTrigger() == SettingsRepository.LIVE_WALLPAPER_TRIGGER_SCREEN_ON - + val shouldPlay = isPreview || + !keyguardManager.isKeyguardLocked || + repository.getLiveWallpaperPlaybackTrigger() == SettingsRepository.LIVE_WALLPAPER_TRIGGER_SCREEN_ON + if (shouldPlay) { executor.removeCallbacksAndMessages(null) exoPlayer?.play() @@ -76,7 +77,7 @@ class LiveWallpaperService : WallpaperService() { override fun onCreate(surfaceHolder: SurfaceHolder?) { super.onCreate(surfaceHolder) repository = SettingsRepository(applicationContext) - + val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_PRESENT) addAction(Intent.ACTION_SCREEN_OFF) @@ -89,9 +90,9 @@ class LiveWallpaperService : WallpaperService() { override fun onVisibilityChanged(visible: Boolean) { if (visible) { - val shouldPlay = isPreview || - !keyguardManager.isKeyguardLocked || - repository.getLiveWallpaperPlaybackTrigger() == SettingsRepository.LIVE_WALLPAPER_TRIGGER_SCREEN_ON + val shouldPlay = isPreview || + !keyguardManager.isKeyguardLocked || + repository.getLiveWallpaperPlaybackTrigger() == SettingsRepository.LIVE_WALLPAPER_TRIGGER_SCREEN_ON if (shouldPlay) exoPlayer?.play() } else { exoPlayer?.pause() @@ -111,9 +112,9 @@ class LiveWallpaperService : WallpaperService() { volume = 0f videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING loadSelectedVideo() - val shouldPlay = isPreview || - !keyguardManager.isKeyguardLocked || - repository.getLiveWallpaperPlaybackTrigger() == SettingsRepository.LIVE_WALLPAPER_TRIGGER_SCREEN_ON + val shouldPlay = isPreview || + !keyguardManager.isKeyguardLocked || + repository.getLiveWallpaperPlaybackTrigger() == SettingsRepository.LIVE_WALLPAPER_TRIGGER_SCREEN_ON playWhenReady = shouldPlay } } @@ -146,7 +147,10 @@ class LiveWallpaperService : WallpaperService() { override fun onDestroy() { super.onDestroy() executor.removeCallbacksAndMessages(null) - try { unregisterReceiver(receiver) } catch (e: Exception) { } + try { + unregisterReceiver(receiver) + } catch (e: Exception) { + } repository.unregisterOnSharedPreferenceChangeListener(prefsListener) exoPlayer?.release() exoPlayer = null 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 1e2c4d2f9..80c0ec73e 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/LocationReachedService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/LocationReachedService.kt @@ -93,7 +93,7 @@ class LocationReachedService : Service() { val activeId = repository.getActiveAlarmId() val alarms = repository.getAlarms() val alarm = alarms.find { it.id == activeId } - + if (alarm != null) { updateProgress(alarm) } else { @@ -109,12 +109,12 @@ class LocationReachedService : Service() { val activeId = repository.getActiveAlarmId() val alarms = repository.getAlarms() val alarm = alarms.find { it.id == activeId } - + if (alarm != null) { repository.saveLastTrip(alarm) repository.updatePausedState(alarm.id, false) } - + repository.saveActiveAlarmId(null) stopSelf() } @@ -135,7 +135,7 @@ class LocationReachedService : Service() { 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 } @@ -228,7 +228,7 @@ class LocationReachedService : Service() { if (distanceTravelled > 0 && elapsed > 0) { val remainingMillis = (currentDistMeters * elapsed / distanceTravelled).toLong() val remainingMinutes = (remainingMillis / 60000).toInt().coerceAtLeast(1) - + etaText = if (remainingMinutes >= 60) { val hrs = remainingMinutes / 60 val mins = remainingMinutes % 60 @@ -247,7 +247,11 @@ class LocationReachedService : Service() { return buildOngoingNotification(null, 0, null) } - private fun buildOngoingNotification(distanceKm: Float?, progress: Int, etaText: String?): Notification { + private fun buildOngoingNotification( + distanceKm: Float?, + progress: Int, + etaText: String? + ): Notification { val stopIntent = Intent(this, LocationReachedService::class.java).apply { action = ACTION_STOP } @@ -274,7 +278,12 @@ class LocationReachedService : Service() { } ?: getString(R.string.location_reached_calculating) val contentText = if (etaText != null) { - getString(R.string.location_reached_service_remaining_with_eta, distanceText, progress, etaText) + getString( + R.string.location_reached_service_remaining_with_eta, + distanceText, + progress, + etaText + ) } else { getString(R.string.location_reached_service_remaining, distanceText, progress) } @@ -304,7 +313,12 @@ class LocationReachedService : Service() { val destinationName = alarm?.name?.ifEmpty { "Destination" } ?: "Destination" val builder = Notification.Builder(this, CHANNEL_ID) .setSmallIcon(finalIconId) - .setContentTitle(getString(R.string.location_reached_service_title, destinationName)) + .setContentTitle( + getString( + R.string.location_reached_service_title, + destinationName + ) + ) .setContentText(if (isPaused) getString(R.string.location_reached_pause) else contentText) .setOngoing(true) .setOnlyAlertOnce(true) @@ -371,7 +385,10 @@ class LocationReachedService : Service() { .invoke(builder, "Paused") } else { distanceKm?.let { - builder.javaClass.getMethod("setShortCriticalText", CharSequence::class.java) + builder.javaClass.getMethod( + "setShortCriticalText", + CharSequence::class.java + ) .invoke(builder, distanceText) } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/NotificationLightingService.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationLightingService.kt index bbbe72063..95dc7209a 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/NotificationLightingService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/NotificationLightingService.kt @@ -120,27 +120,25 @@ class NotificationLightingService : Service() { // Get corner radius from intent, default to OverlayHelper.CORNER_RADIUS_DP cornerRadiusDp = - intent?.getFloatExtra("corner_radius_dp", OverlayHelper.CORNER_RADIUS_DP.toFloat()) - ?: OverlayHelper.CORNER_RADIUS_DP.toFloat() + intent.getFloatExtra("corner_radius_dp", OverlayHelper.CORNER_RADIUS_DP.toFloat()) strokeThicknessDp = - intent?.getFloatExtra("stroke_thickness_dp", OverlayHelper.STROKE_DP.toFloat()) - ?: OverlayHelper.STROKE_DP.toFloat() - isPreview = intent?.getBooleanExtra("is_preview", false) ?: false - val colorModeName = intent?.getStringExtra("color_mode") + intent.getFloatExtra("stroke_thickness_dp", OverlayHelper.STROKE_DP.toFloat()) + isPreview = intent.getBooleanExtra("is_preview", false) + val colorModeName = intent.getStringExtra("color_mode") colorMode = NotificationLightingColorMode.valueOf( colorModeName ?: NotificationLightingColorMode.SYSTEM.name ) - customColor = intent?.getIntExtra("custom_color", 0) ?: 0 - resolvedColor = if (intent?.hasExtra("resolved_color") == true) intent.getIntExtra( + customColor = intent.getIntExtra("custom_color", 0) + resolvedColor = if (intent.hasExtra("resolved_color") == true) intent.getIntExtra( "resolved_color", 0 ) else null - pulseCount = intent?.getIntExtra("pulse_count", 1) ?: 1 - pulseDuration = intent?.getLongExtra("pulse_duration", 3000L) ?: 3000L - val styleName = intent?.getStringExtra("style") + pulseCount = intent.getIntExtra("pulse_count", 1) + pulseDuration = intent.getLongExtra("pulse_duration", 3000L) + val styleName = intent.getStringExtra("style") edgeLightingStyle = if (styleName != null) NotificationLightingStyle.valueOf(styleName) else NotificationLightingStyle.STROKE - val glowSidesArray = intent?.getStringArrayExtra("glow_sides") + val glowSidesArray = intent.getStringArrayExtra("glow_sides") glowSides = glowSidesArray?.mapNotNull { try { NotificationLightingSide.valueOf(it) @@ -149,16 +147,16 @@ class NotificationLightingService : Service() { } }?.toSet() ?: setOf(NotificationLightingSide.LEFT, NotificationLightingSide.RIGHT) - indicatorX = intent?.getFloatExtra("indicator_x", 50f) ?: 50f - indicatorY = intent?.getFloatExtra("indicator_y", 2f) ?: 2f - indicatorScale = intent?.getFloatExtra("indicator_scale", 1.0f) ?: 1.0f - isAmbientDisplay = intent?.getBooleanExtra("is_ambient_display", false) ?: false - sweepPosition = intent?.getStringExtra("sweep_position") ?: "CENTER" - sweepThickness = intent?.getFloatExtra("sweep_thickness", 8f) ?: 8f - randomShapes = intent?.getBooleanExtra("random_shapes", false) ?: false - systemLightingMode = intent?.getIntExtra("system_lighting_mode", 0) ?: 0 - val ignoreScreenState = intent?.getBooleanExtra("ignore_screen_state", false) ?: false - val removePreview = intent?.getBooleanExtra("remove_preview", false) ?: false + indicatorX = intent.getFloatExtra("indicator_x", 50f) + indicatorY = intent.getFloatExtra("indicator_y", 2f) + indicatorScale = intent.getFloatExtra("indicator_scale", 1.0f) + isAmbientDisplay = intent.getBooleanExtra("is_ambient_display", false) + sweepPosition = intent.getStringExtra("sweep_position") ?: "CENTER" + sweepThickness = intent.getFloatExtra("sweep_thickness", 8f) + randomShapes = intent.getBooleanExtra("random_shapes", false) + systemLightingMode = intent.getIntExtra("system_lighting_mode", 0) + val ignoreScreenState = intent.getBooleanExtra("ignore_screen_state", false) + val removePreview = intent.getBooleanExtra("remove_preview", false) if (removePreview) { // If accessibility service is enabled, delegate to it @@ -183,7 +181,7 @@ class NotificationLightingService : Service() { } - val isForegroundStart = intent?.getBooleanExtra("is_foreground_start", false) ?: false + val isForegroundStart = intent.getBooleanExtra("is_foreground_start", false) if (isForegroundStart && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { try { startForeground(NOTIF_ID, buildNotification()) @@ -202,8 +200,8 @@ class NotificationLightingService : Service() { putExtra("stroke_thickness_dp", strokeThicknessDp) putExtra("is_preview", isPreview) putExtra("ignore_screen_state", ignoreScreenState) - putExtra("color_mode", intent?.getStringExtra("color_mode")) - putExtra("custom_color", intent?.getIntExtra("custom_color", 0) ?: 0) + putExtra("color_mode", intent.getStringExtra("color_mode")) + putExtra("custom_color", intent.getIntExtra("custom_color", 0)) putExtra("pulse_count", pulseCount) putExtra("pulse_duration", pulseDuration) putExtra("style", edgeLightingStyle.name) @@ -213,16 +211,16 @@ class NotificationLightingService : Service() { putExtra("indicator_scale", indicatorScale) putExtra("sweep_position", sweepPosition) putExtra("sweep_thickness", sweepThickness) - if (intent?.hasExtra("resolved_color") == true) { + if (intent.hasExtra("resolved_color")) { putExtra("resolved_color", intent.getIntExtra("resolved_color", 0)) } putExtra( "is_ambient_display", - intent?.getBooleanExtra("is_ambient_display", false) ?: false + intent.getBooleanExtra("is_ambient_display", false) ) putExtra( "is_ambient_show_lock_screen", - intent?.getBooleanExtra("is_ambient_show_lock_screen", false) ?: false + intent.getBooleanExtra("is_ambient_show_lock_screen", false) ) putExtra("random_shapes", randomShapes) putExtra("system_lighting_mode", systemLightingMode) @@ -379,9 +377,7 @@ class NotificationLightingService : Service() { // When all overlays are removed, stop foreground if (overlayViews.isEmpty()) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(true) - } + stopForeground(true) } catch (_: Exception) { } } @@ -429,9 +425,7 @@ class NotificationLightingService : Service() { // When all overlays are removed, stop foreground if (overlayViews.isEmpty()) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(true) - } + stopForeground(true) } catch (_: Exception) { } } @@ -441,9 +435,7 @@ class NotificationLightingService : Service() { if (immediate && overlayViews.isEmpty()) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(true) - } + stopForeground(true) } catch (_: Exception) { } } @@ -456,7 +448,7 @@ class NotificationLightingService : Service() { private fun triggerSystemLighting() { if (!com.sameerasw.essentials.utils.ShellUtils.hasPermission(this)) return - val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager + val wm = getSystemService(WINDOW_SERVICE) as WindowManager val metrics = android.util.DisplayMetrics() wm.defaultDisplay.getRealMetrics(metrics) val centerX = metrics.widthPixels / 2 @@ -473,11 +465,9 @@ class NotificationLightingService : Service() { } com.sameerasw.essentials.utils.ShellUtils.runCommand(this, command) - + // No need to keep service running for system ripples as they are fire-and-forget - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(true) - } + stopForeground(true) stopSelf() } diff --git a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt index 2ad9429c0..e410c4e77 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt @@ -84,7 +84,10 @@ class NotificationListener : NotificationListenerService() { val discoveredChannels: MutableList = if (discoveredJson != null) { try { - gson.fromJson(discoveredJson, Array::class.java).toMutableList() + gson.fromJson( + discoveredJson, + Array::class.java + ).toMutableList() } catch (_: Exception) { mutableListOf() } @@ -136,7 +139,10 @@ class NotificationListener : NotificationListenerService() { val discoveredChannels: MutableList = if (discoveredJson != null) { try { - gson.fromJson(discoveredJson, Array::class.java).toMutableList() + gson.fromJson( + discoveredJson, + Array::class.java + ).toMutableList() } catch (_: Exception) { mutableListOf() } @@ -434,7 +440,7 @@ class NotificationListener : NotificationListenerService() { } } // 2. Trigger Glance only if screen is OFF or Screensaver is Active - val powerManager = getSystemService(POWER_SERVICE) as android.os.PowerManager + val powerManager = getSystemService(POWER_SERVICE) as PowerManager val isDreaming = com.sameerasw.essentials.services.dreams.AmbientDreamService.isDreaming if (!powerManager.isInteractive || bypassInteractiveCheck || isDreaming) { @@ -567,7 +573,8 @@ class NotificationListener : NotificationListenerService() { val blockedChannelsJson = prefs.getString("snooze_blocked_channels", null) val blockedChannels: Set = if (blockedChannelsJson != null) { try { - com.google.gson.Gson().fromJson(blockedChannelsJson, Array::class.java).toSet() + com.google.gson.Gson() + .fromJson(blockedChannelsJson, Array::class.java).toSet() } catch (_: Exception) { emptySet() } @@ -665,7 +672,10 @@ class NotificationListener : NotificationListenerService() { val glowSidesJson = prefs.getString("edge_lighting_glow_sides", null) val glowSides: Set = if (glowSidesJson != null) { try { - gson.fromJson(glowSidesJson, Array::class.java).toSet() + gson.fromJson( + glowSidesJson, + Array::class.java + ).toSet() } catch (_: Exception) { setOf(NotificationLightingSide.LEFT, NotificationLightingSide.RIGHT) } @@ -694,53 +704,55 @@ class NotificationListener : NotificationListenerService() { } catch (e: ClassCastException) { prefs.getInt("edge_lighting_sweep_thickness", 8).toFloat() } - val sweepPosition = prefs.getString("edge_lighting_sweep_position", "CENTER") ?: "CENTER" - val randomShapes = prefs.getBoolean("edge_lighting_sweep_random_shapes", true) - val systemLightingMode = prefs.getInt("edge_lighting_system_mode", 0) - - fun startNotificationLighting(resolvedColor: Int? = null) { - val intent = Intent( - applicationContext, - NotificationLightingService::class.java - ).apply { - putExtra("corner_radius_dp", cornerRadius) - putExtra("stroke_thickness_dp", strokeThickness) - putExtra("color_mode", colorMode.name) - putExtra("pulse_count", pulseCount) - putExtra("pulse_duration", pulseDuration) - putExtra("style", styleName) - putExtra("glow_sides", glowSides.map { it.name }.toTypedArray()) - putExtra("indicator_x", indicatorX) - putExtra("indicator_y", indicatorY) - putExtra("indicator_scale", indicatorScale) - if (resolvedColor != null) { - putExtra("resolved_color", resolvedColor) - } else if (colorMode == NotificationLightingColorMode.CUSTOM) { - putExtra( - "custom_color", - prefs.getInt( - "edge_lighting_custom_color", - 0xFF6200EE.toInt() - ) - ) - } - putExtra( - "is_ambient_display", - prefs.getBoolean("edge_lighting_ambient_display", false) - ) - putExtra( - "is_ambient_show_lock_screen", - prefs.getBoolean( - "edge_lighting_ambient_show_lock_screen", - false - ) + val sweepPosition = + prefs.getString("edge_lighting_sweep_position", "CENTER") ?: "CENTER" + val randomShapes = + prefs.getBoolean("edge_lighting_sweep_random_shapes", true) + val systemLightingMode = prefs.getInt("edge_lighting_system_mode", 0) + + fun startNotificationLighting(resolvedColor: Int? = null) { + val intent = Intent( + applicationContext, + NotificationLightingService::class.java + ).apply { + putExtra("corner_radius_dp", cornerRadius) + putExtra("stroke_thickness_dp", strokeThickness) + putExtra("color_mode", colorMode.name) + putExtra("pulse_count", pulseCount) + putExtra("pulse_duration", pulseDuration) + putExtra("style", styleName) + putExtra("glow_sides", glowSides.map { it.name }.toTypedArray()) + putExtra("indicator_x", indicatorX) + putExtra("indicator_y", indicatorY) + putExtra("indicator_scale", indicatorScale) + if (resolvedColor != null) { + putExtra("resolved_color", resolvedColor) + } else if (colorMode == NotificationLightingColorMode.CUSTOM) { + putExtra( + "custom_color", + prefs.getInt( + "edge_lighting_custom_color", + 0xFF6200EE.toInt() ) - putExtra("sweep_position", sweepPosition) - putExtra("sweep_thickness", sweepThickness) - putExtra("random_shapes", randomShapes) - putExtra("system_lighting_mode", systemLightingMode) - putExtra("package_name", sbn.packageName) - } + ) + } + putExtra( + "is_ambient_display", + prefs.getBoolean("edge_lighting_ambient_display", false) + ) + putExtra( + "is_ambient_show_lock_screen", + prefs.getBoolean( + "edge_lighting_ambient_show_lock_screen", + false + ) + ) + putExtra("sweep_position", sweepPosition) + putExtra("sweep_thickness", sweepThickness) + putExtra("random_shapes", randomShapes) + putExtra("system_lighting_mode", systemLightingMode) + putExtra("package_name", sbn.packageName) + } if (PermissionUtils.isAccessibilityServiceEnabled(applicationContext)) { applicationContext.startService(intent) } else { @@ -834,7 +846,8 @@ class NotificationListener : NotificationListenerService() { private fun handleNotificationGlance(sbn: StatusBarNotification, isPosted: Boolean) { try { val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) - val enabled = prefs.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED, false) + val enabled = + prefs.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED, false) if (!enabled) { if (activeGlanceNotifications.isNotEmpty()) { activeGlanceNotifications.clear() @@ -867,13 +880,16 @@ class NotificationListener : NotificationListenerService() { val newValue = if (enable) 1 else 0 if (currentValue != newValue) { Settings.Secure.putInt(contentResolver, "doze_always_on", newValue) - + // If turning OFF and force turn off workaround is enabled, trigger it if (!enable) { - val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + val powerManager = getSystemService(POWER_SERVICE) as PowerManager if (!powerManager.isInteractive) { val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) - val forceTurnOffEnabled = prefs.getBoolean(SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED, false) + val forceTurnOffEnabled = prefs.getBoolean( + SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED, + false + ) if (forceTurnOffEnabled) { sendBroadcast(Intent("FORCE_TURN_OFF_AOD").setPackage(packageName)) } @@ -888,16 +904,21 @@ class NotificationListener : NotificationListenerService() { private fun isAppSelectedForNotificationGlance(packageName: String): Boolean { try { val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) - val sameAsLighting = prefs.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, true) + val sameAsLighting = + prefs.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, true) if (sameAsLighting) { return isAppSelectedForNotificationLighting(packageName) } - val json = prefs.getString(SettingsRepository.KEY_NOTIFICATION_GLANCE_SELECTED_APPS, null) + val json = + prefs.getString(SettingsRepository.KEY_NOTIFICATION_GLANCE_SELECTED_APPS, null) if (json == null) return true val selectedApps: List = - com.google.gson.Gson().fromJson(json, Array::class.java).toList() + com.google.gson.Gson().fromJson( + json, + Array::class.java + ).toList() val app = selectedApps.find { it.packageName == packageName } return app?.isEnabled ?: true @@ -949,7 +970,8 @@ class NotificationListener : NotificationListenerService() { val detectionChannelsJson = prefs.getString("maps_detection_channels", null) val detectionChannels: Set = if (detectionChannelsJson != null) { try { - com.google.gson.Gson().fromJson(detectionChannelsJson, Array::class.java).toSet() + com.google.gson.Gson().fromJson(detectionChannelsJson, Array::class.java) + .toSet() } catch (_: Exception) { emptySet() } @@ -994,7 +1016,7 @@ class NotificationListener : NotificationListenerService() { val onlyShowWhenScreenOff = prefs.getBoolean("edge_lighting_only_screen_off", true) if (onlyShowWhenScreenOff) { val powerManager = - getSystemService(POWER_SERVICE) as android.os.PowerManager + getSystemService(POWER_SERVICE) as PowerManager val isScreenOn = powerManager.isInteractive if (isScreenOn) { return false @@ -1010,7 +1032,10 @@ class NotificationListener : NotificationListenerService() { val gson = com.google.gson.Gson() val selectedApps: List = - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() // Find the app in the saved list val app = selectedApps.find { it.packageName == packageName } @@ -1042,7 +1067,10 @@ class NotificationListener : NotificationListenerService() { val gson = com.google.gson.Gson() val selectedApps: List = - gson.fromJson(json, Array::class.java).toList() + gson.fromJson( + json, + Array::class.java + ).toList() // Find the app in the saved list val app = selectedApps.find { it.packageName == packageName } @@ -1067,12 +1095,12 @@ class NotificationListener : NotificationListenerService() { for (sbn in notifications) { val token = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { sbn.notification.extras.getParcelable( - android.app.Notification.EXTRA_MEDIA_SESSION, + Notification.EXTRA_MEDIA_SESSION, android.media.session.MediaSession.Token::class.java ) } else { @Suppress("DEPRECATION") - sbn.notification.extras.getParcelable(android.app.Notification.EXTRA_MEDIA_SESSION) + sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) } if (token != null) { sessions.add(android.media.session.MediaController(this, token)) diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt index fbfc60405..bda28e088 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt @@ -8,191 +8,194 @@ import com.sameerasw.essentials.domain.diy.Action object CombinedActionExecutor { - suspend fun execute(context: Context, action: com.sameerasw.essentials.domain.diy.Action) { + suspend fun execute(context: Context, action: Action) { kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) { when (action) { - is Action.TurnOnLowPower -> setLowPowerMode(context, true) - is Action.TurnOffLowPower -> setLowPowerMode(context, false) - is Action.HapticVibration -> { - val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val manager = - context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as android.os.VibratorManager - manager.defaultVibrator - } else { - @Suppress("DEPRECATION") - context.getSystemService(Context.VIBRATOR_SERVICE) as android.os.Vibrator - } + is Action.TurnOnLowPower -> setLowPowerMode(context, true) + is Action.TurnOffLowPower -> setLowPowerMode(context, false) + is Action.HapticVibration -> { + val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val manager = + context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as android.os.VibratorManager + manager.defaultVibrator + } else { + @Suppress("DEPRECATION") + context.getSystemService(Context.VIBRATOR_SERVICE) as android.os.Vibrator + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - vibrator.vibrate( - android.os.VibrationEffect.createOneShot( - 50, - android.os.VibrationEffect.DEFAULT_AMPLITUDE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate( + android.os.VibrationEffect.createOneShot( + 50, + android.os.VibrationEffect.DEFAULT_AMPLITUDE + ) ) - ) - } else { - @Suppress("DEPRECATION") - vibrator.vibrate(50) + } else { + @Suppress("DEPRECATION") + vibrator.vibrate(50) + } } - } - is Action.TurnOnFlashlight -> toggleFlashlight(context, true) - is Action.TurnOffFlashlight -> toggleFlashlight(context, false) - is Action.ToggleFlashlight -> { - val camManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager - try { - camManager.cameraIdList[0] - camManager.registerTorchCallback(object : CameraManager.TorchCallback() { - override fun onTorchModeChanged(cameraId: String, enabled: Boolean) { - super.onTorchModeChanged(cameraId, enabled) - camManager.unregisterTorchCallback(this) - try { - camManager.setTorchMode(cameraId, !enabled) - } catch (e: Exception) { - e.printStackTrace() + is Action.TurnOnFlashlight -> toggleFlashlight(context, true) + is Action.TurnOffFlashlight -> toggleFlashlight(context, false) + is Action.ToggleFlashlight -> { + val camManager = + context.getSystemService(Context.CAMERA_SERVICE) as CameraManager + try { + camManager.cameraIdList[0] + camManager.registerTorchCallback(object : CameraManager.TorchCallback() { + override fun onTorchModeChanged(cameraId: String, enabled: Boolean) { + super.onTorchModeChanged(cameraId, enabled) + camManager.unregisterTorchCallback(this) + try { + camManager.setTorchMode(cameraId, !enabled) + } catch (e: Exception) { + e.printStackTrace() + } } - } - }, null) + }, null) + + } catch (e: Exception) { + e.printStackTrace() + } + } - } catch (e: Exception) { - e.printStackTrace() + is Action.ShowNotification -> { + // Placeholder } - } - is Action.ShowNotification -> { - // Placeholder - } + is Action.RemoveNotification -> { + // Placeholder + } - is Action.RemoveNotification -> { - // Placeholder - } + is Action.DimWallpaper -> { + com.sameerasw.essentials.utils.ShellUtils.runCommand( + context, + "cmd wallpaper set-dim-amount ${action.dimAmount}" + ) + } - is Action.DimWallpaper -> { - com.sameerasw.essentials.utils.ShellUtils.runCommand( - context, - "cmd wallpaper set-dim-amount ${action.dimAmount}" - ) - } + is Action.DeviceEffects -> { + if (Build.VERSION.SDK_INT >= 35) { // Android 15+ + val nm = + context.getSystemService(android.app.NotificationManager::class.java) + if (nm.isNotificationPolicyAccessGranted) { + try { + if (action.enabled) { + // ENABLE/UPDATE EFFECTS + val effectsBuilder = try { + android.service.notification.ZenDeviceEffects.Builder() + } catch (e: NoSuchMethodError) { + try { + val constructor = + android.service.notification.ZenDeviceEffects.Builder::class.java.getConstructor( + android.service.notification.ZenDeviceEffects::class.java + ) + constructor.newInstance(null) + } catch (refE: Exception) { + null + } + } ?: return@withContext + + effectsBuilder.setShouldDisplayGrayscale(action.grayscale) + .setShouldSuppressAmbientDisplay(action.suppressAmbient) + .setShouldDimWallpaper(action.dimWallpaper) + .setShouldUseNightMode(action.nightMode) + + val effects = effectsBuilder.build() + + "essentials_focus_mode" + val existingRule = + nm.automaticZenRules.values.find { it.name == "Essentials Focus" } + val ruleKey = + existingRule?.let { nm.automaticZenRules.entries.find { entry -> entry.value == it }?.key } + + val componentName = android.content.ComponentName( + context, + com.sameerasw.essentials.services.EssentialsConditionProvider::class.java + ) + val conditionUri = + com.sameerasw.essentials.services.EssentialsConditionProvider.CONDITION_URI - is Action.DeviceEffects -> { - if (Build.VERSION.SDK_INT >= 35) { // Android 15+ - val nm = context.getSystemService(android.app.NotificationManager::class.java) - if (nm.isNotificationPolicyAccessGranted) { - try { - if (action.enabled) { - // ENABLE/UPDATE EFFECTS - val effectsBuilder = try { - android.service.notification.ZenDeviceEffects.Builder() - } catch (e: NoSuchMethodError) { - try { - val constructor = - android.service.notification.ZenDeviceEffects.Builder::class.java.getConstructor( - android.service.notification.ZenDeviceEffects::class.java + val ruleBuilder = android.app.AutomaticZenRule.Builder( + "Essentials Focus", + conditionUri + ) + .setOwner(componentName) + .setDeviceEffects(effects) + .setInterruptionFilter(android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy( + android.service.notification.ZenPolicy.Builder() + .allowAlarms(true).build() + ) + .setConditionId(conditionUri) + .setConfigurationActivity( + android.content.ComponentName( + context, + com.sameerasw.essentials.MainActivity::class.java ) - constructor.newInstance(null) - } catch (refE: Exception) { - null + ) + + if (ruleKey != null) { + nm.updateAutomaticZenRule(ruleKey, ruleBuilder.build()) + } else { + nm.addAutomaticZenRule(ruleBuilder.build()) } - } ?: return@withContext - - effectsBuilder.setShouldDisplayGrayscale(action.grayscale) - .setShouldSuppressAmbientDisplay(action.suppressAmbient) - .setShouldDimWallpaper(action.dimWallpaper) - .setShouldUseNightMode(action.nightMode) - - val effects = effectsBuilder.build() - - "essentials_focus_mode" - val existingRule = - nm.automaticZenRules.values.find { it.name == "Essentials Focus" } - val ruleKey = - existingRule?.let { nm.automaticZenRules.entries.find { entry -> entry.value == it }?.key } - - val componentName = android.content.ComponentName( - context, - com.sameerasw.essentials.services.EssentialsConditionProvider::class.java - ) - val conditionUri = - com.sameerasw.essentials.services.EssentialsConditionProvider.CONDITION_URI - - val ruleBuilder = android.app.AutomaticZenRule.Builder( - "Essentials Focus", - conditionUri - ) - .setOwner(componentName) - .setDeviceEffects(effects) - .setInterruptionFilter(android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY) - .setZenPolicy( - android.service.notification.ZenPolicy.Builder() - .allowAlarms(true).build() + + // Trigger the condition to be TRUE + com.sameerasw.essentials.services.EssentialsConditionProvider.setConditionState( + context, + true ) - .setConditionId(conditionUri) - .setConfigurationActivity( - android.content.ComponentName( - context, - com.sameerasw.essentials.MainActivity::class.java - ) + + android.util.Log.d( + "DeviceEffects", + "Updated ZenRule for Device Effects" ) - if (ruleKey != null) { - nm.updateAutomaticZenRule(ruleKey, ruleBuilder.build()) } else { - nm.addAutomaticZenRule(ruleBuilder.build()) - } + // DISABLE EFFECTS + val existingRuleEntry = + nm.automaticZenRules.entries.find { it.value.name == "Essentials Focus" } + existingRuleEntry?.let { entry -> + val rule = entry.value + rule.isEnabled = false + nm.updateAutomaticZenRule(entry.key, rule) + } + // Also notify condition false just in case + com.sameerasw.essentials.services.EssentialsConditionProvider.setConditionState( + context, + false + ) - // Trigger the condition to be TRUE - com.sameerasw.essentials.services.EssentialsConditionProvider.setConditionState( - context, - true - ) - - android.util.Log.d( - "DeviceEffects", - "Updated ZenRule for Device Effects" - ) - - } else { - // DISABLE EFFECTS - val existingRuleEntry = - nm.automaticZenRules.entries.find { it.value.name == "Essentials Focus" } - existingRuleEntry?.let { entry -> - val rule = entry.value - rule.isEnabled = false - nm.updateAutomaticZenRule(entry.key, rule) + android.util.Log.d( + "DeviceEffects", + "Disabled ZenRule for Device Effects" + ) } - // Also notify condition false just in case - com.sameerasw.essentials.services.EssentialsConditionProvider.setConditionState( - context, - false - ) - - android.util.Log.d( - "DeviceEffects", - "Disabled ZenRule for Device Effects" - ) - } - } catch (e: Throwable) { - e.printStackTrace() + } catch (e: Throwable) { + e.printStackTrace() + } } } } - } - is Action.SoundMode -> { - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - val ringerMode = when (action.mode) { - Action.SoundModeType.SOUND -> AudioManager.RINGER_MODE_NORMAL - Action.SoundModeType.VIBRATE -> AudioManager.RINGER_MODE_VIBRATE - Action.SoundModeType.SILENT -> AudioManager.RINGER_MODE_SILENT - } - try { - audioManager.ringerMode = ringerMode - } catch (e: Exception) { - e.printStackTrace() + is Action.SoundMode -> { + val audioManager = + context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + val ringerMode = when (action.mode) { + Action.SoundModeType.SOUND -> AudioManager.RINGER_MODE_NORMAL + Action.SoundModeType.VIBRATE -> AudioManager.RINGER_MODE_VIBRATE + Action.SoundModeType.SILENT -> AudioManager.RINGER_MODE_SILENT + } + try { + audioManager.ringerMode = ringerMode + } catch (e: Exception) { + e.printStackTrace() + } } } - } } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/modules/PowerModule.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/modules/PowerModule.kt index 14c2e66b6..4fc1e0c97 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/modules/PowerModule.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/modules/PowerModule.kt @@ -5,7 +5,6 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager -import android.os.PowerManager import com.sameerasw.essentials.domain.diy.Automation import com.sameerasw.essentials.domain.diy.Trigger import com.sameerasw.essentials.services.automation.executors.CombinedActionExecutor diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/modules/TimeModule.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/modules/TimeModule.kt index b09ce8d71..4c9ac85de 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/modules/TimeModule.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/modules/TimeModule.kt @@ -13,7 +13,7 @@ import com.sameerasw.essentials.services.automation.receivers.TimeAutomationRece import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.util.* +import java.util.Calendar import com.sameerasw.essentials.domain.diy.State as DIYState class TimeModule : AutomationModule { @@ -38,41 +38,71 @@ class TimeModule : AutomationModule { override fun updateAutomations(automations: List) { this.automations = automations - appContext?.let { + appContext?.let { checkCurrentStates(it) - scheduleAllAlarms(it) + scheduleAllAlarms(it) } } private fun scheduleAllAlarms(context: Context) { cancelAllAlarms(context) - + automations.forEach { automation -> when (automation.type) { Automation.Type.TRIGGER -> { (automation.trigger as? Trigger.Schedule)?.let { schedule -> - scheduleAlarm(context, automation.id, schedule.hour, schedule.minute, schedule.days, true) + scheduleAlarm( + context, + automation.id, + schedule.hour, + schedule.minute, + schedule.days, + true + ) } } + Automation.Type.STATE -> { (automation.state as? DIYState.TimePeriod)?.let { period -> - scheduleAlarm(context, automation.id, period.startHour, period.startMinute, period.days, true) - scheduleAlarm(context, automation.id, period.endHour, period.endMinute, period.days, false) + scheduleAlarm( + context, + automation.id, + period.startHour, + period.startMinute, + period.days, + true + ) + scheduleAlarm( + context, + automation.id, + period.endHour, + period.endMinute, + period.days, + false + ) } } + else -> {} } } } - private fun scheduleAlarm(context: Context, id: String, hour: Int, minute: Int, days: Set, isEntry: Boolean) { + private fun scheduleAlarm( + context: Context, + id: String, + hour: Int, + minute: Int, + days: Set, + isEntry: Boolean + ) { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(context, TimeAutomationReceiver::class.java).apply { action = TimeAutomationReceiver.ACTION_TRIGGER putExtra(TimeAutomationReceiver.EXTRA_AUTOMATION_ID, id) putExtra(TimeAutomationReceiver.EXTRA_IS_ENTRY, isEntry) } - + val requestCode = (id + isEntry.toString()).hashCode() val pendingIntent = PendingIntent.getBroadcast( context, @@ -82,40 +112,43 @@ class TimeModule : AutomationModule { ) val calendar = calculateNextOccurrence(hour, minute, days) - android.util.Log.d(ID, "Scheduling alarm for automation $id (entry=$isEntry) at ${calendar.time}") - + Log.d( + ID, + "Scheduling alarm for automation $id (entry=$isEntry) at ${calendar.time}" + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - if (alarmManager.canScheduleExactAlarms()) { - alarmManager.setExactAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - calendar.timeInMillis, - pendingIntent - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (alarmManager.canScheduleExactAlarms()) { + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + calendar.timeInMillis, + pendingIntent + ) + } else { + // Fallback to inexact + alarmManager.setAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + calendar.timeInMillis, + pendingIntent + ) + } } else { - // Fallback to inexact - alarmManager.setAndAllowWhileIdle( + alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent ) } - } else { - alarmManager.setExactAndAllowWhileIdle( + } catch (e: SecurityException) { + alarmManager.setAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent ) } - } catch (e: SecurityException) { - alarmManager.setAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - calendar.timeInMillis, - pendingIntent - ) - } -} else { + } else { alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent) } } @@ -126,15 +159,25 @@ class TimeModule : AutomationModule { val intent = Intent(context, TimeAutomationReceiver::class.java).apply { action = TimeAutomationReceiver.ACTION_TRIGGER } - + val rc1 = (automation.id + "true").hashCode() - PendingIntent.getBroadcast(context, rc1, intent, PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)?.let { + PendingIntent.getBroadcast( + context, + rc1, + intent, + PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE + )?.let { alarmManager.cancel(it) it.cancel() } - + val rc2 = (automation.id + "false").hashCode() - PendingIntent.getBroadcast(context, rc2, intent, PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)?.let { + PendingIntent.getBroadcast( + context, + rc2, + intent, + PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE + )?.let { alarmManager.cancel(it) it.cancel() } @@ -172,7 +215,10 @@ class TimeModule : AutomationModule { val currentMinute = now.get(Calendar.MINUTE) val currentDay = now.get(Calendar.DAY_OF_WEEK) - android.util.Log.d(ID, "Checking current states at $currentHour:$currentMinute on day $currentDay") + Log.d( + ID, + "Checking current states at $currentHour:$currentMinute on day $currentDay" + ) automations.filter { it.type == Automation.Type.STATE && it.isEnabled } .forEach { automation -> @@ -189,15 +235,31 @@ class TimeModule : AutomationModule { } val wasActive = activeStateAutomations.contains(automation.id) - + if (isActive && !wasActive) { - Log.d(ID, "State ${automation.id} became active. Executing entry actions.") + Log.d( + ID, + "State ${automation.id} became active. Executing entry actions." + ) activeStateAutomations.add(automation.id) - automation.entryAction?.let { CombinedActionExecutor.execute(context, it) } + automation.entryAction?.let { + CombinedActionExecutor.execute( + context, + it + ) + } } else if (!isActive && wasActive) { - Log.d(ID, "State ${automation.id} became inactive. Executing exit actions.") + Log.d( + ID, + "State ${automation.id} became inactive. Executing exit actions." + ) activeStateAutomations.remove(automation.id) - automation.exitAction?.let { CombinedActionExecutor.execute(context, it) } + automation.exitAction?.let { + CombinedActionExecutor.execute( + context, + it + ) + } } } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/receivers/TimeAutomationReceiver.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/receivers/TimeAutomationReceiver.kt index 192ad404e..f54389cf6 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/receivers/TimeAutomationReceiver.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/receivers/TimeAutomationReceiver.kt @@ -40,10 +40,12 @@ class TimeAutomationReceiver : BroadcastReceiver() { CombinedActionExecutor.execute(context, action) } } + Automation.Type.STATE -> { val action = if (isEntry) automation.entryAction else automation.exitAction action?.let { CombinedActionExecutor.execute(context, it) } } + else -> {} } } catch (e: Exception) { diff --git a/app/src/main/java/com/sameerasw/essentials/services/dreams/AmbientDreamService.kt b/app/src/main/java/com/sameerasw/essentials/services/dreams/AmbientDreamService.kt index 134d64813..14d100a15 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/dreams/AmbientDreamService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/dreams/AmbientDreamService.kt @@ -73,8 +73,8 @@ class AmbientDreamService : DreamService() { private var likeStatusView: ImageView? = null private var volumeIconView: ImageView? = null private var volumeStrokeView: VolumeStrokeView? = null - - private var currentShapePath: android.graphics.Path? = null + + private var currentShapePath: Path? = null // State private var isMusicMode = false @@ -96,16 +96,18 @@ class AmbientDreamService : DreamService() { handler.post { val title = metadata?.getString(android.media.MediaMetadata.METADATA_KEY_TITLE) val artist = metadata?.getString(android.media.MediaMetadata.METADATA_KEY_ARTIST) - + if (title != trackTitle || artist != artistName) { trackTitle = title artistName = artist - + currentController?.let { isAlreadyLiked = checkIsLiked(it) } - - var artBitmap = metadata?.getBitmap(android.media.MediaMetadata.METADATA_KEY_ALBUM_ART) + + var artBitmap = + metadata?.getBitmap(android.media.MediaMetadata.METADATA_KEY_ALBUM_ART) if (artBitmap == null) { - artBitmap = metadata?.getBitmap(android.media.MediaMetadata.METADATA_KEY_ART) + artBitmap = + metadata?.getBitmap(android.media.MediaMetadata.METADATA_KEY_ART) } updateMetadata(artBitmap) } @@ -133,7 +135,7 @@ class AmbientDreamService : DreamService() { private val burnInProtectionRunnable = object : Runnable { override fun run() { if (isDetached) return - + // Revert to clock if Android Auto is running if (com.sameerasw.essentials.utils.AppUtil.isAndroidAutoRunning(this@AmbientDreamService)) { switchToClockMode() @@ -190,7 +192,10 @@ class AmbientDreamService : DreamService() { // Register Media Session Listener try { val mediaSessionManager = getSystemService(MEDIA_SESSION_SERVICE) as MediaSessionManager - val componentName = android.content.ComponentName(this, com.sameerasw.essentials.services.NotificationListener::class.java) + val componentName = android.content.ComponentName( + this, + com.sameerasw.essentials.services.NotificationListener::class.java + ) mediaSessionManager.addOnActiveSessionsChangedListener(sessionListener, componentName) updateActiveSession(mediaSessionManager.getActiveSessions(componentName)) } catch (e: Exception) { @@ -263,7 +268,8 @@ class AmbientDreamService : DreamService() { val size = dpToPx(320f) currentPolygon = com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getRandomPolygon() - currentShapePath = com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getRandomShapePath(size.toFloat()) + currentShapePath = + com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getRandomShapePath(size.toFloat()) // Container for clipping clipContainer = FrameLayout(this).apply { @@ -304,7 +310,7 @@ class AmbientDreamService : DreamService() { FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT ) - setBackgroundColor(0x40000000.toInt()) + setBackgroundColor(0x40000000) } clipContainer?.addView(imageView) @@ -405,12 +411,12 @@ class AmbientDreamService : DreamService() { if (isMusicMode) { volumeStrokeView?.setColor(Color.WHITE) volumeStrokeView?.updatePercentage(perc) - + // Show and schedule hide volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() handler.removeCallbacks(volumeHideRunnable) handler.postDelayed(volumeHideRunnable, 3000) - + handler.removeCallbacks(revertToMusicRunnable) handler.postDelayed(revertToMusicRunnable, 5000) } @@ -466,7 +472,7 @@ class AmbientDreamService : DreamService() { private fun handleIntent(intent: Intent) { if (isDetached) return - + // Skip if Android Auto is running if (com.sameerasw.essentials.utils.AppUtil.isAndroidAutoRunning(this)) { switchToClockMode() @@ -497,7 +503,7 @@ class AmbientDreamService : DreamService() { if (eventType == "volume") { volumeStrokeView?.setColor(Color.WHITE) volumeStrokeView?.updatePercentage(volumePercentage) - + // Show and schedule hide volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() handler.removeCallbacks(volumeHideRunnable) @@ -517,7 +523,8 @@ class AmbientDreamService : DreamService() { private fun updateActiveSession(sessions: List?) { if (isDetached) return - val playingSession = sessions?.firstOrNull { it.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING } + val playingSession = + sessions?.firstOrNull { it.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING } if (playingSession != null) { if (currentController?.sessionToken != playingSession.sessionToken) { @@ -530,8 +537,9 @@ class AmbientDreamService : DreamService() { trackTitle = metadata?.getString(android.media.MediaMetadata.METADATA_KEY_TITLE) artistName = metadata?.getString(android.media.MediaMetadata.METADATA_KEY_ARTIST) isAlreadyLiked = checkIsLiked(playingSession) - - var artBitmap = metadata?.getBitmap(android.media.MediaMetadata.METADATA_KEY_ALBUM_ART) + + var artBitmap = + metadata?.getBitmap(android.media.MediaMetadata.METADATA_KEY_ALBUM_ART) if (artBitmap == null) { artBitmap = metadata?.getBitmap(android.media.MediaMetadata.METADATA_KEY_ART) } @@ -570,10 +578,9 @@ class AmbientDreamService : DreamService() { } catch (e: SecurityException) { // Fallback for Android 16+ or restricted environments try { - val sessions = mutableListOf() - val notifications = - (getSystemService(android.app.NotificationManager::class.java))?.activeNotifications - ?: emptyArray() + mutableListOf() + (getSystemService(android.app.NotificationManager::class.java))?.activeNotifications + ?: emptyArray() emptyList() } catch (_: Exception) { @@ -642,8 +649,9 @@ class AmbientDreamService : DreamService() { // Update Dynamic Shape with Morphing val size = dpToPx(320f).toFloat() - val newPolygon = com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getPolygon("${trackTitle}_${artistName}") - + val newPolygon = + com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getPolygon("${trackTitle}_${artistName}") + if (currentPolygon != null && currentPolygon != newPolygon) { val morph = androidx.graphics.shapes.Morph(currentPolygon!!, newPolygon) morphAnimator?.cancel() @@ -659,7 +667,7 @@ class AmbientDreamService : DreamService() { volumeStrokeView?.updatePath(path) clipContainer?.invalidateOutline() } - + nextImageView?.alpha = progress } addListener(object : android.animation.AnimatorListenerAdapter() { @@ -780,7 +788,7 @@ class AmbientDreamService : DreamService() { // Copy of Inner Class private inner class VolumeStrokeView( context: Context, - private var petalPath: android.graphics.Path, + private var petalPath: Path, private val percentage: Int ) : View(context) { private var currentPercentage: Float = percentage.toFloat() @@ -796,7 +804,7 @@ class AmbientDreamService : DreamService() { private val progressPath = Path() private var isDetached = false - fun updatePath(newPath: android.graphics.Path) { + fun updatePath(newPath: Path) { this.petalPath = newPath this.pathMeasure = PathMeasure(newPath, false) invalidate() diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/AmbientGlanceHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/AmbientGlanceHandler.kt index 1fe38dad6..77416635e 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/AmbientGlanceHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/AmbientGlanceHandler.kt @@ -50,8 +50,8 @@ class AmbientGlanceHandler( private var centerContainer: FrameLayout? = null private var clipContainer: FrameLayout? = null private var textContainer: LinearLayout? = null - - private var currentShapePath: android.graphics.Path? = null + + private var currentShapePath: Path? = null private var currentPolygon: androidx.graphics.shapes.RoundedPolygon? = null private var morphAnimator: android.animation.ValueAnimator? = null @@ -95,11 +95,14 @@ class AmbientGlanceHandler( } // Dismiss if music stops/pauses - val mediaSessionManager = service.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager - val componentName = android.content.ComponentName(service, ScreenOffAccessibilityService::class.java) + val mediaSessionManager = + service.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager + val componentName = + android.content.ComponentName(service, ScreenOffAccessibilityService::class.java) val sessions = mediaSessionManager.getActiveSessions(componentName) - val anyPlaying = sessions.any { it.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING } - + val anyPlaying = + sessions.any { it.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING } + if (!anyPlaying) { fadeOutAndRemove() return @@ -192,7 +195,7 @@ class AmbientGlanceHandler( else if (volumeKey == 25) volumeIconView?.setImageResource(R.drawable.rounded_volume_down_24) volumeIconView?.animate()?.alpha(1f)?.setDuration(200)?.start() volumeStrokeView?.setColor(Color.WHITE) - + // Show and schedule hide volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() handler.removeCallbacks(volumeHideRunnable) @@ -242,7 +245,10 @@ class AmbientGlanceHandler( // Update Dynamic Shape with Morphing val size = dpToPx(320f).toFloat() val randomEnabled = isRandomShapesEnabled() - val newPolygon = com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getPolygon("${trackTitle}_${artistName}", randomEnabled) + val newPolygon = com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getPolygon( + "${trackTitle}_${artistName}", + randomEnabled + ) if (currentPolygon != null && currentPolygon != newPolygon) { val morph = androidx.graphics.shapes.Morph(currentPolygon!!, newPolygon) @@ -259,7 +265,7 @@ class AmbientGlanceHandler( volumeStrokeView?.updatePath(path) clipContainer?.invalidateOutline() } - + nextImageView?.alpha = progress } addListener(object : android.animation.AnimatorListenerAdapter() { @@ -381,8 +387,13 @@ class AmbientGlanceHandler( val size = dpToPx(320f) val randomEnabled = isRandomShapesEnabled() - currentPolygon = com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getRandomPolygon(randomEnabled) - currentShapePath = com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getRandomShapePath(size.toFloat(), randomEnabled) + currentPolygon = + com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getRandomPolygon(randomEnabled) + currentShapePath = + com.sameerasw.essentials.utils.AmbientMusicShapeHelper.getRandomShapePath( + size.toFloat(), + randomEnabled + ) // Container for clipping clipContainer = FrameLayout(context).apply { @@ -429,7 +440,7 @@ class AmbientGlanceHandler( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT ) - setBackgroundColor(0x40000000.toInt()) + setBackgroundColor(0x40000000) } clipContainer?.addView(imageView) @@ -463,7 +474,7 @@ class AmbientGlanceHandler( volumeStrokeView?.setColor(if (eventType == EVENT_VOLUME) Color.WHITE else Color.GRAY) volumeStrokeView?.alpha = if (eventType == EVENT_VOLUME) 1f else 0f centerContainer?.addView(volumeStrokeView) - + if (eventType == EVENT_VOLUME) { handler.removeCallbacks(volumeHideRunnable) handler.postDelayed(volumeHideRunnable, 3000) @@ -796,7 +807,7 @@ class AmbientGlanceHandler( private inner class VolumeStrokeView( context: Context, - private var petalPath: android.graphics.Path, + private var petalPath: Path, private val percentage: Int ) : View(context) { private var currentPercentage: Float = percentage.toFloat() @@ -812,7 +823,7 @@ class AmbientGlanceHandler( private val progressPath = Path() private var isDetached = false - fun updatePath(newPath: android.graphics.Path) { + fun updatePath(newPath: Path) { this.petalPath = newPath this.pathMeasure = PathMeasure(newPath, false) invalidate() diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/AodForceTurnOffHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/AodForceTurnOffHandler.kt index f14885049..68aa6f704 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/AodForceTurnOffHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/AodForceTurnOffHandler.kt @@ -26,28 +26,31 @@ class AodForceTurnOffHandler(private val service: AccessibilityService) { val powerManager = service.getSystemService(Context.POWER_SERVICE) as PowerManager // Only run if screen is not interactive (currently in AOD or off) if (powerManager.isInteractive || isRunning) { - Log.d("AodForceTurnOff", "Skipping forceTurnOff: isInteractive=${powerManager.isInteractive}, isRunning=$isRunning") + Log.d( + "AodForceTurnOff", + "Skipping forceTurnOff: isInteractive=${powerManager.isInteractive}, isRunning=$isRunning" + ) return } - + Log.d("AodForceTurnOff", "Starting forceTurnOff sequence") isRunning = true showOverlay() - + // Sequence: Overlay -> Wake -> Lock -> Remove Overlay // Using slightly longer delays to ensure system registers actions handler.postDelayed({ wakeScreen() - + handler.postDelayed({ lockScreen() - + // Allow time for the lock action to process and screen to turn off handler.postDelayed({ removeOverlay() isRunning = false Log.d("AodForceTurnOff", "ForceTurnOff sequence completed") - }, 600) + }, 600) }, 100) }, 50) } @@ -76,7 +79,8 @@ class AodForceTurnOffHandler(private val service: AccessibilityService) { ).apply { gravity = Gravity.CENTER if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } } @@ -90,6 +94,7 @@ class AodForceTurnOffHandler(private val service: AccessibilityService) { private fun wakeScreen() { val powerManager = service.getSystemService(Context.POWER_SERVICE) as PowerManager + @Suppress("DEPRECATION") val wakeLock = powerManager.newWakeLock( PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, @@ -108,7 +113,8 @@ class AodForceTurnOffHandler(private val service: AccessibilityService) { if (overlayView != null && windowManager != null) { try { windowManager?.removeView(overlayView) - } catch (_: Exception) {} + } catch (_: Exception) { + } overlayView = null } isRunning = false diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt index 6d2862292..f554d622f 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt @@ -1,33 +1,32 @@ package com.sameerasw.essentials.services.handlers import android.accessibilityservice.AccessibilityService +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.content.pm.PackageManager import android.os.Handler import android.os.Looper import android.provider.Settings import android.util.Log +import androidx.core.app.NotificationCompat import com.google.gson.Gson import com.sameerasw.essentials.domain.diy.Automation import com.sameerasw.essentials.domain.diy.DIYRepository import com.sameerasw.essentials.domain.model.AppSelection import com.sameerasw.essentials.services.automation.executors.CombinedActionExecutor -import com.sameerasw.essentials.utils.StatusBarManager import com.sameerasw.essentials.utils.FreezeManager -import com.sameerasw.essentials.domain.model.ShutUpAppConfig +import com.sameerasw.essentials.utils.StatusBarManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import android.app.NotificationManager -import android.app.PendingIntent -import androidx.core.app.NotificationCompat -import android.content.BroadcastReceiver -import android.content.IntentFilter class AppFlowHandler( private val context: Context, @@ -50,6 +49,7 @@ class AppFlowHandler( context?.let { FreezeManager.freezeApp(it, packageName) } cancelNotification(packageName) } + ACTION_ABORT_FREEZE -> { activeCountdowns[packageName]?.cancel() activeCountdowns.remove(packageName) @@ -161,7 +161,7 @@ class AppFlowHandler( 5 -> 30 else -> 0 } - + val lastLeaveTime = lastLeaveTimes[packageName] ?: 0L if (lastLeaveTime > 0) { val now = System.currentTimeMillis() @@ -350,13 +350,15 @@ class AppFlowHandler( if (packageName == "com.android.systemui") return true val intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) - val resolveInfo = context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) + val resolveInfo = + context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) val defaultLauncher = resolveInfo?.activityInfo?.packageName - + if (packageName == defaultLauncher) return true - + // Secondary check for other launchers if not default - val launchers = context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) + val launchers = + context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) return launchers.any { it.activityInfo.packageName == packageName } } @@ -364,19 +366,23 @@ class AppFlowHandler( Log.d("AppFlowHandler", "checkShutUpRestore: old=$oldPackage, new=$newPackage") if (oldPackage == null || oldPackage == newPackage) return - val settingsRepository = com.sameerasw.essentials.data.repository.SettingsRepository(context) + val settingsRepository = + com.sameerasw.essentials.data.repository.SettingsRepository(context) val shutUpConfigs = settingsRepository.loadShutUpConfigs() val wasShutUpConfig = shutUpConfigs.find { it.packageName == oldPackage && it.isEnabled } - + // Check if it was already frozen to avoid duplicate triggers (e.g. on screen off) - val isAlreadyFrozen = oldPackage?.let { FreezeManager.isAppFrozen(context, it) } ?: false - + val isAlreadyFrozen = oldPackage.let { FreezeManager.isAppFrozen(context, it) } + // We consider the new app a Shut-Up app if it's in the list OR if it's the shortcut activity - val isNewAppShutUp = shutUpConfigs.any { it.packageName == newPackage && it.isEnabled } || - newPackage == "com.sameerasw.essentials.ShutUpShortcutActivity" + val isNewAppShutUp = shutUpConfigs.any { it.packageName == newPackage && it.isEnabled } || + newPackage == "com.sameerasw.essentials.ShutUpShortcutActivity" - Log.d("AppFlowHandler", "checkShutUpRestore: wasShutUpConfig=${wasShutUpConfig != null}, isNewAppShutUp=$isNewAppShutUp, isAlreadyFrozen=$isAlreadyFrozen") + Log.d( + "AppFlowHandler", + "checkShutUpRestore: wasShutUpConfig=${wasShutUpConfig != null}, isNewAppShutUp=$isNewAppShutUp, isAlreadyFrozen=$isAlreadyFrozen" + ) // If it's already frozen, we've already handled it if (isAlreadyFrozen) return @@ -384,7 +390,10 @@ class AppFlowHandler( // If we are entering a Shut-Up app, cancel ANY pending countdowns for other apps if (isNewAppShutUp) { if (activeCountdowns.isNotEmpty()) { - Log.d("AppFlowHandler", "checkShutUpRestore: Entering Shut-Up app, cancelling all pending countdowns") + Log.d( + "AppFlowHandler", + "checkShutUpRestore: Entering Shut-Up app, cancelling all pending countdowns" + ) activeCountdowns.values.forEach { it.cancel() } activeCountdowns.keys.forEach { cancelNotification(it) } activeCountdowns.clear() @@ -393,7 +402,10 @@ class AppFlowHandler( if (wasShutUpConfig != null && !isNewAppShutUp) { Log.d("AppFlowHandler", "checkShutUpRestore: Triggering restoration for $oldPackage") - restoreShutUpSettings(settingsRepository, if (wasShutUpConfig.autoArchive) wasShutUpConfig.packageName else null) + restoreShutUpSettings( + settingsRepository, + if (wasShutUpConfig.autoArchive) wasShutUpConfig.packageName else null + ) } } @@ -431,16 +443,17 @@ class AppFlowHandler( private fun showCountdownNotification(packageName: String, appName: String, secondsLeft: Int) { createNotificationChannel() - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val freezeIntent = Intent(ACTION_FREEZE_NOW).apply { `package` = context.packageName putExtra(EXTRA_PACKAGE_NAME, packageName) } val freezePendingIntent = PendingIntent.getBroadcast( - context, - packageName.hashCode() + 1, - freezeIntent, + context, + packageName.hashCode() + 1, + freezeIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) @@ -449,105 +462,126 @@ class AppFlowHandler( putExtra(EXTRA_PACKAGE_NAME, packageName) } val abortPendingIntent = PendingIntent.getBroadcast( - context, - packageName.hashCode() + 2, - abortIntent, + context, + packageName.hashCode() + 2, + abortIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - val title = context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_notif_title) - val text = context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_notif_text, appName, secondsLeft) + val title = + context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_notif_title) + val text = context.getString( + com.sameerasw.essentials.R.string.shut_up_auto_archive_notif_text, + appName, + secondsLeft + ) val criticalText = secondsLeft.toString() - val notification = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - val builder = android.app.Notification.Builder(context, "shutup_alerts_channel") - .setSmallIcon(com.sameerasw.essentials.R.drawable.rounded_snowflake_24) - .setContentTitle(title) - .setContentText(text) - .setOngoing(true) - .setOnlyAlertOnce(true) - .setCategory(android.app.Notification.CATEGORY_SERVICE) - .setShowWhen(false) - .setGroup("shutup_auto_archive") - .setColorized(false) - - if (android.os.Build.VERSION.SDK_INT >= 31) { - builder.setForegroundServiceBehavior(android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE) - } - - builder.addAction( - android.app.Notification.Action.Builder( - android.graphics.drawable.Icon.createWithResource(context, com.sameerasw.essentials.R.drawable.rounded_snowflake_24), - context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_action_freeze), - freezePendingIntent - ).build() - ) - builder.addAction( - android.app.Notification.Action.Builder( - android.graphics.drawable.Icon.createWithResource(context, com.sameerasw.essentials.R.drawable.rounded_close_24), - context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_action_abort), - abortPendingIntent - ).build() - ) - - // Live Update Status Chip - try { - val setRequestPromotedOngoing = builder.javaClass.getMethod("setRequestPromotedOngoing", Boolean::class.javaPrimitiveType) - setRequestPromotedOngoing.invoke(builder, true) - - val setShortCriticalText = builder.javaClass.getMethod("setShortCriticalText", CharSequence::class.java) - setShortCriticalText.invoke(builder, criticalText) - } catch (_: Throwable) { } - - val extras = android.os.Bundle() - extras.putBoolean("android.requestPromotedOngoing", true) - extras.putString("android.shortCriticalText", criticalText) - builder.addExtras(extras) - - builder.setProgress(10, secondsLeft, false) + val notification = + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + val builder = android.app.Notification.Builder(context, "shutup_alerts_channel") + .setSmallIcon(com.sameerasw.essentials.R.drawable.rounded_snowflake_24) + .setContentTitle(title) + .setContentText(text) + .setOngoing(true) + .setOnlyAlertOnce(true) + .setCategory(android.app.Notification.CATEGORY_SERVICE) + .setShowWhen(false) + .setGroup("shutup_auto_archive") + .setColorized(false) + + if (android.os.Build.VERSION.SDK_INT >= 31) { + builder.setForegroundServiceBehavior(android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE) + } - builder.build() - } else { - NotificationCompat.Builder(context, "shutup_alerts_channel") - .setSmallIcon(com.sameerasw.essentials.R.drawable.rounded_snowflake_24) - .setContentTitle(title) - .setContentText(text) - .setPriority(NotificationCompat.PRIORITY_MAX) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setOnlyAlertOnce(true) - .setOngoing(true) - .setProgress(10, secondsLeft, false) - .addAction( - com.sameerasw.essentials.R.drawable.rounded_snowflake_24, - context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_action_freeze), - freezePendingIntent + builder.addAction( + android.app.Notification.Action.Builder( + android.graphics.drawable.Icon.createWithResource( + context, + com.sameerasw.essentials.R.drawable.rounded_snowflake_24 + ), + context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_action_freeze), + freezePendingIntent + ).build() ) - .addAction( - com.sameerasw.essentials.R.drawable.rounded_close_24, - context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_action_abort), - abortPendingIntent + builder.addAction( + android.app.Notification.Action.Builder( + android.graphics.drawable.Icon.createWithResource( + context, + com.sameerasw.essentials.R.drawable.rounded_close_24 + ), + context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_action_abort), + abortPendingIntent + ).build() ) - .addExtras(android.os.Bundle().apply { - putBoolean("android.requestPromotedOngoing", true) - putString("android.shortCriticalText", criticalText) - }) - .build() - } + + // Live Update Status Chip + try { + val setRequestPromotedOngoing = builder.javaClass.getMethod( + "setRequestPromotedOngoing", + Boolean::class.javaPrimitiveType + ) + setRequestPromotedOngoing.invoke(builder, true) + + val setShortCriticalText = builder.javaClass.getMethod( + "setShortCriticalText", + CharSequence::class.java + ) + setShortCriticalText.invoke(builder, criticalText) + } catch (_: Throwable) { + } + + val extras = android.os.Bundle() + extras.putBoolean("android.requestPromotedOngoing", true) + extras.putString("android.shortCriticalText", criticalText) + builder.addExtras(extras) + + builder.setProgress(10, secondsLeft, false) + + builder.build() + } else { + NotificationCompat.Builder(context, "shutup_alerts_channel") + .setSmallIcon(com.sameerasw.essentials.R.drawable.rounded_snowflake_24) + .setContentTitle(title) + .setContentText(text) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setOnlyAlertOnce(true) + .setOngoing(true) + .setProgress(10, secondsLeft, false) + .addAction( + com.sameerasw.essentials.R.drawable.rounded_snowflake_24, + context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_action_freeze), + freezePendingIntent + ) + .addAction( + com.sameerasw.essentials.R.drawable.rounded_close_24, + context.getString(com.sameerasw.essentials.R.string.shut_up_auto_archive_action_abort), + abortPendingIntent + ) + .addExtras(android.os.Bundle().apply { + putBoolean("android.requestPromotedOngoing", true) + putString("android.shortCriticalText", criticalText) + }) + .build() + } Log.d("AppFlowHandler", "Showing notification for $packageName, secondsLeft=$secondsLeft") notificationManager.notify(packageName.hashCode(), notification) } private fun cancelNotification(packageName: String) { - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(packageName.hashCode()) } private fun createNotificationChannel() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channel = android.app.NotificationChannel( "app_detection_service_channel", context.getString(com.sameerasw.essentials.R.string.app_detection_service_channel_name), @@ -571,7 +605,10 @@ class AppFlowHandler( } } - private fun restoreShutUpSettings(repository: com.sameerasw.essentials.data.repository.SettingsRepository, autoArchivePackage: String? = null) { + private fun restoreShutUpSettings( + repository: com.sameerasw.essentials.data.repository.SettingsRepository, + autoArchivePackage: String? = null + ) { val originalSettings = repository.getShutUpOriginalSettings() if (originalSettings.isEmpty()) { if (autoArchivePackage != null) { @@ -582,16 +619,17 @@ class AppFlowHandler( scope.launch { // Delay to ensure the app has fully settled before restoring system settings - kotlinx.coroutines.delay(2000) - - val canWriteSecure = com.sameerasw.essentials.utils.PermissionUtils.canWriteSecureSettings(context) + delay(2000) + + val canWriteSecure = + com.sameerasw.essentials.utils.PermissionUtils.canWriteSecureSettings(context) val canWriteSystem = Settings.System.canWrite(context) originalSettings.forEach { (prefixedKey, value) -> try { val parts = prefixedKey.split(":", limit = 2) if (parts.size < 2) return@forEach - + val table = parts[0] val key = parts[1] @@ -601,11 +639,13 @@ class AppFlowHandler( Settings.Global.putString(context.contentResolver, key, value) } } + "secure" -> { if (canWriteSecure) { Settings.Secure.putString(context.contentResolver, key, value) } } + "system" -> { if (canWriteSystem) { Settings.System.putString(context.contentResolver, key, value) @@ -621,11 +661,15 @@ class AppFlowHandler( repository.saveShutUpOriginalSettings(emptyMap()) // Wait a bit and Restart Shizuku as ADB might have been toggled back on - kotlinx.coroutines.delay(1000) + delay(1000) restartShizuku() - android.widget.Toast.makeText(context, context.getString(com.sameerasw.essentials.R.string.shut_up_toast_restored), android.widget.Toast.LENGTH_SHORT).show() - + android.widget.Toast.makeText( + context, + context.getString(com.sameerasw.essentials.R.string.shut_up_toast_restored), + android.widget.Toast.LENGTH_SHORT + ).show() + // Start auto-archive countdown AFTER everything is restored and Shizuku is starting if (autoArchivePackage != null) { startAutoArchiveCountdown(autoArchivePackage) @@ -645,6 +689,7 @@ class AppFlowHandler( Log.e("AppFlowHandler", "Failed to restart Shizuku", e) } } + companion object { const val ACTION_FREEZE_NOW = "com.sameerasw.essentials.ACTION_FREEZE_NOW" const val ACTION_ABORT_FREEZE = "com.sameerasw.essentials.ACTION_ABORT_FREEZE" diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/ButtonRemapHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/ButtonRemapHandler.kt index 93cf2a3a2..384b12731 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/ButtonRemapHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/ButtonRemapHandler.kt @@ -205,6 +205,7 @@ class ButtonRemapHandler( ) triggerHapticFeedback() } + "Circle to Search" -> { com.sameerasw.essentials.utils.OmniTriggerUtil.trigger(service) triggerHapticFeedback() @@ -254,14 +255,15 @@ class ButtonRemapHandler( } private fun toggleRingerMode(targetMode: Int) { - val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager + val notificationManager = + service.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager if (!notificationManager.isNotificationPolicyAccessGranted) { return } val am = service.getSystemService(Context.AUDIO_SERVICE) as AudioManager val currentMode = am.ringerMode - + try { if (currentMode == targetMode) { am.ringerMode = AudioManager.RINGER_MODE_NORMAL diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/FlashlightHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/FlashlightHandler.kt index b28156dd2..3647b6c6b 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/FlashlightHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/FlashlightHandler.kt @@ -136,7 +136,8 @@ class FlashlightHandler( } FlashlightActionReceiver.ACTION_PULSE_NOTIFICATION -> { - val isPreview = intent.getBooleanExtra(FlashlightActionReceiver.EXTRA_IS_PREVIEW, false) + val isPreview = + intent.getBooleanExtra(FlashlightActionReceiver.EXTRA_IS_PREVIEW, false) pulseFlashlightForNotificationWithCheck(ignoreChecks = isPreview) } } @@ -348,7 +349,7 @@ class FlashlightHandler( fun pulseFlashlightForNotificationWithCheck(ignoreChecks: Boolean = false) { if (isTorchOn) return - + val prefs = service.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) if (!ignoreChecks) { val pulseEnabled = prefs.getBoolean("flashlight_pulse_enabled", false) @@ -548,7 +549,7 @@ class FlashlightHandler( } catch (e: Exception) { // SILENT: Handle silently as per user request } - + if (success) { currentIntensityLevel = overrideIntensity ?: if (prefs.getBoolean( "flashlight_global_enabled", @@ -592,7 +593,8 @@ class FlashlightHandler( ) val type = try { - val resolved = HapticFeedbackType.valueOf(hapticName ?: HapticFeedbackType.DOUBLE.name) + val resolved = + HapticFeedbackType.valueOf(hapticName ?: HapticFeedbackType.DOUBLE.name) if (resolved.name == "LONG") HapticFeedbackType.DOUBLE else resolved } catch (e: Exception) { HapticFeedbackType.DOUBLE diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/NotificationLightingHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/NotificationLightingHandler.kt index d850bdf9c..899bf5567 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/NotificationLightingHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/NotificationLightingHandler.kt @@ -156,7 +156,8 @@ class NotificationLightingHandler( if (immediate) { try { windowManager?.removeView(overlay) - } catch (_: Exception) {} + } catch (_: Exception) { + } iterator.remove() } else { try { diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/OmniGestureOverlayHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/OmniGestureOverlayHandler.kt index 9be2145db..d2a2311f8 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/OmniGestureOverlayHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/OmniGestureOverlayHandler.kt @@ -4,18 +4,27 @@ import android.accessibilityservice.AccessibilityService import android.content.Context import android.graphics.Color import android.graphics.PixelFormat -import android.os.* -import android.view.* +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.os.VibrationEffect +import android.os.Vibrator +import android.os.VibratorManager +import android.view.Gravity +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import android.view.WindowManager import com.sameerasw.essentials.utils.OmniTriggerUtil class OmniGestureOverlayHandler(private val service: AccessibilityService) { private val windowManager = service.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val vibrator = getVibratorInstance() - + private var overlayView: View? = null private val handler = Handler(Looper.getMainLooper()) private val touchSlop = ViewConfiguration.get(service).scaledTouchSlop - + private var startX = 0f private var startY = 0f private var isLongPressActive = false @@ -55,7 +64,8 @@ class OmniGestureOverlayHandler(private val service: AccessibilityService) { ).apply { gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } } @@ -85,11 +95,13 @@ class OmniGestureOverlayHandler(private val service: AccessibilityService) { handler.postDelayed(longPressRunnable, LONG_PRESS_TIMEOUT) startRampingHaptic() } + MotionEvent.ACTION_MOVE -> { if (Math.abs(event.x - startX) > touchSlop || Math.abs(event.y - startY) > touchSlop) { cancelLongPress() } } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> cancelLongPress() } } @@ -126,7 +138,12 @@ class OmniGestureOverlayHandler(private val service: AccessibilityService) { private fun fallbackRampingWaveform(v: Vibrator) { fallbackEffect?.let { runCatching { v.vibrate(it) } - } ?: v.vibrate(VibrationEffect.createOneShot(LONG_PRESS_TIMEOUT, VibrationEffect.DEFAULT_AMPLITUDE)) + } ?: v.vibrate( + VibrationEffect.createOneShot( + LONG_PRESS_TIMEOUT, + VibrationEffect.DEFAULT_AMPLITUDE + ) + ) } private fun triggerFinalTick() { diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/SoundModeHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/SoundModeHandler.kt index 2a2a6db69..e79abe352 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/SoundModeHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/SoundModeHandler.kt @@ -44,7 +44,7 @@ class SoundModeHandler(private val context: Context) { } catch (e: Exception) { // OEM-specific restrictions or race conditions } - + return nextRingerMode } } 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 index 8206ca534..37c76db8a 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/StatusBarIconHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/StatusBarIconHandler.kt @@ -29,7 +29,7 @@ class StatusBarIconHandler(private val context: Context) { private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - + private val networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { updateAll() @@ -39,7 +39,10 @@ class StatusBarIconHandler(private val context: Context) { updateAll() } - override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { updateAll() } } @@ -71,36 +74,40 @@ class StatusBarIconHandler(private val context: Context) { fun unregister() { try { connectivityManager.unregisterNetworkCallback(networkCallback) - } catch (e: Exception) {} - + } catch (e: Exception) { + } + try { context.unregisterReceiver(batteryReceiver) - } catch (e: Exception) {} - + } 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) - + 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) @@ -120,14 +127,15 @@ class StatusBarIconHandler(private val context: Context) { )?.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 + (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 @@ -138,7 +146,7 @@ class StatusBarIconHandler(private val context: Context) { 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 -> @@ -147,7 +155,7 @@ class StatusBarIconHandler(private val context: Context) { 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) } @@ -176,24 +184,28 @@ class StatusBarIconHandler(private val context: Context) { private fun getCurrentNetworkType(): NetworkType { return try { val network = connectivityManager.activeNetwork ?: return NetworkType.NETWORK_OTHER - val capabilities = connectivityManager.getNetworkCapabilities(network) ?: 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 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) { diff --git a/app/src/main/java/com/sameerasw/essentials/services/receivers/AirSyncBridgeReceiver.kt b/app/src/main/java/com/sameerasw/essentials/services/receivers/AirSyncBridgeReceiver.kt index 02fdfdd47..807cde65c 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/receivers/AirSyncBridgeReceiver.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/receivers/AirSyncBridgeReceiver.kt @@ -22,7 +22,7 @@ class AirSyncBridgeReceiver : BroadcastReceiver() { val repository = SettingsRepository(context) val isEnabled = repository.getBoolean(SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED) - + android.util.Log.d( "AirSyncBridge", "Received Mac status broadcast. Bridge enabled: $isEnabled, level=$level, charging=$isCharging" @@ -35,7 +35,8 @@ class AirSyncBridgeReceiver : BroadcastReceiver() { repository.putBoolean(SettingsRepository.KEY_AIRSYNC_MAC_CONNECTED, isConnected) // Trigger widget update directly - val appWidgetManager = context.getSystemService(Context.APPWIDGET_SERVICE) as? android.appwidget.AppWidgetManager + val appWidgetManager = + context.getSystemService(Context.APPWIDGET_SERVICE) as? android.appwidget.AppWidgetManager if (appWidgetManager == null) { pendingResult.finish() return @@ -49,12 +50,14 @@ class AirSyncBridgeReceiver : BroadcastReceiver() { // Define keys matching BatteriesWidget val KEY_AIRSYNC_ENABLED = androidx.datastore.preferences.core.booleanPreferencesKey( - SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED) + SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED + ) val KEY_MAC_LEVEL = androidx.datastore.preferences.core.intPreferencesKey(SettingsRepository.KEY_MAC_BATTERY_LEVEL) val KEY_MAC_CONNECTED = androidx.datastore.preferences.core.booleanPreferencesKey( - SettingsRepository.KEY_AIRSYNC_MAC_CONNECTED) + SettingsRepository.KEY_AIRSYNC_MAC_CONNECTED + ) val glanceIds = glanceAppWidgetManager.getGlanceIds(com.sameerasw.essentials.services.widgets.BatteriesWidget::class.java) @@ -75,7 +78,8 @@ class AirSyncBridgeReceiver : BroadcastReceiver() { // Add charging state val KEY_MAC_IS_CHARGING = androidx.datastore.preferences.core.booleanPreferencesKey( - SettingsRepository.KEY_MAC_BATTERY_IS_CHARGING) + SettingsRepository.KEY_MAC_BATTERY_IS_CHARGING + ) prefs[KEY_MAC_IS_CHARGING] = isCharging } diff --git a/app/src/main/java/com/sameerasw/essentials/services/receivers/SecurityDeviceAdminReceiver.kt b/app/src/main/java/com/sameerasw/essentials/services/receivers/SecurityDeviceAdminReceiver.kt index 8e9bba839..dc47055b0 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/receivers/SecurityDeviceAdminReceiver.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/receivers/SecurityDeviceAdminReceiver.kt @@ -1,9 +1,5 @@ package com.sameerasw.essentials.services.receivers import android.app.admin.DeviceAdminReceiver -import android.content.Context -import android.content.Intent -class SecurityDeviceAdminReceiver : DeviceAdminReceiver() { - -} +class SecurityDeviceAdminReceiver : DeviceAdminReceiver() diff --git a/app/src/main/java/com/sameerasw/essentials/services/receivers/SecurityReceiver.kt b/app/src/main/java/com/sameerasw/essentials/services/receivers/SecurityReceiver.kt index 683af6a92..e8b053ff2 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/receivers/SecurityReceiver.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/receivers/SecurityReceiver.kt @@ -1,6 +1,5 @@ package com.sameerasw.essentials.services.receivers -import android.app.KeyguardManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -11,9 +10,16 @@ import com.sameerasw.essentials.utils.ShellUtils class SecurityReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val settingsRepository = SettingsRepository(context) - val isDisableQsEnabled = settingsRepository.getBoolean(SettingsRepository.KEY_SCREEN_LOCKED_SECURITY_ENABLED, false) - val isHideSystemIconsEnabled = settingsRepository.getBoolean(SettingsRepository.KEY_HIDE_SYSTEM_ICONS, false) - val isHideSystemIconsLockedOnlyEnabled = settingsRepository.getBoolean(SettingsRepository.KEY_HIDE_SYSTEM_ICONS_LOCKED_ONLY, false) + val isDisableQsEnabled = settingsRepository.getBoolean( + SettingsRepository.KEY_SCREEN_LOCKED_SECURITY_ENABLED, + false + ) + val isHideSystemIconsEnabled = + settingsRepository.getBoolean(SettingsRepository.KEY_HIDE_SYSTEM_ICONS, false) + val isHideSystemIconsLockedOnlyEnabled = settingsRepository.getBoolean( + SettingsRepository.KEY_HIDE_SYSTEM_ICONS_LOCKED_ONLY, + false + ) when (intent.action) { Intent.ACTION_SCREEN_OFF -> { @@ -43,10 +49,17 @@ class SecurityReceiver : BroadcastReceiver() { ) } } + Intent.ACTION_USER_PRESENT -> { // Restore QS and System Icons on unlock - com.sameerasw.essentials.utils.StatusBarManager.requestRestore(context, "DisableQsWhenLocked") - com.sameerasw.essentials.utils.StatusBarManager.requestRestore(context, "StatusBarIconAdvancedLocked") + com.sameerasw.essentials.utils.StatusBarManager.requestRestore( + context, + "DisableQsWhenLocked" + ) + com.sameerasw.essentials.utils.StatusBarManager.requestRestore( + context, + "StatusBarIconAdvancedLocked" + ) } } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt index 47d00e186..89686b17e 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt @@ -1,10 +1,7 @@ package com.sameerasw.essentials.services.tiles -import android.Manifest -import android.content.pm.PackageManager import android.graphics.drawable.Icon import android.os.Build -import android.provider.Settings import android.service.quicksettings.Tile import androidx.annotation.RequiresApi import com.sameerasw.essentials.R @@ -48,11 +45,13 @@ class AlwaysOnDisplayTileService : BaseTileService() { setGlanceEnabled(false) setAodEnabled(true) } + isAodEnabled() -> { // On -> Off setAodEnabled(false) setGlanceEnabled(false) } + else -> { // Off -> Dynamic setGlanceEnabled(true) @@ -64,7 +63,7 @@ class AlwaysOnDisplayTileService : BaseTileService() { private fun isAodEnabled(): Boolean { return getSecureInt("doze_always_on", 0) == 1 } - + private fun setAodEnabled(enabled: Boolean) { putSecureInt("doze_always_on", if (enabled) 1 else 0) } diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/BaseTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/BaseTileService.kt index b856c3ae3..dbbd89a53 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/BaseTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/BaseTileService.kt @@ -10,14 +10,11 @@ import androidx.core.content.edit import com.sameerasw.essentials.R import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.utils.ShellUtils -import com.sameerasw.essentials.utils.PermissionUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import android.os.Handler -import android.os.Looper @RequiresApi(Build.VERSION_CODES.N) abstract class BaseTileService : TileService() { @@ -70,10 +67,10 @@ abstract class BaseTileService : TileService() { override fun onClick() { super.onClick() - + // Immediate feedback 1: Haptics HapticUtil.performHapticForService(this) - + if (!hasFeaturePermission() || isProcessing) { return } diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/BatteryNotificationTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/BatteryNotificationTileService.kt index 446ce8951..561a82437 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/BatteryNotificationTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/BatteryNotificationTileService.kt @@ -17,7 +17,7 @@ class BatteryNotificationTileService : BaseTileService() { override fun onTileClick() { val newState = !settingsRepository.isBatteryNotificationEnabled() settingsRepository.setBatteryNotificationEnabled(newState) - + val intent = Intent(this, BatteryNotificationService::class.java) if (newState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -32,7 +32,8 @@ class BatteryNotificationTileService : BaseTileService() { override fun getTileLabel(): String = "Battery Info" - override fun getTileSubtitle(): String = if (settingsRepository.isBatteryNotificationEnabled()) "On" else "Off" + override fun getTileSubtitle(): String = + if (settingsRepository.isBatteryNotificationEnabled()) "On" else "Off" override fun hasFeaturePermission(): Boolean = true diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt index 3a9bd9a49..5c2763e15 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt @@ -3,12 +3,10 @@ package com.sameerasw.essentials.services.tiles import android.content.Intent import android.graphics.drawable.Icon import android.os.Build -import android.provider.Settings import android.service.quicksettings.Tile import androidx.annotation.RequiresApi import com.sameerasw.essentials.FeatureSettingsActivity import com.sameerasw.essentials.R -import com.sameerasw.essentials.utils.PermissionUtils import com.sameerasw.essentials.utils.ShellUtils @RequiresApi(Build.VERSION_CODES.N) @@ -46,7 +44,7 @@ class ChargeQuickTileService : BaseTileService() { override fun onTileClick() { val adaptiveChargingEnabled = getSecureInt(ADAPTIVE_CHARGING_SETTING, 0) == 1 val chargeOptimizationEnabled = getSecureInt(CHARGE_OPTIMIZATION_MODE, 0) == 1 - + when { adaptiveChargingEnabled -> { putSecureInt(CHARGE_OPTIMIZATION_MODE, 1) diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ColorPickerTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ColorPickerTileService.kt index bdb675c75..922821dd4 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ColorPickerTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ColorPickerTileService.kt @@ -1,8 +1,6 @@ package com.sameerasw.essentials.services.tiles import android.app.PendingIntent -import android.content.ActivityNotFoundException -import android.content.ComponentName import android.content.Intent import android.graphics.drawable.Icon import android.os.Build @@ -29,7 +27,10 @@ class ColorPickerTileService : BaseTileService() { override fun getTileState(): Int = Tile.STATE_INACTIVE override fun onTileClick() { - val intent = Intent(this, com.sameerasw.essentials.ui.activities.ColorPickerActivity::class.java).apply { + val intent = Intent( + this, + com.sameerasw.essentials.ui.activities.ColorPickerActivity::class.java + ).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP } diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/PrivateDnsTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/PrivateDnsTileService.kt index 936f108b4..5db406ff4 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/PrivateDnsTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/PrivateDnsTileService.kt @@ -43,13 +43,16 @@ class PrivateDnsTileService : BaseTileService() { MODE_HOSTNAME -> { val hostname = getPrivateDnsHostname() if (!hostname.isNullOrEmpty()) { - val settingsRepository = com.sameerasw.essentials.data.repository.SettingsRepository(this) - val preset = settingsRepository.getPrivateDnsPresets().find { it.hostname == hostname } + val settingsRepository = + com.sameerasw.essentials.data.repository.SettingsRepository(this) + val preset = + settingsRepository.getPrivateDnsPresets().find { it.hostname == hostname } preset?.name ?: hostname } else { getString(R.string.feat_qs_tiles_title) } } + else -> getString(R.string.tile_private_dns_off) } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScaleAnimationsTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScaleAnimationsTileService.kt index ab211944d..a92c16049 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScaleAnimationsTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScaleAnimationsTileService.kt @@ -18,12 +18,13 @@ class ScaleAnimationsTileService : BaseTileService() { } override fun hasFeaturePermission(): Boolean { - return true + return true } override fun getTileIcon(): Icon { val mode = settingsRepository.getScaleAnimationsMode() - val iconRes = if (mode == "glove") R.drawable.round_front_hand_24 else R.drawable.rounded_front_hand_24 + val iconRes = + if (mode == "glove") R.drawable.round_front_hand_24 else R.drawable.rounded_front_hand_24 return Icon.createWithResource(this, iconRes) } @@ -56,15 +57,24 @@ class ScaleAnimationsTileService : BaseTileService() { settingsRepository.setScaleAnimationsMode(newMode) applyProfile(newProfile) - + } private fun applyProfile(profile: ScaleAnimationsProfile) { settingsRepository.setFontScale(profile.fontScale) settingsRepository.setFontWeight(profile.fontWeight) - settingsRepository.setAnimationScale(android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, profile.animatorDurationScale) - settingsRepository.setAnimationScale(android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, profile.transitionAnimationScale) - settingsRepository.setAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE, profile.windowAnimationScale) + settingsRepository.setAnimationScale( + android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, + profile.animatorDurationScale + ) + settingsRepository.setAnimationScale( + android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, + profile.transitionAnimationScale + ) + settingsRepository.setAnimationScale( + android.provider.Settings.Global.WINDOW_ANIMATION_SCALE, + profile.windowAnimationScale + ) settingsRepository.setSmallestWidth(profile.smallestWidth) settingsRepository.setTouchSensitivityEnabled(profile.touchSensitivityEnabled) settingsRepository.setAutoRotateEnabled(profile.autoRotateEnabled) diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenLockedSecurityTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenLockedSecurityTileService.kt index b05476778..38a4ca902 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenLockedSecurityTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenLockedSecurityTileService.kt @@ -7,7 +7,6 @@ import android.os.Build import android.service.quicksettings.Tile import com.sameerasw.essentials.R import com.sameerasw.essentials.ui.activities.TileAuthActivity -import com.sameerasw.essentials.utils.PermissionUtils class ScreenLockedSecurityTileService : BaseTileService() { 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 b5ff6b367..1b6a10bfc 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 @@ -18,7 +18,14 @@ import android.view.accessibility.AccessibilityEvent import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.domain.HapticFeedbackType import com.sameerasw.essentials.services.InputEventListenerService -import com.sameerasw.essentials.services.handlers.* +import com.sameerasw.essentials.services.handlers.AmbientGlanceHandler +import com.sameerasw.essentials.services.handlers.AodForceTurnOffHandler +import com.sameerasw.essentials.services.handlers.AppFlowHandler +import com.sameerasw.essentials.services.handlers.ButtonRemapHandler +import com.sameerasw.essentials.services.handlers.FlashlightHandler +import com.sameerasw.essentials.services.handlers.NotificationLightingHandler +import com.sameerasw.essentials.services.handlers.OmniGestureOverlayHandler +import com.sameerasw.essentials.services.handlers.StatusBarIconHandler import com.sameerasw.essentials.services.receivers.FlashlightActionReceiver import com.sameerasw.essentials.utils.FreezeManager import com.sameerasw.essentials.utils.performHapticFeedback @@ -55,11 +62,15 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene private val preferenceChangeListener = android.content.SharedPreferences.OnSharedPreferenceChangeListener { _, key -> - if (key == "circle_to_search_gesture_enabled" || - key == "circle_to_search_gesture_height" || - key == "circle_to_search_preview_enabled") { + if (key == "circle_to_search_gesture_enabled" || + 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) { + } else if (key == "smart_wifi_enabled" || key == "smart_data_enabled" || key == "battery_percent_mode" || key?.startsWith( + "icon_" + ) == true + ) { statusBarIconHandler.updateAll() } } @@ -183,7 +194,11 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene private fun updateOmniOverlay() { val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) val isGestureEnabled = prefs.getBoolean("circle_to_search_gesture_enabled", false) - val height = try { prefs.getFloat("circle_to_search_gesture_height", 48f) } catch (e: Exception) { 48f } + val height = try { + prefs.getFloat("circle_to_search_gesture_height", 48f) + } catch (e: Exception) { + 48f + } val isPreview = prefs.getBoolean("circle_to_search_preview_enabled", false) omniGestureOverlayHandler.updateOverlay(isGestureEnabled, height, isPreview) } @@ -237,11 +252,13 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene override fun onKeyEvent(event: KeyEvent): Boolean { val keyCode = event.keyCode - val isVolumeKey = keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + val isVolumeKey = + keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN if (isVolumeKey) { // Bypass logic for Camera apps to resolve conflicts with shutter/zoom functions - val foregroundPackage = rootInActiveWindow?.packageName?.toString() ?: appFlowHandler.currentPackage + val foregroundPackage = + rootInActiveWindow?.packageName?.toString() ?: appFlowHandler.currentPackage if (appFlowHandler.isCameraApp(foregroundPackage)) { return false } @@ -303,7 +320,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene val vibrator = getSystemService(VIBRATOR_SERVICE) as? Vibrator vibrator?.let { performHapticFeedback(it, hapticType) } } - performGlobalAction(AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN) + performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN) } "SHOW_NOTIFICATION_LIGHTING" -> notificationLightingHandler.handleIntent(intent) diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/SoundModeTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/SoundModeTileService.kt index 05b1d74f4..8da534da2 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/SoundModeTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/SoundModeTileService.kt @@ -55,7 +55,8 @@ class SoundModeTileService : BaseTileService() { } } - override fun getTileSubtitle(): String = if (hasFeaturePermission()) "Mode" else getString(R.string.permission_missing) + override fun getTileSubtitle(): String = + if (hasFeaturePermission()) "Mode" else getString(R.string.permission_missing) override fun hasFeaturePermission(): Boolean { val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager diff --git a/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidget.kt b/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidget.kt index 5b3f8ebdc..256b30bb9 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidget.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidget.kt @@ -2,19 +2,18 @@ package com.sameerasw.essentials.services.widgets import android.content.Context import android.os.BatteryManager -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat -import androidx.core.graphics.ColorUtils -import androidx.core.graphics.toColorInt +import androidx.compose.ui.unit.times import androidx.glance.GlanceId import androidx.glance.GlanceModifier import androidx.glance.GlanceTheme import androidx.glance.Image import androidx.glance.ImageProvider import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.cornerRadius import androidx.glance.appwidget.provideContent import androidx.glance.background +import androidx.glance.color.ColorProvider import androidx.glance.layout.Alignment import androidx.glance.layout.Box import androidx.glance.layout.Column @@ -25,6 +24,7 @@ import androidx.glance.layout.fillMaxSize import androidx.glance.layout.fillMaxWidth import androidx.glance.layout.height import androidx.glance.layout.padding +import androidx.glance.layout.size import androidx.glance.layout.width import com.sameerasw.essentials.R @@ -79,11 +79,6 @@ class BatteriesWidget : GlanceAppWidget() { androidx.datastore.preferences.core.booleanPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_BATTERY_WIDGET_BACKGROUND_ENABLED) val isBackgroundEnabled = prefs[KEY_BACKGROUND_ENABLED] ?: true - // Force recomposition when theme changes - val THEME_UPDATE_KEY = - androidx.datastore.preferences.core.longPreferencesKey("theme_update_time") - prefs[THEME_UPDATE_KEY] - val showMac = isAirSyncEnabled && macLevel != -1 && isMacConnected val hasBluetooth = isShowBluetoothEnabled && !bluetoothJson.isNullOrEmpty() && bluetoothJson != "[]" @@ -95,24 +90,28 @@ class BatteriesWidget : GlanceAppWidget() { val isAndroidCharging = (batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS) == BatteryManager.BATTERY_STATUS_CHARGING) - val androidFinalStatusIcon = if (isAndroidCharging) R.drawable.rounded_flash_on_24 - else if (androidLevel <= 15) R.drawable.rounded_battery_android_frame_alert_24 - else null + val androidStatusIcon = when { + isAndroidCharging -> R.drawable.rounded_flash_on_24 + androidLevel <= 15 -> R.drawable.rounded_battery_android_frame_alert_24 + else -> null + } batteryItems.add( BatteryItemData( level = androidLevel, iconRes = R.drawable.rounded_mobile_24, name = "Android", - statusIconRes = androidFinalStatusIcon + statusIconRes = androidStatusIcon ) ) // Mac Item if (showMac) { - val macStatusIcon = if (macIsCharging) R.drawable.rounded_flash_on_24 - else if (macLevel <= 15) R.drawable.rounded_battery_android_frame_alert_24 - else null + val macStatusIcon = when { + macIsCharging -> R.drawable.rounded_flash_on_24 + macLevel <= 15 -> R.drawable.rounded_battery_android_frame_alert_24 + else -> null + } batteryItems.add( BatteryItemData( level = macLevel, @@ -126,8 +125,12 @@ class BatteriesWidget : GlanceAppWidget() { // Bluetooth Items if (hasBluetooth) { try { + val gson = com.google.gson.Gson() val devices: List = - com.google.gson.Gson().fromJson(bluetoothJson, Array::class.java).toList() + gson.fromJson( + bluetoothJson, + Array::class.java + ).toList() devices.forEach { device -> val iconRes = when { @@ -143,7 +146,6 @@ class BatteriesWidget : GlanceAppWidget() { else -> R.drawable.rounded_bluetooth_24 } - // Bluetooth doesn't report charging usually, so just Low Battery check val statusIcon = if (device.level <= 15) R.drawable.rounded_battery_android_frame_alert_24 else null @@ -156,37 +158,17 @@ class BatteriesWidget : GlanceAppWidget() { ) ) } - } catch (e: Exception) { - // ignore parsing error + } catch (_: Exception) { } } val displayedItems = batteryItems.take(maxDevices) - // 3. Render - val context = androidx.glance.LocalContext.current - val systemConfig = android.content.res.Resources.getSystem().configuration - - val forcedConfig = - android.content.res.Configuration(context.resources.configuration) - forcedConfig.uiMode = systemConfig.uiMode - - val configContext = context.createConfigurationContext(forcedConfig) - - val basePrimary = GlanceTheme.colors.primary.getColor(configContext).toArgb() - val baseError = GlanceTheme.colors.error.getColor(configContext).toArgb() - val onSurface = GlanceTheme.colors.onSurface.getColor(configContext).toArgb() - val widgetBackgroundColor = - GlanceTheme.colors.widgetBackground.getColor(configContext).toArgb() - - val colors = ThemeColors( - primary = basePrimary, - error = baseError, - warning = "#FFC107".toColorInt(), - track = ColorUtils.setAlphaComponent(onSurface, 30), - surface = widgetBackgroundColor, - iconTint = onSurface - ) + androidx.glance.LocalContext.current + val isSingleItem = displayedItems.size <= 1 + val effectivePadding = if (width < 100.dp || height < 100.dp) 4.dp else 8.dp + val outerPadding = if (isSingleItem && width > 120.dp) 16.dp else effectivePadding + val spacing = 8.dp val backgroundModifier = if (isBackgroundEnabled) { GlanceModifier.background(GlanceTheme.colors.widgetBackground) @@ -194,28 +176,28 @@ class BatteriesWidget : GlanceAppWidget() { GlanceModifier.background(android.graphics.Color.TRANSPARENT) } - val effectivePadding = if (width < 100.dp || height < 100.dp) 4.dp else 8.dp - val isSingleItem = displayedItems.size <= 1 - val items = displayedItems.ifEmpty { - listOf( - BatteryItemData( - androidLevel, - R.drawable.rounded_mobile_24, - "Android" - ) - ) - } - // Dynamic Grid Calculation val itemMinWidth = if (isSingleItem) 120.dp else 72.dp - val columns = (width / itemMinWidth).toInt().coerceIn(1, items.size) - val rows = items.chunked(columns) + val columns = + (width / itemMinWidth).toInt().coerceIn(1, displayedItems.size.coerceAtLeast(1)) + val rows = displayedItems.chunked(columns) + + val availableWidth = + (width - (outerPadding * 2) - (spacing * (columns - 1))).coerceAtLeast(1.dp) + val availableHeight = + (height - (outerPadding * 2) - (rows.size.let { if (it > 1) (it - 1) * spacing.value.dp else 0.dp })).coerceAtLeast( + 1.dp + ) + + val itemWidth = availableWidth / columns + val rowHeight = availableHeight / rows.size.coerceAtLeast(1) + val boxSize = if (itemWidth < rowHeight) itemWidth else rowHeight Column( modifier = GlanceModifier .fillMaxSize() .then(backgroundModifier) - .padding(if (isSingleItem && width > 120.dp) 16.dp else effectivePadding), + .padding(outerPadding), horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically ) { @@ -227,15 +209,19 @@ class BatteriesWidget : GlanceAppWidget() { ) { rowItems.forEachIndexed { colIndex, item -> val itemResolution = if (isSingleItem) 512 else 340 - BatteryItemBox( - configContext, - item, - colors, - size = itemResolution, - modifier = GlanceModifier.defaultWeight().fillMaxHeight() - ) + Box( + modifier = GlanceModifier.defaultWeight().fillMaxHeight(), + contentAlignment = Alignment.Center + ) { + BatteryItemBox( + item = item, + itemSize = boxSize, + sizePx = itemResolution, + modifier = GlanceModifier.size(boxSize) + ) + } if (colIndex < rowItems.size - 1 || rowItems.size < columns) { - Spacer(modifier = GlanceModifier.width(8.dp)) + Spacer(modifier = GlanceModifier.width(spacing)) } } // Filler for consistent sizing @@ -245,13 +231,13 @@ class BatteriesWidget : GlanceAppWidget() { modifier = GlanceModifier.defaultWeight().fillMaxHeight() ) if (i < (columns - rowItems.size - 1)) { - Spacer(modifier = GlanceModifier.width(8.dp)) + Spacer(modifier = GlanceModifier.width(spacing)) } } } } if (rowIndex < rows.size - 1) { - Spacer(modifier = GlanceModifier.height(8.dp)) + Spacer(modifier = GlanceModifier.height(spacing)) } } } @@ -266,51 +252,114 @@ class BatteriesWidget : GlanceAppWidget() { val statusIconRes: Int? = null ) - data class ThemeColors( - val primary: Int, - val error: Int, - val warning: Int, - val track: Int, - val surface: Int, - val iconTint: Int - ) - @androidx.compose.runtime.Composable private fun BatteryItemBox( - context: Context, item: BatteryItemData, - colors: ThemeColors, - size: Int = 300, + itemSize: androidx.compose.ui.unit.Dp, + sizePx: Int = 340, modifier: GlanceModifier = GlanceModifier ) { - val ringColor = when { - item.level <= 10 -> colors.error - item.level < 20 -> colors.warning - else -> colors.primary - } + androidx.glance.LocalContext.current + + // Define color providers for instant theme reactivity + val primaryColor = GlanceTheme.colors.primary + val errorColor = GlanceTheme.colors.error + val onSurfaceColor = GlanceTheme.colors.onSurface + val surfaceColor = GlanceTheme.colors.widgetBackground - val icon = ContextCompat.getDrawable(context, item.iconRes) - val statusIcon = item.statusIconRes?.let { ContextCompat.getDrawable(context, it) } - - val bitmap = com.sameerasw.essentials.utils.BatteryRingDrawer.drawBatteryWidget( - context, - item.level, - ringColor, - colors.track, - colors.iconTint, - colors.surface, - icon, - statusIcon, - size, - size + val trackColorProvider = ColorProvider( + day = androidx.compose.ui.graphics.Color.Black.copy(alpha = 0.1f), + night = androidx.compose.ui.graphics.Color.Black.copy(alpha = 0.6f) ) + val warningColor = ColorProvider( + day = androidx.compose.ui.graphics.Color(0xFFFFC107), + night = androidx.compose.ui.graphics.Color(0xFFFFD54F) + ) + + val ringColorProvider = when { + item.level <= 10 -> errorColor + item.level < 20 -> warningColor + else -> primaryColor + } + + val padding = if (itemSize > 100.dp) 12.dp else 8.dp + val iconPadding = if (itemSize > 100.dp) 32.dp else 24.dp + Box(modifier = modifier, contentAlignment = Alignment.Center) { + // 1. Background Circle + Box( + modifier = GlanceModifier + .fillMaxSize() + .padding(padding) + .background(surfaceColor) + .cornerRadius(100.dp) + ) {} + + // 2. Battery Ring + // Progress Arc + val progressBitmap = com.sameerasw.essentials.utils.BatteryRingDrawer.drawProgressArc( + item.level, + android.graphics.Color.WHITE, + item.statusIconRes != null, + sizePx, + sizePx + ) + Image( + provider = ImageProvider(progressBitmap), + contentDescription = null, + colorFilter = androidx.glance.ColorFilter.tint(ringColorProvider), + modifier = GlanceModifier.fillMaxSize() + ) + + // Track Arc + val trackBitmap = com.sameerasw.essentials.utils.BatteryRingDrawer.drawTrackArc( + item.level, + android.graphics.Color.WHITE, + item.statusIconRes != null, + sizePx, + sizePx + ) Image( - provider = ImageProvider(bitmap), - contentDescription = "${item.name}: ${item.level}%", + provider = ImageProvider(trackBitmap), + contentDescription = null, + colorFilter = androidx.glance.ColorFilter.tint(trackColorProvider), modifier = GlanceModifier.fillMaxSize() ) + + // 3. Center Device Icon + Image( + provider = ImageProvider(item.iconRes), + contentDescription = null, + colorFilter = androidx.glance.ColorFilter.tint(onSurfaceColor), + modifier = GlanceModifier.fillMaxSize().padding(iconPadding) + ) + + // 4. Status Indicator Bubble (at the top) + if (item.statusIconRes != null) { + val bubbleSize = if (itemSize > 100.dp) 32.dp else 24.dp + val bubbleIconPadding = if (itemSize > 100.dp) 6.dp else 4.dp + + Box( + modifier = GlanceModifier.fillMaxSize(), + contentAlignment = Alignment.TopCenter + ) { + Box( + modifier = GlanceModifier + .size(bubbleSize) + .background(ringColorProvider) + .cornerRadius(100.dp), + contentAlignment = Alignment.Center + ) { + Image( + provider = ImageProvider(item.statusIconRes), + contentDescription = null, + colorFilter = androidx.glance.ColorFilter.tint(surfaceColor), + modifier = GlanceModifier.fillMaxSize().padding(bubbleIconPadding) + ) + } + } + } } } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidgetReceiver.kt b/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidgetReceiver.kt index de9c39711..122c74f3c 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidgetReceiver.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/widgets/BatteriesWidgetReceiver.kt @@ -14,26 +14,19 @@ class BatteriesWidgetReceiver : GlanceAppWidgetReceiver() { // Always update widget on configuration changes (including theme changes) if (intent.action == Intent.ACTION_CONFIGURATION_CHANGED) { - val appWidgetManager = context.getSystemService(Context.APPWIDGET_SERVICE) as? android.appwidget.AppWidgetManager + val appWidgetManager = + context.getSystemService(Context.APPWIDGET_SERVICE) as? android.appwidget.AppWidgetManager if (appWidgetManager == null) return kotlinx.coroutines.MainScope().launch { try { - val glanceAppWidgetManager = androidx.glance.appwidget.GlanceAppWidgetManager(context) + val glanceAppWidgetManager = + androidx.glance.appwidget.GlanceAppWidgetManager(context) // Add a small delay to allow system theme colors to propagate kotlinx.coroutines.delay(500) val glanceIds = glanceAppWidgetManager.getGlanceIds(BatteriesWidget::class.java) glanceIds.forEach { glanceId -> - // Update widget state with a timestamp to force re-render - androidx.glance.appwidget.state.updateAppWidgetState( - context, - glanceId - ) { prefs -> - val THEME_UPDATE_KEY = - androidx.datastore.preferences.core.longPreferencesKey("theme_update_time") - prefs[THEME_UPDATE_KEY] = System.currentTimeMillis() - } glanceAppWidget.update(context, glanceId) } } catch (e: Exception) { @@ -59,61 +52,65 @@ class BatteriesWidgetReceiver : GlanceAppWidgetReceiver() { ) { // Trigger update - val appWidgetManager = context.getSystemService(Context.APPWIDGET_SERVICE) as? android.appwidget.AppWidgetManager + val appWidgetManager = + context.getSystemService(Context.APPWIDGET_SERVICE) as? android.appwidget.AppWidgetManager if (appWidgetManager == null) return kotlinx.coroutines.MainScope().launch { try { - val glanceAppWidgetManager = androidx.glance.appwidget.GlanceAppWidgetManager(context) - // Check permissions first - val repository = - com.sameerasw.essentials.data.repository.SettingsRepository(context) - val hasPerm = - com.sameerasw.essentials.utils.PermissionUtils.hasBluetoothPermission(context) - - val isEnabled = repository.isBluetoothDevicesEnabled() - val bluetoothDevices = if (isEnabled && hasPerm) { - com.sameerasw.essentials.utils.BluetoothBatteryUtils.getPairedDevicesBattery( - context - ) - } else { - emptyList() - } + val glanceAppWidgetManager = + androidx.glance.appwidget.GlanceAppWidgetManager(context) + // Check permissions first + val repository = + com.sameerasw.essentials.data.repository.SettingsRepository(context) + val hasPerm = + com.sameerasw.essentials.utils.PermissionUtils.hasBluetoothPermission( + context + ) + + val isEnabled = repository.isBluetoothDevicesEnabled() + val bluetoothDevices = if (isEnabled && hasPerm) { + com.sameerasw.essentials.utils.BluetoothBatteryUtils.getPairedDevicesBattery( + context + ) + } else { + emptyList() + } + + + repository.saveBluetoothDevicesBattery(bluetoothDevices) + + val maxDevices = repository.getBatteryWidgetMaxDevices() + val isBackgroundEnabled = repository.isBatteryWidgetBackgroundEnabled() + val devicesJson = com.google.gson.Gson().toJson(bluetoothDevices) - repository.saveBluetoothDevicesBattery(bluetoothDevices) - - val maxDevices = repository.getBatteryWidgetMaxDevices() - val isBackgroundEnabled = repository.isBatteryWidgetBackgroundEnabled() - - val devicesJson = com.google.gson.Gson().toJson(bluetoothDevices) - - val glanceIds = glanceAppWidgetManager.getGlanceIds(BatteriesWidget::class.java) - glanceIds.forEach { glanceId -> - androidx.glance.appwidget.state.updateAppWidgetState( - context, - glanceId - ) { prefs -> - val KEY_SHOW = - androidx.datastore.preferences.core.booleanPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_SHOW_BLUETOOTH_DEVICES) - val KEY_DATA = - androidx.datastore.preferences.core.stringPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_BLUETOOTH_DEVICES_BATTERY) - val KEY_MAX = - androidx.datastore.preferences.core.intPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_BATTERY_WIDGET_MAX_DEVICES) - val KEY_BG = - androidx.datastore.preferences.core.booleanPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_BATTERY_WIDGET_BACKGROUND_ENABLED) - - prefs[KEY_SHOW] = isEnabled - prefs[KEY_DATA] = devicesJson - prefs[KEY_MAX] = maxDevices - prefs[KEY_BG] = isBackgroundEnabled + val glanceIds = glanceAppWidgetManager.getGlanceIds(BatteriesWidget::class.java) + glanceIds.forEach { glanceId -> + androidx.glance.appwidget.state.updateAppWidgetState( + context, + glanceId + ) { prefs -> + val KEY_SHOW = + androidx.datastore.preferences.core.booleanPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_SHOW_BLUETOOTH_DEVICES) + val KEY_DATA = + androidx.datastore.preferences.core.stringPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_BLUETOOTH_DEVICES_BATTERY) + val KEY_MAX = + androidx.datastore.preferences.core.intPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_BATTERY_WIDGET_MAX_DEVICES) + val KEY_BG = + androidx.datastore.preferences.core.booleanPreferencesKey(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_BATTERY_WIDGET_BACKGROUND_ENABLED) + + prefs[KEY_SHOW] = isEnabled + prefs[KEY_DATA] = devicesJson + prefs[KEY_MAX] = maxDevices + prefs[KEY_BG] = isBackgroundEnabled + } + glanceAppWidget.update(context, glanceId) } - glanceAppWidget.update(context, glanceId) + } catch (e: Exception) { + android.util.Log.e("BatteriesWidget", "Error updating widget", e) } - } catch (e: Exception) { - android.util.Log.e("BatteriesWidget", "Error updating widget", e) } - } try { val requestIntent = diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/AppFreezingActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/AppFreezingActivity.kt index 028407f26..891a25641 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/AppFreezingActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/AppFreezingActivity.kt @@ -49,7 +49,7 @@ class AppFreezingActivity : ComponentActivity() { setContent { val viewModel: MainViewModel = viewModel() val context = LocalContext.current - + LaunchedEffect(Unit) { viewModel.check(context) viewModel.refreshFreezePickedApps(context) @@ -70,7 +70,7 @@ class AppFreezingActivity : ComponentActivity() { val statusBarHeightPx = with(density) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding().toPx() } - + Box( modifier = Modifier .fillMaxSize() @@ -89,8 +89,10 @@ class AppFreezingActivity : ComponentActivity() { viewModel = viewModel, modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues( - top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding(), - bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 130.dp, + top = WindowInsets.statusBars.asPaddingValues() + .calculateTopPadding(), + bottom = WindowInsets.navigationBars.asPaddingValues() + .calculateBottomPadding() + 130.dp, start = 0.dp, end = 0.dp ), @@ -98,9 +100,10 @@ class AppFreezingActivity : ComponentActivity() { finish() }, onSettingsClick = { - val intent = Intent(context, FeatureSettingsActivity::class.java).apply { - putExtra("feature", "Freeze") - } + val intent = + Intent(context, FeatureSettingsActivity::class.java).apply { + putExtra("feature", "Freeze") + } context.startActivity(intent) } ) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt index 74e28bdcd..4b5fc4074 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt @@ -25,8 +25,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LoadingIndicator @@ -100,7 +98,6 @@ class AutomationEditorActivity : ComponentActivity() { } } - @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() @@ -459,9 +456,12 @@ class AutomationEditorActivity : ComponentActivity() { Trigger.ChargerConnected, Trigger.ChargerDisconnected, Trigger.Schedule( - hour = (selectedTrigger as? Trigger.Schedule)?.hour ?: 0, - minute = (selectedTrigger as? Trigger.Schedule)?.minute ?: 0, - days = (selectedTrigger as? Trigger.Schedule)?.days ?: emptySet() + hour = (selectedTrigger as? Trigger.Schedule)?.hour + ?: 0, + minute = (selectedTrigger as? Trigger.Schedule)?.minute + ?: 0, + days = (selectedTrigger as? Trigger.Schedule)?.days + ?: emptySet() ) ) triggers.forEach { trigger -> @@ -483,11 +483,16 @@ class AutomationEditorActivity : ComponentActivity() { DIYState.Charging, DIYState.ScreenOn, DIYState.TimePeriod( - startHour = (selectedState as? DIYState.TimePeriod)?.startHour ?: 0, - startMinute = (selectedState as? DIYState.TimePeriod)?.startMinute ?: 0, - endHour = (selectedState as? DIYState.TimePeriod)?.endHour ?: 0, - endMinute = (selectedState as? DIYState.TimePeriod)?.endMinute ?: 0, - days = (selectedState as? DIYState.TimePeriod)?.days ?: emptySet() + startHour = (selectedState as? DIYState.TimePeriod)?.startHour + ?: 0, + startMinute = (selectedState as? DIYState.TimePeriod)?.startMinute + ?: 0, + endHour = (selectedState as? DIYState.TimePeriod)?.endHour + ?: 0, + endMinute = (selectedState as? DIYState.TimePeriod)?.endMinute + ?: 0, + days = (selectedState as? DIYState.TimePeriod)?.days + ?: emptySet() ) ) states.forEach { state -> diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/ColorPickerActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/ColorPickerActivity.kt index 220564f53..9ebdcae9a 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/ColorPickerActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/ColorPickerActivity.kt @@ -1,9 +1,6 @@ package com.sameerasw.essentials.ui.activities import android.app.Activity -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context import android.content.Intent import android.graphics.Color import android.os.Build @@ -25,28 +22,30 @@ class ColorPickerActivity : ComponentActivity() { private var pickedColor by mutableStateOf(null) private var showBottomSheet by mutableStateOf(false) - private val eyeDropperLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - val color = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - result.data?.getIntExtra("android.intent.extra.COLOR", Color.BLACK) ?: Color.BLACK - } else { - Color.BLACK - } + private val eyeDropperLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + val color = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + result.data?.getIntExtra("android.intent.extra.COLOR", Color.BLACK) + ?: Color.BLACK + } else { + Color.BLACK + } - pickedColor = color - showBottomSheet = true - HapticUtil.performHapticForService(this) - } else { - // If they cancelled the eye dropper and no bottom sheet is showing, finish - if (!showBottomSheet) { - finish() + pickedColor = color + showBottomSheet = true + HapticUtil.performHapticForService(this) + } else { + // If they cancelled the eye dropper and no bottom sheet is showing, finish + if (!showBottomSheet) { + finish() + } } } - } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + setContent { EssentialsTheme { if (showBottomSheet && pickedColor != null) { @@ -63,7 +62,7 @@ class ColorPickerActivity : ComponentActivity() { } } } - + if (savedInstanceState == null) { launchColorPicker() } @@ -74,7 +73,8 @@ class ColorPickerActivity : ComponentActivity() { val intent = Intent("android.intent.action.OPEN_EYE_DROPPER") eyeDropperLauncher.launch(intent) } catch (e: Exception) { - Toast.makeText(this, getString(R.string.toast_eyedropper_failed), Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.toast_eyedropper_failed), Toast.LENGTH_SHORT) + .show() finish() } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/LiveWallpaperActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/LiveWallpaperActivity.kt index 988672dc0..8b54478e9 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/LiveWallpaperActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/LiveWallpaperActivity.kt @@ -5,12 +5,19 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -34,15 +41,16 @@ class LiveWallpaperActivity : ComponentActivity() { setContent { val viewModel: MainViewModel = viewModel() val context = LocalContext.current - + // Sync settings on start remember(context) { viewModel.check(context) } - + val isPitchBlackThemeEnabled by viewModel.isPitchBlackThemeEnabled - + EssentialsTheme(pitchBlackTheme = isPitchBlackThemeEnabled) { - val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() - + val statusBarHeight = + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + Box( modifier = Modifier .fillMaxSize() @@ -54,12 +62,12 @@ class LiveWallpaperActivity : ComponentActivity() { .verticalScroll(rememberScrollState()) ) { Spacer(modifier = Modifier.height(statusBarHeight)) - + LiveWallpaperSettingsUI( viewModel = viewModel, modifier = Modifier.padding(top = 16.dp) ) - + Spacer(modifier = Modifier.height(150.dp)) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/LocationAlarmActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/LocationAlarmActivity.kt index 8fd66769f..35a00c6a8 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/LocationAlarmActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/LocationAlarmActivity.kt @@ -1,7 +1,6 @@ package com.sameerasw.essentials.ui.activities import android.app.KeyguardManager -import android.content.Context import android.os.Build import android.os.Bundle import android.os.VibrationEffect @@ -148,7 +147,7 @@ class LocationAlarmActivity : ComponentActivity() { val activeId = repo.getActiveAlarmId() val alarms = repo.getAlarms() val alarm = alarms.find { it.id == activeId } - + if (alarm != null) { repo.saveLastTrip(alarm) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/PrivateDnsSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/PrivateDnsSettingsActivity.kt index c96a4e281..1836c25e6 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/PrivateDnsSettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/PrivateDnsSettingsActivity.kt @@ -1,33 +1,36 @@ package com.sameerasw.essentials.ui.activities -import android.content.Context import android.os.Bundle import android.provider.Settings import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.RadioButton import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -36,27 +39,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color 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.unit.dp -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.rememberModalBottomSheetState import com.sameerasw.essentials.R +import com.sameerasw.essentials.domain.model.DnsPreset import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.theme.EssentialsTheme import com.sameerasw.essentials.utils.HapticUtil -import androidx.compose.material3.IconButton -import androidx.compose.material3.AlertDialog -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.width -import com.sameerasw.essentials.domain.model.DnsPreset class PrivateDnsSettingsActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -82,7 +75,8 @@ class PrivateDnsSettingsActivity : ComponentActivity() { fun PrivateDnsSettingsOverlay(onDismiss: () -> Unit) { val context = LocalContext.current val view = LocalView.current - val viewModel: com.sameerasw.essentials.viewmodels.MainViewModel = androidx.lifecycle.viewmodel.compose.viewModel() + val viewModel: com.sameerasw.essentials.viewmodels.MainViewModel = + androidx.lifecycle.viewmodel.compose.viewModel() val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val PRIVATE_DNS_MODE = "private_dns_mode" @@ -200,11 +194,14 @@ fun PrivateDnsSettingsOverlay(onDismiss: () -> Unit) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { OutlinedButton( onClick = { - (viewModel as? com.sameerasw.essentials.viewmodels.MainViewModel)?.resetDnsPresets() + viewModel.resetDnsPresets() HapticUtil.performUIHaptic(view) }, modifier = Modifier.height(32.dp), - contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 12.dp, vertical = 0.dp), + contentPadding = androidx.compose.foundation.layout.PaddingValues( + horizontal = 12.dp, + vertical = 0.dp + ), shape = RoundedCornerShape(16.dp) ) { Text( @@ -218,7 +215,10 @@ fun PrivateDnsSettingsOverlay(onDismiss: () -> Unit) { HapticUtil.performUIHaptic(view) }, modifier = Modifier.height(32.dp), - contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 12.dp, vertical = 0.dp), + contentPadding = androidx.compose.foundation.layout.PaddingValues( + horizontal = 12.dp, + vertical = 0.dp + ), shape = RoundedCornerShape(16.dp) ) { Icon( @@ -239,7 +239,10 @@ fun PrivateDnsSettingsOverlay(onDismiss: () -> Unit) { AddDnsPresetDialog( onDismiss = { showAddDialog = false }, onConfirm = { name, host -> - (viewModel as? com.sameerasw.essentials.viewmodels.MainViewModel)?.addDnsPreset(name, host) + viewModel.addDnsPreset( + name, + host + ) showAddDialog = false HapticUtil.performUIHaptic(view) } @@ -247,7 +250,8 @@ fun PrivateDnsSettingsOverlay(onDismiss: () -> Unit) { } RoundedCardContainer { - val presets = (viewModel as? com.sameerasw.essentials.viewmodels.MainViewModel)?.dnsPresets ?: emptyList() + val presets = + viewModel.dnsPresets presets.forEach { preset -> DnsPresetItem( @@ -259,7 +263,9 @@ fun PrivateDnsSettingsOverlay(onDismiss: () -> Unit) { HapticUtil.performUIHaptic(view) }, onDelete = { - (viewModel as? com.sameerasw.essentials.viewmodels.MainViewModel)?.removeDnsPreset(preset) + viewModel.removeDnsPreset( + preset + ) HapticUtil.performUIHaptic(view) } ) @@ -324,7 +330,9 @@ fun DnsSegmentedItem( onClick: () -> Unit ) { Card( - modifier = Modifier.fillMaxWidth().clickable { onClick() }, + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, shape = MaterialTheme.shapes.extraSmall, colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surfaceBright @@ -436,7 +444,12 @@ fun AddDnsPresetDialog( }, confirmButton = { Button( - onClick = { if (name.isNotBlank() && hostname.isNotBlank()) onConfirm(name, hostname) }, + onClick = { + if (name.isNotBlank() && hostname.isNotBlank()) onConfirm( + name, + hostname + ) + }, enabled = name.isNotBlank() && hostname.isNotBlank() ) { Text(stringResource(R.string.action_add)) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/TileAuthActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/TileAuthActivity.kt index b0128985a..423843256 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/TileAuthActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/TileAuthActivity.kt @@ -1,6 +1,5 @@ package com.sameerasw.essentials.ui.activities -import android.content.Context import android.os.Bundle import androidx.core.content.edit import androidx.fragment.app.FragmentActivity diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt index 261eba91e..3f0e49ea9 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/YourAndroidActivity.kt @@ -9,6 +9,7 @@ import androidx.compose.animation.core.LinearEasing 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.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -16,25 +17,13 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.foundation.background -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -44,11 +33,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex @@ -64,8 +51,6 @@ import com.sameerasw.essentials.ui.modifiers.progressiveBlur import com.sameerasw.essentials.ui.theme.EssentialsTheme import com.sameerasw.essentials.utils.DeviceInfo import com.sameerasw.essentials.utils.DeviceUtils -import com.sameerasw.essentials.utils.GSMArenaService -import com.sameerasw.essentials.utils.HapticUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -85,7 +70,11 @@ class YourAndroidViewModel : ViewModel() { var hasRunStartupAnimation = false - fun loadDeviceSpecs(context: android.content.Context, deviceInfo: com.sameerasw.essentials.utils.DeviceInfo, forceRefresh: Boolean = false) { + fun loadDeviceSpecs( + context: android.content.Context, + deviceInfo: DeviceInfo, + forceRefresh: Boolean = false + ) { if (!forceRefresh && _deviceSpecs.value != null) { _isSpecsLoading.value = false return @@ -97,12 +86,12 @@ class YourAndroidViewModel : ViewModel() { _isRefreshing.value = true } else { _isSpecsLoading.value = true - + // Try to load from cache first val cached = withContext(Dispatchers.IO) { com.sameerasw.essentials.utils.DeviceSpecsCache.getCachedSpecs(context) } - + if (cached != null) { _deviceSpecs.value = cached _isSpecsLoading.value = false @@ -118,7 +107,7 @@ class YourAndroidViewModel : ViewModel() { // Generate a prioritized list of search queries val queries = mutableListOf() - + // 1. Marketing name (Manufacturer + Model) if (model.contains(manufacturer, ignoreCase = true)) { queries.add(model) @@ -142,12 +131,12 @@ class YourAndroidViewModel : ViewModel() { } } } - + // 4. Model number directly if it's different from marketing name if (!queries.contains(model)) { queries.add(model) } - + // 5. Device codename (e.g., "shiba", "a51", "r9q") if (deviceCodename.isNotBlank() && !queries.contains(deviceCodename)) { queries.add(deviceCodename) @@ -162,10 +151,14 @@ class YourAndroidViewModel : ViewModel() { if (fetchedSpecs != null) { // Download and cache images - val specsWithImages = com.sameerasw.essentials.utils.DeviceSpecsCache.downloadImages(context, fetchedSpecs) + val specsWithImages = + com.sameerasw.essentials.utils.DeviceSpecsCache.downloadImages( + context, + fetchedSpecs + ) _deviceSpecs.value = specsWithImages } - + _isSpecsLoading.value = false _isRefreshing.value = false } catch (e: Exception) { @@ -187,20 +180,22 @@ class YourAndroidActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - - val isDarkMode = (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES + + val isDarkMode = + (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES window.setBackgroundDrawableResource(if (isDarkMode) android.R.color.black else R.color.app_window_background) setContent { - val mainViewModel: com.sameerasw.essentials.viewmodels.MainViewModel = androidx.lifecycle.viewmodel.compose.viewModel() + val mainViewModel: com.sameerasw.essentials.viewmodels.MainViewModel = + androidx.lifecycle.viewmodel.compose.viewModel() val isPitchBlackThemeEnabled by mainViewModel.isPitchBlackThemeEnabled val isBlurEnabled by mainViewModel.isBlurEnabled - + val viewModel: YourAndroidViewModel = androidx.lifecycle.viewmodel.compose.viewModel() val deviceSpecs by viewModel.deviceSpecs.collectAsState() val isSpecsLoading by viewModel.isSpecsLoading.collectAsState() val isRefreshing by viewModel.isRefreshing.collectAsState() - + val context = androidx.compose.ui.platform.LocalContext.current val deviceInfo = remember { DeviceUtils.getDeviceInfo(context) } var showHelpSheet by remember { mutableStateOf(false) } @@ -216,8 +211,15 @@ class YourAndroidActivity : ComponentActivity() { showToggle = false, hasMoreSettings = false ) { - override fun isEnabled(viewModel: com.sameerasw.essentials.viewmodels.MainViewModel) = true - override fun onToggle(viewModel: com.sameerasw.essentials.viewmodels.MainViewModel, context: android.content.Context, enabled: Boolean) {} + override fun isEnabled(viewModel: com.sameerasw.essentials.viewmodels.MainViewModel) = + true + + override fun onToggle( + viewModel: com.sameerasw.essentials.viewmodels.MainViewModel, + context: android.content.Context, + enabled: Boolean + ) { + } } } @@ -299,7 +301,8 @@ fun YourAndroidContent( } } - val mainViewModel: com.sameerasw.essentials.viewmodels.MainViewModel = androidx.lifecycle.viewmodel.compose.viewModel() + val mainViewModel: com.sameerasw.essentials.viewmodels.MainViewModel = + androidx.lifecycle.viewmodel.compose.viewModel() val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp val initialImageOffset = (screenHeight / 2) - 240.dp - 64.dp @@ -337,7 +340,8 @@ fun YourAndroidContent( ) .verticalScroll(rememberScrollState()) .padding( - top = contentPadding.calculateTopPadding() + WindowInsets.statusBars.asPaddingValues().calculateTopPadding(), + top = contentPadding.calculateTopPadding() + WindowInsets.statusBars.asPaddingValues() + .calculateTopPadding(), bottom = 150.dp, start = 16.dp, end = 16.dp diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/DeviceHeroCard.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/DeviceHeroCard.kt index 6ad7e155d..9b32b3223 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/DeviceHeroCard.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/DeviceHeroCard.kt @@ -1,17 +1,22 @@ package com.sameerasw.essentials.ui.components +import android.content.ComponentName +import android.content.Intent +import android.os.Build import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape @@ -19,48 +24,36 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale +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.unit.Dp import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import com.sameerasw.essentials.R -import com.sameerasw.essentials.data.model.DeviceSpecs -import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer -import android.content.ComponentName -import android.content.Intent -import android.os.Build -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.width -import androidx.compose.material.ripple.rememberRipple -import androidx.compose.material3.Surface -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.window.Dialog import coil.ImageLoader +import coil.compose.AsyncImage import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.request.ImageRequest -import com.sameerasw.essentials.ui.theme.Shapes +import com.sameerasw.essentials.R +import com.sameerasw.essentials.data.model.DeviceSpecs +import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.components.modifiers.shimmer +import com.sameerasw.essentials.ui.theme.Shapes +import com.sameerasw.essentials.utils.DeviceImageMapper import com.sameerasw.essentials.utils.DeviceInfo import com.sameerasw.essentials.utils.DeviceUtils -import com.sameerasw.essentials.utils.DeviceImageMapper import com.sameerasw.essentials.utils.HapticUtil @Composable @@ -76,7 +69,7 @@ fun DeviceHeroCard( val view = LocalView.current val imageUrls = deviceSpecs?.imageUrls ?: emptyList() val isPixel = deviceInfo.manufacturer.contains("Google", ignoreCase = true) - + var showFlashbangDialog by remember { mutableStateOf(false) } val launchIntent = { packageName: String, className: String -> @@ -87,7 +80,7 @@ fun DeviceHeroCard( } context.startActivity(intent) } catch (e: Exception) { - + } } @@ -96,11 +89,14 @@ fun DeviceHeroCard( onDismiss = { showFlashbangDialog = false }, onContinue = { showFlashbangDialog = false - launchIntent("com.google.android.apps.diagnosticstool", "com.google.android.apps.diagnosticstool.login.EndUserLoginActivity") + launchIntent( + "com.google.android.apps.diagnosticstool", + "com.google.android.apps.diagnosticstool.login.EndUserLoginActivity" + ) } ) } - + // Only show the illustration page if it's a Pixel AND we have a mapping val illustrationRes = DeviceImageMapper.getDeviceDrawable(deviceInfo.model) val showIllustration = isPixel && illustrationRes != 0 @@ -146,13 +142,15 @@ fun DeviceHeroCard( } else { // real image from gsmarena (or local cache) val imageIndex = if (showIllustration) page - 1 else page - val imageModel = if (deviceSpecs?.localImagePaths?.isNotEmpty() == true && - deviceSpecs.localImagePaths.size > imageIndex) { - deviceSpecs.localImagePaths[imageIndex] - } else { - imageUrls[imageIndex] - } - + val imageModel = + if (deviceSpecs?.localImagePaths?.isNotEmpty() == true && + deviceSpecs.localImagePaths.size > imageIndex + ) { + deviceSpecs.localImagePaths[imageIndex] + } else { + imageUrls[imageIndex] + } + AsyncImage( model = imageModel, contentDescription = "Device Image", @@ -202,7 +200,7 @@ fun DeviceHeroCard( translationY = contentOffset().toPx() }, style = MaterialTheme.typography.headlineMedium.copy( - fontFamily = null + fontFamily = null ), fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface @@ -366,10 +364,10 @@ fun DeviceHeroCard( Column( modifier = Modifier .fillMaxWidth() - .background( - MaterialTheme.colorScheme.surfaceBright, - shape = Shapes.extraSmall - ) + .background( + MaterialTheme.colorScheme.surfaceBright, + shape = Shapes.extraSmall + ) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) @@ -379,7 +377,10 @@ fun DeviceHeroCard( label = stringResource(id = R.string.label_diagnostics), onClick = { HapticUtil.performVirtualKeyHaptic(view) - launchIntent("com.android.devicediagnostics", "com.android.devicediagnostics.MainActivity") + launchIntent( + "com.android.devicediagnostics", + "com.android.devicediagnostics.MainActivity" + ) } ) PixelToolButton( 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 290507e07..a2df34822 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 @@ -5,9 +5,34 @@ 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.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.FloatingToolbarDefaults +import androidx.compose.material3.FloatingToolbarScrollBehavior +import androidx.compose.material3.HorizontalFloatingToolbar +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -16,11 +41,8 @@ import androidx.compose.ui.platform.LocalDensity 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 -import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem import com.sameerasw.essentials.utils.HapticUtil data class ToolbarItem( @@ -55,11 +77,11 @@ fun EssentialsFloatingToolbar( val configuration = LocalConfiguration.current val fontScale = LocalDensity.current.fontScale val screenWidth = configuration.screenWidthDp - + // Hide label if font scale is large or screen width is too small val isLargeFont = fontScale > 1.25f - val isCompactScreen = screenWidth < 400 - + val isCompactScreen = screenWidth < 400 + val shouldHideLabel = isLargeFont || (isCompactScreen && items.size > 3) val finalFab: (@Composable () -> Unit)? = when { @@ -67,7 +89,8 @@ fun EssentialsFloatingToolbar( onHelpClick != null && fabAction == null -> { { FloatingActionButton( - onClick = {HapticUtil.performUIHaptic(view) + onClick = { + HapticUtil.performUIHaptic(view) onHelpClick() }, containerColor = MaterialTheme.colorScheme.primaryContainer, @@ -82,10 +105,12 @@ fun EssentialsFloatingToolbar( } } } + fabAction != null && fabIconRes != null -> { { FloatingActionButton( - onClick = {HapticUtil.performUIHaptic(view) + onClick = { + HapticUtil.performUIHaptic(view) fabAction() }, containerColor = MaterialTheme.colorScheme.primaryContainer, @@ -100,6 +125,7 @@ fun EssentialsFloatingToolbar( } } } + else -> null } @@ -150,7 +176,9 @@ fun EssentialsFloatingToolbar( style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.background, maxLines = 1, - modifier = Modifier.basicMarquee().weight(1f, fill = false) + modifier = Modifier + .basicMarquee() + .weight(1f, fill = false) ) if (isBeta) { Card( 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 4e75d6125..74d206dbd 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 @@ -24,7 +24,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.carousel.HorizontalMultiBrowseCarousel import androidx.compose.material3.carousel.rememberCarouselState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -42,7 +41,6 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.sameerasw.essentials.R import com.sameerasw.essentials.domain.model.Feature @@ -185,7 +183,9 @@ fun FavoriteCarousel( style = MaterialTheme.typography.labelSmall, textAlign = TextAlign.Center, maxLines = 1, - modifier = Modifier.fillMaxWidth().basicMarquee() + modifier = Modifier + .fillMaxWidth() + .basicMarquee() ) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/HelpAndGuides.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/HelpAndGuides.kt index 6f5501f23..93c3b8c1c 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/HelpAndGuides.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/HelpAndGuides.kt @@ -1,5 +1,6 @@ package com.sameerasw.essentials.ui.components +import android.content.Intent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.Image @@ -12,12 +13,10 @@ import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -43,7 +42,6 @@ import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.sameerasw.essentials.R import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer -import android.content.Intent data class InstructionStep( val instruction: String, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/LocationIconPicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/LocationIconPicker.kt index 7e4c7183e..d8890d1e7 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/LocationIconPicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/LocationIconPicker.kt @@ -2,8 +2,18 @@ package com.sameerasw.essentials.ui.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.material3.carousel.HorizontalMultiBrowseCarousel import androidx.compose.material3.carousel.rememberCarouselState import androidx.compose.runtime.Composable @@ -65,7 +75,8 @@ fun LocationIconPicker( ) { index -> val iconName = icons[index] val isSelected = iconName == selectedIconName - val iconResId = context.resources.getIdentifier(iconName, "drawable", context.packageName) + val iconResId = + context.resources.getIdentifier(iconName, "drawable", context.packageName) Box( modifier = Modifier diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/MadebySameeraswCard.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/MadebySameeraswCard.kt index 0a1a72e1d..1700fafcd 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/MadebySameeraswCard.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/MadebySameeraswCard.kt @@ -6,7 +6,15 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -38,7 +46,7 @@ fun MadebySameeraswCard( val context = LocalContext.current val redditUrl = "https://www.reddit.com/r/MadebySameerasw/" val isDark = isSystemInDarkTheme() - + val brandColor = Color(0xFF49FCBB) val brandColorDark = Color(0xFF007A54) // Darker tone for light theme val accentColor = if (isDark) brandColor else brandColorDark @@ -77,7 +85,11 @@ fun MadebySameeraswCard( .clip(CircleShape) .background(MaterialTheme.colorScheme.surfaceBright) .border(4.dp, MaterialTheme.colorScheme.surfaceBright, CircleShape) - .border(6.dp, accentColor.copy(alpha = 0.5f), CircleShape) // Subtle outer ring + .border( + 6.dp, + accentColor.copy(alpha = 0.5f), + CircleShape + ) // Subtle outer ring .padding(4.dp) .border(2.dp, Color(0xFF49FCBB), CircleShape) // Sharper inner stroke ) { diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/AppToggleItem.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/AppToggleItem.kt index 7d7aee2d5..e76948fc1 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/AppToggleItem.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/AppToggleItem.kt @@ -2,17 +2,13 @@ package com.sameerasw.essentials.ui.components.cards import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -110,7 +106,7 @@ fun AppToggleItem( }, enabled = enabled, modifier = modifier.fillMaxWidth(), - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + verticalAlignment = Alignment.CenterVertically, leadingContent = { if (icon != null) { Image( @@ -182,7 +178,7 @@ fun AppToggleItem( onClick = onClickAction, enabled = enabled, modifier = modifier.fillMaxWidth(), - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + verticalAlignment = Alignment.CenterVertically, leadingContent = { if (icon != null) { Image( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/ConfigPickerItem.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/ConfigPickerItem.kt index 86e1228ce..fa69deb80 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/ConfigPickerItem.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/ConfigPickerItem.kt @@ -1,14 +1,27 @@ package com.sameerasw.essentials.ui.components.cards -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp 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 649be25e3..159f215d2 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 @@ -5,14 +5,14 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -21,11 +21,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.VerticalDivider -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.Spacer import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -114,7 +110,7 @@ fun FeatureCard( HapticUtil.performVirtualKeyHaptic(view) showMenu = true }, - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + verticalAlignment = Alignment.CenterVertically, modifier = modifier .alpha(alpha) .blur(blurRadius), @@ -190,12 +186,13 @@ fun FeatureCard( ) if (!isToggleEnabled && onDisabledToggleClick != null) { - Box(modifier = Modifier - .matchParentSize() - .clickable { - HapticUtil.performVirtualKeyHaptic(view) - onDisabledToggleClick() - }) + Box( + modifier = Modifier + .matchParentSize() + .clickable { + HapticUtil.performVirtualKeyHaptic(view) + onDisabledToggleClick() + }) } } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/IconToggleItem.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/IconToggleItem.kt index fed3d38d1..09f21bcb2 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/IconToggleItem.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/IconToggleItem.kt @@ -1,29 +1,22 @@ package com.sameerasw.essentials.ui.components.cards -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource -import androidx.compose.material3.VerticalDivider import androidx.compose.ui.unit.dp -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.height import com.sameerasw.essentials.utils.HapticUtil @OptIn(androidx.compose.material3.ExperimentalMaterial3ExpressiveApi::class) @@ -72,7 +65,7 @@ fun IconToggleItem( }, enabled = enabled, modifier = modifier.fillMaxWidth(), - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + verticalAlignment = Alignment.CenterVertically, leadingContent = if (finalIconRes != 0) { { Icon( @@ -144,7 +137,7 @@ fun IconToggleItem( }, enabled = enabled, modifier = modifier.fillMaxWidth(), - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + verticalAlignment = Alignment.CenterVertically, leadingContent = if (finalIconRes != 0) { { Icon( @@ -192,7 +185,7 @@ fun IconToggleItem( onClick = onClickAction, enabled = enabled, modifier = modifier.fillMaxWidth(), - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + verticalAlignment = Alignment.CenterVertically, leadingContent = if (finalIconRes != 0) { { Icon( 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 c0cfef4b6..4cbae66ee 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 @@ -2,10 +2,18 @@ package com.sameerasw.essentials.ui.components.cards import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource @@ -28,7 +36,7 @@ fun LocationAlarmCard( modifier: Modifier = Modifier ) { val view = androidx.compose.ui.platform.LocalView.current - + ListItem( modifier = modifier .fillMaxWidth() @@ -39,7 +47,8 @@ fun LocationAlarmCard( }, leadingContent = { val context = androidx.compose.ui.platform.LocalContext.current - val iconResId = context.resources.getIdentifier(alarm.iconResName, "drawable", context.packageName) + val iconResId = + context.resources.getIdentifier(alarm.iconResName, "drawable", context.packageName) Icon( painter = painterResource(id = if (iconResId != 0) iconResId else R.drawable.round_navigation_24), contentDescription = null, @@ -58,9 +67,12 @@ fun LocationAlarmCard( supportingContent = { val context = androidx.compose.ui.platform.LocalContext.current val lastTravelledText = alarm.lastTravelled?.let { - stringResource(R.string.location_reached_last_travelled, com.sameerasw.essentials.utils.TimeUtil.formatRelativeDate(it, context)) + stringResource( + R.string.location_reached_last_travelled, + com.sameerasw.essentials.utils.TimeUtil.formatRelativeDate(it, context) + ) } ?: stringResource(R.string.location_reached_never) - + Text( text = lastTravelledText, style = MaterialTheme.typography.bodySmall, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/PermissionCard.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/PermissionCard.kt index 9892a0776..a929fb90f 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/cards/PermissionCard.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/cards/PermissionCard.kt @@ -12,14 +12,12 @@ import androidx.compose.foundation.layout.size import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalView @@ -58,7 +56,7 @@ fun PermissionCard( } val resolvedSecondaryLabel = when (secondaryActionLabel) { - is Int -> stringResource(id = secondaryActionLabel as Int) + is Int -> stringResource(id = secondaryActionLabel) is String -> secondaryActionLabel else -> null } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/linkActions/LinkPickerAdapter.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/linkActions/LinkPickerAdapter.kt index 619b12f68..9d133f7dc 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/linkActions/LinkPickerAdapter.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/linkActions/LinkPickerAdapter.kt @@ -211,9 +211,11 @@ fun LinkPickerScreen( } } - Column(modifier = Modifier - .fillMaxSize() - .padding(innerPadding)) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { // Link display and Edit action Row( modifier = Modifier diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/menus/SegmentedMenu.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/menus/SegmentedMenu.kt index 60c135b7a..86fc8781d 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/menus/SegmentedMenu.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/menus/SegmentedMenu.kt @@ -8,12 +8,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MenuDefaults import androidx.compose.material3.MenuItemColors import androidx.compose.runtime.Composable +import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import androidx.compose.runtime.staticCompositionLocalOf import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer @Composable diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/CrashReportingPicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/CrashReportingPicker.kt index 316052210..3bb6cae80 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/CrashReportingPicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/CrashReportingPicker.kt @@ -1,9 +1,21 @@ package com.sameerasw.essentials.ui.components.pickers import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ButtonGroupDefaults +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.ToggleButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/DefaultTabPicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/DefaultTabPicker.kt index 84511cb67..98edb0635 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/DefaultTabPicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/DefaultTabPicker.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonGroupDefaults import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/HapticFeedbackPicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/HapticFeedbackPicker.kt index fd91b2712..c9e618995 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/HapticFeedbackPicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/HapticFeedbackPicker.kt @@ -65,7 +65,12 @@ fun HapticFeedbackPicker( else -> ButtonGroupDefaults.connectedMiddleButtonShapes() }, ) { - Text(stringResource(label), fontSize = dimensionResource(R.dimen.font_small).value.sp, modifier= Modifier.basicMarquee(), maxLines = 1) + Text( + stringResource(label), + 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/pickers/LanguagePicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/LanguagePicker.kt index 21d07c27d..7b34c4b0c 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/LanguagePicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/LanguagePicker.kt @@ -1,19 +1,36 @@ package com.sameerasw.essentials.ui.components.pickers -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ExposedDropdownMenuAnchorType +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.platform.LocalView import com.sameerasw.essentials.R -import com.sameerasw.essentials.utils.LanguageUtils import com.sameerasw.essentials.utils.HapticUtil +import com.sameerasw.essentials.utils.LanguageUtils @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -57,7 +74,7 @@ fun LanguagePicker( readOnly = true, modifier = Modifier .widthIn(max = 200.dp) // Limit width to prevent overflow - .menuAnchor(MenuAnchorType.PrimaryEditable, true), + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true), trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), shape = RoundedCornerShape(12.dp), diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/NotificationLightingSystemModePicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/NotificationLightingSystemModePicker.kt index fe0206907..81ae4e940 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/NotificationLightingSystemModePicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/NotificationLightingSystemModePicker.kt @@ -37,7 +37,7 @@ fun NotificationLightingSystemModePicker( R.drawable.rounded_my_location_24 ) val labels = listOf("Charging", "Auth", "Custom") // For semantics/descriptions - + val view = LocalView.current RoundedCardContainer(modifier = Modifier) { 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 bb2800268..808ed23b7 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 @@ -67,7 +67,12 @@ 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, modifier= Modifier.basicMarquee(), maxLines = 1) + 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/BugReportBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/BugReportBottomSheet.kt index b21b45314..cdd2f5543 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/BugReportBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/BugReportBottomSheet.kt @@ -1,21 +1,11 @@ package com.sameerasw.essentials.ui.components.sheets -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context import android.content.Intent import android.net.Uri import android.widget.Toast -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -49,7 +39,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp 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.viewmodels.MainViewModel import io.sentry.Sentry @@ -133,7 +122,9 @@ fun BugReportBottomSheet( value = feedbackMessage, onValueChange = { feedbackMessage = it }, label = { Text(stringResource(R.string.bug_report_feedback_placeholder)) }, - modifier = Modifier.fillMaxWidth().padding(top = 8.dp), + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), shape = MaterialTheme.shapes.large, minLines = 3 ) @@ -158,7 +149,8 @@ fun BugReportBottomSheet( feedback.contactEmail = contactEmail } Sentry.captureFeedback(feedback) - Toast.makeText(context, R.string.msg_feedback_sent, Toast.LENGTH_SHORT).show() + Toast.makeText(context, R.string.msg_feedback_sent, Toast.LENGTH_SHORT) + .show() onDismissRequest() }, modifier = Modifier.fillMaxWidth(), @@ -177,13 +169,16 @@ fun BugReportBottomSheet( text = stringResource(R.string.label_alternatively), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.outline, - modifier = Modifier.align(Alignment.CenterHorizontally).padding(vertical = 4.dp) + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(vertical = 4.dp) ) // GitHub OutlinedButton( onClick = { - val body = "Feedback:\n$feedbackMessage\n\nDevice Info:\n$deviceInfoString\n\n" + val body = + "Feedback:\n$feedbackMessage\n\nDevice Info:\n$deviceInfoString\n\n" val encodedBody = Uri.encode(body) val intent = Intent( Intent.ACTION_VIEW, @@ -205,8 +200,10 @@ fun BugReportBottomSheet( // Email OutlinedButton( onClick = { - val contactLine = if (contactEmail.isNotBlank()) "Contact Email: $contactEmail\n" else "" - val body = "${contactLine}Feedback:\n$feedbackMessage\n\nDevice Info:\n$deviceInfoString\n\n" + val contactLine = + if (contactEmail.isNotBlank()) "Contact Email: $contactEmail\n" else "" + val body = + "${contactLine}Feedback:\n$feedbackMessage\n\nDevice Info:\n$deviceInfoString\n\n" val intent = Intent(Intent.ACTION_SENDTO).apply { data = Uri.parse("mailto:") putExtra(Intent.EXTRA_EMAIL, arrayOf("mail@sameerasw.com")) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ColorPickerBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ColorPickerBottomSheet.kt index 564d91e58..9bcb0c102 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ColorPickerBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/ColorPickerBottomSheet.kt @@ -1,24 +1,20 @@ package com.sameerasw.essentials.ui.components.sheets import android.content.Intent -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialShapes import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet @@ -36,16 +32,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.drawscope.ContentDrawScope -import androidx.compose.ui.graphics.drawscope.drawIntoCanvas -import androidx.compose.ui.graphics.nativeCanvas -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalClipboardManager 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.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -54,8 +44,6 @@ import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.components.pickers.SegmentedPicker import com.sameerasw.essentials.utils.ColorFormatUtils import com.sameerasw.essentials.utils.HapticUtil -import kotlin.math.cos -import kotlin.math.sin @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -68,7 +56,7 @@ fun ColorPickerBottomSheet( val view = LocalView.current val clipboardManager = LocalClipboardManager.current val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - + var selectedFormat by remember { mutableStateOf(ColorFormatUtils.ColorFormat.HEX) } val formattedColor = remember(colorInt, selectedFormat) { ColorFormatUtils.formatColor(colorInt, selectedFormat) @@ -93,7 +81,11 @@ fun ColorPickerBottomSheet( .size(160.dp) .clip(MaterialShapes.Cookie6Sided.toShape()) .background(Color(colorInt)) - .border(5.dp, MaterialTheme.colorScheme.outline, MaterialShapes.Cookie6Sided.toShape()), + .border( + 5.dp, + MaterialTheme.colorScheme.outline, + MaterialShapes.Cookie6Sided.toShape() + ), contentAlignment = Alignment.Center ) { Icon( @@ -105,31 +97,31 @@ fun ColorPickerBottomSheet( } RoundedCardContainer { - // Segmented Picker for Format - SegmentedPicker( - items = ColorFormatUtils.ColorFormat.values().toList(), - selectedItem = selectedFormat, - onItemSelected = { selectedFormat = it }, - labelProvider = { it.name }, - modifier = Modifier.fillMaxWidth() - ) - - // Formatted Color Text - Surface( - color = MaterialTheme.colorScheme.surfaceBright, - shape = MaterialTheme.shapes.extraSmall, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = formattedColor, - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier.padding(16.dp), - textAlign = androidx.compose.ui.text.style.TextAlign.Center + // Segmented Picker for Format + SegmentedPicker( + items = ColorFormatUtils.ColorFormat.values().toList(), + selectedItem = selectedFormat, + onItemSelected = { selectedFormat = it }, + labelProvider = { it.name }, + modifier = Modifier.fillMaxWidth() ) + + // Formatted Color Text + Surface( + color = MaterialTheme.colorScheme.surfaceBright, + shape = MaterialTheme.shapes.extraSmall, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = formattedColor, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(16.dp), + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + } } - } // Actions Row Row( @@ -143,7 +135,9 @@ fun ColorPickerBottomSheet( HapticUtil.performMediumHaptic(view) onRetake() }, - modifier = Modifier.weight(0.75f).height(56.dp) + modifier = Modifier + .weight(0.75f) + .height(56.dp) ) { Icon( painter = painterResource(id = R.drawable.rounded_colorize_24), @@ -164,7 +158,9 @@ fun ColorPickerBottomSheet( val shareIntent = Intent.createChooser(sendIntent, null) context.startActivity(shareIntent) }, - modifier = Modifier.weight(1.5f).height(56.dp) + modifier = Modifier + .weight(1.5f) + .height(56.dp) ) { Icon( painter = painterResource(id = R.drawable.rounded_share_24), @@ -178,10 +174,16 @@ fun ColorPickerBottomSheet( onClick = { HapticUtil.performHeavyHaptic(view) clipboardManager.setText(AnnotatedString(formattedColor)) - android.widget.Toast.makeText(context, "Copied to clipboard", android.widget.Toast.LENGTH_SHORT).show() + android.widget.Toast.makeText( + context, + "Copied to clipboard", + android.widget.Toast.LENGTH_SHORT + ).show() onDismissRequest() }, - modifier = Modifier.weight(1.5f).height(56.dp) + modifier = Modifier + .weight(1.5f) + .height(56.dp) ) { Icon( painter = painterResource(id = R.drawable.rounded_content_copy_24), diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/FeatureHelpBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/FeatureHelpBottomSheet.kt index 24b044ecf..0245c792d 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/FeatureHelpBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/FeatureHelpBottomSheet.kt @@ -91,7 +91,7 @@ fun FeatureHelpBottomSheet( ) { Column(modifier = Modifier.padding(16.dp)) { val description = if (feature.aboutDescription != null) { - stringResource(feature.aboutDescription!!) + stringResource(feature.aboutDescription) } else { stringResource(feature.description) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/InstructionsBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/InstructionsBottomSheet.kt index f40997489..e9d55ab9c 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/InstructionsBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/InstructionsBottomSheet.kt @@ -3,16 +3,8 @@ package com.sameerasw.essentials.ui.components.sheets import android.content.ActivityNotFoundException import android.content.Intent import android.widget.Toast -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -20,28 +12,17 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -50,7 +31,6 @@ import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.sameerasw.essentials.R import com.sameerasw.essentials.ui.components.HelpAndGuidesContent -import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/LocationReachedBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/LocationReachedBottomSheet.kt index cbb4feab0..f6bc054f4 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/LocationReachedBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/LocationReachedBottomSheet.kt @@ -1,8 +1,31 @@ package com.sameerasw.essentials.ui.components.sheets -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.BottomSheetDefaults +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.LoadingIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -10,7 +33,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.sameerasw.essentials.R -import com.sameerasw.essentials.domain.model.LocationAlarm import com.sameerasw.essentials.ui.components.LocationIconPicker import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.viewmodels.LocationReachedViewModel @@ -23,11 +45,10 @@ fun LocationReachedBottomSheet( ) { val tempAlarm by viewModel.tempAlarm val currentAlarm = tempAlarm - val distance by viewModel.currentDistance val view = androidx.compose.ui.platform.LocalView.current - + val isProcessing by viewModel.isProcessingCoordinates - + if (currentAlarm == null && !isProcessing) return ModalBottomSheet( @@ -44,9 +65,9 @@ fun LocationReachedBottomSheet( verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( - text = if (currentAlarm != null && viewModel.savedAlarms.value.any { it.id == currentAlarm.id }) - stringResource(R.string.location_reached_edit_title) - else stringResource(R.string.location_reached_add_title), + text = if (currentAlarm != null && viewModel.savedAlarms.value.any { it.id == currentAlarm.id }) + stringResource(R.string.location_reached_edit_title) + else stringResource(R.string.location_reached_add_title), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface @@ -55,7 +76,9 @@ fun LocationReachedBottomSheet( if (isProcessing && currentAlarm == null) { RoundedCardContainer( containerColor = MaterialTheme.colorScheme.surfaceBright, - modifier = Modifier.fillMaxWidth().height(200.dp) + modifier = Modifier + .fillMaxWidth() + .height(200.dp) ) { Column( modifier = Modifier.fillMaxSize(), @@ -92,8 +115,8 @@ fun LocationReachedBottomSheet( LocationIconPicker( selectedIconName = currentAlarm.iconResName, - onIconSelected = { - viewModel.setTempAlarm(currentAlarm.copy(iconResName = it)) + onIconSelected = { + viewModel.setTempAlarm(currentAlarm.copy(iconResName = it)) } ) @@ -105,7 +128,10 @@ fun LocationReachedBottomSheet( color = MaterialTheme.colorScheme.onSurfaceVariant ) Text( - text = "%.5f, %.5f".format(currentAlarm.latitude, currentAlarm.longitude), + text = "%.5f, %.5f".format( + currentAlarm.latitude, + currentAlarm.longitude + ), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.Medium @@ -115,17 +141,22 @@ fun LocationReachedBottomSheet( // Radius Slider Column { Text( - text = stringResource(R.string.location_reached_radius_label, currentAlarm.radius), + text = stringResource( + R.string.location_reached_radius_label, + currentAlarm.radius + ), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.onSurfaceVariant ) Slider( value = currentAlarm.radius.toFloat(), - onValueChange = { + onValueChange = { if (it.toInt() != currentAlarm.radius) { - com.sameerasw.essentials.utils.HapticUtil.performSliderHaptic(view) + com.sameerasw.essentials.utils.HapticUtil.performSliderHaptic( + view + ) } - viewModel.setTempAlarm(currentAlarm.copy(radius = it.toInt())) + viewModel.setTempAlarm(currentAlarm.copy(radius = it.toInt())) }, valueRange = 100f..5000f, steps = 49 @@ -135,7 +166,8 @@ fun LocationReachedBottomSheet( } } - val isEditing = currentAlarm != null && viewModel.savedAlarms.value.any { it.id == currentAlarm.id } + val isEditing = + currentAlarm != null && viewModel.savedAlarms.value.any { it.id == currentAlarm.id } Row( modifier = Modifier.fillMaxWidth(), @@ -174,7 +206,7 @@ fun LocationReachedBottomSheet( ) { Text(stringResource(R.string.location_reached_cancel_btn)) } - + Button( onClick = { val alarm = tempAlarm diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/PrankBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/PrankBottomSheet.kt index a926c7f18..8aa0f60fa 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/PrankBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/PrankBottomSheet.kt @@ -3,7 +3,6 @@ package com.sameerasw.essentials.ui.components.sheets import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -24,7 +23,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput 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 @@ -128,7 +126,11 @@ fun PrankBottomSheet( false ) HapticUtil.performHeavyHaptic(view) - android.widget.Toast.makeText(view.context, "Prank reset for testing", android.widget.Toast.LENGTH_SHORT).show() + android.widget.Toast.makeText( + view.context, + "Prank reset for testing", + android.widget.Toast.LENGTH_SHORT + ).show() }, onTap = { HapticUtil.performUIHaptic(view) @@ -140,7 +142,7 @@ fun PrankBottomSheet( Text(text = stringResource(R.string.action_continue)) } } - + Spacer(modifier = Modifier.height(16.dp)) } } 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 bbdc449f8..5d858091f 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,11 +1,29 @@ package com.sameerasw.essentials.ui.components.sheets -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width 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.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -169,7 +187,7 @@ fun ShutUpPerAppSettingsSheet( Spacer(modifier = Modifier.width(8.dp)) Text(text = stringResource(R.string.action_create_shortcut)) } - + Spacer(modifier = Modifier.height(32.dp)) } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/SoundModeSettingsSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/SoundModeSettingsSheet.kt index a66329882..5685101ee 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/SoundModeSettingsSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/SoundModeSettingsSheet.kt @@ -2,11 +2,32 @@ package com.sameerasw.essentials.ui.components.sheets import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/TimeSelectionSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/TimeSelectionSheet.kt index 899b9bf66..5f4600c27 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/TimeSelectionSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/TimeSelectionSheet.kt @@ -1,26 +1,44 @@ package com.sameerasw.essentials.ui.components.sheets import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TimePicker +import androidx.compose.material3.rememberTimePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.sameerasw.essentials.R -import com.sameerasw.essentials.domain.diy.State as DIYState import com.sameerasw.essentials.domain.diy.Trigger import com.sameerasw.essentials.ui.components.pickers.MultiSegmentedPicker import com.sameerasw.essentials.utils.HapticUtil -import java.util.* +import java.util.Calendar +import com.sameerasw.essentials.domain.diy.State as DIYState @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -35,7 +53,7 @@ fun TimeSelectionSheet( val view = LocalView.current val isRange = initialState != null val is24Hour = android.text.format.DateFormat.is24HourFormat(context) - + val startPickerState = rememberTimePickerState( initialHour = initialTrigger?.hour ?: initialState?.startHour ?: 0, initialMinute = initialTrigger?.minute ?: initialState?.startMinute ?: 0, @@ -46,7 +64,11 @@ fun TimeSelectionSheet( initialMinute = initialState?.endMinute ?: 0, is24Hour = is24Hour ) - var selectedDays by remember { mutableStateOf(initialTrigger?.days ?: initialState?.days ?: emptySet()) } + var selectedDays by remember { + mutableStateOf( + initialTrigger?.days ?: initialState?.days ?: emptySet() + ) + } var showingEndPicker by remember { mutableStateOf(false) } @@ -128,7 +150,7 @@ fun TimeSelectionSheet( style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface ) - + val days = listOf( Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY, Calendar.SUNDAY @@ -226,7 +248,7 @@ private fun TimeDisplayCard( ) { val context = androidx.compose.ui.platform.LocalContext.current val view = LocalView.current - + val formattedTime = remember(hour, minute) { val calendar = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, hour) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/UpdateBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/UpdateBottomSheet.kt index a89dcea7d..8caa026ff 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/UpdateBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/UpdateBottomSheet.kt @@ -1,10 +1,9 @@ package com.sameerasw.essentials.ui.components.sheets import android.content.Intent -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -16,8 +15,10 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedButton @@ -33,9 +34,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.Icon -import androidx.compose.material3.LoadingIndicator import androidx.core.net.toUri import com.sameerasw.essentials.R import com.sameerasw.essentials.domain.model.UpdateInfo @@ -121,7 +119,9 @@ fun UpdateBottomSheet( ), verticalAlignment = Alignment.CenterVertically, colors = ListItemDefaults.colors( - containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.5f) + containerColor = MaterialTheme.colorScheme.errorContainer.copy( + alpha = 0.5f + ) ), content = { Text( 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 index 944260735..cc3fa0cef 100644 --- 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 @@ -1,9 +1,24 @@ package com.sameerasw.essentials.ui.components.sheets import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -13,10 +28,10 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource 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.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.utils.HapticUtil -import androidx.lifecycle.viewmodel.compose.viewModel import com.sameerasw.essentials.viewmodels.WatchViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -25,7 +40,7 @@ fun WatchInstallHelpBottomSheet( onDismissRequest: () -> Unit, viewModel: WatchViewModel = viewModel() ) { - val context = LocalContext.current + LocalContext.current val uriHandler = LocalUriHandler.current val view = LocalView.current diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sliders/ConfigSliderItem.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sliders/ConfigSliderItem.kt index 2ec924de2..30efbfd04 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sliders/ConfigSliderItem.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sliders/ConfigSliderItem.kt @@ -15,9 +15,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.platform.LocalView import com.sameerasw.essentials.R import com.sameerasw.essentials.utils.HapticUtil import java.math.BigDecimal @@ -61,7 +61,9 @@ fun ConfigSliderItem( Icon( painter = painterResource(id = finalIconRes), contentDescription = null, - modifier = Modifier.padding(end = 12.dp).size(24.dp), + modifier = Modifier + .padding(end = 12.dp) + .size(24.dp), tint = MaterialTheme.colorScheme.primary ) } @@ -69,7 +71,9 @@ fun ConfigSliderItem( Text( text = "$title: ${valueFormatter(value)}", style = MaterialTheme.typography.bodyMedium, - color = if (enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f) + color = if (enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant.copy( + alpha = 0.38f + ) ) if (finalDescription != null) { Text( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/DIYScreen.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/DIYScreen.kt index accf3a085..ad891d4e8 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/DIYScreen.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/DIYScreen.kt @@ -29,8 +29,8 @@ import com.sameerasw.essentials.ui.activities.AutomationEditorActivity import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.components.diy.AutomationItem import com.sameerasw.essentials.ui.components.sheets.NewAutomationSheet -import com.sameerasw.essentials.viewmodels.DIYViewModel import com.sameerasw.essentials.utils.HapticUtil +import com.sameerasw.essentials.viewmodels.DIYViewModel @Composable fun DIYScreen( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/FreezeGridUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/FreezeGridUI.kt index 0561e0cfc..3185a3444 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/FreezeGridUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/FreezeGridUI.kt @@ -1,9 +1,17 @@ package com.sameerasw.essentials.ui.composables -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image +import android.content.Intent +import android.net.Uri +import android.provider.Settings import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.detectTapGestures @@ -21,12 +29,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonGroupDefaults -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -43,14 +51,18 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.input.pointer.pointerInput @@ -58,44 +70,29 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver -import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.BorderStroke import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardCapitalization -import android.content.Intent -import android.net.Uri -import android.provider.Settings -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.blur -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.unit.sp -import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu -import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem -import com.sameerasw.essentials.ui.state.LocalMenuStateManager import com.sameerasw.essentials.R import com.sameerasw.essentials.domain.model.NotificationApp import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer +import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu +import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem +import com.sameerasw.essentials.ui.state.LocalMenuStateManager import com.sameerasw.essentials.utils.FreezeManager import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.utils.ShortcutUtil import com.sameerasw.essentials.viewmodels.MainViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -333,7 +330,10 @@ fun FreezeGridUI( modifier = Modifier.size(20.dp) ) Spacer(Modifier.size(8.dp)) - Text(stringResource(R.string.action_freeze), fontSize = dimensionResource(R.dimen.font_small).value.sp) + Text( + stringResource(R.string.action_freeze), + fontSize = dimensionResource(R.dimen.font_small).value.sp + ) } Spacer(Modifier.size(ButtonGroupDefaults.ConnectedSpaceBetween)) @@ -354,7 +354,10 @@ fun FreezeGridUI( modifier = Modifier.size(20.dp) ) Spacer(Modifier.size(8.dp)) - Text(stringResource(R.string.action_unfreeze), fontSize = dimensionResource(R.dimen.font_small).value.sp) + Text( + stringResource(R.string.action_unfreeze), + fontSize = dimensionResource(R.dimen.font_small).value.sp + ) } // More Menu Button @@ -363,8 +366,8 @@ fun FreezeGridUI( HapticUtil.performVirtualKeyHaptic(view) isMenuExpanded = true }, - enabled = isShizukuAvailable && isShizukuPermissionGranted - , modifier = Modifier.size(dimensionResource(R.dimen.button_normal)) + enabled = isShizukuAvailable && isShizukuPermissionGranted, + modifier = Modifier.size(dimensionResource(R.dimen.button_normal)) ) { Icon( painter = painterResource(id = R.drawable.rounded_more_vert_24), @@ -516,19 +519,31 @@ fun FreezeGridUI( }, onToggleFreeze = { scope.launch(Dispatchers.IO) { - val isCurrentlyFrozen = frozenStates[app.packageName] ?: false + val isCurrentlyFrozen = + frozenStates[app.packageName] ?: false if (isCurrentlyFrozen) { - FreezeManager.unfreezeApp(context, app.packageName) + FreezeManager.unfreezeApp( + context, + app.packageName + ) } else { - FreezeManager.freezeApp(context, app.packageName) + FreezeManager.freezeApp( + context, + app.packageName + ) } withContext(Dispatchers.Main) { - frozenStates[app.packageName] = !isCurrentlyFrozen + frozenStates[app.packageName] = + !isCurrentlyFrozen } } }, onRemove = { - viewModel.updateFreezeAppEnabled(context, app.packageName, false) + viewModel.updateFreezeAppEnabled( + context, + app.packageName, + false + ) } ) } @@ -592,7 +607,7 @@ fun AppGridItem( } val grayscaleMatrix = remember { ColorMatrix().apply { setToSaturation(0.4f) } } - + val borderColor by animateColorAsState( targetValue = if (isHighlighted) MaterialTheme.colorScheme.primary else Color.Transparent, label = "borderColorAnimation" @@ -679,7 +694,11 @@ fun AppGridItem( ) { SegmentedDropdownMenuItem( text = { - Text(if (isFrozen) stringResource(R.string.action_unfreeze) else stringResource(R.string.action_freeze)) + Text( + if (isFrozen) stringResource(R.string.action_unfreeze) else stringResource( + R.string.action_freeze + ) + ) }, onClick = { showMenu = false diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/SetupFeatures.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/SetupFeatures.kt index af4db5f95..354a62e14 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/SetupFeatures.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/SetupFeatures.kt @@ -6,24 +6,30 @@ import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.provider.Settings +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material.icons.rounded.KeyboardArrowDown +import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text @@ -42,6 +48,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext @@ -50,29 +57,20 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.basicMarquee -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.rounded.KeyboardArrowDown -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.OutlinedCard -import androidx.compose.ui.graphics.Color import androidx.core.net.toUri import androidx.fragment.app.FragmentActivity import com.sameerasw.essentials.FeatureSettingsActivity import com.sameerasw.essentials.R -import com.sameerasw.essentials.ui.activities.YourAndroidActivity import com.sameerasw.essentials.domain.registry.FeatureRegistry import com.sameerasw.essentials.domain.registry.PermissionRegistry +import com.sameerasw.essentials.ui.activities.YourAndroidActivity import com.sameerasw.essentials.ui.components.FavoriteCarousel import com.sameerasw.essentials.ui.components.cards.FeatureCard import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.components.sheets.PermissionItem import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet -import com.sameerasw.essentials.utils.DeviceUtils import com.sameerasw.essentials.utils.BiometricSecurityHelper +import com.sameerasw.essentials.utils.DeviceUtils import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.viewmodels.MainViewModel import kotlinx.coroutines.delay @@ -80,7 +78,6 @@ import kotlinx.coroutines.launch private const val FEATURE_MAPS_POWER_SAVING = R.string.feat_maps_power_saving_title -@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SetupFeatures( viewModel: MainViewModel, @@ -838,7 +835,7 @@ fun SetupFeatures( val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current var isFocused by remember { mutableStateOf(false) } - + val pullRefreshState = rememberPullToRefreshState() var isRefreshing by rememberSaveable { mutableStateOf(false) } val scope = rememberCoroutineScope() @@ -853,7 +850,7 @@ fun SetupFeatures( onSearchHandled() } } - + val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current var shouldResetRefreshing by rememberSaveable { mutableStateOf(false) } @@ -887,9 +884,9 @@ fun SetupFeatures( LaunchedEffect(pullRefreshState.distanceFraction) { val fraction = pullRefreshState.distanceFraction val currentBucket = (fraction * 10).toInt() - + if (fraction >= 1f && lastHapticBucket < 10) { - HapticUtil.performUIHaptic(view) + HapticUtil.performUIHaptic(view) lastHapticBucket = 10 } else if (fraction < 1f && currentBucket != lastHapticBucket) { if (currentBucket > lastHapticBucket) { @@ -897,12 +894,12 @@ fun SetupFeatures( } lastHapticBucket = currentBucket } - + if (fraction == 0f) { lastHapticBucket = 0 } } - + PullToRefreshBox( isRefreshing = isRefreshing, onRefresh = { isRefreshing = true }, @@ -924,7 +921,7 @@ fun SetupFeatures( val displayFraction = if (isRefreshing) 1f else pullRefreshState.distanceFraction val thresholdPassed = displayFraction >= 1f val statusBarPadding = contentPadding.calculateTopPadding() - + val cardExpansion by androidx.compose.animation.core.animateDpAsState( targetValue = 120.dp * displayFraction.coerceIn(0f, 1f), animationSpec = androidx.compose.animation.core.spring(stiffness = androidx.compose.animation.core.Spring.StiffnessMediumLow), @@ -943,12 +940,6 @@ fun SetupFeatures( label = "contentColor" ) - val arrowColor by androidx.compose.animation.animateColorAsState( - targetValue = if (thresholdPassed) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant, - animationSpec = androidx.compose.animation.core.tween(durationMillis = 300), - label = "arrowColor" - ) - val borderColor by androidx.compose.animation.animateColorAsState( targetValue = if (thresholdPassed) Color.Transparent else MaterialTheme.colorScheme.outlineVariant, animationSpec = androidx.compose.animation.core.tween(durationMillis = 300), @@ -1003,7 +994,10 @@ fun SetupFeatures( border = BorderStroke(1.dp, borderColor) ) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { Spacer(modifier = Modifier.height(statusBarPadding)) Row(verticalAlignment = Alignment.CenterVertically) { Box( @@ -1018,7 +1012,8 @@ fun SetupFeatures( modifier = Modifier .size(24.dp) .graphicsLayer { - rotationZ = (displayFraction * 180f).coerceIn(0f, 180f) + rotationZ = + (displayFraction * 180f).coerceIn(0f, 180f) }, tint = contentColor ) @@ -1040,230 +1035,234 @@ fun SetupFeatures( } } } - + OutlinedTextField( value = viewModel.searchQuery.value, - onValueChange = { new -> - viewModel.onSearchQueryChanged(new, context) - }, + onValueChange = { new -> + viewModel.onSearchQueryChanged(new, context) + }, maxLines = 1, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - .focusRequester(focusRequester) - .onFocusChanged { isFocused = it.isFocused }, - leadingIcon = { - Icon( - painter = painterResource(id = R.drawable.rounded_search_24), - contentDescription = stringResource(R.string.label_search_content_description), - modifier = Modifier.size(24.dp) - ) - }, - placeholder = { - if (!isFocused && viewModel.searchQuery.value.isEmpty()) - Text( - text = stringResource(R.string.search_placeholder), - maxLines = 1, - modifier = Modifier.basicMarquee() - ) - }, - shape = MaterialTheme.shapes.extraExtraLarge, - singleLine = true, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = MaterialTheme.colorScheme.primary, - unfocusedBorderColor = MaterialTheme.colorScheme.onSurfaceVariant, - focusedContainerColor = MaterialTheme.colorScheme.surfaceBright - ) - ) - - if (pinnedFeatureKeys.isNotEmpty() && viewModel.searchQuery.value.isEmpty()) { - FavoriteCarousel( - pinnedKeys = pinnedFeatureKeys, - onFeatureClick = { feature -> - BiometricSecurityHelper.runWithAuth( - activity = context as FragmentActivity, - feature = feature, - action = { - feature.onClick(context, viewModel) - } + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .focusRequester(focusRequester) + .onFocusChanged { isFocused = it.isFocused }, + leadingIcon = { + Icon( + painter = painterResource(id = R.drawable.rounded_search_24), + contentDescription = stringResource(R.string.label_search_content_description), + modifier = Modifier.size(24.dp) ) }, - onFeatureLongClick = { feature -> - viewModel.togglePinFeature(feature.id) + placeholder = { + if (!isFocused && viewModel.searchQuery.value.isEmpty()) + Text( + text = stringResource(R.string.search_placeholder), + maxLines = 1, + modifier = Modifier.basicMarquee() + ) }, - modifier = Modifier.padding(top = 16.dp, bottom = 16.dp) + shape = MaterialTheme.shapes.extraExtraLarge, + singleLine = true, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = MaterialTheme.colorScheme.primary, + unfocusedBorderColor = MaterialTheme.colorScheme.onSurfaceVariant, + focusedContainerColor = MaterialTheme.colorScheme.surfaceBright + ) ) - } - val searchQuery = viewModel.searchQuery.value - val searchResults = viewModel.searchResults.value - val isSearchingViewModel = viewModel.isSearching.value + if (pinnedFeatureKeys.isNotEmpty() && viewModel.searchQuery.value.isEmpty()) { + FavoriteCarousel( + pinnedKeys = pinnedFeatureKeys, + onFeatureClick = { feature -> + BiometricSecurityHelper.runWithAuth( + activity = context as FragmentActivity, + feature = feature, + action = { + feature.onClick(context, viewModel) + } + ) + }, + onFeatureLongClick = { feature -> + viewModel.togglePinFeature(feature.id) + }, + modifier = Modifier.padding(top = 16.dp, bottom = 16.dp) + ) + } + + val searchQuery = viewModel.searchQuery.value + val searchResults = viewModel.searchResults.value + val isSearchingViewModel = viewModel.isSearching.value - // Loading indicator while filtering - if (isSearchingViewModel) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - LoadingIndicator() + // Loading indicator while filtering + if (isSearchingViewModel) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + LoadingIndicator() + } } - } - // No results view - if (!isSearchingViewModel && searchQuery.isNotEmpty() && searchResults.isEmpty()) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 64.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "¯\\_(ツ)_/¯", - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Text( - text = stringResource(id = R.string.search_no_results, searchQuery), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(top = 8.dp) - ) + // No results view + if (!isSearchingViewModel && searchQuery.isNotEmpty() && searchResults.isEmpty()) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 64.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "¯\\_(ツ)_/¯", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = stringResource(id = R.string.search_no_results, searchQuery), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 8.dp) + ) + } } - } - if (searchQuery.isNotEmpty()) { - // Render Search Results - if (searchResults.isNotEmpty()) { - Text( - text = stringResource(R.string.search_results_title), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(start = 32.dp, top = 16.dp, bottom = 8.dp), - color = MaterialTheme.colorScheme.onSurfaceVariant - ) + if (searchQuery.isNotEmpty()) { + // Render Search Results + if (searchResults.isNotEmpty()) { + Text( + text = stringResource(R.string.search_results_title), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(start = 32.dp, top = 16.dp, bottom = 8.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) - RoundedCardContainer( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - for (result in searchResults) { - FeatureCard( - title = result.title, - isEnabled = true, - onToggle = {}, - onClick = { - val feature = allFeatures.find { it.id == result.featureKey } - if (feature != null) { + RoundedCardContainer( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + for (result in searchResults) { + FeatureCard( + title = result.title, + isEnabled = true, + onToggle = {}, + onClick = { + val feature = allFeatures.find { it.id == result.featureKey } + if (feature != null) { + BiometricSecurityHelper.runWithAuth( + activity = context as FragmentActivity, + feature = feature, + action = { + context.startActivity( + Intent( + context, + FeatureSettingsActivity::class.java + ).apply { + putExtra("feature", result.featureKey) + result.targetSettingHighlightKey?.let { + putExtra("highlight_setting", it) + } + } + ) + } + ) + } else { + context.startActivity( + Intent( + context, + FeatureSettingsActivity::class.java + ).apply { + putExtra("feature", result.featureKey) + result.targetSettingHighlightKey?.let { + putExtra("highlight_setting", it) + } + } + ) + } + }, + iconRes = result.icon ?: R.drawable.rounded_settings_24, + modifier = Modifier.padding(horizontal = 0.dp, vertical = 0.dp), + showToggle = false, + hasMoreSettings = true, + isBeta = result.isBeta, + descriptionOverride = if (result.parentFeature != null) "${result.parentFeature} > ${result.description}" else result.description, + isPinned = pinnedFeatureKeys.contains(result.featureKey), + onPinToggle = { + viewModel.togglePinFeature(result.featureKey) + }, + onHelpClick = if (allFeatures.find { it.id == result.featureKey }?.aboutDescription != null) { + { + val feature = + allFeatures.find { it.id == result.featureKey } + if (feature != null) { + selectedHelpFeature = feature + showHelpSheet = true + } + } + } else null + ) + } + } + } + } else { + // Render Top Level Features (No Categories) + val topLevelFeatures = + allFeatures.filter { it.parentFeatureId == null && it.isVisibleInMain } + + if (topLevelFeatures.isNotEmpty()) { + RoundedCardContainer( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + topLevelFeatures.forEachIndexed { index, feature -> + FeatureCard( + title = feature.title, + isEnabled = feature.isEnabled(viewModel), + onToggle = { enabled -> BiometricSecurityHelper.runWithAuth( activity = context as FragmentActivity, feature = feature, + isToggle = true, action = { - context.startActivity( - Intent( - context, - FeatureSettingsActivity::class.java - ).apply { - putExtra("feature", result.featureKey) - result.targetSettingHighlightKey?.let { - putExtra("highlight_setting", it) - } - } - ) + feature.onToggle(viewModel, context, enabled) } ) - } else { - context.startActivity( - Intent(context, FeatureSettingsActivity::class.java).apply { - putExtra("feature", result.featureKey) - result.targetSettingHighlightKey?.let { - putExtra("highlight_setting", it) - } + }, + onClick = { + BiometricSecurityHelper.runWithAuth( + activity = context as FragmentActivity, + feature = feature, + action = { + feature.onClick(context, viewModel) } ) - } - }, - iconRes = result.icon ?: R.drawable.rounded_settings_24, - modifier = Modifier.padding(horizontal = 0.dp, vertical = 0.dp), - showToggle = false, - hasMoreSettings = true, - isBeta = result.isBeta, - descriptionOverride = if (result.parentFeature != null) "${result.parentFeature} > ${result.description}" else result.description, - isPinned = pinnedFeatureKeys.contains(result.featureKey), - onPinToggle = { - viewModel.togglePinFeature(result.featureKey) - }, - onHelpClick = if (allFeatures.find { it.id == result.featureKey }?.aboutDescription != null) { - { - val feature = allFeatures.find { it.id == result.featureKey } - if (feature != null) { + }, + iconRes = feature.iconRes, + modifier = Modifier.padding(horizontal = 0.dp, vertical = 0.dp), + isToggleEnabled = feature.isToggleEnabled(viewModel, context), + showToggle = feature.showToggle, + hasMoreSettings = feature.hasMoreSettings, + onDisabledToggleClick = { + if (feature.id == "Screen locked security") { + feature.onClick(context, viewModel) + } else { + currentFeature = feature.title + showSheet = true + } + }, + description = feature.description, + isBeta = feature.isBeta, + isPinned = pinnedFeatureKeys.contains(feature.id), + onPinToggle = { + viewModel.togglePinFeature(feature.id) + }, + onHelpClick = if (feature.aboutDescription != null) { + { selectedHelpFeature = feature showHelpSheet = true } - } - } else null - ) - } - } - } - } else { - // Render Top Level Features (No Categories) - val topLevelFeatures = - allFeatures.filter { it.parentFeatureId == null && it.isVisibleInMain } - - if (topLevelFeatures.isNotEmpty()) { - RoundedCardContainer( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - topLevelFeatures.forEachIndexed { index, feature -> - FeatureCard( - title = feature.title, - isEnabled = feature.isEnabled(viewModel), - onToggle = { enabled -> - BiometricSecurityHelper.runWithAuth( - activity = context as FragmentActivity, - feature = feature, - isToggle = true, - action = { - feature.onToggle(viewModel, context, enabled) - } - ) - }, - onClick = { - BiometricSecurityHelper.runWithAuth( - activity = context as FragmentActivity, - feature = feature, - action = { - feature.onClick(context, viewModel) - } - ) - }, - iconRes = feature.iconRes, - modifier = Modifier.padding(horizontal = 0.dp, vertical = 0.dp), - isToggleEnabled = feature.isToggleEnabled(viewModel, context), - showToggle = feature.showToggle, - hasMoreSettings = feature.hasMoreSettings, - onDisabledToggleClick = { - if (feature.id == "Screen locked security") { - feature.onClick(context, viewModel) - } else { - currentFeature = feature.title - showSheet = true - } - }, - description = feature.description, - isBeta = feature.isBeta, - isPinned = pinnedFeatureKeys.contains(feature.id), - onPinToggle = { - viewModel.togglePinFeature(feature.id) - }, - onHelpClick = if (feature.aboutDescription != null) { - { - selectedHelpFeature = feature - showHelpSheet = true - } - } else null - ) - } + } else null + ) } + } } else { val topLevelFeatures = diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/WelcomeScreen.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/WelcomeScreen.kt index 915b03bcf..715af0437 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/WelcomeScreen.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/WelcomeScreen.kt @@ -1,17 +1,56 @@ package com.sameerasw.essentials.ui.composables import android.content.Intent -import android.os.Build -import android.view.View import android.content.res.Configuration -import androidx.compose.animation.* -import androidx.compose.animation.core.* -import androidx.compose.foundation.* +import android.os.Build +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectDragGestures -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -28,8 +67,8 @@ 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 com.sameerasw.essentials.ui.components.HelpAndGuidesContent import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.core.net.toUri import coil.ImageLoader import coil.compose.AsyncImage @@ -37,18 +76,18 @@ import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.request.ImageRequest import com.sameerasw.essentials.R -import com.sameerasw.essentials.ui.theme.GoogleSansFlexRounded +import com.sameerasw.essentials.ui.components.HelpAndGuidesContent +import com.sameerasw.essentials.ui.components.WhatsNewCustomContent import com.sameerasw.essentials.ui.components.cards.IconToggleItem -import com.sameerasw.essentials.ui.components.sheets.UpdateBottomSheet import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer -import com.sameerasw.essentials.viewmodels.MainViewModel -import com.sameerasw.essentials.utils.HapticUtil -import com.sameerasw.essentials.utils.DeviceUtils -import androidx.compose.ui.unit.sp import com.sameerasw.essentials.ui.components.pickers.CrashReportingPicker import com.sameerasw.essentials.ui.components.pickers.LanguagePicker +import com.sameerasw.essentials.ui.components.sheets.UpdateBottomSheet import com.sameerasw.essentials.ui.components.text.SimpleMarkdown -import com.sameerasw.essentials.ui.components.WhatsNewCustomContent +import com.sameerasw.essentials.ui.theme.GoogleSansFlexRounded +import com.sameerasw.essentials.utils.DeviceUtils +import com.sameerasw.essentials.utils.HapticUtil +import com.sameerasw.essentials.viewmodels.MainViewModel import kotlinx.coroutines.launch import kotlin.math.PI import kotlin.math.atan2 @@ -70,7 +109,7 @@ fun WelcomeScreen( ) { val view = LocalView.current val context = LocalContext.current - val scope = rememberCoroutineScope() + rememberCoroutineScope() var currentStep by remember { mutableStateOf(OnboardingStep.WELCOME) } val rotationAnimatable = remember { Animatable(0f) } @@ -231,8 +270,10 @@ fun WelcomeStepContent( onDragStart = { scope.launch { rotationAnimatable.stop() } currentRotation = rotationAnimatable.value - lastMajorNotch = kotlin.math.round(currentRotation / majorStep).toInt() - lastMinorNotch = kotlin.math.round(currentRotation / minorStep).toInt() + lastMajorNotch = + kotlin.math.round(currentRotation / majorStep).toInt() + lastMinorNotch = + kotlin.math.round(currentRotation / minorStep).toInt() }, onDrag = { change, _ -> val oldAngle = atan2( @@ -259,13 +300,15 @@ fun WelcomeStepContent( } // Minor notches - Subtle texture only during drag - val currentMinorNotch = kotlin.math.round(currentRotation / minorStep).toInt() + val currentMinorNotch = + kotlin.math.round(currentRotation / minorStep).toInt() if (currentMinorNotch != lastMinorNotch) { HapticUtil.performMicroHaptic(view) lastMinorNotch = currentMinorNotch } - lastMajorNotch = kotlin.math.round(currentRotation / majorStep).toInt() + lastMajorNotch = + kotlin.math.round(currentRotation / majorStep).toInt() scope.launch { rotationAnimatable.snapTo(currentRotation) @@ -280,7 +323,8 @@ fun WelcomeStepContent( stiffness = Spring.StiffnessLow ) ) { - val currentMajorNotch = kotlin.math.round(value / majorStep).toInt() + val currentMajorNotch = + kotlin.math.round(value / majorStep).toInt() if (currentMajorNotch != lastMajorNotch) { HapticUtil.performMediumHaptic(view) lastMajorNotch = currentMajorNotch @@ -351,7 +395,7 @@ fun WelcomeStepContent( } Spacer(modifier = Modifier.height(16.dp)) - + if (!isWhatsNewFlow) { val appLanguage by viewModel.appLanguage RoundedCardContainer(modifier = Modifier.padding(horizontal = 16.dp)) { @@ -431,7 +475,7 @@ fun AcknowledgementStepContent( .padding(horizontal = 24.dp) .verticalScroll(rememberScrollState()) ) { - + Spacer(modifier = Modifier.height(16.dp)) Text( @@ -602,12 +646,16 @@ fun FeatureIntroStepContent( verticalArrangement = Arrangement.spacedBy(12.dp) ) { GifItem( - modifier = Modifier.fillMaxWidth().height(320.dp), + modifier = Modifier + .fillMaxWidth() + .height(320.dp), imageLoader = imageLoader, gifResId = R.drawable.feature_help ) GifItem( - modifier = Modifier.fillMaxWidth().height(320.dp), + modifier = Modifier + .fillMaxWidth() + .height(320.dp), imageLoader = imageLoader, gifResId = R.drawable.tile_help ) @@ -776,7 +824,9 @@ fun WhatsNewStepContent( ) { if (isCheckingUpdate) { Box( - modifier = Modifier.fillMaxWidth().height(200.dp), + modifier = Modifier + .fillMaxWidth() + .height(200.dp), contentAlignment = Alignment.Center ) { CircularProgressIndicator() @@ -799,7 +849,8 @@ fun WhatsNewStepContent( Spacer(modifier = Modifier.height(16.dp)) - val webUrl = updateInfo?.releaseUrl ?: "https://github.com/sameerasw/essentials/releases" + val webUrl = updateInfo?.releaseUrl + ?: "https://github.com/sameerasw/essentials/releases" TextButton( onClick = { HapticUtil.performVirtualKeyHaptic(view) @@ -943,7 +994,9 @@ fun PreferencesStepContent( Text( text = stringResource(R.string.label_app_settings), style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(start = 12.dp, bottom = 8.dp).fillMaxWidth(), + modifier = Modifier + .padding(start = 12.dp, bottom = 8.dp) + .fillMaxWidth(), color = MaterialTheme.colorScheme.primary, textAlign = TextAlign.Start ) @@ -993,7 +1046,9 @@ fun PreferencesStepContent( Text( text = stringResource(R.string.label_updates), style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(start = 12.dp, bottom = 8.dp).fillMaxWidth(), + modifier = Modifier + .padding(start = 12.dp, bottom = 8.dp) + .fillMaxWidth(), color = MaterialTheme.colorScheme.primary, textAlign = TextAlign.Start ) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt index c44f0dfe1..489482255 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt @@ -93,7 +93,7 @@ fun AlwaysOnDisplaySettingsUI( modifier = Modifier.highlight(highlightSetting == "notification_glance_same_apps") ) - val isAccessibilityEnabled = viewModel.isAccessibilityEnabled.value + viewModel.isAccessibilityEnabled.value IconToggleItem( iconRes = R.drawable.rounded_power_settings_new_24, title = stringResource(R.string.feat_aod_force_turn_off_title), @@ -101,9 +101,14 @@ fun AlwaysOnDisplaySettingsUI( onCheckedChange = { checked -> HapticUtil.performVirtualKeyHaptic(view) // Check latest snapshot inside lambda - val currentlyEnabled = com.sameerasw.essentials.utils.PermissionUtils.isAccessibilityServiceEnabled(context) + val currentlyEnabled = + com.sameerasw.essentials.utils.PermissionUtils.isAccessibilityServiceEnabled( + context + ) if (checked && !currentlyEnabled) { - com.sameerasw.essentials.utils.PermissionUtils.openAccessibilitySettings(context) + com.sameerasw.essentials.utils.PermissionUtils.openAccessibilitySettings( + context + ) } else { viewModel.toggleAodForceTurnOffEnabled(checked) } @@ -119,12 +124,12 @@ fun AlwaysOnDisplaySettingsUI( color = MaterialTheme.colorScheme.onSurfaceVariant ) - Text( - text = stringResource(R.string.feat_aod_force_turn_off_desc), - modifier = Modifier.padding(16.dp), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) + Text( + text = stringResource(R.string.feat_aod_force_turn_off_desc), + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) if (!viewModel.isNotificationGlanceSameAsLightingEnabled.value) { Button( @@ -145,7 +150,12 @@ fun AlwaysOnDisplaySettingsUI( AppSelectionSheet( onDismissRequest = { showAppSelectionSheet = false }, onLoadApps = { viewModel.loadNotificationGlanceSelectedApps(it) }, - onSaveApps = { ctx, apps -> viewModel.saveNotificationGlanceSelectedApps(ctx, apps) }, + onSaveApps = { ctx, apps -> + viewModel.saveNotificationGlanceSelectedApps( + ctx, + apps + ) + }, onAppToggle = { ctx, pkg, enabled -> viewModel.updateNotificationGlanceAppEnabled( ctx, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AppLockSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AppLockSettingsUI.kt index 0f2bb2174..4f2a64571 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AppLockSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AppLockSettingsUI.kt @@ -18,12 +18,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.fragment.app.FragmentActivity import com.sameerasw.essentials.R +import com.sameerasw.essentials.ui.components.cards.ConfigPickerItem import com.sameerasw.essentials.ui.components.cards.FeatureCard import com.sameerasw.essentials.ui.components.cards.IconToggleItem -import com.sameerasw.essentials.ui.components.cards.ConfigPickerItem import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer -import com.sameerasw.essentials.ui.components.sheets.AppSelectionSheet import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem +import com.sameerasw.essentials.ui.components.sheets.AppSelectionSheet import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.utils.BiometricHelper import com.sameerasw.essentials.viewmodels.MainViewModel @@ -42,8 +42,9 @@ fun AppLockSettingsUI( val isUseUsageAccess by viewModel.isUseUsageAccess val isAccessibilityEnabled by viewModel.isAccessibilityEnabled val isUsageStatsPermissionGranted by viewModel.isUsageStatsPermissionGranted - - val canEnableAppLock = if (isUseUsageAccess) isUsageStatsPermissionGranted else isAccessibilityEnabled + + val canEnableAppLock = + if (isUseUsageAccess) isUsageStatsPermissionGranted else isAccessibilityEnabled val delayLabels = listOf( stringResource(R.string.app_lock_auto_lock_delay_none), diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/BatteriesSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/BatteriesSettingsUI.kt index ddc6f7671..18d9b5703 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/BatteriesSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/BatteriesSettingsUI.kt @@ -4,25 +4,23 @@ import android.content.Intent import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.material3.Slider -import androidx.compose.material3.Text -import androidx.compose.material3.ListItem -import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightPulseSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightPulseSettingsUI.kt index b77cabe0f..adc6741b7 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightPulseSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FlashlightPulseSettingsUI.kt @@ -24,8 +24,8 @@ 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.sheets.AppSelectionSheet -import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.ui.components.sliders.ConfigSliderItem +import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.viewmodels.MainViewModel diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt index 127ef0767..8947aced8 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/FreezeSettingsUI.kt @@ -5,8 +5,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,6 +13,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button @@ -36,23 +36,22 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.sameerasw.essentials.R import com.sameerasw.essentials.domain.model.FreezeMode -import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu -import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem import com.sameerasw.essentials.ui.components.cards.AppToggleItem import com.sameerasw.essentials.ui.components.cards.FeatureCard import com.sameerasw.essentials.ui.components.cards.IconToggleItem import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer +import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu +import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem import com.sameerasw.essentials.ui.components.pickers.SegmentedPicker import com.sameerasw.essentials.ui.components.sheets.AppSelectionSheet import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet @@ -176,7 +175,10 @@ fun FreezeSettingsUI( modifier = Modifier.size(20.dp) ) Spacer(Modifier.size(8.dp)) - Text(stringResource(R.string.action_freeze), fontSize = dimensionResource(R.dimen.font_small).value.sp) + Text( + stringResource(R.string.action_freeze), + fontSize = dimensionResource(R.dimen.font_small).value.sp + ) } // Unfreeze Button @@ -195,7 +197,10 @@ fun FreezeSettingsUI( modifier = Modifier.size(20.dp) ) Spacer(Modifier.size(8.dp)) - Text(stringResource(R.string.action_unfreeze), fontSize = dimensionResource(R.dimen.font_small).value.sp) + Text( + stringResource(R.string.action_unfreeze), + fontSize = dimensionResource(R.dimen.font_small).value.sp + ) } // More Menu Button @@ -204,8 +209,9 @@ fun FreezeSettingsUI( HapticUtil.performVirtualKeyHaptic(view) isMenuExpanded = true }, - enabled = isShizukuAvailable && isShizukuPermissionGranted - , modifier = Modifier.size(dimensionResource(R.dimen.button_normal))) { + enabled = isShizukuAvailable && isShizukuPermissionGranted, + modifier = Modifier.size(dimensionResource(R.dimen.button_normal)) + ) { Icon( painter = painterResource(id = R.drawable.rounded_more_vert_24), contentDescription = stringResource(R.string.content_desc_more_options) @@ -300,127 +306,127 @@ fun FreezeSettingsUI( color = MaterialTheme.colorScheme.onSurfaceVariant ) - RoundedCardContainer{ - SegmentedPicker( - items = FreezeMode.entries, - selectedItem = FreezeMode.fromInt(viewModel.freezeMode.intValue), - onItemSelected = { mode -> - if (viewModel.freezeMode.intValue != mode.value) { - if (viewModel.anyAppsCurrentlyFrozen(context)) { - showModeWarningResult = true - } else { - HapticUtil.performVirtualKeyHaptic(view) - viewModel.setFreezeMode(mode.value, context) + RoundedCardContainer { + SegmentedPicker( + items = FreezeMode.entries, + selectedItem = FreezeMode.fromInt(viewModel.freezeMode.intValue), + onItemSelected = { mode -> + if (viewModel.freezeMode.intValue != mode.value) { + if (viewModel.anyAppsCurrentlyFrozen(context)) { + showModeWarningResult = true + } else { + HapticUtil.performVirtualKeyHaptic(view) + viewModel.setFreezeMode(mode.value, context) + } } - } - }, - labelProvider = { mode -> - when (mode) { - FreezeMode.FREEZE -> context.getString(R.string.freeze_mode_freeze) - FreezeMode.SUSPEND -> context.getString(R.string.freeze_mode_suspend) - } - }, - iconProvider = { mode -> - Icon( - painter = painterResource( - id = when (mode) { - FreezeMode.FREEZE -> R.drawable.rounded_mode_cool_24 - FreezeMode.SUSPEND -> R.drawable.rounded_pause_24 + }, + labelProvider = { mode -> + when (mode) { + FreezeMode.FREEZE -> context.getString(R.string.freeze_mode_freeze) + FreezeMode.SUSPEND -> context.getString(R.string.freeze_mode_suspend) + } + }, + iconProvider = { mode -> + Icon( + painter = painterResource( + id = when (mode) { + FreezeMode.FREEZE -> R.drawable.rounded_mode_cool_24 + FreezeMode.SUSPEND -> R.drawable.rounded_pause_24 + } + ), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + }, + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.surfaceBright, + ) + .padding(8.dp) + ) { + HorizontalPager( + state = pagerState, + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.Top + ) { page -> + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.surfaceContainer, + shape = RoundedCornerShape(20.dp) + ) + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + if (page == 0) { + Text( + text = stringResource(R.string.freeze_mode_description_freeze_title), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringResource(R.string.freeze_mode_description_freeze_body), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = stringResource(R.string.freeze_mode_description_freeze_warning), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.error, + fontWeight = FontWeight.Bold + ) + } else { + Text( + text = stringResource(R.string.freeze_mode_description_suspend_title), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringResource(R.string.freeze_mode_description_suspend_body), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = stringResource(R.string.freeze_mode_description_suspend_footer), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontWeight = FontWeight.Medium + ) } - ), - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - }, - ) + } + } - Column( - modifier = Modifier - .fillMaxWidth() - .background( - color = MaterialTheme.colorScheme.surfaceBright, - ) - .padding(8.dp) - ) { - HorizontalPager( - state = pagerState, - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.Top - ) { page -> - Column( + // Pagination Indicators + Row( modifier = Modifier .fillMaxWidth() - .background( - color = MaterialTheme.colorScheme.surfaceContainer, - shape = RoundedCornerShape(20.dp) - ) - .padding(20.dp), - verticalArrangement = Arrangement.spacedBy(10.dp) + .padding(top = 8.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically ) { - if (page == 0) { - Text( - text = stringResource(R.string.freeze_mode_description_freeze_title), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary, - ) - Text( - text = stringResource(R.string.freeze_mode_description_freeze_body), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Text( - text = stringResource(R.string.freeze_mode_description_freeze_warning), - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.error, - fontWeight = FontWeight.Bold + repeat(2) { iteration -> + val isActive = pagerState.currentPage == iteration + val color by animateColorAsState( + targetValue = if (isActive) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f), + label = "dotColor" ) - } else { - Text( - text = stringResource(R.string.freeze_mode_description_suspend_title), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary, - ) - Text( - text = stringResource(R.string.freeze_mode_description_suspend_body), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Text( - text = stringResource(R.string.freeze_mode_description_suspend_footer), - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontWeight = FontWeight.Medium + + Box( + modifier = Modifier + .padding(4.dp) + .size(if (isActive) 8.dp else 6.dp) + .background(color, CircleShape) ) } } } - - // Pagination Indicators - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - repeat(2) { iteration -> - val isActive = pagerState.currentPage == iteration - val color by animateColorAsState( - targetValue = if (isActive) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f), - label = "dotColor" - ) - - Box( - modifier = Modifier - .padding(4.dp) - .size(if (isActive) 8.dp else 6.dp) - .background(color, CircleShape) - ) - } - } } - } Text( text = stringResource(R.string.settings_section_automation), @@ -634,7 +640,9 @@ fun FreezeSettingsUI( androidx.compose.material3.AlertDialog( onDismissRequest = { showModeWarningResult = false }, confirmButton = { - androidx.compose.material3.TextButton(onClick = { showModeWarningResult = false }) { + androidx.compose.material3.TextButton(onClick = { + showModeWarningResult = false + }) { Text(stringResource(id = R.string.action_ok)) } }, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/KeyboardSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/KeyboardSettingsUI.kt index 41db7d279..0a4c00c43 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/KeyboardSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/KeyboardSettingsUI.kt @@ -1,7 +1,6 @@ package com.sameerasw.essentials.ui.composables.configs import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -319,7 +318,7 @@ fun KeyboardSettingsUI( } Spacer(Modifier.height(32.dp)) - + if (viewModel.isUserDictionarySheetVisible.value) { com.sameerasw.essentials.ui.composables.sheets.UserDictionaryBottomSheet( viewModel = viewModel, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LiveWallpaperSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LiveWallpaperSettingsUI.kt index ee124a939..7bfc1a510 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LiveWallpaperSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/LiveWallpaperSettingsUI.kt @@ -13,13 +13,33 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -62,8 +82,13 @@ fun LiveWallpaperSettingsUI( onResult = { uri -> uri?.let { try { - context.contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION) - } catch (e: Exception) { e.printStackTrace() } + context.contentResolver.takePersistableUriPermission( + it, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } catch (e: Exception) { + e.printStackTrace() + } repository.addLiveWallpaperCustomVideo(it.toString()) availableVideos = repository.getLiveWallpaperAvailableVideos() selectedVideo = it.toString() @@ -71,12 +96,13 @@ fun LiveWallpaperSettingsUI( } } ) - + LaunchedEffect(availableVideos) { if (selectedVideo == SettingsRepository.LIVE_WALLPAPER_DEFAULT_VIDEO && availableVideos.isNotEmpty()) { - val first = availableVideos.firstOrNull { it != SettingsRepository.LIVE_WALLPAPER_DEFAULT_VIDEO } - ?: availableVideos.firstOrNull() - + val first = + availableVideos.firstOrNull { it != SettingsRepository.LIVE_WALLPAPER_DEFAULT_VIDEO } + ?: availableVideos.firstOrNull() + if (first != null) { selectedVideo = first repository.saveLiveWallpaperSelectedVideo(first) @@ -110,7 +136,9 @@ fun LiveWallpaperSettingsUI( Icon( painter = painterResource(id = R.drawable.rounded_open_in_new_24), contentDescription = null, - modifier = Modifier.size(24.dp).padding(end = 8.dp) + modifier = Modifier + .size(24.dp) + .padding(end = 8.dp) ) Text( text = stringResource(R.string.btn_apply), @@ -130,7 +158,7 @@ fun LiveWallpaperSettingsUI( SettingsRepository.LIVE_WALLPAPER_TRIGGER_UNLOCK to stringResource(R.string.live_wallpaper_trigger_unlock), SettingsRepository.LIVE_WALLPAPER_TRIGGER_SCREEN_ON to stringResource(R.string.live_wallpaper_trigger_screen_on) ) - + SegmentedPicker( items = options, selectedItem = options.find { it.first == playbackTrigger } ?: options.first(), @@ -149,7 +177,7 @@ fun LiveWallpaperSettingsUI( modifier = Modifier.padding(start = 16.dp, top = 8.dp), color = MaterialTheme.colorScheme.onSurfaceVariant ) - + LazyVerticalGrid( columns = GridCells.Fixed(3), contentPadding = PaddingValues(vertical = 8.dp), @@ -185,14 +213,19 @@ fun LiveWallpaperSettingsUI( ) } } - + Spacer(modifier = Modifier.height(80.dp)) } } @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) @Composable -fun ThumbnailItem(videoName: String, isSelected: Boolean, onClick: () -> Unit, onRemove: (() -> Unit)? = null) { +fun ThumbnailItem( + videoName: String, + isSelected: Boolean, + onClick: () -> Unit, + onRemove: (() -> Unit)? = null +) { val context = LocalContext.current val view = LocalView.current var thumbnail by remember { mutableStateOf(null) } @@ -207,7 +240,9 @@ fun ThumbnailItem(videoName: String, isSelected: Boolean, onClick: () -> Unit, o val uri = Uri.parse("android.resource://${context.packageName}/$resId") retriever.setDataSource(context, uri) retriever.getFrameAtTime(0) - } catch (e: Exception) { null } + } catch (e: Exception) { + null + } } } else { try { @@ -216,7 +251,9 @@ fun ThumbnailItem(videoName: String, isSelected: Boolean, onClick: () -> Unit, o retriever.setDataSource(context, uri) retriever.getFrameAtTime(0) } - } catch (e: Exception) { null } + } catch (e: Exception) { + null + } } } } @@ -251,7 +288,9 @@ fun ThumbnailItem(videoName: String, isSelected: Boolean, onClick: () -> Unit, o contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize() ) - } ?: Box(modifier = Modifier.fillMaxSize().background(Color.Black)) + } ?: Box(modifier = Modifier + .fillMaxSize() + .background(Color.Black)) if (isSelected) { Box( 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 7e00444fd..027e04771 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 @@ -2,20 +2,35 @@ package com.sameerasw.essentials.ui.composables.configs import android.os.Build import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.LinearWavyProgressIndicator +import androidx.compose.material3.LoadingIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -26,7 +41,6 @@ import com.sameerasw.essentials.R import com.sameerasw.essentials.ui.components.cards.LocationAlarmCard import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.components.sheets.LocationReachedBottomSheet -import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.viewmodels.LocationReachedViewModel import com.sameerasw.essentials.viewmodels.MainViewModel @@ -84,9 +98,9 @@ fun LocationReachedSettingsUI( onStop = { locationViewModel.stopTracking() }, onPause = { locationViewModel.pauseTracking() }, onResume = { locationViewModel.resumeTracking() }, - onStart = { + onStart = { HapticUtil.performVirtualKeyHaptic(view) - locationViewModel.startTracking(it) + locationViewModel.startTracking(it) } ) } @@ -110,34 +124,34 @@ fun LocationReachedSettingsUI( RoundedCardContainer( modifier = Modifier.fillMaxWidth(), ) { - savedAlarms.forEachIndexed { index, alarm -> - LocationAlarmCard( - alarm = alarm, - isActive = activeAlarmId == alarm.id, - isAnyTracking = activeAlarmId != null, - onStart = { - HapticUtil.performVirtualKeyHaptic(view) - locationViewModel.startTracking(alarm.id) - }, - onStop = { - HapticUtil.performVirtualKeyHaptic(view) - locationViewModel.stopTracking() - }, - onPause = { - HapticUtil.performVirtualKeyHaptic(view) - locationViewModel.pauseTracking() - }, - onResume = { - HapticUtil.performVirtualKeyHaptic(view) - locationViewModel.resumeTracking() - }, - onClick = { - HapticUtil.performVirtualKeyHaptic(view) - locationViewModel.setTempAlarm(alarm) - locationViewModel.setShowBottomSheet(true) - } - ) - } + savedAlarms.forEachIndexed { index, alarm -> + LocationAlarmCard( + alarm = alarm, + isActive = activeAlarmId == alarm.id, + isAnyTracking = activeAlarmId != null, + onStart = { + HapticUtil.performVirtualKeyHaptic(view) + locationViewModel.startTracking(alarm.id) + }, + onStop = { + HapticUtil.performVirtualKeyHaptic(view) + locationViewModel.stopTracking() + }, + onPause = { + HapticUtil.performVirtualKeyHaptic(view) + locationViewModel.pauseTracking() + }, + onResume = { + HapticUtil.performVirtualKeyHaptic(view) + locationViewModel.resumeTracking() + }, + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + locationViewModel.setTempAlarm(alarm) + locationViewModel.setShowBottomSheet(true) + } + ) + } } } } @@ -185,14 +199,18 @@ fun LocationReachedSettingsUI( title = stringResource(R.string.location_reached_fsi_title), description = stringResource(R.string.location_reached_fsi_desc), isChecked = false, - onCheckedChange = { mainViewModel.requestFullScreenIntentPermission(context) }, + onCheckedChange = { + mainViewModel.requestFullScreenIntentPermission( + context + ) + }, iconRes = R.drawable.rounded_info_24, showToggle = false ) } } } - + item { Spacer(modifier = Modifier.height(100.dp)) } @@ -238,7 +256,11 @@ fun TopStatusCard( ) { if (isTracking) { val context = LocalContext.current - val iconResId = context.resources.getIdentifier(displayAlarm?.iconResName ?: "round_navigation_24", "drawable", context.packageName) + val iconResId = context.resources.getIdentifier( + displayAlarm?.iconResName ?: "round_navigation_24", + "drawable", + context.packageName + ) Icon( painter = painterResource(id = if (iconResId != 0) iconResId else R.drawable.round_navigation_24), @@ -337,7 +359,11 @@ fun TopStatusCard( contentDescription = null ) Spacer(modifier = Modifier.width(8.dp)) - Text(if (isPaused) stringResource(R.string.location_reached_resume) else stringResource(R.string.location_reached_pause)) + Text( + if (isPaused) stringResource(R.string.location_reached_resume) else stringResource( + R.string.location_reached_pause + ) + ) } Button( @@ -351,14 +377,21 @@ fun TopStatusCard( ), shape = androidx.compose.foundation.shape.CircleShape ) { - Icon(painterResource(R.drawable.rounded_close_24), contentDescription = null) + 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 - val iconResId = context.resources.getIdentifier(lastTrip.iconResName, "drawable", context.packageName) + val iconResId = context.resources.getIdentifier( + lastTrip.iconResName, + "drawable", + context.packageName + ) Icon( painter = painterResource(id = if (iconResId != 0) iconResId else R.drawable.round_navigation_24), 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 index c4a198858..2b41004a9 100644 --- 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 @@ -3,37 +3,52 @@ 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.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.material3.carousel.HorizontalMultiBrowseCarousel import androidx.compose.material3.carousel.rememberCarouselState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow 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.input.pointer.pointerInput 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) +import com.sameerasw.essentials.viewmodels.MainViewModel + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun LockScreenClockSettingsUI( viewModel: MainViewModel, @@ -59,14 +74,46 @@ fun LockScreenClockSettingsUI( 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) + 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 + ) ) } @@ -84,7 +131,8 @@ fun LockScreenClockSettingsUI( ) } - val isDefaultStyleSelected = currentClockId == "DEFAULT" || currentClockId == "DIGITAL_CLOCK_FLEX" + val isDefaultStyleSelected = + currentClockId == "DEFAULT" || currentClockId == "DIGITAL_CLOCK_FLEX" val carouselState = rememberCarouselState { clockOptions.size } @@ -122,7 +170,8 @@ fun LockScreenClockSettingsUI( .height(200.dp) ) { index -> val option = clockOptions[index] - val isSelected = if (option.id == "DEFAULT") isDefaultStyleSelected else currentClockId == option.id + val isSelected = + if (option.id == "DEFAULT") isDefaultStyleSelected else currentClockId == option.id Box( modifier = Modifier @@ -171,11 +220,13 @@ fun LockScreenClockSettingsUI( RoundedCardContainer { Column( - modifier = Modifier.fillMaxWidth() - .background( - color = MaterialTheme.colorScheme.surfaceBright, - shape = RoundedCornerShape(MaterialTheme.shapes.extraSmall.bottomEnd) - ).padding(16.dp), + 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 @@ -191,7 +242,11 @@ fun LockScreenClockSettingsUI( isSelected = viewModel.lockScreenClockSelectedColorId.value == colorOption.id, onClick = { HapticUtil.performUIHaptic(view) - viewModel.setLockScreenClockColor(colorOption.id, colorOption.seedColor, context) + viewModel.setLockScreenClockColor( + colorOption.id, + colorOption.seedColor, + context + ) } ) } @@ -289,7 +344,9 @@ fun ColorCircle( ) .border( width = if (isSelected) 3.dp else 1.dp, - color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline.copy(alpha = 0.2f), + color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline.copy( + alpha = 0.2f + ), shape = CircleShape ) .pointerInput(colorOption.id) { @@ -307,7 +364,7 @@ fun ColorCircle( tint = MaterialTheme.colorScheme.onSurfaceVariant ) } - + if (isSelected) { Box( modifier = Modifier diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/NotificationLightingSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/NotificationLightingSettingsUI.kt index 390e09faf..a5e294399 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/NotificationLightingSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/NotificationLightingSettingsUI.kt @@ -1,5 +1,7 @@ package com.sameerasw.essentials.ui.composables.configs +import android.content.Intent +import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -20,9 +22,6 @@ import androidx.compose.material3.ButtonGroupDefaults import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SegmentedButton -import androidx.compose.material3.SegmentedButtonDefaults -import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text import androidx.compose.material3.ToggleButton import androidx.compose.runtime.Composable @@ -48,7 +47,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.sameerasw.essentials.R import com.sameerasw.essentials.domain.model.NotificationLightingColorMode -import com.sameerasw.essentials.domain.model.NotificationLightingSide import com.sameerasw.essentials.domain.model.NotificationLightingStyle import com.sameerasw.essentials.domain.model.NotificationLightingSweepPosition import com.sameerasw.essentials.ui.components.cards.IconToggleItem @@ -60,8 +58,6 @@ import com.sameerasw.essentials.ui.components.pickers.NotificationLightingSystem import com.sameerasw.essentials.ui.components.sheets.AppSelectionSheet import com.sameerasw.essentials.ui.components.sheets.PermissionItem import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet -import android.content.Intent -import android.net.Uri import com.sameerasw.essentials.ui.components.sliders.ConfigSliderItem import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.utils.HapticUtil @@ -156,13 +152,18 @@ fun NotificationLightingSettingsUI( HapticUtil.performVirtualKeyHaptic(view) showAppSelectionSheet = true }, - modifier = Modifier.fillMaxWidth().height(64.dp) + modifier = Modifier + .fillMaxWidth() + .height(64.dp) ) { Row( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically - ){ - Icon(painter = painterResource(id = R.drawable.rounded_apps_24), contentDescription = "Apps") + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_apps_24), + contentDescription = "Apps" + ) Text(stringResource(R.string.action_select_apps)) } } @@ -248,7 +249,7 @@ fun NotificationLightingSettingsUI( ) } } - } + } // Stroke Adjustment Section (For STROKE style) if (style == NotificationLightingStyle.STROKE) { @@ -611,135 +612,135 @@ fun NotificationLightingSettingsUI( ) if (viewModel.notificationLightingColorMode.value == NotificationLightingColorMode.CUSTOM) { - Column( - modifier = Modifier - .fillMaxWidth() - .background( - color = MaterialTheme.colorScheme.surfaceBright, - shape = RoundedCornerShape(MaterialTheme.shapes.extraSmall.bottomEnd) - ) - .padding(bottom = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - val allColors = remember { - val colors = mutableListOf() - val totalColumns = 21 - - for (page in 0..2) { - - val row1 = mutableListOf() - val row2 = mutableListOf() - val row3 = mutableListOf() - - for (col in 0..6) { - val globalCol = page * 7 + col - val hue = (globalCol.toFloat() / totalColumns) * 360f - - // Row 1: Light - row1.add( - android.graphics.Color.HSVToColor( - floatArrayOf( - hue, - 0.4f, - 1.0f + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.surfaceBright, + shape = RoundedCornerShape(MaterialTheme.shapes.extraSmall.bottomEnd) + ) + .padding(bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + val allColors = remember { + val colors = mutableListOf() + val totalColumns = 21 + + for (page in 0..2) { + + val row1 = mutableListOf() + val row2 = mutableListOf() + val row3 = mutableListOf() + + for (col in 0..6) { + val globalCol = page * 7 + col + val hue = (globalCol.toFloat() / totalColumns) * 360f + + // Row 1: Light + row1.add( + android.graphics.Color.HSVToColor( + floatArrayOf( + hue, + 0.4f, + 1.0f + ) ) ) - ) - // Row 2: Regular - row2.add( - android.graphics.Color.HSVToColor( - floatArrayOf( - hue, - 0.85f, - 1.0f + // Row 2: Regular + row2.add( + android.graphics.Color.HSVToColor( + floatArrayOf( + hue, + 0.85f, + 1.0f + ) ) ) - ) - // Row 3: Dark - row3.add( - android.graphics.Color.HSVToColor( - floatArrayOf( - hue, - 1.0f, - 0.55f + // Row 3: Dark + row3.add( + android.graphics.Color.HSVToColor( + floatArrayOf( + hue, + 1.0f, + 0.55f + ) ) ) - ) + } + colors.addAll(row1) + colors.addAll(row2) + colors.addAll(row3) } - colors.addAll(row1) - colors.addAll(row2) - colors.addAll(row3) + colors } - colors - } - val pages = allColors.chunked(21) - val pagerState = rememberPagerState(pageCount = { pages.size }) - val currentCustomColor = viewModel.notificationLightingCustomColor.intValue + val pages = allColors.chunked(21) + val pagerState = rememberPagerState(pageCount = { pages.size }) + val currentCustomColor = viewModel.notificationLightingCustomColor.intValue - HorizontalPager( - state = pagerState, - modifier = Modifier - .fillMaxWidth() - .height(170.dp) - ) { pageIndex -> - val pageColors = pages[pageIndex] - Column( + HorizontalPager( + state = pagerState, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 8.dp), - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - val rows = pageColors.chunked(7) - rows.forEach { rowColors -> - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - rowColors.forEach { colorInt -> - ColorCircle( - color = Color(colorInt), - isSelected = currentCustomColor == colorInt, - size = 36.dp, - onClick = { - HapticUtil.performVirtualKeyHaptic(view) - viewModel.setNotificationLightingCustomColor( - colorInt, - context - ) - viewModel.triggerNotificationLighting(context) - } - ) + .height(170.dp) + ) { pageIndex -> + val pageColors = pages[pageIndex] + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + val rows = pageColors.chunked(7) + rows.forEach { rowColors -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + rowColors.forEach { colorInt -> + ColorCircle( + color = Color(colorInt), + isSelected = currentCustomColor == colorInt, + size = 36.dp, + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + viewModel.setNotificationLightingCustomColor( + colorInt, + context + ) + viewModel.triggerNotificationLighting(context) + } + ) + } } } } } - } - // Pager Indicator - Row( - Modifier - .height(8.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.Center - ) { - repeat(pages.size) { iteration -> - val color = - if (pagerState.currentPage == iteration) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant - Box( - modifier = Modifier - .padding(horizontal = 4.dp) - .clip(CircleShape) - .background(color) - .size(8.dp) - ) + // Pager Indicator + Row( + Modifier + .height(8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + repeat(pages.size) { iteration -> + val color = + if (pagerState.currentPage == iteration) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant + Box( + modifier = Modifier + .padding(horizontal = 4.dp) + .clip(CircleShape) + .background(color) + .size(8.dp) + ) + } } } } } } - } if (style != NotificationLightingStyle.SYSTEM) { Text( @@ -817,7 +818,10 @@ fun NotificationLightingSettingsUI( dependentFeatures = listOf(R.string.notification_lighting_style_system), actionLabel = R.string.perm_shizuku_install_action, action = { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/thedjchi/Shizuku")) + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://github.com/thedjchi/Shizuku") + ) context.startActivity(intent) }, secondaryActionLabel = R.string.action_refresh, 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 625e490b4..5f0217f17 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 @@ -2,36 +2,34 @@ package com.sameerasw.essentials.ui.composables.configs import android.content.Intent import android.net.Uri +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver 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.sheets.PermissionItem import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet -import com.sameerasw.essentials.ui.modifiers.highlight -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.shrinkVertically -import androidx.compose.runtime.DisposableEffect import com.sameerasw.essentials.ui.components.sliders.ConfigSliderItem +import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.viewmodels.MainViewModel -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.compose.ui.platform.LocalLifecycleOwner enum class PermissionModule { HIDE_GESTURE_BAR, @@ -55,7 +53,8 @@ fun OtherCustomizationsSettingsUI( val isShizukuGranted = viewModel.isShizukuPermissionGranted.value val isRootAvailable = viewModel.isRootAvailable.value val isRootGranted = viewModel.isRootPermissionGranted.value - val isShellGranted = (isShizukuAvailable && isShizukuGranted) || (isRootAvailable && isRootGranted) + val isShellGranted = + (isShizukuAvailable && isShizukuGranted) || (isRootAvailable && isRootGranted) val isAccessibilityEnabled = viewModel.isAccessibilityEnabled.value val isUsageStatsGranted = viewModel.isUsageStatsPermissionGranted.value val useUsageAccess = viewModel.isUseUsageAccess.value @@ -89,9 +88,16 @@ fun OtherCustomizationsSettingsUI( iconRes = R.drawable.rounded_accessibility_new_24, title = R.string.perm_accessibility_title, description = R.string.perm_accessibility_desc_common, - dependentFeatures = listOf(R.string.feat_hide_gesture_bar_on_launcher_title, R.string.feat_circle_to_search_gesture_title), + dependentFeatures = listOf( + R.string.feat_hide_gesture_bar_on_launcher_title, + R.string.feat_circle_to_search_gesture_title + ), actionLabel = if (isAccessibilityEnabled) R.string.label_enabled else R.string.perm_action_enable, - action = { com.sameerasw.essentials.utils.PermissionUtils.openAccessibilitySettings(context) }, + action = { + com.sameerasw.essentials.utils.PermissionUtils.openAccessibilitySettings( + context + ) + }, isGranted = isAccessibilityEnabled ) @@ -108,9 +114,11 @@ fun OtherCustomizationsSettingsUI( val permissionsToShow = when (requestingPermissionFor) { PermissionModule.HIDE_GESTURE_BAR -> listOf(shizukuPermission) PermissionModule.SHOW_ON_LAUNCHER -> { - val appDetectionPermission = if (useUsageAccess) usageStatsPermission else accessibilityPermission + val appDetectionPermission = + if (useUsageAccess) usageStatsPermission else accessibilityPermission listOf(shizukuPermission, appDetectionPermission) } + PermissionModule.CIRCLE_TO_SEARCH -> listOf(shizukuPermission, accessibilityPermission) PermissionModule.DISABLE_ROTATION_SUGGESTION -> listOf(shizukuPermission) else -> emptyList() @@ -129,12 +137,15 @@ fun OtherCustomizationsSettingsUI( .padding(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp) ) { - val isShizukuGranted = viewModel.isShizukuAvailable.value && viewModel.isShizukuPermissionGranted.value - val isRootGranted = viewModel.isRootAvailable.value && viewModel.isRootPermissionGranted.value + val isShizukuGranted = + viewModel.isShizukuAvailable.value && viewModel.isShizukuPermissionGranted.value + val isRootGranted = + viewModel.isRootAvailable.value && viewModel.isRootPermissionGranted.value val isShellGranted = isShizukuGranted || isRootGranted val isAccessibilityEnabled = viewModel.isAccessibilityEnabled.value val isUsageStatsGranted = viewModel.isUsageStatsPermissionGranted.value - val isAppDetectionGranted = if (viewModel.isUseUsageAccess.value) isUsageStatsGranted else isAccessibilityEnabled + val isAppDetectionGranted = + if (viewModel.isUseUsageAccess.value) isUsageStatsGranted else isAccessibilityEnabled RoundedCardContainer( modifier = Modifier, 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 352be8299..433a93007 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 @@ -51,7 +51,6 @@ import com.sameerasw.essentials.services.tiles.AdaptiveBrightnessTileService import com.sameerasw.essentials.services.tiles.AlwaysOnDisplayTileService import com.sameerasw.essentials.services.tiles.AppFreezingTileService import com.sameerasw.essentials.services.tiles.AppLockTileService -import com.sameerasw.essentials.services.tiles.BatteryNotificationTileService import com.sameerasw.essentials.services.tiles.BubblesTileService import com.sameerasw.essentials.services.tiles.CaffeinateTileService import com.sameerasw.essentials.services.tiles.ChargeQuickTileService @@ -66,8 +65,8 @@ import com.sameerasw.essentials.services.tiles.NotificationLightingTileService import com.sameerasw.essentials.services.tiles.PrivateDnsTileService import com.sameerasw.essentials.services.tiles.PrivateNotificationsTileService import com.sameerasw.essentials.services.tiles.RefreshRateTileService -import com.sameerasw.essentials.services.tiles.ScreenLockedSecurityTileService import com.sameerasw.essentials.services.tiles.ScaleAnimationsTileService +import com.sameerasw.essentials.services.tiles.ScreenLockedSecurityTileService import com.sameerasw.essentials.services.tiles.SoundModeTileService import com.sameerasw.essentials.services.tiles.StayAwakeTileService import com.sameerasw.essentials.services.tiles.TapToWakeTileService @@ -461,10 +460,10 @@ fun QuickSettingsTilesSettingsUI( val addedTiles by viewModel.addedQSTiles val componentName = ComponentName(context, tile.serviceClass) - val isAdded = addedTiles.any { - it.contains(componentName.flattenToString()) || - it.contains(componentName.flattenToShortString()) || - it.contains(tile.serviceClass.name) + val isAdded = addedTiles.any { + it.contains(componentName.flattenToString()) || + it.contains(componentName.flattenToShortString()) || + it.contains(tile.serviceClass.name) } QSTileCard( @@ -487,7 +486,8 @@ fun QuickSettingsTilesSettingsUI( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val statusBarManager = context.getSystemService(StatusBarManager::class.java) - val componentName = ComponentName(context, tile.serviceClass) + val componentName = + ComponentName(context, tile.serviceClass) statusBarManager.requestAddTileService( componentName, @@ -522,7 +522,7 @@ fun QuickSettingsTilesSettingsUI( } // Determine if we need a spacer for the last odd item if (rowTiles.size < 2) { - androidx.compose.foundation.layout.Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.weight(1f)) } } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RefreshRateSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RefreshRateSettingsUI.kt index 27baf8a52..2f4e73ef2 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RefreshRateSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RefreshRateSettingsUI.kt @@ -153,7 +153,9 @@ fun RefreshRateSettingsUI( text = stringResource(R.string.refresh_rate_reset_desc), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.weight(1f).padding(end = 8.dp) + modifier = Modifier + .weight(1f) + .padding(end = 8.dp) ) Button( onClick = { @@ -173,7 +175,9 @@ fun RefreshRateSettingsUI( text = stringResource(R.string.msg_refresh_rate_permission_required), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.error, - modifier = Modifier.weight(1f).padding(end = 8.dp) + modifier = Modifier + .weight(1f) + .padding(end = 8.dp) ) Button( onClick = { diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RemoteLockSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RemoteLockSettingsUI.kt index 88fc0866c..da9e1cb28 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RemoteLockSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/RemoteLockSettingsUI.kt @@ -1,18 +1,23 @@ package com.sameerasw.essentials.ui.composables.configs -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -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.data.repository.SettingsRepository -import com.sameerasw.essentials.ui.components.cards.ConfigPickerItem import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer -import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.utils.PermissionUtils import com.sameerasw.essentials.viewmodels.MainViewModel @@ -28,7 +33,7 @@ fun RemoteLockSettingsUI( val context = LocalContext.current val settingsRepository = remember { SettingsRepository(context) } var showPermissionSheet by remember { mutableStateOf(false) } - + val isAccessibilityEnabled by mainViewModel.isAccessibilityEnabled val isDeviceAdminEnabled by mainViewModel.isDeviceAdminEnabled val remoteLockMode by watchViewModel.remoteLockMode @@ -102,7 +107,7 @@ fun RemoteLockSettingsUI( modifier = Modifier.highlight(highlightSetting == "remote_lock_mode") ) } - + Text( text = stringResource(R.string.remote_lock_mode_admin_note), style = MaterialTheme.typography.labelMedium, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/ScreenLockedSecuritySettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/ScreenLockedSecuritySettingsUI.kt index 8f50776cf..70ba48bf1 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/ScreenLockedSecuritySettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/ScreenLockedSecuritySettingsUI.kt @@ -25,7 +25,6 @@ import com.sameerasw.essentials.ui.components.sheets.PermissionItem import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.utils.BiometricHelper -import com.sameerasw.essentials.utils.ShellUtils import com.sameerasw.essentials.viewmodels.MainViewModel @Composable @@ -42,7 +41,8 @@ fun ScreenLockedSecuritySettingsUI( val isShizukuGranted = viewModel.isShizukuPermissionGranted.value val isRootAvailable = viewModel.isRootAvailable.value val isRootGranted = viewModel.isRootPermissionGranted.value - val isShellGranted = (isShizukuAvailable && isShizukuGranted) || (isRootAvailable && isRootGranted) + val isShellGranted = + (isShizukuAvailable && isShizukuGranted) || (isRootAvailable && isRootGranted) PermissionsBottomSheet( onDismissRequest = { showPermissionSheet = false }, @@ -90,8 +90,10 @@ fun ScreenLockedSecuritySettingsUI( spacing = 2.dp, cornerRadius = 24.dp ) { - val isShizukuGranted = viewModel.isShizukuAvailable.value && viewModel.isShizukuPermissionGranted.value - val isRootGranted = viewModel.isRootAvailable.value && viewModel.isRootPermissionGranted.value + val isShizukuGranted = + viewModel.isShizukuAvailable.value && viewModel.isShizukuPermissionGranted.value + val isRootGranted = + viewModel.isRootAvailable.value && viewModel.isRootPermissionGranted.value val isShellGranted = isShizukuGranted || isRootGranted IconToggleItem( 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 6aceba2a0..cc22146e9 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 @@ -1,8 +1,18 @@ package com.sameerasw.essentials.ui.composables.configs -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext @@ -29,7 +39,7 @@ fun ShutUpSettingsUI( val context = LocalContext.current var isAppSelectionSheetOpen by remember { mutableStateOf(false) } var selectedConfigForEditing by remember { mutableStateOf(null) } - + val configs by viewModel.shutUpConfigs Column( @@ -65,7 +75,8 @@ fun ShutUpSettingsUI( configs.forEach { config -> val appName = remember(config.packageName) { try { - val appInfo = context.packageManager.getApplicationInfo(config.packageName, 0) + val appInfo = + context.packageManager.getApplicationInfo(config.packageName, 0) context.packageManager.getApplicationLabel(appInfo).toString() } catch (e: Exception) { config.packageName @@ -82,7 +93,7 @@ fun ShutUpSettingsUI( null } } - + FeatureCard( title = appName, description = config.packageName, @@ -134,7 +145,7 @@ fun ShutUpSettingsUI( if (isAppSelectionSheetOpen) { AppSelectionSheet( onDismissRequest = { isAppSelectionSheetOpen = false }, - onLoadApps = { ctx -> + onLoadApps = { ctx -> viewModel.shutUpConfigs.value.map { AppSelection(it.packageName, true) } }, onSaveApps = { ctx, apps -> viewModel.saveShutUpSelectedApps(ctx, apps) } @@ -146,10 +157,11 @@ fun ShutUpSettingsUI( val isFrozen = remember(selectedConfigForEditing) { frozenApps.any { it.packageName == selectedConfigForEditing?.packageName } } - + ShutUpPerAppSettingsSheet( onDismissRequest = { selectedConfigForEditing = null }, - config = configs.find { it.packageName == selectedConfigForEditing?.packageName } ?: selectedConfigForEditing!!, + config = configs.find { it.packageName == selectedConfigForEditing?.packageName } + ?: selectedConfigForEditing!!, onConfigChanged = { viewModel.updateShutUpConfig(it) }, onCreateShortcut = { viewModel.createShutUpShortcut(context, it) }, isFrozen = isFrozen, 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 0698357ec..aa75580a4 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 @@ -3,8 +3,8 @@ package com.sameerasw.essentials.ui.composables.configs import android.Manifest import android.content.Context import android.content.Intent -import android.net.Uri import android.content.pm.PackageManager +import android.net.Uri import androidx.activity.ComponentActivity import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -107,7 +107,10 @@ fun StatusBarIconSettingsUI( dependentFeatures = listOf(R.string.stb_advanced_flags_title), actionLabel = R.string.perm_shizuku_install_action, action = { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/thedjchi/Shizuku")) + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://github.com/thedjchi/Shizuku") + ) context.startActivity(intent) }, isGranted = viewModel.isShizukuAvailable.value || viewModel.isRootAvailable.value @@ -246,19 +249,20 @@ fun StatusBarIconSettingsUI( ) == PackageManager.PERMISSION_GRANTED) if (isSwitchDisabled) { - Box(modifier = Modifier - .matchParentSize() - .clickable { - HapticUtil.performUIHaptic(view) - val hasPermission = ContextCompat.checkSelfPermission( - context, - Manifest.permission.READ_PHONE_STATE - ) == PackageManager.PERMISSION_GRANTED + Box( + modifier = Modifier + .matchParentSize() + .clickable { + HapticUtil.performUIHaptic(view) + val hasPermission = ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_PHONE_STATE + ) == PackageManager.PERMISSION_GRANTED - if (!hasPermission) { - showPermissionSheet = true - } - }) + if (!hasPermission) { + showPermissionSheet = true + } + }) } } @@ -333,7 +337,7 @@ fun StatusBarIconSettingsUI( Row( modifier = Modifier .fillMaxWidth() - .padding(top = 16.dp, start = 20.dp, end = 16.dp, bottom = 2.dp), + .padding(top = 16.dp, start = 20.dp, end = 16.dp, bottom = 2.dp), verticalAlignment = androidx.compose.ui.Alignment.CenterVertically ) { Text( @@ -396,7 +400,8 @@ fun StatusBarIconSettingsUI( color = MaterialTheme.colorScheme.onSurfaceVariant ) - val isAdvancedEnabled = viewModel.isShizukuAvailable.value || viewModel.isRootAvailable.value + val isAdvancedEnabled = + viewModel.isShizukuAvailable.value || viewModel.isRootAvailable.value RoundedCardContainer( modifier = Modifier, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/TextAnimationsSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/TextAnimationsSettingsUI.kt index 169f9dc2b..9bce1ef38 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/TextAnimationsSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/TextAnimationsSettingsUI.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -18,15 +19,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.unit.dp import com.sameerasw.essentials.R 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.utils.HapticUtil -import com.sameerasw.essentials.ui.components.pickers.SegmentedPicker -import androidx.compose.material3.Icon -import androidx.compose.ui.res.painterResource import com.sameerasw.essentials.viewmodels.MainViewModel @OptIn(ExperimentalMaterial3ExpressiveApi::class) @@ -84,10 +84,11 @@ fun TextAnimationsSettingsUI( onCheckedChange = { viewModel.setAutoRotateEnabled(it) } ) - val timeoutValues = listOf(15000L, 30000L, 60000L, 120000L, 300000L, 600000L, 1800000L) - val currentTimeoutIndex = timeoutValues.indexOf(viewModel.screenTimeout.value).coerceAtLeast(0) + val timeoutValues = listOf(15000L, 30000L, 60000L, 120000L, 300000L, 600000L, 1800000L) + val currentTimeoutIndex = + timeoutValues.indexOf(viewModel.screenTimeout.value).coerceAtLeast(0) - com.sameerasw.essentials.ui.components.sliders.ConfigSliderItem( + ConfigSliderItem( title = stringResource(R.string.label_screen_timeout), value = currentTimeoutIndex.toFloat(), onValueChange = { index -> @@ -181,7 +182,7 @@ fun TextAnimationsSettingsUI( RoundedCardContainer(spacing = 2.dp) { val isEnabled = viewModel.hasShizukuPermission.value - + ConfigSliderItem( title = stringResource(R.string.label_smallest_width), value = viewModel.smallestWidth.intValue.toFloat(), @@ -229,7 +230,9 @@ fun TextAnimationsSettingsUI( text = stringResource(R.string.msg_shizuku_permission_required), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.error, - modifier = Modifier.weight(1f).padding(end = 8.dp) + modifier = Modifier + .weight(1f) + .padding(end = 8.dp) ) Button( onClick = { @@ -261,7 +264,10 @@ fun TextAnimationsSettingsUI( title = stringResource(R.string.label_animator_duration_scale), value = viewModel.animatorDurationScale.floatValue, onValueChange = { - viewModel.setAnimationScale(android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, it) + viewModel.setAnimationScale( + android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, + it + ) HapticUtil.performSliderHaptic(view) }, valueRange = 0f..10f, @@ -274,7 +280,10 @@ fun TextAnimationsSettingsUI( title = stringResource(R.string.label_transition_animation_scale), value = viewModel.transitionAnimationScale.floatValue, onValueChange = { - viewModel.setAnimationScale(android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, it) + viewModel.setAnimationScale( + android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, + it + ) HapticUtil.performSliderHaptic(view) }, valueRange = 0f..10f, @@ -287,7 +296,10 @@ fun TextAnimationsSettingsUI( title = stringResource(R.string.label_window_animation_scale), value = viewModel.windowAnimationScale.floatValue, onValueChange = { - viewModel.setAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE, it) + viewModel.setAnimationScale( + android.provider.Settings.Global.WINDOW_ANIMATION_SCALE, + it + ) HapticUtil.performSliderHaptic(view) }, valueRange = 0f..10f, 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 65dcbd686..7e21845d8 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 @@ -1,10 +1,22 @@ package com.sameerasw.essentials.ui.composables.configs import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -103,7 +115,7 @@ fun WatchSettingsUI( color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f), modifier = Modifier.padding(top = 4.dp, bottom = 12.dp) ) - val context = LocalContext.current + LocalContext.current /* Button( onClick = { viewModel.openPlayStoreOnWatch(context) }, @@ -118,9 +130,9 @@ fun WatchSettingsUI( */ Button( - onClick = { + onClick = { HapticUtil.performUIHaptic(view) - uriHandler.openUri("http://github.com/sameerasw/essentials-wear/releases/latest") + uriHandler.openUri("http://github.com/sameerasw/essentials-wear/releases/latest") }, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.buttonColors( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/sheets/UserDictionaryBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/sheets/UserDictionaryBottomSheet.kt index 16b5464c0..7fe6ca555 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/sheets/UserDictionaryBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/sheets/UserDictionaryBottomSheet.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -16,7 +15,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -35,12 +33,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext 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 com.sameerasw.essentials.viewmodels.MainViewModel -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -50,8 +46,8 @@ fun UserDictionaryBottomSheet( ) { val context = LocalContext.current val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - val scope = rememberCoroutineScope() - + rememberCoroutineScope() + // Load words on open LaunchedEffect(Unit) { viewModel.loadUserDictionaryWords(context) @@ -112,8 +108,7 @@ fun UserDictionaryBottomSheet( ) } else { val view = androidx.compose.ui.platform.LocalView.current - LazyColumn( - ) { + LazyColumn { item { RoundedCardContainer( spacing = 2.dp @@ -155,36 +150,39 @@ fun UserDictionaryBottomSheet( RoundedCardContainer( spacing = 2.dp ) { - words.entries.toList().sortedByDescending { it.value }.forEach { (word, freq) -> - Row( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceBright) - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = word, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.weight(1f) - ) - - IconButton( - onClick = { - com.sameerasw.essentials.utils.HapticUtil.performVirtualKeyHaptic(view) - viewModel.deleteUserWord(word, context) - } - ) { - Icon( - painter = painterResource(R.drawable.rounded_delete_24), - contentDescription = "Delete", - tint = MaterialTheme.colorScheme.error - ) - } - } - } + words.entries.toList().sortedByDescending { it.value } + .forEach { (word, _) -> + Row( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceBright) + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = word, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(1f) + ) + + IconButton( + onClick = { + com.sameerasw.essentials.utils.HapticUtil.performVirtualKeyHaptic( + view + ) + viewModel.deleteUserWord(word, context) + } + ) { + Icon( + painter = painterResource(R.drawable.rounded_delete_24), + contentDescription = "Delete", + tint = MaterialTheme.colorScheme.error + ) + } + } + } } } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/watermark/WatermarkScreen.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/watermark/WatermarkScreen.kt index 2257ed43e..a28feb380 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/watermark/WatermarkScreen.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/watermark/WatermarkScreen.kt @@ -133,7 +133,12 @@ fun WatermarkScreen( modifier = Modifier.padding(bottom = 16.dp, end = 16.dp), containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - elevation = androidx.compose.material3.FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp) + elevation = androidx.compose.material3.FloatingActionButtonDefaults.elevation( + 0.dp, + 0.dp, + 0.dp, + 0.dp + ) ) { Icon( painter = painterResource(R.drawable.rounded_edit_24), diff --git a/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiData.kt b/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiData.kt index ccf6e29f4..d8b129a49 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiData.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiData.kt @@ -2,18 +2,18 @@ package com.sameerasw.essentials.ui.ime import android.content.Context import android.util.Log -import com.google.gson.Gson -import com.google.gson.annotations.SerializedName -import com.sameerasw.essentials.R -import java.io.InputStreamReader +import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.compose.runtime.State +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import com.sameerasw.essentials.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.InputStreamReader @androidx.annotation.Keep data class EmojiObject( @@ -50,7 +50,7 @@ object EmojiData { val inputStream = context.assets.open("emojis.json") val reader = InputStreamReader(inputStream) val response = Gson().fromJson(reader, EmojiDataResponse::class.java) - + val loadedCategories = mutableListOf() val loadedAllEmojis = mutableListOf() @@ -60,12 +60,13 @@ object EmojiData { combinedEmojis.addAll(emojiList) loadedAllEmojis.addAll(emojiList) } - + if (combinedEmojis.isNotEmpty()) { loadedCategories.add( EmojiCategory( name = categoryName, - iconRes = categoryIcons[categoryName] ?: R.drawable.rounded_action_key_24, + iconRes = categoryIcons[categoryName] + ?: R.drawable.rounded_action_key_24, emojis = combinedEmojis ) ) @@ -95,7 +96,7 @@ object EmojiData { isLoaded = true _isLoading.value = false } - + reader.close() inputStream.close() } catch (e: Exception) { diff --git a/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiPicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiPicker.kt index df4b0026c..d12ae22b4 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiPicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiPicker.kt @@ -4,8 +4,18 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState -import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -14,8 +24,18 @@ import androidx.compose.foundation.pager.PagerDefaults import androidx.compose.foundation.pager.VerticalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -26,7 +46,6 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -111,7 +130,7 @@ fun EmojiPicker( val category = EmojiData.categories.getOrNull(pageIndex) if (category != null) { val gridState = gridStates.getOrPut(pageIndex) { LazyGridState() } - + Column(modifier = Modifier.fillMaxSize()) { // Category Header within the page Text( @@ -140,13 +159,13 @@ fun EmojiPicker( ) { emojiObj -> val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() - + Box( modifier = Modifier .aspectRatio(1f) .clip(RoundedCornerShape(keyRoundness)) .background( - if (isPressed) MaterialTheme.colorScheme.surfaceContainerHighest + if (isPressed) MaterialTheme.colorScheme.surfaceContainerHighest else Color.Transparent ) .clickable( @@ -183,7 +202,7 @@ fun EmojiPicker( EmojiData.categories.forEachIndexed { index, category -> val isSelected = index == pagerState.currentPage val interactionSource = remember { MutableInteractionSource() } - + IconButton( onClick = { scope.launch { @@ -199,15 +218,15 @@ fun EmojiPicker( .padding(vertical = 2.dp) .clip(RoundedCornerShape(keyRoundness / 2)) .background( - if (isSelected) MaterialTheme.colorScheme.primaryContainer + if (isSelected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent ) ) { Icon( painter = painterResource(id = category.iconRes), contentDescription = category.name, - tint = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer - else MaterialTheme.colorScheme.onSurfaceVariant, + tint = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer + else MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(24.dp) ) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/ime/KaomojiData.kt b/app/src/main/java/com/sameerasw/essentials/ui/ime/KaomojiData.kt index e4df616aa..ddb5ff040 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/ime/KaomojiData.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/ime/KaomojiData.kt @@ -2,19 +2,18 @@ package com.sameerasw.essentials.ui.ime import android.content.Context import android.util.Log -import com.google.gson.Gson -import com.google.gson.annotations.SerializedName -import com.sameerasw.essentials.R -import java.io.InputStreamReader +import androidx.annotation.Keep +import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.compose.runtime.State +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import androidx.annotation.Keep import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.InputStreamReader @Keep data class KaomojiObject( @@ -22,7 +21,7 @@ data class KaomojiObject( @SerializedName("value") val value: String ) -@androidx.annotation.Keep +@Keep data class KaomojiCategory( val name: String, val kaomojis: List @@ -49,7 +48,7 @@ object KaomojiData { val inputStream = context.assets.open("kaomoji.json") val reader = InputStreamReader(inputStream) val response = Gson().fromJson(reader, KaomojiDataResponse::class.java) - + val grouped = response.kaomoji.groupBy { it.category } val loadedCategories = grouped.map { (categoryName, list) -> KaomojiCategory( @@ -66,7 +65,7 @@ object KaomojiData { isLoaded = true _isLoading.value = false } - + reader.close() inputStream.close() } catch (e: Exception) { diff --git a/app/src/main/java/com/sameerasw/essentials/ui/ime/KaomojiPicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/ime/KaomojiPicker.kt index ed65c6598..b1cfabbce 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/ime/KaomojiPicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/ime/KaomojiPicker.kt @@ -1,11 +1,24 @@ package com.sameerasw.essentials.ui.ime import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed @@ -14,8 +27,16 @@ import androidx.compose.foundation.pager.PagerDefaults import androidx.compose.foundation.pager.VerticalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -25,11 +46,8 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.foundation.basicMarquee -import androidx.compose.foundation.lazy.grid.GridCells 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.dp import androidx.compose.ui.unit.sp @@ -118,10 +136,14 @@ fun KaomojiPicker( val gridState = gridStates.getOrPut(pageIndex) { LazyGridState() } val context = androidx.compose.ui.platform.LocalContext.current val categoryNameRes = remember(category.name) { - context.resources.getIdentifier("kaomoji_cat_${category.name}", "string", context.packageName) + context.resources.getIdentifier( + "kaomoji_cat_${category.name}", + "string", + context.packageName + ) } - val localizedName = if (categoryNameRes != 0) androidx.compose.ui.res.stringResource(categoryNameRes) else category.name.replaceFirstChar { it.uppercase() } - + if (categoryNameRes != 0) androidx.compose.ui.res.stringResource(categoryNameRes) else category.name.replaceFirstChar { it.uppercase() } + Column(modifier = Modifier.fillMaxSize()) { // Category Header within the page // Text( @@ -150,13 +172,13 @@ fun KaomojiPicker( ) { index, kaomojiObj -> val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() - + Box( modifier = Modifier .height(48.dp) .clip(RoundedCornerShape(keyRoundness)) .background( - if (isPressed) MaterialTheme.colorScheme.surfaceContainerHighest + if (isPressed) MaterialTheme.colorScheme.surfaceContainerHighest else MaterialTheme.colorScheme.surfaceContainerLow ) .clickable( @@ -199,12 +221,17 @@ fun KaomojiPicker( val category = KaomojiData.categories[index] val isSelected = index == pagerState.currentPage val interactionSource = remember { MutableInteractionSource() } - + val context = androidx.compose.ui.platform.LocalContext.current val categoryNameRes = remember(category.name) { - context.resources.getIdentifier("kaomoji_cat_${category.name}", "string", context.packageName) + context.resources.getIdentifier( + "kaomoji_cat_${category.name}", + "string", + context.packageName + ) } - val localizedName = if (categoryNameRes != 0) androidx.compose.ui.res.stringResource(categoryNameRes) else category.name.replaceFirstChar { it.uppercase() } + val localizedName = + if (categoryNameRes != 0) androidx.compose.ui.res.stringResource(categoryNameRes) else category.name.replaceFirstChar { it.uppercase() } Box( modifier = Modifier @@ -236,7 +263,7 @@ fun KaomojiPicker( fontSize = 10.sp ), color = if (isSelected) MaterialTheme.colorScheme.background - else MaterialTheme.colorScheme.secondary, + else MaterialTheme.colorScheme.secondary, maxLines = 1, modifier = Modifier .padding(horizontal = 4.dp) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt b/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt index 2676a42ea..e4094b62c 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt @@ -16,7 +16,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown -import androidx.compose.ui.input.pointer.positionChange import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectVerticalDragGestures @@ -33,7 +32,6 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -48,7 +46,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.carousel.HorizontalMultiBrowseCarousel import androidx.compose.material3.carousel.rememberCarouselState -import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -67,13 +64,14 @@ import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.changedToUp +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalViewConfiguration @@ -90,8 +88,6 @@ import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.zIndex import com.sameerasw.essentials.R -import com.sameerasw.essentials.data.repository.SettingsRepository -import com.sameerasw.essentials.ime.EssentialsInputMethodService import com.sameerasw.essentials.ime.Suggestion import com.sameerasw.essentials.ime.SuggestionType import com.sameerasw.essentials.utils.HapticUtil @@ -237,7 +233,7 @@ fun KeyButton( val scope = rememberCoroutineScope() val view = LocalView.current - val viewConfiguration = LocalViewConfiguration.current + LocalViewConfiguration.current Box( modifier = modifier @@ -250,11 +246,11 @@ fun KeyButton( val press = PressInteraction.Press(offset) scope.launch { interactionSource.emit(press) } onPress() - + var isReleased = false val repeatJob = if (onRepeat != null) { scope.launch { - delay(500) + delay(500) while (!isReleased) { if (canRepeat?.invoke() != false) { onRepeat() @@ -438,7 +434,7 @@ fun KeyboardInputView( fun handleKeyPress(keyCode: Int) { onKeyPress(keyCode) - if (keyCode == android.view.KeyEvent.KEYCODE_DEL) { + if (keyCode == KeyEvent.KEYCODE_DEL) { if (currentWord.isNotEmpty()) { currentWord = currentWord.dropLast(1) } @@ -581,14 +577,14 @@ fun KeyboardInputView( if (baselineX == null) { baselineX = change.position.x } - + // Slide selection - relative movement from start - val deltaX = change.position.x - (baselineX ?: change.position.x) + val deltaX = change.position.x - baselineX val stepWidthPx = with(density) { 40.dp.toPx() } - + val index = ((deltaX / stepWidthPx) + initialAccentIndex).toInt() .coerceIn(0, longPressVariants.size - 1) - + if (index != selectedAccentIndex) { selectedAccentIndex = index performLightHaptic() @@ -794,14 +790,14 @@ fun KeyboardInputView( isKaomojiMode = false } } else if (desc == "Backspace") { - onKeyPress(android.view.KeyEvent.KEYCODE_DEL) + onKeyPress(KeyEvent.KEYCODE_DEL) } else if (desc == "Expand") { isSuggestionsCollapsed = false } }, onRepeat = { if (desc == "Backspace") { - onKeyPress(android.view.KeyEvent.KEYCODE_DEL) + onKeyPress(KeyEvent.KEYCODE_DEL) performLightHaptic() } }, @@ -822,9 +818,13 @@ fun KeyboardInputView( contentColor = if ((desc == "Clipboard" && isClipboardMode) || (desc == "Emoji" && (isEmojiMode || isKaomojiMode))) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface, shape = RoundedCornerShape(animatedRadius), modifier = if (desc == "Expand") { - Modifier.width(50.dp).fillMaxHeight() + Modifier + .width(50.dp) + .fillMaxHeight() } else { - Modifier.weight(1.3f).fillMaxHeight() + Modifier + .weight(1.3f) + .fillMaxHeight() } ) { Icon( @@ -994,7 +994,8 @@ fun KeyboardInputView( else -> { Column( - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() .blur(animatedBlurRadius), verticalArrangement = Arrangement.spacedBy(4.dp) ) { @@ -1009,7 +1010,6 @@ fun KeyboardInputView( key(char) { val numInteraction = remember { MutableInteractionSource() } - val isPressed by numInteraction.collectIsPressedAsState() KeyButton( onClick = { handleType(char) }, onPress = { performLightHaptic() }, @@ -1070,19 +1070,25 @@ fun KeyboardInputView( val variants = mutableListOf() val xRatio = (index + 0.5f) / 10f var startIndex = 0 - + if (xRatio < 0.35f) { - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) variants.addAll(accents) startIndex = 0 } else if (xRatio > 0.65f) { variants.addAll(accents) - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) startIndex = variants.size - 1 } else { val half = accents.size / 2 variants.addAll(accents.take(half)) - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) startIndex = variants.size - 1 variants.addAll(accents.drop(half)) } @@ -1161,21 +1167,28 @@ fun KeyboardInputView( if (isAccentedCharactersEnabled) KeyAccentMap[char] ?: emptyList() else emptyList() val variants = mutableListOf() - val xRatio = (index + 0.5f) / currentRow2.size.toFloat() + val xRatio = + (index + 0.5f) / currentRow2.size.toFloat() var startIndex = 0 if (xRatio < 0.35f) { - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) variants.addAll(accents) startIndex = 0 } else if (xRatio > 0.65f) { variants.addAll(accents) - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) startIndex = variants.size - 1 } else { val half = accents.size / 2 variants.addAll(accents.take(half)) - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) startIndex = variants.size - 1 variants.addAll(accents.drop(half)) } @@ -1351,17 +1364,23 @@ fun KeyboardInputView( var startIndex = 0 if (xRatio < 0.35f) { - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) variants.addAll(accents) startIndex = 0 } else if (xRatio > 0.65f) { variants.addAll(accents) - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) startIndex = variants.size - 1 } else { val half = accents.size / 2 variants.addAll(accents.take(half)) - if (secondary != null) variants.add(secondary) + if (secondary != null) variants.add( + secondary + ) startIndex = variants.size - 1 variants.addAll(accents.drop(half)) } @@ -1623,7 +1642,6 @@ fun KeyboardInputView( targetValue = if (isPressedSpace) 4.dp else keyRoundness, label = "cornerRadius" ) - var accumulatedDx by remember { mutableStateOf(0f) } val sweepThreshold = 25f // pixels per cursor move val animatedColorSpace by animateColorAsState( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/modifiers/ProgressiveBlurModifier.kt b/app/src/main/java/com/sameerasw/essentials/ui/modifiers/ProgressiveBlurModifier.kt index 490fa2f73..cebe9454a 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/modifiers/ProgressiveBlurModifier.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/modifiers/ProgressiveBlurModifier.kt @@ -83,19 +83,20 @@ fun Modifier.progressiveBlur( showGradientOverlay: Boolean = true ): Modifier = composed { val overlayColor = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.65f) - - val blurModifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && blurRadius > 0f) { - Modifier.graphicsLayer { - val shader = RuntimeShader(PROGRESSIVE_BLUR_SKSL) - shader.setFloatUniform("blurRadius", blurRadius) - shader.setFloatUniform("height", height) - shader.setFloatUniform("contentHeight", size.height) - shader.setIntUniform("isTop", if (direction == BlurDirection.TOP) 1 else 0) - - renderEffect = RenderEffect.createRuntimeShaderEffect(shader, "content") - .asComposeRenderEffect() - } - } else Modifier + + val blurModifier = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && blurRadius > 0f) { + Modifier.graphicsLayer { + val shader = RuntimeShader(PROGRESSIVE_BLUR_SKSL) + shader.setFloatUniform("blurRadius", blurRadius) + shader.setFloatUniform("height", height) + shader.setFloatUniform("contentHeight", size.height) + shader.setIntUniform("isTop", if (direction == BlurDirection.TOP) 1 else 0) + + renderEffect = RenderEffect.createRuntimeShaderEffect(shader, "content") + .asComposeRenderEffect() + } + } else Modifier val gradientModifier = if (showGradientOverlay) { Modifier.drawWithContent { @@ -107,6 +108,7 @@ fun Modifier.progressiveBlur( endY = height ) to height } + BlurDirection.BOTTOM -> { Brush.verticalGradient( colors = listOf(Color.Transparent, overlayColor), @@ -118,5 +120,7 @@ fun Modifier.progressiveBlur( } } else Modifier - this.then(blurModifier).then(gradientModifier) + this + .then(blurModifier) + .then(gradientModifier) } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/AmbientMusicShapeHelper.kt b/app/src/main/java/com/sameerasw/essentials/utils/AmbientMusicShapeHelper.kt index 8cabc3d49..f33e46e10 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/AmbientMusicShapeHelper.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/AmbientMusicShapeHelper.kt @@ -1,6 +1,5 @@ package com.sameerasw.essentials.utils -import android.graphics.Matrix import android.graphics.Path import androidx.compose.material3.MaterialShapes import androidx.graphics.shapes.RoundedPolygon @@ -61,14 +60,20 @@ object AmbientMusicShapeHelper { return allShapes[random.nextInt(allShapes.size)] } - fun updatePathFromMorph(morph: androidx.graphics.shapes.Morph, progress: Float, size: Float, targetPath: Path, rotation: Float = 0f) { + fun updatePathFromMorph( + morph: androidx.graphics.shapes.Morph, + progress: Float, + size: Float, + targetPath: Path, + rotation: Float = 0f + ) { val rawPath = morph.toPath(progress) val matrix = android.graphics.Matrix() matrix.postScale(size, size) if (rotation != 0f) { matrix.postRotate(rotation, size / 2f, size / 2f) } - + targetPath.reset() targetPath.set(rawPath) targetPath.transform(matrix) 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 175a0debe..fdfe39688 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/AppUtil.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/AppUtil.kt @@ -243,7 +243,8 @@ object AppUtil { */ fun isAndroidAutoRunning(context: Context): Boolean { // 1. Check UiModeManager - val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as? android.app.UiModeManager + val uiModeManager = + context.getSystemService(Context.UI_MODE_SERVICE) as? android.app.UiModeManager if (uiModeManager?.currentModeType == android.content.res.Configuration.UI_MODE_TYPE_CAR) return true // 2. Check Configuration @@ -251,14 +252,16 @@ object AppUtil { if ((config.uiMode and android.content.res.Configuration.UI_MODE_TYPE_MASK) == android.content.res.Configuration.UI_MODE_TYPE_CAR) return true // 3. Check for Android Auto Projection (Virtual Displays) - val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as? android.hardware.display.DisplayManager + val displayManager = + context.getSystemService(Context.DISPLAY_SERVICE) as? android.hardware.display.DisplayManager val displays = displayManager?.displays if (displays != null) { for (display in displays) { val name = display.name ?: "" - if (name.contains("Android Auto", ignoreCase = true) || + if (name.contains("Android Auto", ignoreCase = true) || name.contains("Wireless Projector", ignoreCase = true) || - name.contains("Car", ignoreCase = true)) { + name.contains("Car", ignoreCase = true) + ) { return true } } @@ -266,8 +269,12 @@ object AppUtil { // 4. Check for active Car packages try { - val carPackages = listOf("com.google.android.projection.gearhead", "com.google.android.apps.auto.carservice") - val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? android.app.ActivityManager + val carPackages = listOf( + "com.google.android.projection.gearhead", + "com.google.android.apps.auto.carservice" + ) + val activityManager = + context.getSystemService(Context.ACTIVITY_SERVICE) as? android.app.ActivityManager val processes = activityManager?.runningAppProcesses if (processes != null) { for (process in processes) { @@ -278,7 +285,8 @@ object AppUtil { } } } - } catch (_: Exception) {} + } catch (_: Exception) { + } return false } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/BatteryRingDrawer.kt b/app/src/main/java/com/sameerasw/essentials/utils/BatteryRingDrawer.kt index 13a0aa465..3558b1be6 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/BatteryRingDrawer.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/BatteryRingDrawer.kt @@ -12,155 +12,209 @@ import kotlin.math.PI object BatteryRingDrawer { - fun drawBatteryWidget( + /** + * Optimized ring drawing for Glance components. + * Only draws the arcs, leaving icons and backgrounds to Glance native primitives. + */ + fun drawBatteryRing( context: Context, batteryLevel: Int, @ColorInt ringColor: Int, @ColorInt trackColor: Int, - @ColorInt iconTint: Int, - @ColorInt backgroundColor: Int, - deviceIcon: Drawable?, - statusIcon: Drawable?, // New parameter for charging/warning icon + hasStatusIcon: Boolean, width: Int, height: Int ): Bitmap { val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) - val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - style = Paint.Style.FILL - color = backgroundColor - } - val centerX = width / 2f - val centerY = height / 2f + val progressBitmap = drawProgressArc(batteryLevel, ringColor, hasStatusIcon, width, height) + val trackBitmap = drawTrackArc(batteryLevel, trackColor, hasStatusIcon, width, height) + + canvas.drawBitmap(trackBitmap, 0f, 0f, null) + canvas.drawBitmap(progressBitmap, 0f, 0f, null) + + return bitmap + } + + fun drawProgressArc( + batteryLevel: Int, + @ColorInt color: Int, + hasStatusIcon: Boolean, + width: Int, + height: Int + ): Bitmap { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + val clampedLevel = batteryLevel.coerceIn(0, 100) + if (clampedLevel <= 0) return bitmap val strokeWidth = width * 0.11f val padding = strokeWidth + (width * 0.05f) - val rect = RectF( - padding, - padding, - width - padding, - height - padding - ) + val rect = RectF(padding, padding, width - padding, height - padding) val radius = rect.width() / 2f - val bubbleRadius = radius + (strokeWidth / 2f) + val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + this.strokeWidth = strokeWidth + this.color = color + } - canvas.drawCircle(centerX, centerY, bubbleRadius, bgPaint) + val topGapDegrees = if (hasStatusIcon) 60f else 0f + val capAngleDegrees = ((strokeWidth / 2f) / radius) * (180f / PI.toFloat()) + val startAngle = -90f + (topGapDegrees / 2) + val totalAvailableSweep = 360f - topGapDegrees + val progressSweepRaw = (clampedLevel / 100f) * totalAvailableSweep + val segmentGapDegrees = 8f - // Config + val visualStart = startAngle + val visualEnd = if (clampedLevel >= 100) { + startAngle + totalAvailableSweep + } else { + (startAngle + progressSweepRaw - (segmentGapDegrees / 2)).coerceAtLeast(startAngle) + } + + val visualSpan = visualEnd - visualStart + if (visualSpan > (capAngleDegrees * 2)) { + val drawStart = visualStart + capAngleDegrees + val drawSweep = (visualEnd - capAngleDegrees) - drawStart + if (drawSweep > 0) { + canvas.drawArc(rect, drawStart, drawSweep, false, paint) + } + } else if (visualSpan > 0) { + val center = visualStart + visualSpan / 2 + paint.style = Paint.Style.FILL + canvas.drawArc(rect, center, 0.1f, false, paint) + } + + return bitmap + } + + fun drawTrackArc( + batteryLevel: Int, + @ColorInt color: Int, + hasStatusIcon: Boolean, + width: Int, + height: Int + ): Bitmap { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + val clampedLevel = batteryLevel.coerceIn(0, 100) + if (clampedLevel >= 100) return bitmap + + val strokeWidth = width * 0.11f val trackStrokeWidth = strokeWidth * 0.5f + val padding = strokeWidth + (width * 0.05f) + val rect = RectF(padding, padding, width - padding, height - padding) + val radius = rect.width() / 2f val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE strokeCap = Paint.Cap.ROUND + this.strokeWidth = trackStrokeWidth + this.color = color } - // Dynamic Gap: 60 degrees if status icon present, otherwise 0 (full circle) - val topGapDegrees = if (statusIcon != null) 60f else 0f - - val capAngleDegrees = ((strokeWidth / 2f) / radius) * (180f / PI.toFloat()) + val topGapDegrees = if (hasStatusIcon) 60f else 0f val trackCapAngleDegrees = ((trackStrokeWidth / 2f) / radius) * (180f / PI.toFloat()) - - // Start drawing from: -90 (top) + half gap val startAngle = -90f + (topGapDegrees / 2) val totalAvailableSweep = 360f - topGapDegrees - - val clampedLevel = batteryLevel.coerceIn(0, 100) - val progressSweepRaw = (clampedLevel / 100f) * totalAvailableSweep - - // Visual Gap between segments val segmentGapDegrees = 8f - // --- Draw Progress Arc --- - if (clampedLevel > 0) { - paint.strokeWidth = strokeWidth - paint.color = ringColor + val visualStart = (startAngle + progressSweepRaw + (segmentGapDegrees / 2)) + .coerceAtMost(startAngle + totalAvailableSweep) + val visualEnd = startAngle + totalAvailableSweep - val visualStart = startAngle - val visualEnd = if (clampedLevel >= 100) { - startAngle + totalAvailableSweep - } else { - (startAngle + progressSweepRaw - (segmentGapDegrees / 2)).coerceAtLeast(startAngle) - } - - val visualSpan = visualEnd - visualStart - - if (visualSpan > (capAngleDegrees * 2)) { - val drawStart = visualStart + capAngleDegrees - val drawSweep = (visualEnd - capAngleDegrees) - drawStart - if (drawSweep > 0) { - canvas.drawArc(rect, drawStart, drawSweep, false, paint) - } - } else { - if (visualSpan > 0) { - val center = visualStart + visualSpan / 2 - paint.style = Paint.Style.FILL - canvas.drawArc(rect, center, 0.1f, false, paint) - } + val visualSpan = visualEnd - visualStart + if (visualSpan > (trackCapAngleDegrees * 2)) { + val drawStart = visualStart + trackCapAngleDegrees + val drawSweep = (visualEnd - trackCapAngleDegrees) - drawStart + if (drawSweep > 0) { + canvas.drawArc(rect, drawStart, drawSweep, false, paint) } } - // --- Draw Track Arc (Filler) --- - if (clampedLevel < 100) { - paint.strokeWidth = trackStrokeWidth - paint.color = trackColor + return bitmap + } - val visualStart = (startAngle + progressSweepRaw + (segmentGapDegrees / 2)) - .coerceAtMost(startAngle + totalAvailableSweep) + /** + * Legacy method for non-Glance components (e.g. Notifications). + */ + fun drawBatteryWidget( + context: Context, + batteryLevel: Int, + @ColorInt ringColor: Int, + @ColorInt trackColor: Int, + @ColorInt iconTint: Int, + @ColorInt backgroundColor: Int, + deviceIcon: Drawable?, + statusIcon: Drawable?, + width: Int, + height: Int + ): Bitmap { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) - val visualEnd = startAngle + totalAvailableSweep + val strokeWidth = width * 0.11f + val centerX = width / 2f + val centerY = height / 2f - val visualSpan = visualEnd - visualStart - if (visualSpan > (trackCapAngleDegrees * 2)) { - val drawStart = visualStart + trackCapAngleDegrees - val drawSweep = (visualEnd - trackCapAngleDegrees) - drawStart - if (drawSweep > 0) { - canvas.drawArc(rect, drawStart, drawSweep, false, paint) - } - } + // 1. Draw Background + val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.FILL + color = backgroundColor } + val padding = strokeWidth + (width * 0.05f) + val rect = RectF(padding, padding, width - padding, height - padding) + val bubbleRadius = (rect.width() / 2f) + (strokeWidth / 2f) + canvas.drawCircle(centerX, centerY, bubbleRadius, bgPaint) - // --- Draw Status Icon (Top) if present --- + // 2. Draw Ring + val ringBitmap = drawBatteryRing( + context, batteryLevel, ringColor, trackColor, statusIcon != null, width, height + ) + canvas.drawBitmap(ringBitmap, 0f, 0f, null) + + // 3. Draw Status Icon Bubble if (statusIcon != null) { val smallIconRadius = strokeWidth * 1.3f - val centerX = width / 2f val iconCenterY = rect.top - val smallIconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - style = Paint.Style.FILL - color = ringColor - } - // Bubble Background - canvas.drawCircle(centerX, iconCenterY, smallIconRadius, smallIconPaint) + bgPaint.color = ringColor + canvas.drawCircle(centerX, iconCenterY, smallIconRadius, bgPaint) val iconSize = (smallIconRadius * 1.5f).toInt() - val iconLeft = (centerX - iconSize / 2).toInt() - val iconTop = (iconCenterY - iconSize / 2).toInt() - statusIcon.setBounds(iconLeft, iconTop, iconLeft + iconSize, iconTop + iconSize) + val iconOffset = iconSize / 2 + statusIcon.setBounds( + (centerX - iconOffset).toInt(), + (iconCenterY - iconOffset).toInt(), + (centerX + iconOffset).toInt(), + (iconCenterY + iconOffset).toInt() + ) statusIcon.setTint(backgroundColor) statusIcon.draw(canvas) } - // --- Draw Center Device Icon --- - val innerPadding = strokeWidth * 1.5f + // 4. Draw Device Icon deviceIcon?.let { - val availableWidth = (rect.width() - innerPadding * 2).toInt() - val availableHeight = (rect.height() - innerPadding * 2).toInt() - val iconBitmap = it.toBitmap(availableWidth, availableHeight) - - val iconLeft = (width - iconBitmap.width) / 2f - val iconTop = (height - iconBitmap.height) / 2f - - val iconPaint = Paint(Paint.ANTI_ALIAS_FLAG) - val colorFilter = android.graphics.PorterDuffColorFilter( - iconTint, - android.graphics.PorterDuff.Mode.SRC_IN + val innerPadding = strokeWidth * 1.5f + val iconSize = (rect.width() - innerPadding * 2).toInt() + val iconBitmap = it.toBitmap(iconSize, iconSize) + + val iconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + colorFilter = android.graphics.PorterDuffColorFilter( + iconTint, + android.graphics.PorterDuff.Mode.SRC_IN + ) + } + canvas.drawBitmap( + iconBitmap, + (width - iconBitmap.width) / 2f, + (height - iconBitmap.height) / 2f, + iconPaint ) - iconPaint.colorFilter = colorFilter - - canvas.drawBitmap(iconBitmap, iconLeft, iconTop, iconPaint) } return bitmap diff --git a/app/src/main/java/com/sameerasw/essentials/utils/ColorFormatUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/ColorFormatUtils.kt index 942b4dad3..cea5606d7 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/ColorFormatUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/ColorFormatUtils.kt @@ -18,11 +18,13 @@ object ColorFormatUtils { val b = Color.blue(color) "rgb($r, $g, $b)" } + ColorFormat.HSL -> { val hsl = FloatArray(3) AndroidColorUtils.colorToHSL(color, hsl) String.format("hsl(%.0f, %.0f%%, %.0f%%)", hsl[0], hsl[1] * 100, hsl[2] * 100) } + ColorFormat.HSV -> { val hsv = FloatArray(3) Color.colorToHSV(color, hsv) diff --git a/app/src/main/java/com/sameerasw/essentials/utils/DeviceImageMapper.kt b/app/src/main/java/com/sameerasw/essentials/utils/DeviceImageMapper.kt index 34556d7f0..597bd17d0 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/DeviceImageMapper.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/DeviceImageMapper.kt @@ -33,7 +33,7 @@ object DeviceImageMapper { R.drawable.pixel_9pro_9proxl_10_10pro_10proxl m.contains("pixel 9") -> R.drawable.pixel_9 - + // Default fallback else -> 0 } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/DeviceSpecsCache.kt b/app/src/main/java/com/sameerasw/essentials/utils/DeviceSpecsCache.kt index 335586c58..8f40573fb 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/DeviceSpecsCache.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/DeviceSpecsCache.kt @@ -26,7 +26,7 @@ object DeviceSpecsCache { if (!file.exists()) return null val json = file.readText() val specs = gson.fromJson(json, DeviceSpecs::class.java) - + // Validate essential data integrity or refetch @Suppress("SENSELESS_COMPARISON") if (specs == null || specs.detailSpec == null) { @@ -72,36 +72,37 @@ object DeviceSpecsCache { * Downloads and saves device images locally. * Returns a copy of [specs] with the [DeviceSpecs.localImagePaths] populated. */ - suspend fun downloadImages(context: Context, specs: DeviceSpecs): DeviceSpecs = withContext(Dispatchers.IO) { - val dir = File(context.filesDir, IMAGES_DIR) - if (!dir.exists()) dir.mkdirs() + suspend fun downloadImages(context: Context, specs: DeviceSpecs): DeviceSpecs = + withContext(Dispatchers.IO) { + val dir = File(context.filesDir, IMAGES_DIR) + if (!dir.exists()) dir.mkdirs() + + val localPaths = mutableListOf() + specs.imageUrls.forEachIndexed { index, url -> + try { + // Use a stable filename based on index and extension + val extension = url.substringAfterLast(".", "jpg").split("?").first() + val fileName = "device_image_${index}.${extension}" + val file = File(dir, fileName) - val localPaths = mutableListOf() - specs.imageUrls.forEachIndexed { index, url -> - try { - // Use a stable filename based on index and extension - val extension = url.substringAfterLast(".", "jpg").split("?").first() - val fileName = "device_image_${index}.${extension}" - val file = File(dir, fileName) - - // Only download if it doesn't already exist or if it's the first image (often updated) - if (!file.exists()) { - URL(url).openStream().use { input -> - FileOutputStream(file).use { output -> - input.copyTo(output) + // Only download if it doesn't already exist or if it's the first image (often updated) + if (!file.exists()) { + URL(url).openStream().use { input -> + FileOutputStream(file).use { output -> + input.copyTo(output) + } } } + localPaths.add(file.absolutePath) + } catch (e: Exception) { + e.printStackTrace() + // If download fails, we don't add to localPaths, + // UI will fall back to imageUrls } - localPaths.add(file.absolutePath) - } catch (e: Exception) { - e.printStackTrace() - // If download fails, we don't add to localPaths, - // UI will fall back to imageUrls } + + val updatedSpecs = specs.copy(localImagePaths = localPaths) + saveSpecs(context, updatedSpecs) + updatedSpecs } - - val updatedSpecs = specs.copy(localImagePaths = localPaths) - saveSpecs(context, updatedSpecs) - updatedSpecs - } } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/DeviceUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/DeviceUtils.kt index 7a292623c..44fa033bc 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/DeviceUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/DeviceUtils.kt @@ -181,12 +181,13 @@ object DeviceUtils { fun isBlurProblematicDevice(): Boolean { // Samsung devices on One UI 7 (Android 15) or below have a broken blur implementation // that causes a gray screen overlay. Disable it for them. (╯°□°)╯︵ ┻━┻ - return Build.MANUFACTURER.equalsIgnoreCase("samsung") && + return Build.MANUFACTURER.equalsIgnoreCase("samsung") && Build.VERSION.SDK_INT <= 35 // Android 15 } fun isPowerSaveMode(context: Context): Boolean { - val powerManager = context.getSystemService(Context.POWER_SERVICE) as? android.os.PowerManager + val powerManager = + context.getSystemService(Context.POWER_SERVICE) as? android.os.PowerManager return powerManager?.isPowerSaveMode == true } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt b/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt index 01d15bfd5..c394214c6 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/FreezeManager.kt @@ -3,7 +3,6 @@ package com.sameerasw.essentials.utils import android.content.Context import android.os.Build import android.os.IBinder -import android.os.PersistableBundle import android.util.Log import org.lsposed.hiddenapibypass.HiddenApiBypass import rikka.shizuku.Shizuku @@ -286,14 +285,20 @@ object FreezeManager { setAppRestrictedWithShizuku(packageName, suspended) } - val pm = getService("package", "android.content.pm.IPackageManager\$Stub") ?: return false + val pm = + getService("package", "android.content.pm.IPackageManager\$Stub") ?: return false val callerPackage = getSuspenderPackage() val userId = getUserId() val dialogInfo = if (suspended) { val builderClass = Class.forName("android.content.pm.SuspendDialogInfo\$Builder") val builder = HiddenApiBypass.newInstance(builderClass) - HiddenApiBypass.invoke(builderClass, builder, "setNeutralButtonAction", 1 /*BUTTON_ACTION_UNSUSPEND*/) + HiddenApiBypass.invoke( + builderClass, + builder, + "setNeutralButtonAction", + 1 /*BUTTON_ACTION_UNSUSPEND*/ + ) HiddenApiBypass.invoke(builderClass, builder, "build") } else null @@ -301,26 +306,59 @@ object FreezeManager { return try { when (version) { 0 -> HiddenApiBypass.invoke( - pm.javaClass, pm, "setPackagesSuspendedAsUser", - arrayOf(packageName), suspended, null, null, dialogInfo, 0, callerPackage, userId, userId + pm.javaClass, + pm, + "setPackagesSuspendedAsUser", + arrayOf(packageName), + suspended, + null, + null, + dialogInfo, + 0, + callerPackage, + userId, + userId ) as? Array<*> + 1 -> HiddenApiBypass.invoke( - pm.javaClass, pm, "setPackagesSuspendedAsUser", - arrayOf(packageName), suspended, null, null, dialogInfo, callerPackage, userId + pm.javaClass, + pm, + "setPackagesSuspendedAsUser", + arrayOf(packageName), + suspended, + null, + null, + dialogInfo, + callerPackage, + userId ) as? Array<*> + 2 -> HiddenApiBypass.invoke( pm.javaClass, pm, "setPackagesSuspendedAsUser", arrayOf(packageName), suspended, null, null, null, callerPackage, userId ) as? Array<*> - else -> pm.javaClass.getMethod("setPackagesSuspendedAsUser", Array::class.java, Boolean::class.javaPrimitiveType, Int::class.javaPrimitiveType) + + else -> pm.javaClass.getMethod( + "setPackagesSuspendedAsUser", + Array::class.java, + Boolean::class.javaPrimitiveType, + Int::class.javaPrimitiveType + ) .invoke(pm, arrayOf(packageName), suspended, userId) as? Array<*> } - } catch (_: Exception) { null } + } catch (_: Exception) { + null + } } val result = when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> callSetPackagesSuspended(0) ?: callSetPackagesSuspended(1) - Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> callSetPackagesSuspended(1) ?: callSetPackagesSuspended(2) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> callSetPackagesSuspended( + 0 + ) ?: callSetPackagesSuspended(1) + + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> callSetPackagesSuspended(1) + ?: callSetPackagesSuspended(2) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> callSetPackagesSuspended(2) else -> callSetPackagesSuspended(3) } @@ -333,23 +371,43 @@ object FreezeManager { } private fun forceStopAppWithShizuku(packageName: String) { - val am = getService(Context.ACTIVITY_SERVICE, "android.app.IActivityManager\$Stub") ?: return + val am = + getService(Context.ACTIVITY_SERVICE, "android.app.IActivityManager\$Stub") ?: return try { HiddenApiBypass.invoke(am.javaClass, am, "forceStopPackage", packageName, getUserId()) - } catch (e: Exception) { e.printStackTrace() } + } catch (e: Exception) { + e.printStackTrace() + } } private fun setAppRestrictedWithShizuku(packageName: String, restricted: Boolean) { - val appops = getService(Context.APP_OPS_SERVICE, "com.android.internal.app.IAppOpsService\$Stub") ?: return + val appops = + getService(Context.APP_OPS_SERVICE, "com.android.internal.app.IAppOpsService\$Stub") + ?: return try { val appOpsManagerClass = Class.forName("android.app.AppOpsManager") - val op = HiddenApiBypass.invoke(appOpsManagerClass, null, "strOpToOp", "android:run_any_in_background") as Int + val op = HiddenApiBypass.invoke( + appOpsManagerClass, + null, + "strOpToOp", + "android:run_any_in_background" + ) as Int val uid = getPackageUid(packageName) if (uid != -1) { val mode = if (restricted) 1 /*MODE_IGNORED*/ else 0 /*MODE_ALLOWED*/ - HiddenApiBypass.invoke(appops.javaClass, appops, "setMode", op, uid, packageName, mode) + HiddenApiBypass.invoke( + appops.javaClass, + appops, + "setMode", + op, + uid, + packageName, + mode + ) } - } catch (e: Exception) { e.printStackTrace() } + } catch (e: Exception) { + e.printStackTrace() + } } private fun getPackageUid(packageName: String): Int { @@ -369,9 +427,12 @@ object FreezeManager { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { HiddenApiBypass.invoke(stubClass, null, "asInterface", ShizukuBinderWrapper(binder)) } else { - stubClass.getMethod("asInterface", IBinder::class.java).invoke(null, ShizukuBinderWrapper(binder)) + stubClass.getMethod("asInterface", IBinder::class.java) + .invoke(null, ShizukuBinderWrapper(binder)) } - } catch (_: Exception) { null } + } catch (_: Exception) { + null + } } private fun getSuspenderPackage(): String = @@ -396,9 +457,12 @@ object FreezeManager { if (ShizukuUtils.isShizukuAvailable() && ShizukuUtils.hasPermission()) { try { val pm = getService("package", "android.content.pm.IPackageManager\$Stub") - if (pm != null) { + if (pm != null) { val userId = getUserId() - Log.d("FreezeManager", "Shizuku: setting $packageName to $newState for user $userId") + Log.d( + "FreezeManager", + "Shizuku: setting $packageName to $newState for user $userId" + ) HiddenApiBypass.invoke( pm.javaClass, pm, "setApplicationEnabledSetting", packageName, newState, 0, userId, "android" diff --git a/app/src/main/java/com/sameerasw/essentials/utils/GSMArenaService.kt b/app/src/main/java/com/sameerasw/essentials/utils/GSMArenaService.kt index 75db61b10..a3e234618 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/GSMArenaService.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/GSMArenaService.kt @@ -36,17 +36,18 @@ object GSMArenaService { .get() var results = searchDoc.select(".makers li") - + // Fallback for model numbers (SM-G990B etc) which often don't show up in quick search if (results.isEmpty()) { - val freeSearchUrl = "$BASE_URL/results.php3?sFreeSearch=yes&sFreeText=$formattedQuery" + val freeSearchUrl = + "$BASE_URL/results.php3?sFreeSearch=yes&sFreeText=$formattedQuery" searchDoc = Jsoup.connect(freeSearchUrl) .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") .timeout(30000) .get() results = searchDoc.select(".makers li") } - + if (results.isEmpty()) return null val bestMatchingElement = results.firstOrNull { element -> @@ -56,7 +57,7 @@ object GSMArenaService { val devicePath = bestMatchingElement?.select("a")?.attr("href") ?: "" val searchThumbnail = bestMatchingElement?.select("img")?.attr("src") ?: "" - + if (devicePath.isBlank()) return null val deviceUrl = @@ -149,9 +150,28 @@ object GSMArenaService { val prefName = preferredName.lowercase() val prefModel = preferredModel.lowercase() - val variants = listOf("pro", "max", "plus", "xl", "ultra", "fold", "flip", "power", "neo", "gt", "lite", "ace", "prime", "edge", "fe") + val variants = listOf( + "pro", + "max", + "plus", + "xl", + "ultra", + "fold", + "flip", + "power", + "neo", + "gt", + "lite", + "ace", + "prime", + "edge", + "fe" + ) for (variant in variants) { - if (found.contains(variant) && !prefName.contains(variant) && !prefModel.contains(variant)) { + if (found.contains(variant) && !prefName.contains(variant) && !prefModel.contains( + variant + ) + ) { return false } } @@ -159,7 +179,9 @@ object GSMArenaService { if (found.contains(prefName) || found.contains(prefModel)) { val modelIndex = found.indexOf(prefName).takeIf { it != -1 } ?: found.indexOf(prefModel) if (modelIndex != -1) { - val afterModel = found.substring(modelIndex + (if (found.contains(prefName)) prefName.length else prefModel.length)).trim() + val afterModel = + found.substring(modelIndex + (if (found.contains(prefName)) prefName.length else prefModel.length)) + .trim() if (afterModel.isNotEmpty()) { val firstWord = afterModel.split(" ").firstOrNull() ?: "" if (variants.contains(firstWord)) return false diff --git a/app/src/main/java/com/sameerasw/essentials/utils/OmniTriggerUtil.kt b/app/src/main/java/com/sameerasw/essentials/utils/OmniTriggerUtil.kt index 17359f274..e990ada0d 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/OmniTriggerUtil.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/OmniTriggerUtil.kt @@ -7,7 +7,6 @@ import android.os.Bundle import android.os.IBinder import android.os.SystemClock import android.util.Log -import androidx.annotation.RequiresApi import com.sameerasw.essentials.shizuku.ShizukuPermissionHelper import org.lsposed.hiddenapibypass.HiddenApiBypass import java.lang.reflect.Method @@ -25,8 +24,9 @@ object OmniTriggerUtil { if (iVimsClass != null) return runCatching { iVimsClass = Class.forName("com.android.internal.app.IVoiceInteractionManagerService") - vimsInterfaceMethod = Class.forName("com.android.internal.app.IVoiceInteractionManagerService\$Stub") - .getMethod("asInterface", IBinder::class.java) + vimsInterfaceMethod = + Class.forName("com.android.internal.app.IVoiceInteractionManagerService\$Stub") + .getMethod("asInterface", IBinder::class.java) serviceManagerClass = Class.forName("android.os.ServiceManager") getServiceMethod = serviceManagerClass?.getMethod("getService", String::class.java) } @@ -34,7 +34,7 @@ object OmniTriggerUtil { fun trigger(context: Context): Boolean { ensureReflection() - + val bundle = Bundle().apply { putLong("invocation_time_ms", SystemClock.elapsedRealtime()) putInt("omni.entry_point", 1) @@ -72,9 +72,24 @@ object OmniTriggerUtil { private fun invokeShowSession(clazz: Class<*>, vims: Any, bundle: Bundle): Boolean { return runCatching { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - HiddenApiBypass.invoke(clazz, vims, "showSessionFromSession", null, bundle, 7, "hyperOS_home") as Boolean? ?: false + HiddenApiBypass.invoke( + clazz, + vims, + "showSessionFromSession", + null, + bundle, + 7, + "hyperOS_home" + ) as Boolean? ?: false } else { - HiddenApiBypass.invoke(clazz, vims, "showSessionFromSession", null, bundle, 7) as Boolean? ?: false + HiddenApiBypass.invoke( + clazz, + vims, + "showSessionFromSession", + null, + bundle, + 7 + ) as Boolean? ?: false } }.getOrDefault(false) } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/OverlayHelper.kt b/app/src/main/java/com/sameerasw/essentials/utils/OverlayHelper.kt index ddaa23a94..1488bb7a3 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/OverlayHelper.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/OverlayHelper.kt @@ -33,7 +33,6 @@ import androidx.savedstate.SavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner import com.sameerasw.essentials.domain.model.NotificationLightingSide import com.sameerasw.essentials.domain.model.NotificationLightingStyle -import com.sameerasw.essentials.domain.model.NotificationLightingSweepPosition import androidx.compose.ui.graphics.Color as ComposeColor /** @@ -122,49 +121,61 @@ object OverlayHelper { } val density = context.resources.displayMetrics.density - val glowSizePx = (80 * density).toInt() + val glowSizePx = (80 * density).toInt() if (sides.contains(NotificationLightingSide.LEFT)) { - val leftGlow = GlowSideView(context, color, NotificationLightingSide.LEFT, blurRadiusDp).apply { - tag = "left_glow" - alpha = 0.5f - layoutParams = FrameLayout.LayoutParams(glowSizePx, ViewGroup.LayoutParams.MATCH_PARENT).apply { - gravity = Gravity.START + val leftGlow = + GlowSideView(context, color, NotificationLightingSide.LEFT, blurRadiusDp).apply { + tag = "left_glow" + alpha = 0.5f + layoutParams = + FrameLayout.LayoutParams(glowSizePx, ViewGroup.LayoutParams.MATCH_PARENT) + .apply { + gravity = Gravity.START + } } - } overlay.addView(leftGlow) } if (sides.contains(NotificationLightingSide.RIGHT)) { - val rightGlow = GlowSideView(context, color, NotificationLightingSide.RIGHT, blurRadiusDp).apply { - tag = "right_glow" - alpha = 0.5f - layoutParams = FrameLayout.LayoutParams(glowSizePx, ViewGroup.LayoutParams.MATCH_PARENT).apply { - gravity = Gravity.END + val rightGlow = + GlowSideView(context, color, NotificationLightingSide.RIGHT, blurRadiusDp).apply { + tag = "right_glow" + alpha = 0.5f + layoutParams = + FrameLayout.LayoutParams(glowSizePx, ViewGroup.LayoutParams.MATCH_PARENT) + .apply { + gravity = Gravity.END + } } - } overlay.addView(rightGlow) } if (sides.contains(NotificationLightingSide.TOP)) { - val topGlow = GlowSideView(context, color, NotificationLightingSide.TOP, blurRadiusDp).apply { - tag = "top_glow" - alpha = 0.5f - layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, glowSizePx).apply { - gravity = Gravity.TOP + val topGlow = + GlowSideView(context, color, NotificationLightingSide.TOP, blurRadiusDp).apply { + tag = "top_glow" + alpha = 0.5f + layoutParams = + FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, glowSizePx) + .apply { + gravity = Gravity.TOP + } } - } overlay.addView(topGlow) } if (sides.contains(NotificationLightingSide.BOTTOM)) { - val bottomGlow = GlowSideView(context, color, NotificationLightingSide.BOTTOM, blurRadiusDp).apply { - tag = "bottom_glow" - alpha = 0.5f - layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, glowSizePx).apply { - gravity = Gravity.BOTTOM + val bottomGlow = + GlowSideView(context, color, NotificationLightingSide.BOTTOM, blurRadiusDp).apply { + tag = "bottom_glow" + alpha = 0.5f + layoutParams = + FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, glowSizePx) + .apply { + gravity = Gravity.BOTTOM + } } - } overlay.addView(bottomGlow) } @@ -223,7 +234,6 @@ object OverlayHelper { randomShapes: Boolean, showBackground: Boolean ): FrameLayout { - val glowRadiusDp = 15f val overlay = FrameLayout(context) if (showBackground) { overlay.setBackgroundColor(Color.BLACK) @@ -259,7 +269,7 @@ object OverlayHelper { style = android.graphics.Paint.Style.STROKE this.color = this@SweepShapeView.color strokeWidth = context.resources.displayMetrics.density * strokeDp - + maskFilter = android.graphics.BlurMaskFilter( context.resources.displayMetrics.density * 15f, android.graphics.BlurMaskFilter.Blur.NORMAL @@ -283,14 +293,14 @@ object OverlayHelper { if (polygon != null) { // Get path from polygon scaled to size val shapePath = polygon.toPath() - + // Scale and move path val matrix = android.graphics.Matrix() // Shapes from toPath() are normalized to [0, 1] range. // Scale to currentRadius * 2 and center it. matrix.postScale(currentRadius * 2f, currentRadius * 2f) matrix.postTranslate(centerX - currentRadius, centerY - currentRadius) - + shapePath.transform(matrix) canvas.drawPath(shapePath, paint) } else { @@ -330,14 +340,17 @@ object OverlayHelper { 0f, 0f, w, 0f, color, Color.TRANSPARENT, android.graphics.Shader.TileMode.CLAMP ) + NotificationLightingSide.RIGHT -> android.graphics.LinearGradient( w, 0f, 0f, 0f, color, Color.TRANSPARENT, android.graphics.Shader.TileMode.CLAMP ) + NotificationLightingSide.TOP -> android.graphics.LinearGradient( 0f, 0f, 0f, h, color, Color.TRANSPARENT, android.graphics.Shader.TileMode.CLAMP ) + NotificationLightingSide.BOTTOM -> android.graphics.LinearGradient( 0f, h, 0f, 0f, color, Color.TRANSPARENT, android.graphics.Shader.TileMode.CLAMP @@ -488,7 +501,6 @@ object OverlayHelper { pulseDurationMillis: Long = 3000L, onAnimationEnd: (() -> Unit)? = null ) { - val sweepGlowRadiusDp = 15f if (style == NotificationLightingStyle.GLOW) { val vg = view as? ViewGroup if (vg != null) { @@ -812,7 +824,8 @@ object OverlayHelper { // Max radius to cover the whole screen from the start point val maxDistX = Math.max(startX, screenWidth - startX) val maxDistY = Math.max(startY, screenHeight - startY) - val maxRadius = Math.sqrt((maxDistX * maxDistX + maxDistY * maxDistY).toDouble()).toFloat() + (15f * displayMetrics.density) + val maxRadius = Math.sqrt((maxDistX * maxDistX + maxDistY * maxDistY).toDouble()) + .toFloat() + (15f * displayMetrics.density) var pulseCount = 0 diff --git a/app/src/main/java/com/sameerasw/essentials/utils/RefreshRateUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/RefreshRateUtils.kt index 3e0206126..2e364bd99 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/RefreshRateUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/RefreshRateUtils.kt @@ -62,6 +62,7 @@ object RefreshRateUtils { return when { min > 0f && peak > 0f && min.roundToInt() != peak.roundToInt() -> "${min.roundToInt()}-${peak.roundToInt()} Hz" + else -> "${getDisplayValue(context).roundToInt()} Hz" } } @@ -172,9 +173,9 @@ object RefreshRateUtils { if (!isMinUnset) return false return isUnsetValue(rawPeak) || - isInfinityValue(rawPeak) || - peak <= 0f || - peak.roundToInt() == DEFAULT_SYSTEM_REFRESH_RATE.roundToInt() + isInfinityValue(rawPeak) || + peak <= 0f || + peak.roundToInt() == DEFAULT_SYSTEM_REFRESH_RATE.roundToInt() } private fun isUnsetValue(rawValue: String?): Boolean { @@ -185,7 +186,7 @@ object RefreshRateUtils { private fun isInfinityValue(rawValue: String?): Boolean { val trimmed = rawValue?.trim().orEmpty() return trimmed.equals("Infinity", ignoreCase = true) || - trimmed.equals("inf", ignoreCase = true) + trimmed.equals("inf", ignoreCase = true) } private fun getHighestSupportedRefreshRate(context: Context): Float { diff --git a/app/src/main/java/com/sameerasw/essentials/utils/ServiceUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/ServiceUtils.kt index 60dcb689a..088f7375a 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/ServiceUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/ServiceUtils.kt @@ -4,35 +4,43 @@ import android.content.Context import android.content.Intent import android.os.Build import com.sameerasw.essentials.data.repository.SettingsRepository +import com.sameerasw.essentials.domain.diy.Automation +import com.sameerasw.essentials.domain.diy.DIYRepository import com.sameerasw.essentials.services.AppDetectionService import com.sameerasw.essentials.services.BatteryNotificationService -import com.sameerasw.essentials.domain.diy.DIYRepository -import com.sameerasw.essentials.domain.diy.Automation object ServiceUtils { fun startRequiredServices(context: Context) { val settingsRepository = SettingsRepository(context) - + startAppDetectionServiceIfNeeded(context, settingsRepository) startBatteryNotificationServiceIfNeeded(context, settingsRepository) } - private fun startAppDetectionServiceIfNeeded(context: Context, settingsRepository: SettingsRepository) { - val isAppLockEnabled = settingsRepository.getBoolean(SettingsRepository.KEY_APP_LOCK_ENABLED) - val isDynamicNightLightEnabled = settingsRepository.getBoolean(SettingsRepository.KEY_DYNAMIC_NIGHT_LIGHT_ENABLED) - val isHideGestureBarOnLauncherEnabled = settingsRepository.getBoolean(SettingsRepository.KEY_HIDE_GESTURE_BAR_ON_LAUNCHER_ENABLED) - val isUseUsageAccess = settingsRepository.getBoolean(SettingsRepository.KEY_USE_USAGE_ACCESS) - - val hasAppAutomations = DIYRepository.automations.value.any { - it.isEnabled && it.type == Automation.Type.APP + private fun startAppDetectionServiceIfNeeded( + context: Context, + settingsRepository: SettingsRepository + ) { + val isAppLockEnabled = + settingsRepository.getBoolean(SettingsRepository.KEY_APP_LOCK_ENABLED) + val isDynamicNightLightEnabled = + settingsRepository.getBoolean(SettingsRepository.KEY_DYNAMIC_NIGHT_LIGHT_ENABLED) + val isHideGestureBarOnLauncherEnabled = + settingsRepository.getBoolean(SettingsRepository.KEY_HIDE_GESTURE_BAR_ON_LAUNCHER_ENABLED) + val isUseUsageAccess = + settingsRepository.getBoolean(SettingsRepository.KEY_USE_USAGE_ACCESS) + + val hasAppAutomations = DIYRepository.automations.value.any { + it.isEnabled && it.type == Automation.Type.APP } - + val shutUpConfigs = settingsRepository.loadShutUpConfigs() val hasShutUpApps = shutUpConfigs.any { it.isEnabled } - - val shouldRun = isUseUsageAccess && (isAppLockEnabled || isDynamicNightLightEnabled || isHideGestureBarOnLauncherEnabled || hasAppAutomations || hasShutUpApps) - + + val shouldRun = + isUseUsageAccess && (isAppLockEnabled || isDynamicNightLightEnabled || isHideGestureBarOnLauncherEnabled || hasAppAutomations || hasShutUpApps) + val intent = Intent(context, AppDetectionService::class.java) if (shouldRun) { try { @@ -49,9 +57,12 @@ object ServiceUtils { } } - private fun startBatteryNotificationServiceIfNeeded(context: Context, settingsRepository: SettingsRepository) { + private fun startBatteryNotificationServiceIfNeeded( + context: Context, + settingsRepository: SettingsRepository + ) { val isBatteryNotificationEnabled = settingsRepository.isBatteryNotificationEnabled() - + val intent = Intent(context, BatteryNotificationService::class.java) if (isBatteryNotificationEnabled) { try { diff --git a/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt index 069485d18..e1dff2ef0 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt @@ -81,10 +81,11 @@ object ShizukuUtils { fun getSystemBinder(name: String): IBinder? { if (!hasPermission() || !isBinderAlive) return null - val service = moe.shizuku.server.IShizukuService.Stub.asInterface(binder) + val service = IShizukuService.Stub.asInterface(binder) return try { // Try known method names for Shizuku v13 - val method = service.javaClass.methods.find { it.name == "getSystemBinder" || it.name == "getService" } + val method = + service.javaClass.methods.find { it.name == "getSystemBinder" || it.name == "getService" } if (method != null) { if (method.parameterCount == 1) { method.invoke(service, name) as? IBinder 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 3509448a9..6284f1c6c 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/ShortcutUtil.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/ShortcutUtil.kt @@ -6,7 +6,6 @@ import android.content.pm.ShortcutInfo import android.content.pm.ShortcutManager import android.graphics.drawable.Icon import android.os.Build -import androidx.compose.ui.graphics.asAndroidBitmap import com.sameerasw.essentials.ShortcutHandlerActivity import com.sameerasw.essentials.domain.model.NotificationApp @@ -27,7 +26,14 @@ object ShortcutUtil { val shortcut = ShortcutInfo.Builder(context, app.packageName) .setShortLabel(app.appName) .setLongLabel(app.appName) - .setIcon(Icon.createWithBitmap(AppUtil.getShortcutIcon(context, app.packageName))) + .setIcon( + Icon.createWithBitmap( + AppUtil.getShortcutIcon( + context, + app.packageName + ) + ) + ) .setIntent(intent) .build() diff --git a/app/src/main/java/com/sameerasw/essentials/utils/TimeUtil.kt b/app/src/main/java/com/sameerasw/essentials/utils/TimeUtil.kt index 316246090..165155a90 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/TimeUtil.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/TimeUtil.kt @@ -33,7 +33,12 @@ object TimeUtil { context.getString(R.string.today) } } - days == 1L || (days == 0L && !isSameDay(now, timestamp)) -> context.getString(R.string.yesterday) + + days == 1L || (days == 0L && !isSameDay( + now, + timestamp + )) -> context.getString(R.string.yesterday) + days < 7L -> context.getString(R.string.time_days_ago, days.toInt()) days < 30L -> context.getString(R.string.time_weeks_ago, (days / 7).toInt()) days < 365L -> context.getString(R.string.time_months_ago, (days / 30).toInt()) @@ -45,6 +50,6 @@ object TimeUtil { val cal1 = java.util.Calendar.getInstance().apply { timeInMillis = t1 } val cal2 = java.util.Calendar.getInstance().apply { timeInMillis = t2 } return cal1.get(java.util.Calendar.YEAR) == cal2.get(java.util.Calendar.YEAR) && - cal1.get(java.util.Calendar.DAY_OF_YEAR) == cal2.get(java.util.Calendar.DAY_OF_YEAR) + cal1.get(java.util.Calendar.DAY_OF_YEAR) == cal2.get(java.util.Calendar.DAY_OF_YEAR) } } diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt index 3b4f2cabe..3a0d9bba6 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.core.content.FileProvider import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.gson.Gson import com.sameerasw.essentials.R import com.sameerasw.essentials.data.repository.GitHubRepository import com.sameerasw.essentials.data.repository.SettingsRepository @@ -24,7 +25,6 @@ import java.io.InputStream import java.io.OutputStream import java.net.HttpURLConnection import java.net.URL -import com.google.gson.Gson class AppUpdatesViewModel : ViewModel() { private val gitHubRepository = GitHubRepository() @@ -513,51 +513,52 @@ class AppUpdatesViewModel : ViewModel() { } } - fun exportTrackedRepos(context: Context, outputStream: OutputStream) { + fun exportTrackedRepos(context: Context, outputStream: OutputStream) { + try { + val repos = SettingsRepository(context).getTrackedRepos() + val json = gson.toJson(repos) + outputStream.write(json.toByteArray()) + outputStream.flush() + } catch (e: Exception) { + e.printStackTrace() + } finally { try { - val repos = SettingsRepository(context).getTrackedRepos() - val json = gson.toJson(repos) - outputStream.write(json.toByteArray()) - outputStream.flush() + outputStream.close() } catch (e: Exception) { - e.printStackTrace() - } finally { - try { - outputStream.close() - } catch (e: Exception) { - } } } + } - fun importTrackedRepos(context: Context, inputStream: InputStream): Boolean { - return try { - val json = inputStream.bufferedReader().use { it.readText() } - val importedRepos: List = gson.fromJson(json, Array::class.java).toList() - if (importedRepos.isNotEmpty()) { - val settingsRepo = SettingsRepository(context) - val currentRepos = settingsRepo.getTrackedRepos().toMutableList() - importedRepos.forEach { imported -> - val index = currentRepos.indexOfFirst { it.fullName == imported.fullName } - if (index != -1) { - currentRepos[index] = imported - } else { - currentRepos.add(imported) - } + fun importTrackedRepos(context: Context, inputStream: InputStream): Boolean { + return try { + val json = inputStream.bufferedReader().use { it.readText() } + val importedRepos: List = + gson.fromJson(json, Array::class.java).toList() + if (importedRepos.isNotEmpty()) { + val settingsRepo = SettingsRepository(context) + val currentRepos = settingsRepo.getTrackedRepos().toMutableList() + importedRepos.forEach { imported -> + val index = currentRepos.indexOfFirst { it.fullName == imported.fullName } + if (index != -1) { + currentRepos[index] = imported + } else { + currentRepos.add(imported) } - settingsRepo.saveTrackedRepos(currentRepos) - loadTrackedRepos(context) - true - } else { - false } - } catch (e: Exception) { - e.printStackTrace() + settingsRepo.saveTrackedRepos(currentRepos) + loadTrackedRepos(context) + true + } else { false - } finally { - try { - inputStream.close() - } catch (e: Exception) { - } + } + } catch (e: Exception) { + e.printStackTrace() + false + } finally { + try { + inputStream.close() + } catch (e: Exception) { } } - } \ No newline at end of file + } +} \ No newline at end of file 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 997bbf44a..515df2748 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/LocationReachedViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/LocationReachedViewModel.kt @@ -2,13 +2,11 @@ package com.sameerasw.essentials.viewmodels import android.app.Application import android.content.Intent -import android.widget.Toast import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.google.android.gms.location.LocationServices import com.google.android.gms.location.Priority -import com.sameerasw.essentials.R import com.sameerasw.essentials.data.repository.LocationReachedRepository import com.sameerasw.essentials.domain.model.LocationAlarm import com.sameerasw.essentials.services.LocationReachedService @@ -129,7 +127,7 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl fun startTracking(alarmId: String) { val alarm = savedAlarms.value.find { it.id == alarmId } ?: return - + // Stop any previous tracking if (activeAlarmId.value != null && activeAlarmId.value != alarmId) { stopTracking() @@ -155,7 +153,7 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl repository.saveStartDistance(dist) } } - + // Clear last trip when starting new lastTrip.value = null repository.saveLastTrip(null) @@ -164,7 +162,7 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl fun stopTracking() { val id = activeAlarmId.value ?: return val alarm = savedAlarms.value.find { it.id == id } - + if (alarm != null) { // Save as last trip lastTrip.value = alarm @@ -258,7 +256,7 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl val elapsedMillis = System.currentTimeMillis() - startT val distanceTravelled = startDistMeters - currentDistMeters - + if (distanceTravelled <= 0 || elapsedMillis <= 0) { remainingTimeMinutes.value = null return @@ -335,12 +333,14 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl if (lat != 0.0 && lng != 0.0) { android.util.Log.d("LocationReachedVM", "Parsed coordinates: $lat, $lng") - repository.setTempAlarm(LocationAlarm( - latitude = lat, - longitude = lng, - name = "New Destination", - isEnabled = false - )) + repository.setTempAlarm( + LocationAlarm( + latitude = lat, + longitude = lng, + name = "New Destination", + isEnabled = false + ) + ) repository.setShowBottomSheet(true) updateCurrentDistance() repository.setIsProcessing(false) @@ -377,12 +377,14 @@ class LocationReachedViewModel(application: Application) : AndroidViewModel(appl val lat = pathMatch.groupValues[1].toDoubleOrNull() ?: 0.0 val lng = pathMatch.groupValues[2].toDoubleOrNull() ?: 0.0 if (lat != 0.0 && lng != 0.0) { - repository.setTempAlarm(LocationAlarm( - latitude = lat, - longitude = lng, - name = "New Destination", - isEnabled = false - )) + repository.setTempAlarm( + LocationAlarm( + latitude = lat, + longitude = lng, + name = "New Destination", + isEnabled = false + ) + ) repository.setShowBottomSheet(true) updateCurrentDistance() } 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 dd40ac4ed..cf90bb4ee 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -2,7 +2,6 @@ package com.sameerasw.essentials.viewmodels import android.Manifest import android.app.Activity -import com.sameerasw.essentials.domain.model.DnsPreset import android.app.ActivityManager import android.app.admin.DevicePolicyManager import android.content.BroadcastReceiver @@ -12,9 +11,7 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.database.ContentObserver -import android.util.Log import android.net.Uri -import android.os.Build import android.os.Handler import android.os.Looper import android.os.PowerManager @@ -40,13 +37,14 @@ import com.sameerasw.essentials.data.repository.UpdateRepository import com.sameerasw.essentials.domain.HapticFeedbackType import com.sameerasw.essentials.domain.MapsState import com.sameerasw.essentials.domain.model.AppSelection +import com.sameerasw.essentials.domain.model.DnsPreset import com.sameerasw.essentials.domain.model.NotificationApp import com.sameerasw.essentials.domain.model.NotificationLightingColorMode import com.sameerasw.essentials.domain.model.NotificationLightingSide import com.sameerasw.essentials.domain.model.NotificationLightingStyle import com.sameerasw.essentials.domain.model.NotificationLightingSweepPosition -import com.sameerasw.essentials.domain.model.SearchableItem import com.sameerasw.essentials.domain.model.ScaleAnimationsProfile +import com.sameerasw.essentials.domain.model.SearchableItem import com.sameerasw.essentials.domain.model.UpdateInfo import com.sameerasw.essentials.domain.registry.SearchRegistry import com.sameerasw.essentials.services.CaffeinateWakeLockService @@ -130,7 +128,8 @@ class MainViewModel : ViewModel() { val isAodForceTurnOffEnabled = mutableStateOf(false) val isAutoAccessibilityEnabled = mutableStateOf(false) val isNotificationGlanceSameAsLightingEnabled = mutableStateOf(true) - val isOnboardingCompleted = mutableStateOf(true) // Default to true so it doesn't flash on first check if not loaded + val isOnboardingCompleted = + mutableStateOf(true) // Default to true so it doesn't flash on first check if not loaded val isWhatsNewVisible = mutableStateOf(false) val dnsPresets = mutableStateListOf() val addedQSTiles = mutableStateOf>(emptySet()) @@ -151,15 +150,16 @@ class MainViewModel : ViewModel() { // Live Wallpaper val liveWallpaperSelectedVideo = mutableStateOf(SettingsRepository.LIVE_WALLPAPER_DEFAULT_VIDEO) - val liveWallpaperPlaybackTrigger = mutableStateOf(SettingsRepository.LIVE_WALLPAPER_TRIGGER_UNLOCK) + val liveWallpaperPlaybackTrigger = + mutableStateOf(SettingsRepository.LIVE_WALLPAPER_TRIGGER_UNLOCK) val liveWallpaperCustomVideos = mutableStateListOf() - val shutUpConfigs = mutableStateOf>(emptyList()) + val shutUpConfigs = + mutableStateOf>(emptyList()) val isShutUpLoading = mutableStateOf(false) val isShutUpAttemptShizukuRestart = mutableStateOf(true) - data class CalendarAccount( val id: Long, val name: String, @@ -290,27 +290,38 @@ class MainViewModel : ViewModel() { Settings.System.getUriFor(Settings.System.FONT_SCALE) -> { fontScale.floatValue = settingsRepository.getFontScale() } + Settings.Secure.getUriFor("font_weight_adjustment") -> { fontWeight.intValue = settingsRepository.getFontWeight() } + Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE) -> { - animatorDurationScale.floatValue = settingsRepository.getAnimationScale(Settings.Global.ANIMATOR_DURATION_SCALE) + animatorDurationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.ANIMATOR_DURATION_SCALE) } + Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE) -> { - transitionAnimationScale.floatValue = settingsRepository.getAnimationScale(Settings.Global.TRANSITION_ANIMATION_SCALE) + transitionAnimationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.TRANSITION_ANIMATION_SCALE) } + Settings.Global.getUriFor(Settings.Global.WINDOW_ANIMATION_SCALE) -> { - windowAnimationScale.floatValue = settingsRepository.getAnimationScale(Settings.Global.WINDOW_ANIMATION_SCALE) + windowAnimationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.WINDOW_ANIMATION_SCALE) } + Settings.Secure.getUriFor("display_density_forced") -> { smallestWidth.intValue = settingsRepository.getSmallestWidth() } + Settings.Secure.getUriFor("doze_always_on") -> { isAodEnabled.value = settingsRepository.isAodEnabled() } + Settings.Secure.getUriFor("sysui_qs_tiles") -> { appContext?.let { updateAddedQSTiles(it) } } + Settings.System.getUriFor("peak_refresh_rate"), Settings.System.getUriFor("min_refresh_rate") -> { appContext?.let { syncRefreshRateState(it) } @@ -322,7 +333,10 @@ class MainViewModel : ViewModel() { private val preferenceChangeListener = object : android.content.SharedPreferences.OnSharedPreferenceChangeListener { - override fun onSharedPreferenceChanged(sharedPreferences: android.content.SharedPreferences?, key: String?) { + override fun onSharedPreferenceChanged( + sharedPreferences: android.content.SharedPreferences?, + key: String? + ) { if (key == null) return when (key) { @@ -367,7 +381,8 @@ class MainViewModel : ViewModel() { settingsRepository.getInt(key, 1) SettingsRepository.KEY_FREEZE_AUTO_EXCLUDED_APPS -> { - freezeAutoExcludedApps.value = settingsRepository.getFreezeAutoExcludedApps() + freezeAutoExcludedApps.value = + settingsRepository.getFreezeAutoExcludedApps() } SettingsRepository.KEY_FREEZE_MODE -> { @@ -477,7 +492,8 @@ class MainViewModel : ViewModel() { } SettingsRepository.KEY_AMBIENT_MUSIC_GLANCE_DOCKED_MODE -> { - isAmbientMusicGlanceDockedModeEnabled.value = settingsRepository.getBoolean(key) + isAmbientMusicGlanceDockedModeEnabled.value = + settingsRepository.getBoolean(key) } SettingsRepository.KEY_CALENDAR_SYNC_ENABLED -> { @@ -492,22 +508,44 @@ class MainViewModel : ViewModel() { appContext?.let { refreshTrackedUpdates(it) } } - SettingsRepository.KEY_FONT_SCALE -> fontScale.floatValue = settingsRepository.getFontScale() - SettingsRepository.KEY_FONT_WEIGHT -> fontWeight.intValue = settingsRepository.getFontWeight() - SettingsRepository.KEY_ANIMATOR_DURATION_SCALE -> animatorDurationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.ANIMATOR_DURATION_SCALE) - SettingsRepository.KEY_TRANSITION_ANIMATION_SCALE -> transitionAnimationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE) - SettingsRepository.KEY_WINDOW_ANIMATION_SCALE -> windowAnimationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE) - SettingsRepository.KEY_SMALLEST_WIDTH -> smallestWidth.intValue = settingsRepository.getSmallestWidth() - SettingsRepository.KEY_REFRESH_RATE_MODE -> refreshRateMode.value = settingsRepository.getRefreshRateMode() + SettingsRepository.KEY_FONT_SCALE -> fontScale.floatValue = + settingsRepository.getFontScale() + + SettingsRepository.KEY_FONT_WEIGHT -> fontWeight.intValue = + settingsRepository.getFontWeight() + + SettingsRepository.KEY_ANIMATOR_DURATION_SCALE -> animatorDurationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.ANIMATOR_DURATION_SCALE) + + SettingsRepository.KEY_TRANSITION_ANIMATION_SCALE -> transitionAnimationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.TRANSITION_ANIMATION_SCALE) + + SettingsRepository.KEY_WINDOW_ANIMATION_SCALE -> windowAnimationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.WINDOW_ANIMATION_SCALE) + + SettingsRepository.KEY_SMALLEST_WIDTH -> smallestWidth.intValue = + settingsRepository.getSmallestWidth() + + SettingsRepository.KEY_REFRESH_RATE_MODE -> refreshRateMode.value = + settingsRepository.getRefreshRateMode() + SettingsRepository.KEY_REFRESH_RATE_FIXED, SettingsRepository.KEY_REFRESH_RATE_MIN, SettingsRepository.KEY_REFRESH_RATE_PEAK -> { appContext?.let { syncRefreshRateState(it) } } - SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED -> isNotificationGlanceEnabled.value = settingsRepository.getBoolean(key) - SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED -> isAodForceTurnOffEnabled.value = settingsRepository.getBoolean(key) - SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING -> isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(key, true) - SettingsRepository.KEY_AUTO_ACCESSIBILITY_ENABLED -> isAutoAccessibilityEnabled.value = settingsRepository.getBoolean(key) + + SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED -> isNotificationGlanceEnabled.value = + settingsRepository.getBoolean(key) + + SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED -> isAodForceTurnOffEnabled.value = + settingsRepository.getBoolean(key) + + SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING -> isNotificationGlanceSameAsLightingEnabled.value = + settingsRepository.getBoolean(key, true) + + SettingsRepository.KEY_AUTO_ACCESSIBILITY_ENABLED -> isAutoAccessibilityEnabled.value = + settingsRepository.getBoolean(key) SettingsRepository.KEY_USE_BLUR -> { appContext?.let { updateBlurState(it) } @@ -523,7 +561,8 @@ class MainViewModel : ViewModel() { } SettingsRepository.KEY_FLASHLIGHT_PULSE_MAX_INTENSITY -> { - flashlightPulseMaxIntensity.floatValue = settingsRepository.getFloat(key, 0.5f) + flashlightPulseMaxIntensity.floatValue = + settingsRepository.getFloat(key, 0.5f) } SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_ENABLED -> { @@ -531,7 +570,8 @@ class MainViewModel : ViewModel() { } SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_HEIGHT -> { - circleToSearchGestureHeight.floatValue = settingsRepository.getFloat(key, 48f) + circleToSearchGestureHeight.floatValue = + settingsRepository.getFloat(key, 48f) } SettingsRepository.KEY_CIRCLE_TO_SEARCH_PREVIEW_ENABLED -> { @@ -544,11 +584,13 @@ class MainViewModel : ViewModel() { } SettingsRepository.KEY_LIVE_WALLPAPER_SELECTED_VIDEO -> { - liveWallpaperSelectedVideo.value = settingsRepository.getLiveWallpaperSelectedVideo() + liveWallpaperSelectedVideo.value = + settingsRepository.getLiveWallpaperSelectedVideo() } SettingsRepository.KEY_LIVE_WALLPAPER_PLAYBACK_TRIGGER -> { - liveWallpaperPlaybackTrigger.value = settingsRepository.getLiveWallpaperPlaybackTrigger() + liveWallpaperPlaybackTrigger.value = + settingsRepository.getLiveWallpaperPlaybackTrigger() } SettingsRepository.KEY_LIVE_WALLPAPER_CUSTOM_VIDEOS -> { @@ -557,11 +599,19 @@ class MainViewModel : ViewModel() { } SettingsRepository.KEY_SHUT_UP_ATTEMPT_SHIZUKU_RESTART -> { - isShutUpAttemptShizukuRestart.value = settingsRepository.isShutUpAttemptShizukuRestartEnabled() + isShutUpAttemptShizukuRestart.value = + settingsRepository.isShutUpAttemptShizukuRestartEnabled() } + SettingsRepository.KEY_DISABLE_ROTATION_SUGGESTION -> { - isDisableRotationSuggestionEnabled.value = settingsRepository.getBoolean(key) - appContext?.let { applyDisableRotationSuggestion(it, isDisableRotationSuggestionEnabled.value) } + isDisableRotationSuggestionEnabled.value = + settingsRepository.getBoolean(key) + appContext?.let { + applyDisableRotationSuggestion( + it, + isDisableRotationSuggestionEnabled.value + ) + } } } } @@ -602,13 +652,18 @@ class MainViewModel : ViewModel() { fun saveShutUpSelectedApps(context: Context, apps: List) { val currentConfigs = settingsRepository.loadShutUpConfigs().associateBy { it.packageName } val newConfigs = apps.filter { it.isEnabled }.map { - currentConfigs[it.packageName] ?: com.sameerasw.essentials.domain.model.ShutUpAppConfig(it.packageName) + currentConfigs[it.packageName] ?: com.sameerasw.essentials.domain.model.ShutUpAppConfig( + it.packageName + ) } settingsRepository.saveShutUpConfigs(newConfigs) loadShutUpConfigs() } - fun createShutUpShortcut(context: Context, config: com.sameerasw.essentials.domain.model.ShutUpAppConfig) { + fun createShutUpShortcut( + context: Context, + config: com.sameerasw.essentials.domain.model.ShutUpAppConfig + ) { val appName = try { val appInfo = context.packageManager.getApplicationInfo(config.packageName, 0) context.packageManager.getApplicationLabel(appInfo).toString() @@ -616,23 +671,33 @@ class MainViewModel : ViewModel() { config.packageName } - val intent = Intent(context, com.sameerasw.essentials.ShutUpShortcutActivity::class.java).apply { - action = Intent.ACTION_MAIN - putExtra("package_name", config.packageName) - data = Uri.parse("shutup://${config.packageName}") - } + val intent = + Intent(context, com.sameerasw.essentials.ShutUpShortcutActivity::class.java).apply { + action = Intent.ACTION_MAIN + putExtra("package_name", config.packageName) + data = Uri.parse("shutup://${config.packageName}") + } if (androidx.core.content.pm.ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { val appIcon = AppUtil.getShortcutIcon(context, config.packageName) - val pinShortcutInfo = androidx.core.content.pm.ShortcutInfoCompat.Builder(context, config.packageName) - .setShortLabel(appName) - .setIcon(androidx.core.graphics.drawable.IconCompat.createWithBitmap(appIcon)) - .setIntent(intent) - .build() + val pinShortcutInfo = + androidx.core.content.pm.ShortcutInfoCompat.Builder(context, config.packageName) + .setShortLabel(appName) + .setIcon(androidx.core.graphics.drawable.IconCompat.createWithBitmap(appIcon)) + .setIntent(intent) + .build() - androidx.core.content.pm.ShortcutManagerCompat.requestPinShortcut(context, pinShortcutInfo, null) - Toast.makeText(context, context.getString(R.string.shut_up_shortcut_created, appName), Toast.LENGTH_SHORT).show() + androidx.core.content.pm.ShortcutManagerCompat.requestPinShortcut( + context, + pinShortcutInfo, + null + ) + Toast.makeText( + context, + context.getString(R.string.shut_up_shortcut_created, appName), + Toast.LENGTH_SHORT + ).show() } } @@ -653,35 +718,52 @@ class MainViewModel : ViewModel() { isWriteSecureSettingsEnabled.value = PermissionUtils.canWriteSecureSettings(context) isShizukuAvailable.value = ShizukuUtils.isShizukuAvailable() isShizukuPermissionGranted.value = ShizukuUtils.hasPermission() - isAutoAccessibilityEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_AUTO_ACCESSIBILITY_ENABLED) - isHideGestureBarEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_HIDE_GESTURE_BAR_ENABLED, false) - isCircleToSearchGestureEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_ENABLED, false) - circleToSearchGestureHeight.floatValue = settingsRepository.getFloat(SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_HEIGHT, 48f) - isCircleToSearchPreviewEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_CIRCLE_TO_SEARCH_PREVIEW_ENABLED, false) - 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) + isAutoAccessibilityEnabled.value = + settingsRepository.getBoolean(SettingsRepository.KEY_AUTO_ACCESSIBILITY_ENABLED) + isHideGestureBarEnabled.value = + settingsRepository.getBoolean(SettingsRepository.KEY_HIDE_GESTURE_BAR_ENABLED, false) + isCircleToSearchGestureEnabled.value = settingsRepository.getBoolean( + SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_ENABLED, + false + ) + circleToSearchGestureHeight.floatValue = + settingsRepository.getFloat(SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_HEIGHT, 48f) + isCircleToSearchPreviewEnabled.value = settingsRepository.getBoolean( + SettingsRepository.KEY_CIRCLE_TO_SEARCH_PREVIEW_ENABLED, + false + ) + 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() + lockScreenClockSelectedColorId.value = + settingsRepository.getLockScreenClockSelectedColorId() lockScreenClockSeedColor.intValue = settingsRepository.getLockScreenClockSeedColor() loadShutUpConfigs() if (isHideGestureBarEnabled.value) { applyHideGestureBar(context, true) } - + updateAppDetectionService(context) if (isAutoAccessibilityEnabled.value && !isAccessibilityEnabled.value) { - val serviceName = "${context.packageName}/${ScreenOffAccessibilityService::class.java.name}" + val serviceName = + "${context.packageName}/${ScreenOffAccessibilityService::class.java.name}" var success = false if (isWriteSecureSettingsEnabled.value) { @@ -690,7 +772,11 @@ class MainViewModel : ViewModel() { context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES ) ?: "" - val newServices = if (enabledServices.isEmpty()) serviceName else if (!enabledServices.contains(serviceName)) "$enabledServices:$serviceName" else enabledServices + val newServices = + if (enabledServices.isEmpty()) serviceName else if (!enabledServices.contains( + serviceName + ) + ) "$enabledServices:$serviceName" else enabledServices Settings.Secure.putString( context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, @@ -708,9 +794,14 @@ class MainViewModel : ViewModel() { } if (success) { - isAccessibilityEnabled.value = PermissionUtils.isAccessibilityServiceEnabled(context) + isAccessibilityEnabled.value = + PermissionUtils.isAccessibilityServiceEnabled(context) if (isAccessibilityEnabled.value) { - android.widget.Toast.makeText(context, "Accessibility auto-granted", android.widget.Toast.LENGTH_SHORT).show() + Toast.makeText( + context, + "Accessibility auto-granted", + Toast.LENGTH_SHORT + ).show() } } } @@ -834,12 +925,15 @@ class MainViewModel : ViewModel() { isWidgetEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_WIDGET_ENABLED) isStatusBarIconControlEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_STATUS_BAR_ICON_CONTROL_ENABLED) - + fontScale.floatValue = settingsRepository.getFontScale() fontWeight.intValue = settingsRepository.getFontWeight() - animatorDurationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.ANIMATOR_DURATION_SCALE) - transitionAnimationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE) - windowAnimationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE) + animatorDurationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.ANIMATOR_DURATION_SCALE) + transitionAnimationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.TRANSITION_ANIMATION_SCALE) + windowAnimationScale.floatValue = + settingsRepository.getAnimationScale(Settings.Global.WINDOW_ANIMATION_SCALE) smallestWidth.intValue = settingsRepository.getSmallestWidth() refreshRateMode.value = settingsRepository.getRefreshRateMode() syncRefreshRateState(context) @@ -864,11 +958,15 @@ class MainViewModel : ViewModel() { notificationLightingStyle.value = settingsRepository.getNotificationLightingStyle() notificationLightingColorMode.value = settingsRepository.getNotificationLightingColorMode() - isUseUsageAccess.value = settingsRepository.getBoolean(SettingsRepository.KEY_USE_USAGE_ACCESS) - isOnboardingCompleted.value = settingsRepository.getBoolean(SettingsRepository.KEY_ONBOARDING_COMPLETED, false) - - val lastShownCounter = settingsRepository.getInt(SettingsRepository.KEY_WHATS_NEW_LAST_SHOWN_COUNTER, 0) - isWhatsNewVisible.value = isOnboardingCompleted.value && lastShownCounter < com.sameerasw.essentials.BuildConfig.WHATS_NEW_COUNTER + isUseUsageAccess.value = + settingsRepository.getBoolean(SettingsRepository.KEY_USE_USAGE_ACCESS) + isOnboardingCompleted.value = + settingsRepository.getBoolean(SettingsRepository.KEY_ONBOARDING_COMPLETED, false) + + val lastShownCounter = + settingsRepository.getInt(SettingsRepository.KEY_WHATS_NEW_LAST_SHOWN_COUNTER, 0) + isWhatsNewVisible.value = + isOnboardingCompleted.value && lastShownCounter < com.sameerasw.essentials.BuildConfig.WHATS_NEW_COUNTER notificationLightingCustomColor.intValue = settingsRepository.getInt( SettingsRepository.KEY_EDGE_LIGHTING_CUSTOM_COLOR, @@ -887,9 +985,9 @@ class MainViewModel : ViewModel() { isRootEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_USE_ROOT) if (isRootEnabled.value) { - isRootAvailable.value = com.sameerasw.essentials.utils.RootUtils.isRootAvailable() + isRootAvailable.value = RootUtils.isRootAvailable() isRootPermissionGranted.value = - com.sameerasw.essentials.utils.RootUtils.isRootPermissionGranted() + RootUtils.isRootPermissionGranted() } else { isRootAvailable.value = false isRootPermissionGranted.value = false @@ -898,14 +996,21 @@ class MainViewModel : ViewModel() { notificationLightingIndicatorScale.value = settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_INDICATOR_SCALE, 1.0f) notificationLightingGlowSides.value = settingsRepository.getNotificationLightingGlowSides() - notificationLightingSweepPosition.value = settingsRepository.getNotificationLightingSweepPosition() - notificationLightingSweepThickness.floatValue = settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_SWEEP_THICKNESS, 8f) - notificationLightingSweepRandomShapes.value = settingsRepository.getBoolean(SettingsRepository.KEY_EDGE_LIGHTING_SWEEP_RANDOM_SHAPES, true) + notificationLightingSweepPosition.value = + settingsRepository.getNotificationLightingSweepPosition() + notificationLightingSweepThickness.floatValue = + settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_SWEEP_THICKNESS, 8f) + notificationLightingSweepRandomShapes.value = settingsRepository.getBoolean( + SettingsRepository.KEY_EDGE_LIGHTING_SWEEP_RANDOM_SHAPES, + true + ) MapsState.isEnabled = isMapsPowerSavingEnabled.value hapticFeedbackType.value = settingsRepository.getHapticFeedbackType() defaultTab.value = settingsRepository.getDIYTab() - sentryReportMode.value = settingsRepository.getString(SettingsRepository.KEY_SENTRY_REPORT_MODE, "auto") ?: "auto" + sentryReportMode.value = + settingsRepository.getString(SettingsRepository.KEY_SENTRY_REPORT_MODE, "auto") + ?: "auto" // Live Wallpaper initialization liveWallpaperSelectedVideo.value = settingsRepository.getLiveWallpaperSelectedVideo() @@ -1038,13 +1143,17 @@ class MainViewModel : ViewModel() { isLongPressSymbolsEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_KEYBOARD_LONG_PRESS_SYMBOLS, false) isAccentedCharactersEnabled.value = - settingsRepository.getBoolean(SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS, false) + settingsRepository.getBoolean( + SettingsRepository.KEY_KEYBOARD_ACCENTED_CHARACTERS, + false + ) isAirSyncConnectionEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_AIRSYNC_CONNECTION_ENABLED) - + // April Fools Check - isAprilFoolsShown.value = settingsRepository.getBoolean(SettingsRepository.KEY_APRIL_FOOLS_SHOWN) + isAprilFoolsShown.value = + settingsRepository.getBoolean(SettingsRepository.KEY_APRIL_FOOLS_SHOWN) if (!isAprilFoolsShown.value) { val calendar = java.util.Calendar.getInstance() val month = calendar.get(java.util.Calendar.MONTH) @@ -1127,9 +1236,14 @@ class MainViewModel : ViewModel() { isCalendarSyncPeriodicEnabled.value = settingsRepository.isCalendarSyncPeriodicEnabled() isBatteryNotificationEnabled.value = settingsRepository.isBatteryNotificationEnabled() selectedCalendarIds.value = settingsRepository.getCalendarSyncSelectedCalendars() - isNotificationGlanceEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED) - isAodForceTurnOffEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED) - isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, true) + isNotificationGlanceEnabled.value = + settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED) + isAodForceTurnOffEnabled.value = + settingsRepository.getBoolean(SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED) + isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean( + SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, + true + ) scaleAnimationsMode.value = settingsRepository.getScaleAnimationsMode() isTouchSensitivityEnabled.value = settingsRepository.getTouchSensitivityEnabled() isAutoRotateEnabled.value = settingsRepository.getAutoRotateEnabled() @@ -1148,7 +1262,10 @@ class MainViewModel : ViewModel() { } private fun stopBatteryNotificationService(context: Context) { - val intent = Intent(context, com.sameerasw.essentials.services.BatteryNotificationService::class.java) + val intent = Intent( + context, + com.sameerasw.essentials.services.BatteryNotificationService::class.java + ) context.stopService(intent) } @@ -1226,10 +1343,10 @@ class MainViewModel : ViewModel() { isAccentedCharactersEnabled.value = enabled settingsRepository.setAccentedCharactersEnabled(enabled) } - + fun loadUserDictionaryWords(context: Context) { - val ims = context.applicationContext as? com.sameerasw.essentials.ime.EssentialsInputMethodService - + context.applicationContext as? com.sameerasw.essentials.ime.EssentialsInputMethodService + viewModelScope.launch(Dispatchers.IO) { val file = java.io.File(context.filesDir, "user_dict.txt") if (file.exists()) { @@ -1237,20 +1354,20 @@ class MainViewModel : ViewModel() { file.forEachLine { line -> val parts = line.split(" ") if (parts.size >= 2) { - map[parts[0]] = parts[1].toLongOrNull() ?: 1L + map[parts[0]] = parts[1].toLongOrNull() ?: 1L } } withContext(Dispatchers.Main) { userDictionaryWords.value = map } } else { - withContext(Dispatchers.Main) { + withContext(Dispatchers.Main) { userDictionaryWords.value = emptyMap() } } } } - + fun deleteUserWord(word: String, context: Context) { viewModelScope.launch(Dispatchers.IO) { // Read, remove, write @@ -1259,7 +1376,10 @@ class MainViewModel : ViewModel() { val lines = file.readLines().filter { !it.startsWith("$word ") } file.writeText(lines.joinToString("\n")) loadUserDictionaryWords(context) - settingsRepository.putLong(SettingsRepository.KEY_USER_DICT_LAST_UPDATE, System.currentTimeMillis()) + settingsRepository.putLong( + SettingsRepository.KEY_USER_DICT_LAST_UPDATE, + System.currentTimeMillis() + ) } } } @@ -1272,7 +1392,10 @@ class MainViewModel : ViewModel() { withContext(Dispatchers.Main) { userDictionaryWords.value = emptyMap() } - settingsRepository.putLong(SettingsRepository.KEY_USER_DICT_LAST_UPDATE, System.currentTimeMillis()) + settingsRepository.putLong( + SettingsRepository.KEY_USER_DICT_LAST_UPDATE, + System.currentTimeMillis() + ) } } } @@ -1291,7 +1414,7 @@ class MainViewModel : ViewModel() { val useBlurSetting = settingsRepository.getBoolean(SettingsRepository.KEY_USE_BLUR, true) val isProblematic = DeviceUtils.isBlurProblematicDevice() val isPowerSave = DeviceUtils.isPowerSaveMode(context) - + isBlurSettingEnabled.value = useBlurSetting isBlurEnabled.value = useBlurSetting && !isProblematic && !isPowerSave } @@ -1422,7 +1545,10 @@ class MainViewModel : ViewModel() { fun setAmbientShowLockScreenEnabled(enabled: Boolean, context: Context) { isAmbientShowLockScreenEnabled.value = enabled - settingsRepository.putBoolean(SettingsRepository.KEY_EDGE_LIGHTING_AMBIENT_SHOW_LOCK_SCREEN, enabled) + settingsRepository.putBoolean( + SettingsRepository.KEY_EDGE_LIGHTING_AMBIENT_SHOW_LOCK_SCREEN, + enabled + ) } fun setHideGestureBarEnabled(enabled: Boolean, context: Context) { @@ -1433,7 +1559,10 @@ class MainViewModel : ViewModel() { fun setCircleToSearchGestureEnabled(enabled: Boolean, context: Context) { isCircleToSearchGestureEnabled.value = enabled - settingsRepository.putBoolean(SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_ENABLED, enabled) + settingsRepository.putBoolean( + SettingsRepository.KEY_CIRCLE_TO_SEARCH_GESTURE_ENABLED, + enabled + ) } fun setCircleToSearchGestureHeight(height: Float) { @@ -1443,17 +1572,26 @@ class MainViewModel : ViewModel() { fun setCircleToSearchPreviewEnabled(enabled: Boolean) { isCircleToSearchPreviewEnabled.value = enabled - settingsRepository.putBoolean(SettingsRepository.KEY_CIRCLE_TO_SEARCH_PREVIEW_ENABLED, enabled) + settingsRepository.putBoolean( + SettingsRepository.KEY_CIRCLE_TO_SEARCH_PREVIEW_ENABLED, + enabled + ) } fun setHideGestureBarOnLauncherEnabled(enabled: Boolean, context: Context) { isHideGestureBarOnLauncherEnabled.value = enabled - settingsRepository.putBoolean(SettingsRepository.KEY_HIDE_GESTURE_BAR_ON_LAUNCHER_ENABLED, enabled) - + settingsRepository.putBoolean( + SettingsRepository.KEY_HIDE_GESTURE_BAR_ON_LAUNCHER_ENABLED, + enabled + ) + if (!enabled) { - com.sameerasw.essentials.utils.StatusBarManager.requestRestore(context, "GestureBarAutomation") + com.sameerasw.essentials.utils.StatusBarManager.requestRestore( + context, + "GestureBarAutomation" + ) } - + updateAppDetectionService(context) } @@ -1553,7 +1691,8 @@ class MainViewModel : ViewModel() { // Update effective seed color based on new tone if (lockScreenClockSelectedColorId.value != "DEFAULT") { - val effectiveSeed = calculateEffectiveSeedColor(lockScreenClockSelectedColorId.value, value) + val effectiveSeed = + calculateEffectiveSeedColor(lockScreenClockSelectedColorId.value, value) lockScreenClockSeedColor.intValue = effectiveSeed settingsRepository.setLockScreenClockSeedColor(effectiveSeed) } @@ -1563,7 +1702,10 @@ class MainViewModel : ViewModel() { fun setLockScreenClockColor(id: String, seed: Int, context: Context) { lockScreenClockSelectedColorId.value = id - val effectiveSeed = if (id == "DEFAULT") 0 else calculateEffectiveSeedColor(id, lockScreenClockColorTone.intValue) + val effectiveSeed = if (id == "DEFAULT") 0 else calculateEffectiveSeedColor( + id, + lockScreenClockColorTone.intValue + ) lockScreenClockSeedColor.intValue = effectiveSeed settingsRepository.setLockScreenClockSelectedColorId(id) settingsRepository.setLockScreenClockSeedColor(effectiveSeed) @@ -1616,7 +1758,10 @@ class MainViewModel : ViewModel() { setOf(com.sameerasw.essentials.utils.StatusBarManager.FLAG_HOME) ) } else { - com.sameerasw.essentials.utils.StatusBarManager.requestRestore(context, "HideGestureBar") + com.sameerasw.essentials.utils.StatusBarManager.requestRestore( + context, + "HideGestureBar" + ) } } @@ -1747,7 +1892,10 @@ class MainViewModel : ViewModel() { fun setAmbientMusicGlanceRandomShapesEnabled(enabled: Boolean) { isAmbientMusicGlanceRandomShapesEnabled.value = enabled - settingsRepository.putBoolean(SettingsRepository.KEY_AMBIENT_MUSIC_GLANCE_RANDOM_SHAPES, enabled) + settingsRepository.putBoolean( + SettingsRepository.KEY_AMBIENT_MUSIC_GLANCE_RANDOM_SHAPES, + enabled + ) } fun switchScaleAnimationsMode(mode: String) { @@ -1770,7 +1918,7 @@ class MainViewModel : ViewModel() { // 2. Load new profile val newProfile = settingsRepository.getScaleAnimationsProfile(mode) - + // 3. Update mode scaleAnimationsMode.value = mode settingsRepository.setScaleAnimationsMode(mode) @@ -1778,9 +1926,18 @@ class MainViewModel : ViewModel() { // 4. Apply new profile setFontScale(newProfile.fontScale) setFontWeight(newProfile.fontWeight) - setAnimationScale(android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, newProfile.animatorDurationScale) - setAnimationScale(android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, newProfile.transitionAnimationScale) - setAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE, newProfile.windowAnimationScale) + setAnimationScale( + Settings.Global.ANIMATOR_DURATION_SCALE, + newProfile.animatorDurationScale + ) + setAnimationScale( + Settings.Global.TRANSITION_ANIMATION_SCALE, + newProfile.transitionAnimationScale + ) + setAnimationScale( + Settings.Global.WINDOW_ANIMATION_SCALE, + newProfile.windowAnimationScale + ) setSmallestWidth(newProfile.smallestWidth) setTouchSensitivityEnabled(newProfile.touchSensitivityEnabled) setAutoRotateEnabled(newProfile.autoRotateEnabled) @@ -1819,7 +1976,10 @@ class MainViewModel : ViewModel() { if (mode == RefreshRateUtils.MODE_RANGE) { if (minRefreshRate.floatValue <= 0f && peakRefreshRate.floatValue <= 0f) { val seedValue = when { - fixedRefreshRate.floatValue > 0f -> RefreshRateUtils.normalizeRate(fixedRefreshRate.floatValue) + fixedRefreshRate.floatValue > 0f -> RefreshRateUtils.normalizeRate( + fixedRefreshRate.floatValue + ) + else -> 60f } minRefreshRate.floatValue = seedValue @@ -1973,7 +2133,12 @@ class MainViewModel : ViewModel() { ) } - private fun persistRefreshRateStateIfNeeded(mode: String, fixed: Float, min: Float, peak: Float) { + private fun persistRefreshRateStateIfNeeded( + mode: String, + fixed: Float, + min: Float, + peak: Float + ) { val storedMode = settingsRepository.getRefreshRateMode() val storedFixed = settingsRepository.getFloat(SettingsRepository.KEY_REFRESH_RATE_FIXED, 0f) val storedMin = settingsRepository.getFloat(SettingsRepository.KEY_REFRESH_RATE_MIN, 0f) @@ -2015,9 +2180,14 @@ class MainViewModel : ViewModel() { fun setAnimationScale(key: String, scale: Float) { when (key) { - android.provider.Settings.Global.ANIMATOR_DURATION_SCALE -> animatorDurationScale.floatValue = scale - android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE -> transitionAnimationScale.floatValue = scale - android.provider.Settings.Global.WINDOW_ANIMATION_SCALE -> windowAnimationScale.floatValue = scale + Settings.Global.ANIMATOR_DURATION_SCALE -> animatorDurationScale.floatValue = + scale + + Settings.Global.TRANSITION_ANIMATION_SCALE -> transitionAnimationScale.floatValue = + scale + + Settings.Global.WINDOW_ANIMATION_SCALE -> windowAnimationScale.floatValue = + scale } settingsRepository.setAnimationScale(key, scale) } @@ -2028,9 +2198,9 @@ class MainViewModel : ViewModel() { } fun resetAnimationsToDefault() { - setAnimationScale(android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) - setAnimationScale(android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1.0f) - setAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE, 1.0f) + setAnimationScale(Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) + setAnimationScale(Settings.Global.TRANSITION_ANIMATION_SCALE, 1.0f) + setAnimationScale(Settings.Global.WINDOW_ANIMATION_SCALE, 1.0f) } fun updateSmallestWidth(width: Int) { @@ -2223,7 +2393,10 @@ class MainViewModel : ViewModel() { fun setFlashlightPulseMaxIntensity(intensity: Float) { flashlightPulseMaxIntensity.floatValue = intensity - settingsRepository.putFloat(SettingsRepository.KEY_FLASHLIGHT_PULSE_MAX_INTENSITY, intensity) + settingsRepository.putFloat( + SettingsRepository.KEY_FLASHLIGHT_PULSE_MAX_INTENSITY, + intensity + ) } fun previewFlashlightPulse(context: Context) { @@ -2243,7 +2416,10 @@ class MainViewModel : ViewModel() { val radius = cornerRadiusDp ?: settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_CORNER_RADIUS, 20f) val thickness = strokeThicknessDp - ?: settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_STROKE_THICKNESS, 8f) + ?: settingsRepository.getFloat( + SettingsRepository.KEY_EDGE_LIGHTING_STROKE_THICKNESS, + 8f + ) putExtra("corner_radius_dp", radius) putExtra("stroke_thickness_dp", thickness) @@ -2285,7 +2461,8 @@ class MainViewModel : ViewModel() { fun triggerNotificationLightingSystem(context: Context) { if (!ShellUtils.hasPermission(context)) return - val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager + val windowManager = + context.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager val metrics = android.util.DisplayMetrics() windowManager.defaultDisplay.getRealMetrics(metrics) val centerX = metrics.widthPixels / 2 @@ -2323,8 +2500,10 @@ class MainViewModel : ViewModel() { } fun requestWriteSecureSettingsPermission(context: Context) { - val adbCommand = "adb shell pm grant ${context.packageName} android.permission.WRITE_SECURE_SETTINGS" - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager + val adbCommand = + "adb shell pm grant ${context.packageName} android.permission.WRITE_SECURE_SETTINGS" + val clipboard = + context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager val clip = android.content.ClipData.newPlainText("adb_command", adbCommand) clipboard.setPrimaryClip(clip) } @@ -2377,7 +2556,7 @@ class MainViewModel : ViewModel() { notificationLightingIndicatorX.value = x notificationLightingIndicatorY.value = y notificationLightingIndicatorScale.value = scale - + try { val intent = Intent(context, NotificationLightingService::class.java).apply { addLightingExtras(styleOverride = NotificationLightingStyle.INDICATOR) @@ -2395,7 +2574,7 @@ class MainViewModel : ViewModel() { ) { notificationLightingSweepPosition.value = position notificationLightingSweepThickness.floatValue = thickness - + try { val intent = Intent(context, NotificationLightingService::class.java).apply { addLightingExtras(styleOverride = NotificationLightingStyle.SWEEP) @@ -2614,7 +2793,7 @@ class MainViewModel : ViewModel() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { try { val intent = Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT).apply { - data = android.net.Uri.fromParts("package", context.packageName, null) + data = Uri.fromParts("package", context.packageName, null) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) @@ -2912,7 +3091,12 @@ class MainViewModel : ViewModel() { fun anyAppsCurrentlyFrozen(context: Context): Boolean { val picked = freezePickedApps.value - return picked.any { com.sameerasw.essentials.utils.FreezeManager.isAppFrozen(context, it.packageName) } + return picked.any { + com.sameerasw.essentials.utils.FreezeManager.isAppFrozen( + context, + it.packageName + ) + } } fun setFreezeMode(mode: Int, context: Context) { @@ -3014,7 +3198,10 @@ class MainViewModel : ViewModel() { enabled ) if (!enabled) { - com.sameerasw.essentials.utils.StatusBarManager.requestRestore(context, "DisableQsWhenLocked") + com.sameerasw.essentials.utils.StatusBarManager.requestRestore( + context, + "DisableQsWhenLocked" + ) } } @@ -3038,7 +3225,10 @@ class MainViewModel : ViewModel() { settingsRepository.putFloat(SettingsRepository.KEY_EDGE_LIGHTING_INDICATOR_SCALE, scale) } - fun setNotificationLightingSweepPosition(position: NotificationLightingSweepPosition, context: Context) { + fun setNotificationLightingSweepPosition( + position: NotificationLightingSweepPosition, + context: Context + ) { notificationLightingSweepPosition.value = position settingsRepository.saveNotificationLightingSweepPosition(position) } @@ -3050,11 +3240,13 @@ class MainViewModel : ViewModel() { fun saveNotificationLightingSweepRandomShapes(context: Context, enabled: Boolean) { notificationLightingSweepRandomShapes.value = enabled - settingsRepository.putBoolean(SettingsRepository.KEY_EDGE_LIGHTING_SWEEP_RANDOM_SHAPES, enabled) + settingsRepository.putBoolean( + SettingsRepository.KEY_EDGE_LIGHTING_SWEEP_RANDOM_SHAPES, + enabled + ) } - fun exportConfigs(context: Context, outputStream: java.io.OutputStream) { settingsRepository.exportConfigs(outputStream) } @@ -3142,9 +3334,13 @@ class MainViewModel : ViewModel() { settingsRepository.putBoolean(SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED, enabled) isAodForceTurnOffEnabled.value = enabled } + fun setNotificationGlanceSameAsLightingEnabled(enabled: Boolean) { isNotificationGlanceSameAsLightingEnabled.value = enabled - settingsRepository.putBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, enabled) + settingsRepository.putBoolean( + SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, + enabled + ) } fun loadNotificationGlanceSelectedApps(context: Context): List { @@ -3155,7 +3351,11 @@ class MainViewModel : ViewModel() { settingsRepository.saveNotificationGlanceSelectedApps(apps) } - fun updateNotificationGlanceAppEnabled(context: Context, packageName: String, enabled: Boolean) { + fun updateNotificationGlanceAppEnabled( + context: Context, + packageName: String, + enabled: Boolean + ) { settingsRepository.updateNotificationGlanceAppSelection(packageName, enabled) } @@ -3165,7 +3365,7 @@ class MainViewModel : ViewModel() { try { context.contentResolver.unregisterContentObserver(contentObserver) } catch (e: Exception) { - + } try { powerSaveReceiver?.let { context.unregisterReceiver(it) } @@ -3182,13 +3382,19 @@ class MainViewModel : ViewModel() { isOnboardingCompleted.value = completed settingsRepository.putBoolean(SettingsRepository.KEY_ONBOARDING_COMPLETED, completed) if (completed) { - settingsRepository.putInt(SettingsRepository.KEY_WHATS_NEW_LAST_SHOWN_COUNTER, com.sameerasw.essentials.BuildConfig.WHATS_NEW_COUNTER) + settingsRepository.putInt( + SettingsRepository.KEY_WHATS_NEW_LAST_SHOWN_COUNTER, + com.sameerasw.essentials.BuildConfig.WHATS_NEW_COUNTER + ) } } fun completeWhatsNew() { isWhatsNewVisible.value = false - settingsRepository.putInt(SettingsRepository.KEY_WHATS_NEW_LAST_SHOWN_COUNTER, com.sameerasw.essentials.BuildConfig.WHATS_NEW_COUNTER) + settingsRepository.putInt( + SettingsRepository.KEY_WHATS_NEW_LAST_SHOWN_COUNTER, + com.sameerasw.essentials.BuildConfig.WHATS_NEW_COUNTER + ) } fun resetOnboarding(context: Context) { 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 077226507..e26111588 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/StatusBarIconViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/StatusBarIconViewModel.kt @@ -1,17 +1,13 @@ package com.sameerasw.essentials.viewmodels -import android.Manifest import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.pm.PackageManager import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.os.BatteryManager import android.provider.Settings -import android.telephony.TelephonyManager import androidx.compose.runtime.mutableStateOf -import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.lifecycle.ViewModel import com.sameerasw.essentials.domain.StatusBarIconRegistry @@ -21,8 +17,6 @@ import com.sameerasw.essentials.utils.updateIconBlacklistSetting import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch class StatusBarIconViewModel : ViewModel() { val isWriteSecureSettingsEnabled = mutableStateOf(false) @@ -33,7 +27,7 @@ class StatusBarIconViewModel : ViewModel() { val batteryPercentageMode = mutableStateOf(0) // 0: Hide, 1: Always, 2: Charging val isPrivacyChipEnabled = mutableStateOf(true) val isWriteSettingsEnabled = mutableStateOf(false) - + val isHideSystemIconsEnabled = mutableStateOf(false) val isHideSystemIconsLockedOnlyEnabled = mutableStateOf(false) val isHideClockEnabled = mutableStateOf(false) @@ -426,7 +420,8 @@ class StatusBarIconViewModel : ViewModel() { private fun loadAdvancedFlags(context: Context) { val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) isHideSystemIconsEnabled.value = prefs.getBoolean(PREF_HIDE_SYSTEM_ICONS, false) - isHideSystemIconsLockedOnlyEnabled.value = prefs.getBoolean(PREF_HIDE_SYSTEM_ICONS_LOCKED_ONLY, false) + isHideSystemIconsLockedOnlyEnabled.value = + prefs.getBoolean(PREF_HIDE_SYSTEM_ICONS_LOCKED_ONLY, false) isHideClockEnabled.value = prefs.getBoolean(PREF_HIDE_CLOCK, false) isHideNotificationIconsEnabled.value = prefs.getBoolean(PREF_HIDE_NOTIFICATION_ICONS, false) applyAdvancedFlags(context) diff --git a/app/src/main/res/drawable/accessibility_1.png b/app/src/main/res/drawable/accessibility_1.png deleted file mode 100644 index d46e27f8c..000000000 Binary files a/app/src/main/res/drawable/accessibility_1.png and /dev/null differ diff --git a/app/src/main/res/drawable/accessibility_1.webp b/app/src/main/res/drawable/accessibility_1.webp new file mode 100644 index 000000000..3227e10a8 Binary files /dev/null and b/app/src/main/res/drawable/accessibility_1.webp differ diff --git a/app/src/main/res/drawable/accessibility_2.png b/app/src/main/res/drawable/accessibility_2.png deleted file mode 100644 index c5ab6268f..000000000 Binary files a/app/src/main/res/drawable/accessibility_2.png and /dev/null differ diff --git a/app/src/main/res/drawable/accessibility_2.webp b/app/src/main/res/drawable/accessibility_2.webp new file mode 100644 index 000000000..70f4d719a Binary files /dev/null and b/app/src/main/res/drawable/accessibility_2.webp differ diff --git a/app/src/main/res/drawable/accessibility_3.png b/app/src/main/res/drawable/accessibility_3.png deleted file mode 100644 index 1aee70a22..000000000 Binary files a/app/src/main/res/drawable/accessibility_3.png and /dev/null differ diff --git a/app/src/main/res/drawable/accessibility_3.webp b/app/src/main/res/drawable/accessibility_3.webp new file mode 100644 index 000000000..fff2e2b2d Binary files /dev/null and b/app/src/main/res/drawable/accessibility_3.webp differ diff --git a/app/src/main/res/drawable/android12.png b/app/src/main/res/drawable/android12.png deleted file mode 100644 index ad1b6cfb7..000000000 Binary files a/app/src/main/res/drawable/android12.png and /dev/null differ diff --git a/app/src/main/res/drawable/android12.webp b/app/src/main/res/drawable/android12.webp new file mode 100644 index 000000000..32d46a11e Binary files /dev/null and b/app/src/main/res/drawable/android12.webp differ diff --git a/app/src/main/res/drawable/android13.png b/app/src/main/res/drawable/android13.png deleted file mode 100644 index 9e8dbefbb..000000000 Binary files a/app/src/main/res/drawable/android13.png and /dev/null differ diff --git a/app/src/main/res/drawable/android13.webp b/app/src/main/res/drawable/android13.webp new file mode 100644 index 000000000..0c8bc125f Binary files /dev/null and b/app/src/main/res/drawable/android13.webp differ diff --git a/app/src/main/res/drawable/android14.png b/app/src/main/res/drawable/android14.png deleted file mode 100644 index 5a21d7a61..000000000 Binary files a/app/src/main/res/drawable/android14.png and /dev/null differ diff --git a/app/src/main/res/drawable/android14.webp b/app/src/main/res/drawable/android14.webp new file mode 100644 index 000000000..30ac9f187 Binary files /dev/null and b/app/src/main/res/drawable/android14.webp differ diff --git a/app/src/main/res/drawable/android15.png b/app/src/main/res/drawable/android15.png deleted file mode 100644 index 18f4ec0ed..000000000 Binary files a/app/src/main/res/drawable/android15.png and /dev/null differ diff --git a/app/src/main/res/drawable/android15.webp b/app/src/main/res/drawable/android15.webp new file mode 100644 index 000000000..9ddbd1fce Binary files /dev/null and b/app/src/main/res/drawable/android15.webp differ diff --git a/app/src/main/res/drawable/android16.png b/app/src/main/res/drawable/android16.png deleted file mode 100644 index 5bc915231..000000000 Binary files a/app/src/main/res/drawable/android16.png and /dev/null differ diff --git a/app/src/main/res/drawable/android16.webp b/app/src/main/res/drawable/android16.webp new file mode 100644 index 000000000..1074eb7be Binary files /dev/null and b/app/src/main/res/drawable/android16.webp differ diff --git a/app/src/main/res/drawable/android17.png b/app/src/main/res/drawable/android17.png deleted file mode 100644 index d5d777188..000000000 Binary files a/app/src/main/res/drawable/android17.png and /dev/null differ diff --git a/app/src/main/res/drawable/android17.webp b/app/src/main/res/drawable/android17.webp new file mode 100644 index 000000000..a6b2ab953 Binary files /dev/null and b/app/src/main/res/drawable/android17.webp differ diff --git a/app/src/main/res/drawable/avatar.png b/app/src/main/res/drawable/avatar.png deleted file mode 100644 index 8ba979654..000000000 Binary files a/app/src/main/res/drawable/avatar.png and /dev/null differ diff --git a/app/src/main/res/drawable/avatar.webp b/app/src/main/res/drawable/avatar.webp new file mode 100644 index 000000000..5c3284460 Binary files /dev/null and b/app/src/main/res/drawable/avatar.webp differ diff --git a/app/src/main/res/drawable/clock_bignum.jpg b/app/src/main/res/drawable/clock_bignum.jpg deleted file mode 100644 index e6096439b..000000000 Binary files a/app/src/main/res/drawable/clock_bignum.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_bignum.webp b/app/src/main/res/drawable/clock_bignum.webp new file mode 100644 index 000000000..1cf0337fd Binary files /dev/null and b/app/src/main/res/drawable/clock_bignum.webp differ diff --git a/app/src/main/res/drawable/clock_calligraphy.jpg b/app/src/main/res/drawable/clock_calligraphy.jpg deleted file mode 100644 index 4a854385f..000000000 Binary files a/app/src/main/res/drawable/clock_calligraphy.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_calligraphy.webp b/app/src/main/res/drawable/clock_calligraphy.webp new file mode 100644 index 000000000..d7c13a761 Binary files /dev/null and b/app/src/main/res/drawable/clock_calligraphy.webp differ diff --git a/app/src/main/res/drawable/clock_flex.jpg b/app/src/main/res/drawable/clock_flex.jpg deleted file mode 100644 index 6d28646cb..000000000 Binary files a/app/src/main/res/drawable/clock_flex.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_flex.webp b/app/src/main/res/drawable/clock_flex.webp new file mode 100644 index 000000000..35d111c0e Binary files /dev/null and b/app/src/main/res/drawable/clock_flex.webp differ diff --git a/app/src/main/res/drawable/clock_growth.jpg b/app/src/main/res/drawable/clock_growth.jpg deleted file mode 100644 index b7cbb1c5c..000000000 Binary files a/app/src/main/res/drawable/clock_growth.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_growth.webp b/app/src/main/res/drawable/clock_growth.webp new file mode 100644 index 000000000..e7b34816f Binary files /dev/null and b/app/src/main/res/drawable/clock_growth.webp differ diff --git a/app/src/main/res/drawable/clock_handwritten.jpg b/app/src/main/res/drawable/clock_handwritten.jpg deleted file mode 100644 index 9b75a556a..000000000 Binary files a/app/src/main/res/drawable/clock_handwritten.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_handwritten.webp b/app/src/main/res/drawable/clock_handwritten.webp new file mode 100644 index 000000000..24d61c2d4 Binary files /dev/null and b/app/src/main/res/drawable/clock_handwritten.webp differ diff --git a/app/src/main/res/drawable/clock_inflate.jpg b/app/src/main/res/drawable/clock_inflate.jpg deleted file mode 100644 index e556407e3..000000000 Binary files a/app/src/main/res/drawable/clock_inflate.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_inflate.webp b/app/src/main/res/drawable/clock_inflate.webp new file mode 100644 index 000000000..88bda9259 Binary files /dev/null and b/app/src/main/res/drawable/clock_inflate.webp differ diff --git a/app/src/main/res/drawable/clock_metro.jpg b/app/src/main/res/drawable/clock_metro.jpg deleted file mode 100644 index 4beec0c5b..000000000 Binary files a/app/src/main/res/drawable/clock_metro.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_metro.webp b/app/src/main/res/drawable/clock_metro.webp new file mode 100644 index 000000000..3bb16be54 Binary files /dev/null and b/app/src/main/res/drawable/clock_metro.webp differ diff --git a/app/src/main/res/drawable/clock_overlap.jpg b/app/src/main/res/drawable/clock_overlap.jpg deleted file mode 100644 index 376d3c235..000000000 Binary files a/app/src/main/res/drawable/clock_overlap.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_overlap.webp b/app/src/main/res/drawable/clock_overlap.webp new file mode 100644 index 000000000..adfcd624b Binary files /dev/null and b/app/src/main/res/drawable/clock_overlap.webp differ diff --git a/app/src/main/res/drawable/clock_weather.jpg b/app/src/main/res/drawable/clock_weather.jpg deleted file mode 100644 index 7dbc2ed9a..000000000 Binary files a/app/src/main/res/drawable/clock_weather.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/clock_weather.webp b/app/src/main/res/drawable/clock_weather.webp new file mode 100644 index 000000000..8e9d902e6 Binary files /dev/null and b/app/src/main/res/drawable/clock_weather.webp differ diff --git a/app/src/main/res/drawable/madebysameerasw_cover.jpg b/app/src/main/res/drawable/madebysameerasw_cover.jpg deleted file mode 100644 index 287de9edb..000000000 Binary files a/app/src/main/res/drawable/madebysameerasw_cover.jpg and /dev/null differ diff --git a/app/src/main/res/drawable/madebysameerasw_cover.webp b/app/src/main/res/drawable/madebysameerasw_cover.webp new file mode 100644 index 000000000..a9fe74fcd Binary files /dev/null and b/app/src/main/res/drawable/madebysameerasw_cover.webp differ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94ec054b7..497e3a7c9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "9.0.0" +agp = "9.2.1" kotlin = "2.3.0" coreKtx = "1.17.0" junit = "4.13.2" @@ -17,7 +17,7 @@ playServicesLocation = "21.3.0" playServicesWearable = "18.1.0" gson = "2.10.1" material = "1.13.0" -material3 = "1.5.0-alpha17" +material3 = "1.5.0-alpha19" foundationLayoutVersion = "1.10.1" foundationVersion = "1.10.1" work = "2.9.1" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2a46124d4..8d43560cd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ #Thu Dec 04 16:25:39 IST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME