mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
feat: Onboarding screen (partial)
This commit is contained in:
parent
59b11b16e2
commit
d086df7287
17 changed files with 987 additions and 10 deletions
|
@ -147,15 +147,9 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
// Compose
|
// Compose
|
||||||
implementation(androidx.activity.compose)
|
implementation(androidx.activity.compose)
|
||||||
implementation(compose.foundation)
|
implementation(compose.bundles.compose)
|
||||||
implementation(compose.animation)
|
|
||||||
implementation(compose.ui)
|
|
||||||
debugImplementation(compose.ui.tooling)
|
debugImplementation(compose.ui.tooling)
|
||||||
implementation(compose.ui.tooling.preview)
|
|
||||||
implementation(compose.material)
|
|
||||||
implementation(compose.material3)
|
|
||||||
implementation(libs.compose.theme.adapter3)
|
implementation(libs.compose.theme.adapter3)
|
||||||
implementation(compose.icons)
|
|
||||||
implementation(libs.accompanist.webview)
|
implementation(libs.accompanist.webview)
|
||||||
implementation(androidx.glance.appwidget)
|
implementation(androidx.glance.appwidget)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package dev.yokai.domain.base
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.core.preference.Preference
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||||
import eu.kanade.tachiyomi.core.preference.getEnum
|
import eu.kanade.tachiyomi.core.preference.getEnum
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
||||||
|
@ -17,4 +18,6 @@ class BasePreferences(private val preferenceStore: PreferenceStore) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
||||||
|
|
||||||
|
fun hasShownOnboarding() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package dev.yokai.presentation.component
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.AdaptiveIconDrawable
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.requiredSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppIcon(size: Dp, modifier: Modifier = Modifier) {
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
LocalContext.current.resources,
|
||||||
|
R.mipmap.ic_launcher,
|
||||||
|
LocalContext.current.theme
|
||||||
|
)
|
||||||
|
?.let { drawable ->
|
||||||
|
val bitmap =
|
||||||
|
Bitmap.createBitmap(
|
||||||
|
drawable.intrinsicWidth,
|
||||||
|
drawable.intrinsicHeight,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
)
|
||||||
|
val canvas = android.graphics.Canvas(bitmap)
|
||||||
|
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||||
|
drawable.draw(canvas)
|
||||||
|
Image(
|
||||||
|
bitmap = bitmap.asImageBitmap(),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier.requiredSize(size)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun adaptiveIconPainterResource(@DrawableRes id: Int): Painter {
|
||||||
|
val res = LocalContext.current.resources
|
||||||
|
val theme = LocalContext.current.theme
|
||||||
|
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// Android O supports adaptive icons, try loading this first (even though this is least
|
||||||
|
// likely to be the format).
|
||||||
|
val adaptiveIcon = ResourcesCompat.getDrawable(res, id, theme) as? AdaptiveIconDrawable
|
||||||
|
if (adaptiveIcon != null) {
|
||||||
|
BitmapPainter(adaptiveIcon.toBitmap().asImageBitmap())
|
||||||
|
} else {
|
||||||
|
// We couldn't load the drawable as an Adaptive Icon, just use painterResource
|
||||||
|
painterResource(id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We're not on Android O or later, just use painterResource
|
||||||
|
painterResource(id)
|
||||||
|
}
|
||||||
|
}
|
332
app/src/main/java/dev/yokai/presentation/component/ThemeItem.kt
Normal file
332
app/src/main/java/dev/yokai/presentation/component/ThemeItem.kt
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
package dev.yokai.presentation.component
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ColorScheme
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
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.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.google.accompanist.themeadapter.material3.createMdc3Theme
|
||||||
|
import dev.yokai.presentation.theme.HalfAlpha
|
||||||
|
import dev.yokai.presentation.theme.SecondaryItemAlpha
|
||||||
|
import dev.yokai.presentation.theme.Size
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.system.Themes
|
||||||
|
import eu.kanade.tachiyomi.util.system.isInNightMode
|
||||||
|
|
||||||
|
private data class ContextTheme(
|
||||||
|
val colorScheme: ColorScheme,
|
||||||
|
val isThemeMatchesApp: Boolean,
|
||||||
|
val theme: Themes,
|
||||||
|
val isDarkTheme: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun Context.colorSchemeFromAdapter(theme: Themes, isDarkTheme: Boolean): ContextTheme {
|
||||||
|
val configuration = Configuration(this.resources.configuration)
|
||||||
|
configuration.uiMode =
|
||||||
|
if (isDarkTheme) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
|
||||||
|
val themeContext = this.createConfigurationContext(configuration)
|
||||||
|
themeContext.setTheme(theme.styleRes)
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION") val colorScheme =
|
||||||
|
createMdc3Theme(
|
||||||
|
context = themeContext,
|
||||||
|
layoutDirection = LayoutDirection.Ltr,
|
||||||
|
setTextColors = true,
|
||||||
|
readTypography = false,
|
||||||
|
)
|
||||||
|
.colorScheme!!
|
||||||
|
|
||||||
|
val themeMatchesApp =
|
||||||
|
if (this.isInNightMode()) {
|
||||||
|
isDarkTheme
|
||||||
|
} else {
|
||||||
|
!isDarkTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContextTheme(colorScheme, themeMatchesApp, theme, isDarkTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ThemeItem(theme: Themes, isDarkTheme: Boolean, selected: Boolean, onClick: () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val contextTheme = context.colorSchemeFromAdapter(theme, isDarkTheme)
|
||||||
|
|
||||||
|
ThemeItemNaive(contextTheme = contextTheme, selected = selected, onClick = onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ThemeItemNaive(contextTheme: ContextTheme, selected: Boolean, onClick: () -> Unit) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.width(110.dp)) {
|
||||||
|
ThemePreviewItem(
|
||||||
|
contextTheme.colorScheme,
|
||||||
|
selected,
|
||||||
|
selectedColor = MaterialTheme.colorScheme.primary,
|
||||||
|
contextTheme.isThemeMatchesApp,
|
||||||
|
onClick
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = if (contextTheme.isDarkTheme) contextTheme.theme.darkNameRes else contextTheme.theme.nameRes),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ThemePreviewItem(
|
||||||
|
colorScheme: ColorScheme,
|
||||||
|
selected: Boolean,
|
||||||
|
selectedColor: Color,
|
||||||
|
themeMatchesApp: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
val actualSelectedColor =
|
||||||
|
when {
|
||||||
|
themeMatchesApp && selected -> colorScheme.primary
|
||||||
|
selected -> selectedColor.copy(alpha = HalfAlpha)
|
||||||
|
else -> Color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
val padding = 6
|
||||||
|
val outer = 26
|
||||||
|
val inner = outer - padding
|
||||||
|
OutlinedCard(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.height(180.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(outer.dp),
|
||||||
|
border = BorderStroke(width = Size.tiny, color = actualSelectedColor),
|
||||||
|
) {
|
||||||
|
OutlinedCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(180.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(6.dp),
|
||||||
|
shape = RoundedCornerShape(inner.dp),
|
||||||
|
colors = CardDefaults.outlinedCardColors(containerColor = colorScheme.background),
|
||||||
|
border = BorderStroke(width = 1.dp, color = colorScheme.surfaceVariant),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(40.dp)
|
||||||
|
.padding(Size.small),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(15.dp)
|
||||||
|
.weight(0.7f)
|
||||||
|
.padding(start = Size.tiny, end = Size.small)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.onSurface,
|
||||||
|
shape = RoundedCornerShape(6.dp),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(0.3f),
|
||||||
|
contentAlignment = Alignment.CenterEnd,
|
||||||
|
) {
|
||||||
|
if (selected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = stringResource(R.string.selected),
|
||||||
|
tint = selectedColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(0.6f)
|
||||||
|
.padding(horizontal = Size.small),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(30.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = colorScheme.onSurface.copy(alpha = SecondaryItemAlpha),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(30.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = Size.small, end = Size.small),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(15.dp)
|
||||||
|
.weight(0.8f)
|
||||||
|
.padding(end = Size.tiny)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.onSurface,
|
||||||
|
shape = RoundedCornerShape(6.dp),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(15.dp)
|
||||||
|
.weight(0.3f)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.secondary,
|
||||||
|
shape = RoundedCornerShape(6.dp),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(15.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(end = Size.medium),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(0.5f)
|
||||||
|
.padding(end = Size.tiny)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.onSurface,
|
||||||
|
shape = RoundedCornerShape(6.dp),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(0.6f)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.onSurface,
|
||||||
|
shape = RoundedCornerShape(6.dp),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
color = colorScheme.surfaceVariant,
|
||||||
|
tonalElevation = Size.small,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(30.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = Size.extraTiny, horizontal = 10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(0.2f),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(6.dp)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.onSurface.copy(alpha = SecondaryItemAlpha),
|
||||||
|
shape = CircleShape,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(0.2f),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(6.dp)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.secondary,
|
||||||
|
shape = CircleShape,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(0.2f),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(6.dp)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.onSurface.copy(alpha = SecondaryItemAlpha),
|
||||||
|
shape = CircleShape,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun ThemeItemPreviewDark() {
|
||||||
|
val contextTheme = ContextTheme(
|
||||||
|
colorScheme = darkColorScheme(),
|
||||||
|
isThemeMatchesApp = true,
|
||||||
|
theme = Themes.DEFAULT,
|
||||||
|
isDarkTheme = true,
|
||||||
|
)
|
||||||
|
Surface {
|
||||||
|
ThemeItemNaive(contextTheme = contextTheme, selected = true) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO, showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun ThemeItemPreviewLight() {
|
||||||
|
val contextTheme = ContextTheme(
|
||||||
|
colorScheme = lightColorScheme(),
|
||||||
|
isThemeMatchesApp = true,
|
||||||
|
theme = Themes.DEFAULT,
|
||||||
|
isDarkTheme = false,
|
||||||
|
)
|
||||||
|
Surface {
|
||||||
|
ThemeItemNaive(contextTheme = contextTheme, selected = false) {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.yokai.presentation.core.util
|
||||||
|
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import dev.yokai.presentation.theme.SecondaryItemAlpha
|
||||||
|
|
||||||
|
fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha)
|
|
@ -0,0 +1,184 @@
|
||||||
|
package dev.yokai.presentation.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
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.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.RocketLaunch
|
||||||
|
import androidx.compose.material.icons.outlined.RocketLaunch
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBarDefaults
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import dev.yokai.presentation.core.util.secondaryItemAlpha
|
||||||
|
import dev.yokai.presentation.theme.Size
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InfoScreen(
|
||||||
|
icon: ImageVector,
|
||||||
|
headingText: String,
|
||||||
|
subtitleText: String,
|
||||||
|
tint: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
acceptText: String,
|
||||||
|
onAcceptClick: () -> Unit,
|
||||||
|
canAccept: Boolean,
|
||||||
|
rejectText: String? = null,
|
||||||
|
onRejectClick: (() -> Unit)? = null,
|
||||||
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
InfoScreen(
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = Size.small)
|
||||||
|
.size(Size.huge),
|
||||||
|
tint = tint,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headingText = headingText,
|
||||||
|
subtitleText = subtitleText,
|
||||||
|
acceptText = acceptText,
|
||||||
|
onAcceptClick = onAcceptClick,
|
||||||
|
canAccept = canAccept,
|
||||||
|
rejectText = rejectText,
|
||||||
|
onRejectClick = onRejectClick,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InfoScreen(
|
||||||
|
icon: @Composable () -> Unit,
|
||||||
|
headingText: String,
|
||||||
|
subtitleText: String,
|
||||||
|
tint: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
acceptText: String,
|
||||||
|
onAcceptClick: () -> Unit,
|
||||||
|
canAccept: Boolean,
|
||||||
|
rejectText: String? = null,
|
||||||
|
onRejectClick: (() -> Unit)? = null,
|
||||||
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
val strokeWidth = Dp.Hairline
|
||||||
|
val borderColor = MaterialTheme.colorScheme.outline
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.drawBehind {
|
||||||
|
drawLine(
|
||||||
|
borderColor,
|
||||||
|
Offset(0f, 0f),
|
||||||
|
Offset(size.width, 0f),
|
||||||
|
strokeWidth.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.windowInsetsPadding(NavigationBarDefaults.windowInsets)
|
||||||
|
.padding(
|
||||||
|
horizontal = Size.medium,
|
||||||
|
vertical = Size.small,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
enabled = canAccept,
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = tint),
|
||||||
|
onClick = onAcceptClick,
|
||||||
|
) {
|
||||||
|
Text(text = acceptText)
|
||||||
|
}
|
||||||
|
if (rejectText != null && onRejectClick != null) {
|
||||||
|
OutlinedButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
border =
|
||||||
|
BorderStroke(
|
||||||
|
width = Size.extraExtraTiny,
|
||||||
|
color = tint,
|
||||||
|
),
|
||||||
|
onClick = onRejectClick,
|
||||||
|
) {
|
||||||
|
Text(text = rejectText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
// Status bar scrim
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.zIndex(2f)
|
||||||
|
.secondaryItemAlpha()
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(paddingValues.calculateTopPadding()),
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(top = Size.huge)
|
||||||
|
.padding(horizontal = Size.medium),
|
||||||
|
) {
|
||||||
|
icon()
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = headingText,
|
||||||
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = subtitleText,
|
||||||
|
modifier = Modifier
|
||||||
|
.secondaryItemAlpha()
|
||||||
|
.padding(vertical = Size.small),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun InfoScreenPreview() {
|
||||||
|
InfoScreen(
|
||||||
|
icon = Icons.Outlined.RocketLaunch,
|
||||||
|
headingText = "Welcome!",
|
||||||
|
subtitleText = "Subtitle",
|
||||||
|
acceptText = "Accept",
|
||||||
|
onAcceptClick = {},
|
||||||
|
canAccept = true,
|
||||||
|
rejectText = "Reject",
|
||||||
|
onRejectClick = {},
|
||||||
|
) {
|
||||||
|
Text(text = "Hello World")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package dev.yokai.presentation.onboarding
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import dev.yokai.domain.base.BasePreferences
|
||||||
|
import eu.kanade.tachiyomi.core.preference.collectAsState
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class OnboardingController :
|
||||||
|
BaseComposeController() {
|
||||||
|
|
||||||
|
val basePreferences by injectLazy<BasePreferences>()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun ScreenContent() {
|
||||||
|
|
||||||
|
val hasShownOnboarding by basePreferences.hasShownOnboarding().collectAsState()
|
||||||
|
|
||||||
|
val finishOnboarding: () -> Unit = {
|
||||||
|
basePreferences.hasShownOnboarding().set(true)
|
||||||
|
router.popCurrentController()
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler(
|
||||||
|
enabled = !hasShownOnboarding,
|
||||||
|
onBack = {
|
||||||
|
// Prevent exiting if onboarding hasn't been completed
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
OnboardingScreen(
|
||||||
|
onComplete = finishOnboarding
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package dev.yokai.presentation.onboarding
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.RocketLaunch
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import dev.yokai.presentation.onboarding.steps.ThemeStep
|
||||||
|
import dev.yokai.presentation.theme.Size
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import soup.compose.material.motion.animation.materialSharedAxisX
|
||||||
|
import soup.compose.material.motion.animation.rememberSlideDistance
|
||||||
|
|
||||||
|
@SuppressLint("UnusedContentLambdaTargetStateParameter")
|
||||||
|
@Composable
|
||||||
|
fun OnboardingScreen(
|
||||||
|
onComplete: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val slideDistance = rememberSlideDistance()
|
||||||
|
|
||||||
|
var currentStep by rememberSaveable { mutableIntStateOf(0) }
|
||||||
|
val steps = remember {
|
||||||
|
listOf(
|
||||||
|
ThemeStep(),
|
||||||
|
ThemeStep(),
|
||||||
|
ThemeStep(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val isLastStep = currentStep == steps.lastIndex
|
||||||
|
|
||||||
|
BackHandler(enabled = currentStep != 0, onBack = { currentStep-- })
|
||||||
|
|
||||||
|
InfoScreen(
|
||||||
|
icon = Icons.Outlined.RocketLaunch,
|
||||||
|
headingText = stringResource(R.string.onboarding_heading),
|
||||||
|
subtitleText = stringResource(R.string.onboarding_description),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
acceptText = stringResource(
|
||||||
|
if (isLastStep)
|
||||||
|
R.string.onboarding_finish
|
||||||
|
else {
|
||||||
|
R.string.next
|
||||||
|
}
|
||||||
|
),
|
||||||
|
canAccept = steps[currentStep].isComplete,
|
||||||
|
onAcceptClick = {
|
||||||
|
if (isLastStep) {
|
||||||
|
onComplete()
|
||||||
|
} else {
|
||||||
|
currentStep++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = Size.small)
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
|
) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = currentStep,
|
||||||
|
transitionSpec = {
|
||||||
|
materialSharedAxisX(
|
||||||
|
forward = targetState > initialState,
|
||||||
|
slideDistance = slideDistance,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = "stepContent",
|
||||||
|
) {
|
||||||
|
steps[currentStep].Content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package dev.yokai.presentation.onboarding.steps
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
internal interface OnboardingStep {
|
||||||
|
|
||||||
|
val isComplete: Boolean
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Content()
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
package dev.yokai.presentation.onboarding.steps
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.SwitchDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import com.google.android.material.color.DynamicColors
|
||||||
|
import dev.yokai.presentation.component.ThemeItem
|
||||||
|
import dev.yokai.presentation.theme.Size
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.core.preference.collectAsState
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.Themes
|
||||||
|
import eu.kanade.tachiyomi.util.system.appDelegateNightMode
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class ThemeStep : OnboardingStep {
|
||||||
|
override val isComplete: Boolean = true
|
||||||
|
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val nightModePreference = preferences.nightMode()
|
||||||
|
|
||||||
|
val nightMode by nightModePreference.collectAsState()
|
||||||
|
|
||||||
|
val followingSystemTheme by remember(nightMode) {
|
||||||
|
derivedStateOf { nightMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM }
|
||||||
|
}
|
||||||
|
|
||||||
|
val darkAppTheme by preferences.darkTheme().collectAsState()
|
||||||
|
val lightAppTheme by preferences.lightTheme().collectAsState()
|
||||||
|
val supportsDynamic = DynamicColors.isDynamicColorAvailable()
|
||||||
|
|
||||||
|
Themes.entries
|
||||||
|
.filter {
|
||||||
|
(!it.isDarkTheme || it.followsSystem) &&
|
||||||
|
(it.styleRes != R.style.Theme_Tachiyomi_Monet || supportsDynamic)
|
||||||
|
}
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
val lightThemes by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
Themes.entries
|
||||||
|
.filter {
|
||||||
|
(!it.isDarkTheme || it.followsSystem) &&
|
||||||
|
(it.styleRes != R.style.Theme_Tachiyomi_Monet || supportsDynamic)
|
||||||
|
}
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val darkThemes by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
Themes.entries
|
||||||
|
.filter {
|
||||||
|
(it.isDarkTheme || it.followsSystem) &&
|
||||||
|
(it.styleRes != R.style.Theme_Tachiyomi_Monet || supportsDynamic)
|
||||||
|
}
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.padding(Size.medium)) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.follow_system_theme))
|
||||||
|
Switch(
|
||||||
|
checked = nightMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
|
||||||
|
colors =
|
||||||
|
SwitchDefaults.colors(
|
||||||
|
checkedTrackColor = MaterialTheme.colorScheme.primary
|
||||||
|
),
|
||||||
|
onCheckedChange = {
|
||||||
|
when (it) {
|
||||||
|
true -> {
|
||||||
|
preferences
|
||||||
|
.nightMode()
|
||||||
|
.set(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
|
(context as? Activity)?.let { activity ->
|
||||||
|
ActivityCompat.recreate(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false -> preferences.nightMode().set(context.appDelegateNightMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(Size.medium)
|
||||||
|
) {
|
||||||
|
lightThemes.forEach { theme ->
|
||||||
|
val isSelected =
|
||||||
|
remember(darkAppTheme, lightAppTheme, nightMode) {
|
||||||
|
isSelected(theme, false, darkAppTheme, lightAppTheme, nightMode)
|
||||||
|
}
|
||||||
|
ThemeItem(
|
||||||
|
theme = theme,
|
||||||
|
isDarkTheme = false,
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = {
|
||||||
|
themeClicked(
|
||||||
|
theme,
|
||||||
|
context,
|
||||||
|
isSelected = isSelected,
|
||||||
|
followingSystemTheme = followingSystemTheme,
|
||||||
|
isDarkTheme = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(Size.medium)
|
||||||
|
) {
|
||||||
|
darkThemes.forEach { theme ->
|
||||||
|
val isSelected =
|
||||||
|
remember(darkAppTheme, lightAppTheme, nightMode) {
|
||||||
|
isSelected(theme, true, darkAppTheme, lightAppTheme, nightMode)
|
||||||
|
}
|
||||||
|
ThemeItem(
|
||||||
|
theme = theme,
|
||||||
|
isDarkTheme = true,
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = {
|
||||||
|
themeClicked(
|
||||||
|
theme,
|
||||||
|
context,
|
||||||
|
isSelected = isSelected,
|
||||||
|
followingSystemTheme = followingSystemTheme,
|
||||||
|
isDarkTheme = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSelected(
|
||||||
|
theme: Themes,
|
||||||
|
isDarkTheme: Boolean,
|
||||||
|
darkAppTheme: Themes,
|
||||||
|
lightAppTheme: Themes,
|
||||||
|
nightMode: Int
|
||||||
|
): Boolean {
|
||||||
|
return when (nightMode) {
|
||||||
|
AppCompatDelegate.MODE_NIGHT_YES -> darkAppTheme == theme && isDarkTheme
|
||||||
|
AppCompatDelegate.MODE_NIGHT_NO -> lightAppTheme == theme && !isDarkTheme
|
||||||
|
else ->
|
||||||
|
(darkAppTheme == theme && isDarkTheme) || (lightAppTheme == theme && !isDarkTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun themeClicked(
|
||||||
|
theme: Themes,
|
||||||
|
context: Context,
|
||||||
|
isSelected: Boolean,
|
||||||
|
followingSystemTheme: Boolean,
|
||||||
|
isDarkTheme: Boolean
|
||||||
|
) {
|
||||||
|
val nightMode =
|
||||||
|
when (isDarkTheme) {
|
||||||
|
true -> {
|
||||||
|
preferences.darkTheme().set(theme)
|
||||||
|
AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
}
|
||||||
|
false -> {
|
||||||
|
preferences.lightTheme().set(theme)
|
||||||
|
AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (followingSystemTheme && isSelected) {
|
||||||
|
preferences.nightMode().set(nightMode)
|
||||||
|
} else if (!followingSystemTheme) {
|
||||||
|
preferences.nightMode().set(nightMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
(context as? Activity)?.let { activity -> ActivityCompat.recreate(activity) }
|
||||||
|
}
|
||||||
|
}
|
21
app/src/main/java/dev/yokai/presentation/theme/Constants.kt
Normal file
21
app/src/main/java/dev/yokai/presentation/theme/Constants.kt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package dev.yokai.presentation.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
const val SecondaryItemAlpha = .78f
|
||||||
|
const val HalfAlpha = .5f
|
||||||
|
|
||||||
|
object Size {
|
||||||
|
val none = 0.dp
|
||||||
|
val extraExtraTiny = 1.dp
|
||||||
|
val extraTiny = 2.dp
|
||||||
|
val tiny = 4.dp
|
||||||
|
val small = 8.dp
|
||||||
|
val smedium = 12.dp
|
||||||
|
val medium = 16.dp
|
||||||
|
val large = 24.dp
|
||||||
|
val extraLarge = 32.dp
|
||||||
|
val huge = 48.dp
|
||||||
|
val extraHuge = 56.dp
|
||||||
|
val navBarSize = 68.dp
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
package eu.kanade.tachiyomi.core.preference
|
package eu.kanade.tachiyomi.core.preference
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
@ -73,3 +77,9 @@ fun Preference<Boolean>.toggle(): Boolean {
|
||||||
set(!get())
|
set(!get())
|
||||||
return get()
|
return get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <T> Preference<T>.collectAsState(): State<T> {
|
||||||
|
val flow = remember(this) { changes() }
|
||||||
|
return flow.collectAsState(initial = get())
|
||||||
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ import com.google.common.primitives.Ints.max
|
||||||
import dev.yokai.domain.base.BasePreferences
|
import dev.yokai.domain.base.BasePreferences
|
||||||
import dev.yokai.domain.ui.settings.ReaderPreferences
|
import dev.yokai.domain.ui.settings.ReaderPreferences
|
||||||
import dev.yokai.presentation.extension.repo.ExtensionRepoController
|
import dev.yokai.presentation.extension.repo.ExtensionRepoController
|
||||||
|
import dev.yokai.presentation.onboarding.OnboardingController
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.Migrations
|
import eu.kanade.tachiyomi.Migrations
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
@ -519,6 +520,9 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
|
||||||
// Set start screen
|
// Set start screen
|
||||||
if (!handleIntentAction(intent)) {
|
if (!handleIntentAction(intent)) {
|
||||||
goToStartingTab()
|
goToStartingTab()
|
||||||
|
if (!basePreferences.hasShownOnboarding().get()) {
|
||||||
|
router.pushController(OnboardingController().withFadeInTransaction())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
<string name="external_storage_permission_notice">TachiyomiJ2K requires access to all files in Android 11 to download chapters, create automatic backups, and read local series. \n\nOn the next screen, enable \"Allow access to manage all files.\"</string>
|
<string name="external_storage_permission_notice">TachiyomiJ2K requires access to all files in Android 11 to download chapters, create automatic backups, and read local series. \n\nOn the next screen, enable \"Allow access to manage all files.\"</string>
|
||||||
<string name="external_storage_download_notice">TachiyomiJ2K requires access to all files to download chapters. Tap here, then enable \"Allow access to manage all files.\"</string>
|
<string name="external_storage_download_notice">TachiyomiJ2K requires access to all files to download chapters. Tap here, then enable \"Allow access to manage all files.\"</string>
|
||||||
|
|
||||||
|
<string name="onboarding_heading">Welcome!</string>
|
||||||
|
<string name="onboarding_description">Let\'s pick some defaults. You can always change these things later in the settings.</string>
|
||||||
|
<string name="onboarding_finish">Get started</string>
|
||||||
|
|
||||||
<!--Models-->
|
<!--Models-->
|
||||||
|
|
||||||
<!-- Manga Type -->
|
<!-- Manga Type -->
|
||||||
|
|
|
@ -22,6 +22,5 @@ android.enableJetifier=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
org.gradle.jvmargs=-Xmx2048M
|
org.gradle.jvmargs=-Xmx2048M
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
|
||||||
android.nonTransitiveRClass=false
|
android.nonTransitiveRClass=false
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
|
@ -8,8 +8,12 @@ animation = { module = "androidx.compose.animation:animation", version.ref = "co
|
||||||
foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
|
foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
|
||||||
material = { module = "androidx.compose.material:material", version.ref = "compose" }
|
material = { module = "androidx.compose.material:material", version.ref = "compose" }
|
||||||
material3 = { module = "androidx.compose.material3:material3", version = "1.2.1" }
|
material3 = { module = "androidx.compose.material3:material3", version = "1.2.1" }
|
||||||
|
material-motion = { module = "io.github.fornewid:material-motion-compose-core", version = "1.0.7" }
|
||||||
lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
||||||
ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
|
ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
|
||||||
ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
|
ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
|
||||||
ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
|
ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
|
||||||
icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" }
|
icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" }
|
||||||
|
|
||||||
|
[bundles]
|
||||||
|
compose = [ "animation", "foundation", "material", "material3", "material-motion", "ui", "ui-tooling-preview", "icons" ]
|
||||||
|
|
|
@ -29,7 +29,7 @@ flexbox = { module = "com.google.android.flexbox:flexbox", version = "3.0.0" }
|
||||||
flexible-adapter-ui = { module = "com.github.arkon.FlexibleAdapter:flexible-adapter-ui", version.ref = "flexible-adapter" }
|
flexible-adapter-ui = { module = "com.github.arkon.FlexibleAdapter:flexible-adapter-ui", version.ref = "flexible-adapter" }
|
||||||
flexible-adapter = { module = "com.github.arkon.FlexibleAdapter:flexible-adapter", version.ref = "flexible-adapter" }
|
flexible-adapter = { module = "com.github.arkon.FlexibleAdapter:flexible-adapter", version.ref = "flexible-adapter" }
|
||||||
google-services = { module = "com.google.gms:google-services", version = "4.4.1" }
|
google-services = { module = "com.google.gms:google-services", version = "4.4.1" }
|
||||||
gradle = { module = "com.android.tools.build:gradle", version = "8.1.4" }
|
gradle = { module = "com.android.tools.build:gradle", version = "8.2.0" }
|
||||||
guava = { module = "com.google.guava:guava", version = "31.1-android" }
|
guava = { module = "com.google.guava:guava", version = "31.1-android" }
|
||||||
image-decoder = { module = "com.github.tachiyomiorg:image-decoder", version = "e08e9be535" }
|
image-decoder = { module = "com.github.tachiyomiorg:image-decoder", version = "e08e9be535" }
|
||||||
injekt-core = { module = "com.github.inorichi.injekt:injekt-core", version = "65b0440" }
|
injekt-core = { module = "com.github.inorichi.injekt:injekt-core", version = "65b0440" }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue