Skip to content

Commit

Permalink
Custom themes (#269)
Browse files Browse the repository at this point in the history
* Update SettingsAppearanceScreen.kt

* Update SettingsAppearanceScreen.kt

* fix spotless

* improve

- Put custom theme in front
- Set default color for custom theme
- Using pre-defined padding values
- Make the color picker scrollable for phone in landscape mode

* fixes and new name

* Update ThemeColorPickerWidget.kt
  • Loading branch information
kana-shii authored and cuong-tran committed Aug 25, 2024
1 parent 840829a commit 2266447
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 4 deletions.
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,13 @@ dependencies {
implementation(libs.swipe)
implementation(libs.compose.webview)
implementation(libs.compose.grid)

// KMK -->
implementation(libs.palette.ktx)
implementation(libs.material.kolor)
implementation(libs.haze)
implementation(compose.colorpicker)
// KMK <--

// Logging
implementation(libs.timber)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class UiPreferences(
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)

// KMK -->
fun colorTheme() = preferenceStore.getInt("pref_color_theme", 0xFFDF0090.toInt())

fun themeCoverBased() = preferenceStore.getBoolean("pref_theme_cover_based_key", true)

fun themeCoverBasedStyle() = preferenceStore.getEnum("pref_theme_cover_based_style_key", PaletteStyle.Vibrant)
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ enum class AppTheme(val titleRes: StringResource?) {
DEFAULT(MR.strings.label_default),
MONET(MR.strings.theme_monet),

// Kuukiyomi themes
CUSTOM(KMR.strings.theme_custom),

// Aniyomi themes
COTTONCANDY(KMR.strings.theme_cottoncandy),
MOCHA(KMR.strings.theme_mocha),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import com.materialkolor.PaletteStyle
import eu.kanade.core.preference.asState
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.appearance.AppCustomThemeColorPickerScreen
import eu.kanade.presentation.more.settings.screen.appearance.AppLanguageScreen
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
Expand All @@ -36,6 +38,7 @@ import uy.kohesive.injekt.api.get
import java.time.LocalDate

object SettingsAppearanceScreen : SearchableSettings {
private fun readResolve(): Any = SettingsAppearanceScreen

@ReadOnlyComposable
@Composable
Expand Down Expand Up @@ -63,6 +66,7 @@ object SettingsAppearanceScreen : SearchableSettings {
uiPreferences: UiPreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow

val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
Expand Down Expand Up @@ -95,6 +99,14 @@ object SettingsAppearanceScreen : SearchableSettings {
)
}
},
// KMK -->
Preference.PreferenceItem.TextPreference(
title = stringResource(KMR.strings.pref_custom_color),
enabled = appTheme == AppTheme.CUSTOM,
subtitle = stringResource(KMR.strings.custom_color_description),
onClick = { navigator.push(AppCustomThemeColorPickerScreen()) },
),
// KMK <--
Preference.PreferenceItem.SwitchPreference(
pref = amoledPref,
title = stringResource(MR.strings.pref_dark_theme_pure_black),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package eu.kanade.presentation.more.settings.screen.appearance

import android.app.Activity
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.ActivityCompat
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.more.settings.widget.ThemeColorPickerWidget
import eu.kanade.presentation.util.Screen
import tachiyomi.i18n.kmk.KMR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get

class AppCustomThemeColorPickerScreen : Screen() {

@Composable
override fun Content() {
val uiPreferences: UiPreferences = Injekt.get()

val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val controller = rememberColorPickerController()

val customColorPref = uiPreferences.colorTheme()
val customColor by customColorPref.collectAsState()

val appThemePref = uiPreferences.appTheme()

val currentColor by remember {
mutableIntStateOf(customColor)
}

LaunchedEffect(customColorPref) {
customColorPref.set(currentColor)
}

Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(KMR.strings.pref_custom_color),
navigateUp = navigator::pop,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
Column(
modifier = Modifier.padding(contentPadding),
) {
ThemeColorPickerWidget(
controller = controller,
initialColor = Color(currentColor),
onItemClick = { color, appTheme ->
customColorPref.set(color.toArgb())
appThemePref.set(appTheme)
(context as? Activity)?.let { ActivityCompat.recreate(it) }
},
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package eu.kanade.presentation.more.settings.widget

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.ImageBitmapConfig
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.github.skydoves.colorpicker.compose.BrightnessSlider
import com.github.skydoves.colorpicker.compose.ColorPickerController

@Composable
fun CustomBrightnessSlider(
initialColor: Color,
controller: ColorPickerController,
modifier: Modifier = Modifier,
) {
// Define your colors and sizes directly
val borderColor = Color.LightGray // Color for the slider border
val thumbRadius = 12.dp // Example thumb radius
val trackHeight = 4.dp // Example track height
val borderSize = 1.dp // Example border size for the slider

// Set up the paint for the thumb (wheel)
val wheelPaint = Paint().apply {
color = Color.White
alpha = 1.0f
}

// This function creates the ImageBitmap for the gradient background of the slider
@Composable
fun rememberSliderGradientBitmap(
width: Dp,
height: Dp,
startColor: Color,
endColor: Color,
): ImageBitmap {
val sizePx = with(LocalDensity.current) { IntSize(width.roundToPx(), height.roundToPx()) }
return remember(sizePx, startColor, endColor) {
ImageBitmap(sizePx.width, sizePx.height, ImageBitmapConfig.Argb8888).apply {
val canvas = Canvas(this)
val shader = LinearGradientShader(
colors = listOf(startColor, endColor),
from = Offset(0f, 0f),
to = Offset(sizePx.width.toFloat(), 0f),
tileMode = TileMode.Clamp,
)
val paint = Paint().apply {
this.shader = shader
}
canvas.drawRect(
0f,
0f,
sizePx.width.toFloat(),
sizePx.height.toFloat(),
paint,
)
}
}
}

// Obtain the Composable's size for the gradient background
val sliderWidth = 20.dp // Example width, adjust to your needs
val sliderHeight = thumbRadius * 2 // The height is double the thumb radius
val gradientBitmap = rememberSliderGradientBitmap(
width = sliderWidth, // Subtract the thumb radii from the total width
height = trackHeight,
startColor = Color.White,
endColor = Color.White,
)

BrightnessSlider(
modifier = modifier
.height(sliderHeight)
.fillMaxWidth()
.padding(horizontal = thumbRadius), // Padding equals thumb radius
controller = controller,
initialColor = initialColor,
borderRadius = thumbRadius, // Use thumbRadius for the rounded corners
borderSize = borderSize,
borderColor = borderColor, // Use borderColor for the slider border
wheelRadius = thumbRadius,
wheelColor = Color.White, // Thumb (wheel) color
wheelImageBitmap = gradientBitmap, // Use the generated gradient bitmap as the background
wheelAlpha = 1.0f, // Full opacity for the thumb
wheelPaint = wheelPaint, // Use the defined wheel paint
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package eu.kanade.presentation.more.settings.widget

import android.graphics.Bitmap
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
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.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.github.skydoves.colorpicker.compose.ColorEnvelope
import com.github.skydoves.colorpicker.compose.ColorPickerController
import com.github.skydoves.colorpicker.compose.HsvColorPicker
import eu.kanade.domain.ui.model.AppTheme
import tachiyomi.i18n.kmk.KMR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import kotlin.math.roundToInt

@Composable
internal fun ThemeColorPickerWidget(
initialColor: Color,
controller: ColorPickerController,
onItemClick: (Color, AppTheme) -> Unit,
) {
var selectedColor by remember { mutableStateOf(initialColor) }
var showConfirmButton by remember { mutableStateOf(false) }

val wheelSize = with(LocalDensity.current) { 20.dp.toPx().roundToInt() }
val wheelStrokeWidth = with(LocalDensity.current) { 2.dp.toPx() }

// Remember a wheel bitmap
val wheelBitmap = remember(wheelSize, wheelStrokeWidth) {
val bitmap = Bitmap.createBitmap(wheelSize, wheelSize, Bitmap.Config.ARGB_8888)
val canvas = android.graphics.Canvas(bitmap)
val paint = android.graphics.Paint().apply {
color = android.graphics.Color.WHITE
style = android.graphics.Paint.Style.STROKE
strokeWidth = wheelStrokeWidth
isAntiAlias = true
}

// Draw the circle for wheel indicator
canvas.drawCircle(
wheelSize / 2f,
wheelSize / 2f,
wheelSize / 2f - wheelStrokeWidth,
paint,
)
bitmap.asImageBitmap()
}

BasePreferenceWidget(
subcomponent = {
Column(
modifier = Modifier
.padding(horizontal = MaterialTheme.padding.large)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier = Modifier
.padding(
vertical = MaterialTheme.padding.medium,
),
) {
HsvColorPicker(
modifier = Modifier
.size(300.dp),
controller = controller,
wheelImageBitmap = wheelBitmap,
initialColor = initialColor,
onColorChanged = { colorEnvelope: ColorEnvelope ->
selectedColor = colorEnvelope.color
showConfirmButton = true
},
)
}
CustomBrightnessSlider(
modifier = Modifier
.fillMaxWidth(),
controller = controller,
initialColor = initialColor,
)
AnimatedVisibility(
visible = showConfirmButton,
enter = fadeIn() + expandVertically(),
modifier = Modifier
.padding(top = MaterialTheme.padding.large),
) {
Button(
onClick = {
onItemClick(selectedColor, AppTheme.CUSTOM)
},
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
content = {
Text(text = stringResource(KMR.strings.action_confirm_color))
},
)
}
}
},
)
}
Loading

0 comments on commit 2266447

Please sign in to comment.