Target Android 14 (SDK 34) and add permission onboarding step
(cherry picked from commit 9e0068715f3ba3d1627c4b7539b90fb782f8122f)
This commit is contained in:
parent
8aaf8df708
commit
13b3bec8ad
4 changed files with 192 additions and 2 deletions
|
@ -36,7 +36,7 @@ fun OnboardingScreen(
|
||||||
listOf(
|
listOf(
|
||||||
ThemeStep(),
|
ThemeStep(),
|
||||||
StorageStep(),
|
StorageStep(),
|
||||||
// TODO: prompt for notification permissions when bumping target to Android 13
|
PermissionStep(),
|
||||||
GuidesStep(onRestoreBackup = onRestoreBackup),
|
GuidesStep(onRestoreBackup = onRestoreBackup),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
object AndroidConfig {
|
object AndroidConfig {
|
||||||
const val compileSdk = 34
|
const val compileSdk = 34
|
||||||
const val minSdk = 23
|
const val minSdk = 23
|
||||||
const val targetSdk = 32
|
const val targetSdk = 34
|
||||||
const val ndk = "22.1.7171670"
|
const val ndk = "22.1.7171670"
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,6 +183,15 @@
|
||||||
<string name="onboarding_storage_info">Select a folder where %1$s will store chapter downloads, backups, and more.\n\nA dedicated folder is recommended.\n\nSelected folder: %2$s</string>
|
<string name="onboarding_storage_info">Select a folder where %1$s will store chapter downloads, backups, and more.\n\nA dedicated folder is recommended.\n\nSelected folder: %2$s</string>
|
||||||
<string name="onboarding_storage_action_select">Select a folder</string>
|
<string name="onboarding_storage_action_select">Select a folder</string>
|
||||||
<string name="onboarding_storage_selection_required">A folder must be selected</string>
|
<string name="onboarding_storage_selection_required">A folder must be selected</string>
|
||||||
|
<string name="onboarding_permission_type_required">Required</string>
|
||||||
|
<string name="onboarding_permission_type_optional">Optional</string>
|
||||||
|
<string name="onboarding_permission_install_apps">Install apps permission</string>
|
||||||
|
<string name="onboarding_permission_install_apps_description">To install source extensions.</string>
|
||||||
|
<string name="onboarding_permission_notifications">Notification permission</string>
|
||||||
|
<string name="onboarding_permission_notifications_description">Get notified for library updates and more.</string>
|
||||||
|
<string name="onboarding_permission_ignore_battery_opts">Background battery usage</string>
|
||||||
|
<string name="onboarding_permission_ignore_battery_opts_description">Avoid interruptions to long-running library updates, downloads, and backup restores.</string>
|
||||||
|
<string name="onboarding_permission_action_grant">Grant</string>
|
||||||
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
|
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
|
||||||
<string name="onboarding_guides_returning_user">Already used %s before?</string>
|
<string name="onboarding_guides_returning_user">Already used %s before?</string>
|
||||||
|
|
||||||
|
|
Reference in a new issue