Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The custom german spyware that is also a theme #1710

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ dependencies {

implementation(androidx.interpolator)

implementation(compose.colorpicker)

implementation(androidx.paging.runtime)
implementation(androidx.paging.compose)

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 @@ -24,6 +24,8 @@ class UiPreferences(
if (DeviceUtil.isDynamicColorAvailable) { AppTheme.MONET } else { AppTheme.DEFAULT },
)

fun colorTheme() = preferenceStore.getInt("pref_color_theme", 0)

fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)

fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
Expand Down
1 change: 1 addition & 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 @@ -6,6 +6,7 @@ import tachiyomi.i18n.MR
enum class AppTheme(val titleRes: StringResource?) {
DEFAULT(MR.strings.label_default),
MONET(MR.strings.theme_monet),
CUSTOM(MR.strings.theme_custom),
CLOUDFLARE(MR.strings.theme_cloudflare),
COTTONCANDY(MR.strings.theme_cottoncandy),
DOOM(MR.strings.theme_doom),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import androidx.core.app.ActivityCompat
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.domain.ui.model.NavStyle
import eu.kanade.domain.ui.model.StartScreen
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 Down Expand Up @@ -48,10 +50,12 @@ object SettingsAppearanceScreen : SearchableSettings {
}

@Composable
@Suppress("SpreadOperator")
private fun getThemeGroup(
uiPreferences: UiPreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow

val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
Expand All @@ -62,6 +66,18 @@ object SettingsAppearanceScreen : SearchableSettings {
val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState()

val customPreferenceItem = if (appTheme == AppTheme.CUSTOM) {
listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_custom_color),
subtitle = stringResource(MR.strings.custom_color_description),
onClick = { navigator.push(AppCustomThemeColorPickerScreen()) },
),
)
} else {
emptyList()
}

return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_theme),
preferenceItems = persistentListOf(
Expand All @@ -84,6 +100,7 @@ object SettingsAppearanceScreen : SearchableSettings {
)
}
},
*customPreferenceItem.toTypedArray(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to use PreferenceGroup instead of trying to change a persistent list?

Copy link
Member Author

@LuftVerbot LuftVerbot Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to make another PreferenceGroup just for the custom theme. Or can you merge two PreferenceGroups together?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not, but perhaps

Suggested change
*customPreferenceItem.toTypedArray(),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_custom_color),
enabled = appTheme == AppTheme.CUSTOM,
subtitle = stringResource(MR.strings.custom_color_description),
onClick = { navigator.push(AppCustomThemeColorPickerScreen()) },
),

would be better

Copy link
Member Author

@LuftVerbot LuftVerbot Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, seems like a good idea. Would've used mutableListOf as another solution. Will try it when I get back home. thx seco

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,78 @@
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.runtime.setValue
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.MR
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(MR.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,113 @@
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.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.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 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 = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(modifier = Modifier) {
HsvColorPicker(
modifier = Modifier
.size(with(LocalDensity.current) { 300.dp }),
controller = controller,
wheelImageBitmap = wheelBitmap,
initialColor = initialColor,
onColorChanged = { colorEnvelope: ColorEnvelope ->
selectedColor = colorEnvelope.color
showConfirmButton = true
},
)
}
Spacer(modifier = Modifier.height(16.dp))
CustomBrightnessSlider(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
controller = controller,
initialColor = initialColor,
)
AnimatedVisibility(
visible = showConfirmButton,
enter = fadeIn() + expandVertically(),
) {
Button(
onClick = {
onItemClick(selectedColor, AppTheme.CUSTOM)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.height(48.dp),
content = {
Text("Confirm Color")
},
)
}
}
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.presentation.theme.colorscheme.BaseColorScheme
import eu.kanade.presentation.theme.colorscheme.CloudflareColorScheme
import eu.kanade.presentation.theme.colorscheme.CottoncandyColorScheme
import eu.kanade.presentation.theme.colorscheme.CustomColorScheme
import eu.kanade.presentation.theme.colorscheme.DoomColorScheme
import eu.kanade.presentation.theme.colorscheme.GreenAppleColorScheme
import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme
Expand Down Expand Up @@ -72,6 +73,8 @@ private fun getThemeColorScheme(
val uiPreferences = Injekt.get<UiPreferences>()
val colorScheme = if (appTheme == AppTheme.MONET) {
MonetColorScheme(LocalContext.current)
} else if (appTheme == AppTheme.CUSTOM) {
CustomColorScheme(uiPreferences)
} else {
colorSchemes.getOrDefault(appTheme, TachiyomiColorScheme)
}
Expand Down
Loading
Loading