|
@@ -0,0 +1,181 @@
|
|
|
+package eu.kanade.presentation.more.onboarding
|
|
|
+
|
|
|
+import android.Manifest
|
|
|
+import android.annotation.SuppressLint
|
|
|
+import android.content.Intent
|
|
|
+import android.content.pm.PackageManager
|
|
|
+import android.net.Uri
|
|
|
+import android.os.Build
|
|
|
+import android.os.PowerManager
|
|
|
+import android.provider.Settings
|
|
|
+import androidx.activity.compose.rememberLauncherForActivityResult
|
|
|
+import androidx.activity.result.contract.ActivityResultContracts
|
|
|
+import androidx.compose.foundation.layout.Column
|
|
|
+import androidx.compose.foundation.layout.Spacer
|
|
|
+import androidx.compose.foundation.layout.height
|
|
|
+import androidx.compose.foundation.layout.padding
|
|
|
+import androidx.compose.material.icons.Icons
|
|
|
+import androidx.compose.material.icons.filled.Check
|
|
|
+import androidx.compose.material3.Icon
|
|
|
+import androidx.compose.material3.ListItem
|
|
|
+import androidx.compose.material3.ListItemDefaults
|
|
|
+import androidx.compose.material3.MaterialTheme
|
|
|
+import androidx.compose.material3.OutlinedButton
|
|
|
+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.setValue
|
|
|
+import androidx.compose.ui.Modifier
|
|
|
+import androidx.compose.ui.graphics.Color
|
|
|
+import androidx.compose.ui.platform.LocalContext
|
|
|
+import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
|
+import androidx.compose.ui.unit.dp
|
|
|
+import androidx.core.content.getSystemService
|
|
|
+import androidx.lifecycle.DefaultLifecycleObserver
|
|
|
+import androidx.lifecycle.LifecycleOwner
|
|
|
+import tachiyomi.i18n.MR
|
|
|
+import tachiyomi.presentation.core.i18n.stringResource
|
|
|
+import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|
|
+
|
|
|
+internal class PermissionStep : OnboardingStep {
|
|
|
+
|
|
|
+ private var installGranted by mutableStateOf(false)
|
|
|
+ private var notificationGranted by mutableStateOf(false)
|
|
|
+ private var batteryGranted by mutableStateOf(false)
|
|
|
+
|
|
|
+ override val isComplete: Boolean
|
|
|
+ get() = installGranted
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ override fun Content() {
|
|
|
+ val context = LocalContext.current
|
|
|
+ val lifecycleOwner = LocalLifecycleOwner.current
|
|
|
+
|
|
|
+ DisposableEffect(lifecycleOwner.lifecycle) {
|
|
|
+ val observer = object : DefaultLifecycleObserver {
|
|
|
+ override fun onResume(owner: LifecycleOwner) {
|
|
|
+ installGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
+ context.packageManager.canRequestPackageInstalls()
|
|
|
+ } else {
|
|
|
+ @Suppress("DEPRECATION")
|
|
|
+ Settings.Secure.getInt(context.contentResolver, Settings.Secure.INSTALL_NON_MARKET_APPS) != 0
|
|
|
+ }
|
|
|
+ notificationGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
+ context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) ==
|
|
|
+ PackageManager.PERMISSION_GRANTED
|
|
|
+ } else {
|
|
|
+ true
|
|
|
+ }
|
|
|
+ batteryGranted = context.getSystemService<PowerManager>()!!
|
|
|
+ .isIgnoringBatteryOptimizations(context.packageName)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ lifecycleOwner.lifecycle.addObserver(observer)
|
|
|
+ onDispose {
|
|
|
+ lifecycleOwner.lifecycle.removeObserver(observer)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Column(
|
|
|
+ modifier = Modifier.padding(vertical = 16.dp),
|
|
|
+ ) {
|
|
|
+ SectionHeader(stringResource(MR.strings.onboarding_permission_type_required))
|
|
|
+
|
|
|
+ PermissionItem(
|
|
|
+ title = stringResource(MR.strings.onboarding_permission_install_apps),
|
|
|
+ subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description),
|
|
|
+ granted = installGranted,
|
|
|
+ onButtonClick = {
|
|
|
+ val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
+ Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
|
|
|
+ data = Uri.parse("package:${context.packageName}")
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Intent(Settings.ACTION_SECURITY_SETTINGS)
|
|
|
+ }
|
|
|
+ context.startActivity(intent)
|
|
|
+ },
|
|
|
+ )
|
|
|
+
|
|
|
+ Spacer(modifier = Modifier.height(16.dp))
|
|
|
+
|
|
|
+ SectionHeader(stringResource(MR.strings.onboarding_permission_type_optional))
|
|
|
+
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
+ val permissionRequester = rememberLauncherForActivityResult(
|
|
|
+ contract = ActivityResultContracts.RequestPermission(),
|
|
|
+ onResult = {
|
|
|
+ // no-op. resulting checks is being done on resume
|
|
|
+ },
|
|
|
+ )
|
|
|
+ PermissionItem(
|
|
|
+ title = stringResource(MR.strings.onboarding_permission_notifications),
|
|
|
+ subtitle = stringResource(MR.strings.onboarding_permission_notifications_description),
|
|
|
+ granted = notificationGranted,
|
|
|
+ onButtonClick = { permissionRequester.launch(Manifest.permission.POST_NOTIFICATIONS) },
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ PermissionItem(
|
|
|
+ title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts),
|
|
|
+ subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description),
|
|
|
+ granted = batteryGranted,
|
|
|
+ onButtonClick = {
|
|
|
+ @SuppressLint("BatteryLife")
|
|
|
+ val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
|
|
+ data = Uri.parse("package:${context.packageName}")
|
|
|
+ }
|
|
|
+ context.startActivity(intent)
|
|
|
+ },
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ private fun SectionHeader(
|
|
|
+ text: String,
|
|
|
+ modifier: Modifier = Modifier,
|
|
|
+ ) {
|
|
|
+ Text(
|
|
|
+ text = text,
|
|
|
+ style = MaterialTheme.typography.titleLarge,
|
|
|
+ modifier = modifier
|
|
|
+ .padding(horizontal = 16.dp)
|
|
|
+ .secondaryItemAlpha(),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @Composable
|
|
|
+ private fun PermissionItem(
|
|
|
+ title: String,
|
|
|
+ subtitle: String,
|
|
|
+ granted: Boolean,
|
|
|
+ modifier: Modifier = Modifier,
|
|
|
+ onButtonClick: () -> Unit,
|
|
|
+ ) {
|
|
|
+ ListItem(
|
|
|
+ modifier = modifier,
|
|
|
+ headlineContent = { Text(text = title) },
|
|
|
+ supportingContent = { Text(text = subtitle) },
|
|
|
+ trailingContent = {
|
|
|
+ OutlinedButton(
|
|
|
+ enabled = !granted,
|
|
|
+ onClick = onButtonClick,
|
|
|
+ ) {
|
|
|
+ if (granted) {
|
|
|
+ Icon(
|
|
|
+ imageVector = Icons.Default.Check,
|
|
|
+ contentDescription = null,
|
|
|
+ tint = MaterialTheme.colorScheme.primary,
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ Text(stringResource(MR.strings.onboarding_permission_action_grant))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|