feat: graduate built-in colour picker to GA
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
2116c75fc1
commit
2825d71507
|
|
@ -262,7 +262,6 @@ dependencies {
|
|||
implementation "androidx.media3:media3-ui:$media3_version"
|
||||
|
||||
// Compose libraries
|
||||
implementation "com.github.skydoves:colorpicker-compose:1.0.5"
|
||||
implementation "me.saket.telephoto:zoomable-image:1.0.0-alpha02"
|
||||
implementation "me.saket.telephoto:zoomable-image-glide:1.0.0-alpha02"
|
||||
|
||||
|
|
|
|||
|
|
@ -17,24 +17,6 @@ sealed class LabsAccessControlVariates {
|
|||
data class Restricted(val predicate: () -> Boolean) : LabsAccessControlVariates()
|
||||
}
|
||||
|
||||
@FeatureFlag("BuiltInColourPicker")
|
||||
sealed class BuiltInColourPickerVariates {
|
||||
@Treatment(
|
||||
"Use the built-in colour picker"
|
||||
)
|
||||
object Enabled : BuiltInColourPickerVariates()
|
||||
|
||||
@Treatment(
|
||||
"Use the built-in colour picker for users that meet certain or all criteria (implementation-specific)"
|
||||
)
|
||||
data class Restricted(val predicate: () -> Boolean) : BuiltInColourPickerVariates()
|
||||
|
||||
@Treatment(
|
||||
"Use the colour picker from the external library"
|
||||
)
|
||||
object Disabled : BuiltInColourPickerVariates()
|
||||
}
|
||||
|
||||
@FeatureFlag("MediaConversations")
|
||||
sealed class MediaConversationsVariates {
|
||||
@Treatment(
|
||||
|
|
@ -61,20 +43,6 @@ object FeatureFlags {
|
|||
is LabsAccessControlVariates.Restricted -> (labsAccessControl as LabsAccessControlVariates.Restricted).predicate()
|
||||
}
|
||||
|
||||
@FeatureFlag("BuiltInColourPicker")
|
||||
var builtInColourPicker by mutableStateOf<BuiltInColourPickerVariates>(
|
||||
BuiltInColourPickerVariates.Restricted {
|
||||
RevoltAPI.selfId == SpecialUsers.JENNIFER
|
||||
}
|
||||
)
|
||||
|
||||
val builtInColourPickerGranted: Boolean
|
||||
get() = when (builtInColourPicker) {
|
||||
is BuiltInColourPickerVariates.Enabled -> true
|
||||
is BuiltInColourPickerVariates.Restricted -> (builtInColourPicker as BuiltInColourPickerVariates.Restricted).predicate()
|
||||
is BuiltInColourPickerVariates.Disabled -> false
|
||||
}
|
||||
|
||||
@FeatureFlag("MediaConversations")
|
||||
var mediaConversations by mutableStateOf<MediaConversationsVariates>(
|
||||
MediaConversationsVariates.Restricted {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,318 @@
|
|||
package chat.revolt.internals
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
// Auto-generated, do not touch
|
||||
// Generator: https://gist.github.com/infi/0aea10320069f0ca71d0c49cde5ad13c
|
||||
|
||||
object TailwindColourScheme {
|
||||
val slate = listOf(
|
||||
0xFFF8FAFC,
|
||||
0xFFF1F5F9,
|
||||
0xFFE2E8F0,
|
||||
0xFFCBD5E1,
|
||||
0xFF94A3B8,
|
||||
0xFF64748B,
|
||||
0xFF475569,
|
||||
0xFF334155,
|
||||
0xFF1E293B,
|
||||
0xFF0F172A,
|
||||
0xFF020617,
|
||||
).map { Color(it) }
|
||||
|
||||
val gray = listOf(
|
||||
0xFFF9FAFB,
|
||||
0xFFF3F4F6,
|
||||
0xFFE5E7EB,
|
||||
0xFFD1D5DB,
|
||||
0xFF9CA3AF,
|
||||
0xFF6B7280,
|
||||
0xFF4B5563,
|
||||
0xFF374151,
|
||||
0xFF1F2937,
|
||||
0xFF111827,
|
||||
0xFF030712,
|
||||
).map { Color(it) }
|
||||
|
||||
val zinc = listOf(
|
||||
0xFFFAFAFA,
|
||||
0xFFF4F4F5,
|
||||
0xFFE4E4E7,
|
||||
0xFFD4D4D8,
|
||||
0xFFA1A1AA,
|
||||
0xFF71717A,
|
||||
0xFF52525B,
|
||||
0xFF3F3F46,
|
||||
0xFF27272A,
|
||||
0xFF18181B,
|
||||
0xFF09090B,
|
||||
).map { Color(it) }
|
||||
|
||||
val neutral = listOf(
|
||||
0xFFFAFAFA,
|
||||
0xFFF5F5F5,
|
||||
0xFFE5E5E5,
|
||||
0xFFD4D4D4,
|
||||
0xFFA3A3A3,
|
||||
0xFF737373,
|
||||
0xFF525252,
|
||||
0xFF404040,
|
||||
0xFF262626,
|
||||
0xFF171717,
|
||||
0xFF0A0A0A,
|
||||
).map { Color(it) }
|
||||
|
||||
val stone = listOf(
|
||||
0xFFFAFAF9,
|
||||
0xFFF5F5F4,
|
||||
0xFFE7E5E4,
|
||||
0xFFD6D3D1,
|
||||
0xFFA8A29E,
|
||||
0xFF78716C,
|
||||
0xFF57534E,
|
||||
0xFF44403C,
|
||||
0xFF292524,
|
||||
0xFF1C1917,
|
||||
0xFF0C0A09,
|
||||
).map { Color(it) }
|
||||
|
||||
val red = listOf(
|
||||
0xFFFEF2F2,
|
||||
0xFFFEE2E2,
|
||||
0xFFFECACA,
|
||||
0xFFFCA5A5,
|
||||
0xFFF87171,
|
||||
0xFFEF4444,
|
||||
0xFFDC2626,
|
||||
0xFFB91C1C,
|
||||
0xFF991B1B,
|
||||
0xFF7F1D1D,
|
||||
0xFF450A0A,
|
||||
).map { Color(it) }
|
||||
|
||||
val orange = listOf(
|
||||
0xFFFFF7ED,
|
||||
0xFFFFEDD5,
|
||||
0xFFFED7AA,
|
||||
0xFFFDBA74,
|
||||
0xFFFB923C,
|
||||
0xFFF97316,
|
||||
0xFFEA580C,
|
||||
0xFFC2410C,
|
||||
0xFF9A3412,
|
||||
0xFF7C2D12,
|
||||
0xFF431407,
|
||||
).map { Color(it) }
|
||||
|
||||
val amber = listOf(
|
||||
0xFFFFFBEB,
|
||||
0xFFFEF3C7,
|
||||
0xFFFDE68A,
|
||||
0xFFFCD34D,
|
||||
0xFFFBBF24,
|
||||
0xFFF59E0B,
|
||||
0xFFD97706,
|
||||
0xFFB45309,
|
||||
0xFF92400E,
|
||||
0xFF78350F,
|
||||
0xFF451A03,
|
||||
).map { Color(it) }
|
||||
|
||||
val yellow = listOf(
|
||||
0xFFFEFCE8,
|
||||
0xFFFEF9C3,
|
||||
0xFFFEF08A,
|
||||
0xFFFDE047,
|
||||
0xFFFACC15,
|
||||
0xFFEAB308,
|
||||
0xFFCA8A04,
|
||||
0xFFA16207,
|
||||
0xFF854D0E,
|
||||
0xFF713F12,
|
||||
0xFF422006,
|
||||
).map { Color(it) }
|
||||
|
||||
val lime = listOf(
|
||||
0xFFF7FEE7,
|
||||
0xFFECFCCB,
|
||||
0xFFD9F99D,
|
||||
0xFFBEF264,
|
||||
0xFFA3E635,
|
||||
0xFF84CC16,
|
||||
0xFF65A30D,
|
||||
0xFF4D7C0F,
|
||||
0xFF3F6212,
|
||||
0xFF365314,
|
||||
0xFF1A2E05,
|
||||
).map { Color(it) }
|
||||
|
||||
val green = listOf(
|
||||
0xFFF0FDF4,
|
||||
0xFFDCFCE7,
|
||||
0xFFBBF7D0,
|
||||
0xFF86EFAC,
|
||||
0xFF4ADE80,
|
||||
0xFF22C55E,
|
||||
0xFF16A34A,
|
||||
0xFF15803D,
|
||||
0xFF166534,
|
||||
0xFF14532D,
|
||||
0xFF052E16,
|
||||
).map { Color(it) }
|
||||
|
||||
val emerald = listOf(
|
||||
0xFFECFDF5,
|
||||
0xFFD1FAE5,
|
||||
0xFFA7F3D0,
|
||||
0xFF6EE7B7,
|
||||
0xFF34D399,
|
||||
0xFF10B981,
|
||||
0xFF059669,
|
||||
0xFF047857,
|
||||
0xFF065F46,
|
||||
0xFF064E3B,
|
||||
0xFF022C22,
|
||||
).map { Color(it) }
|
||||
|
||||
val teal = listOf(
|
||||
0xFFF0FDFA,
|
||||
0xFFCCFBF1,
|
||||
0xFF99F6E4,
|
||||
0xFF5EEAD4,
|
||||
0xFF2DD4BF,
|
||||
0xFF14B8A6,
|
||||
0xFF0D9488,
|
||||
0xFF0F766E,
|
||||
0xFF115E59,
|
||||
0xFF134E4A,
|
||||
0xFF042F2E,
|
||||
).map { Color(it) }
|
||||
|
||||
val cyan = listOf(
|
||||
0xFFECFEFF,
|
||||
0xFFCFFAFE,
|
||||
0xFFA5F3FC,
|
||||
0xFF67E8F9,
|
||||
0xFF22D3EE,
|
||||
0xFF06B6D4,
|
||||
0xFF0891B2,
|
||||
0xFF0E7490,
|
||||
0xFF155E75,
|
||||
0xFF164E63,
|
||||
0xFF083344,
|
||||
).map { Color(it) }
|
||||
|
||||
val sky = listOf(
|
||||
0xFFF0F9FF,
|
||||
0xFFE0F2FE,
|
||||
0xFFBAE6FD,
|
||||
0xFF7DD3FC,
|
||||
0xFF38BDF8,
|
||||
0xFF0EA5E9,
|
||||
0xFF0284C7,
|
||||
0xFF0369A1,
|
||||
0xFF075985,
|
||||
0xFF0C4A6E,
|
||||
0xFF082F49,
|
||||
).map { Color(it) }
|
||||
|
||||
val blue = listOf(
|
||||
0xFFEFF6FF,
|
||||
0xFFDBEAFE,
|
||||
0xFFBFDBFE,
|
||||
0xFF93C5FD,
|
||||
0xFF60A5FA,
|
||||
0xFF3B82F6,
|
||||
0xFF2563EB,
|
||||
0xFF1D4ED8,
|
||||
0xFF1E40AF,
|
||||
0xFF1E3A8A,
|
||||
0xFF172554,
|
||||
).map { Color(it) }
|
||||
|
||||
val indigo = listOf(
|
||||
0xFFEEF2FF,
|
||||
0xFFE0E7FF,
|
||||
0xFFC7D2FE,
|
||||
0xFFA5B4FC,
|
||||
0xFF818CF8,
|
||||
0xFF6366F1,
|
||||
0xFF4F46E5,
|
||||
0xFF4338CA,
|
||||
0xFF3730A3,
|
||||
0xFF312E81,
|
||||
0xFF1E1B4B,
|
||||
).map { Color(it) }
|
||||
|
||||
val violet = listOf(
|
||||
0xFFF5F3FF,
|
||||
0xFFEDE9FE,
|
||||
0xFFDDD6FE,
|
||||
0xFFC4B5FD,
|
||||
0xFFA78BFA,
|
||||
0xFF8B5CF6,
|
||||
0xFF7C3AED,
|
||||
0xFF6D28D9,
|
||||
0xFF5B21B6,
|
||||
0xFF4C1D95,
|
||||
0xFF2E1065,
|
||||
).map { Color(it) }
|
||||
|
||||
val purple = listOf(
|
||||
0xFFFAF5FF,
|
||||
0xFFF3E8FF,
|
||||
0xFFE9D5FF,
|
||||
0xFFD8B4FE,
|
||||
0xFFC084FC,
|
||||
0xFFA855F7,
|
||||
0xFF9333EA,
|
||||
0xFF7E22CE,
|
||||
0xFF6B21A8,
|
||||
0xFF581C87,
|
||||
0xFF3B0764,
|
||||
).map { Color(it) }
|
||||
|
||||
val fuchsia = listOf(
|
||||
0xFFFDF4FF,
|
||||
0xFFFAE8FF,
|
||||
0xFFF5D0FE,
|
||||
0xFFF0ABFC,
|
||||
0xFFE879F9,
|
||||
0xFFD946EF,
|
||||
0xFFC026D3,
|
||||
0xFFA21CAF,
|
||||
0xFF86198F,
|
||||
0xFF701A75,
|
||||
0xFF4A044E,
|
||||
).map { Color(it) }
|
||||
|
||||
val pink = listOf(
|
||||
0xFFFDF2F8,
|
||||
0xFFFCE7F3,
|
||||
0xFFFBCFE8,
|
||||
0xFFF9A8D4,
|
||||
0xFFF472B6,
|
||||
0xFFEC4899,
|
||||
0xFFDB2777,
|
||||
0xFFBE185D,
|
||||
0xFF9D174D,
|
||||
0xFF831843,
|
||||
0xFF500724,
|
||||
).map { Color(it) }
|
||||
|
||||
val rose = listOf(
|
||||
0xFFFFF1F2,
|
||||
0xFFFFE4E6,
|
||||
0xFFFECDD3,
|
||||
0xFFFDA4AF,
|
||||
0xFFFB7185,
|
||||
0xFFF43F5E,
|
||||
0xFFE11D48,
|
||||
0xFFBE123C,
|
||||
0xFF9F1239,
|
||||
0xFF881337,
|
||||
0xFF4C0519,
|
||||
).map { Color(it) }
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -26,11 +26,6 @@ import androidx.compose.foundation.verticalScroll
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.ripple.LocalRippleTheme
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
|
|
@ -44,17 +39,14 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
|
|
@ -71,25 +63,17 @@ import androidx.navigation.NavController
|
|||
import chat.revolt.R
|
||||
import chat.revolt.api.RevoltCbor
|
||||
import chat.revolt.api.RevoltJson
|
||||
import chat.revolt.api.settings.FeatureFlags
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.api.settings.SyncedSettings
|
||||
import chat.revolt.components.generic.ListHeader
|
||||
import chat.revolt.components.generic.SheetEnd
|
||||
import chat.revolt.components.screens.settings.appearance.ColourChip
|
||||
import chat.revolt.components.screens.settings.appearance.CornerRadiusPicker
|
||||
import chat.revolt.internals.extensions.BottomSheetInsets
|
||||
import chat.revolt.sheets.ColourPickerSheet
|
||||
import chat.revolt.ui.theme.ClearRippleTheme
|
||||
import chat.revolt.ui.theme.OverridableColourScheme
|
||||
import chat.revolt.ui.theme.Theme
|
||||
import chat.revolt.ui.theme.getFieldByName
|
||||
import chat.revolt.ui.theme.systemSupportsDynamicColors
|
||||
import com.github.skydoves.colorpicker.compose.AlphaSlider
|
||||
import com.github.skydoves.colorpicker.compose.BrightnessSlider
|
||||
import com.github.skydoves.colorpicker.compose.ColorEnvelope
|
||||
import com.github.skydoves.colorpicker.compose.HsvColorPicker
|
||||
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -259,8 +243,9 @@ fun AppearanceSettingsScreen(
|
|||
},
|
||||
windowInsets = BottomSheetInsets
|
||||
) {
|
||||
if (FeatureFlags.builtInColourPickerGranted) {
|
||||
ColourPickerSheet(initialValue = viewModel.selectedOverrideInitialValue ?: 0) {
|
||||
ColourPickerSheet(
|
||||
initialValue = viewModel.selectedOverrideInitialValue ?: 0,
|
||||
onColourSelected = {
|
||||
viewModel.updateColourOverrides(
|
||||
viewModel.selectedOverrideName ?: return@ColourPickerSheet,
|
||||
it
|
||||
|
|
@ -269,28 +254,24 @@ fun AppearanceSettingsScreen(
|
|||
sheetState.hide()
|
||||
viewModel.overridePickerSheetVisible = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ColourSelectorSheet(
|
||||
initialValue = Color(viewModel.selectedOverrideInitialValue ?: 0),
|
||||
onConfirm = { color ->
|
||||
viewModel.updateColourOverrides(
|
||||
viewModel.selectedOverrideName ?: return@ColourSelectorSheet,
|
||||
color?.toArgb()
|
||||
)
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
viewModel.overridePickerSheetVisible = false
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
viewModel.overridePickerSheetVisible = false
|
||||
}
|
||||
},
|
||||
onUseDefaultColour = {
|
||||
viewModel.updateColourOverrides(
|
||||
viewModel.selectedOverrideName ?: return@ColourPickerSheet,
|
||||
null
|
||||
)
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
viewModel.overridePickerSheetVisible = false
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
viewModel.overridePickerSheetVisible = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -535,123 +516,4 @@ fun AppearanceSettingsScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColourSelectorSheet(
|
||||
initialValue: Color,
|
||||
onConfirm: (Color?) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val controller = rememberColorPickerController()
|
||||
val colour = remember { mutableStateOf(initialValue) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(20.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
HsvColorPicker(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(450.dp)
|
||||
.padding(10.dp),
|
||||
controller = controller,
|
||||
onColorChanged = { colorEnvelope: ColorEnvelope ->
|
||||
colour.value = colorEnvelope.color
|
||||
},
|
||||
)
|
||||
|
||||
AlphaSlider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp),
|
||||
controller = controller,
|
||||
)
|
||||
|
||||
BrightnessSlider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp),
|
||||
controller = controller,
|
||||
)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalRippleTheme provides ClearRippleTheme
|
||||
) {
|
||||
ColourChip(
|
||||
color = colour.value,
|
||||
text = "#${
|
||||
(0xFFFFFF and colour.value.toArgb()).toString(16).padStart(6, '0').uppercase()
|
||||
}",
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth()
|
||||
) {}
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onConfirm(null)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_appearance_colour_overrides_reset)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.cancel)
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
onConfirm(colour.value)
|
||||
},
|
||||
enabled = colour.value != initialValue,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_appearance_colour_overrides_apply)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SheetEnd()
|
||||
}
|
||||
|
|
@ -1,20 +1,35 @@
|
|||
package chat.revolt.sheets
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
|
|
@ -22,21 +37,26 @@ import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
|||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.R
|
||||
import chat.revolt.components.generic.SheetEnd
|
||||
import chat.revolt.internals.TailwindColourScheme
|
||||
|
||||
enum class ColourPickerMode {
|
||||
Sliders,
|
||||
|
|
@ -44,42 +64,187 @@ enum class ColourPickerMode {
|
|||
Hex
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) -> Unit) {
|
||||
var selectedColour by remember { mutableIntStateOf(initialValue and 0xFFFFFF) }
|
||||
private fun Color.asHsv(): Triple<Float, Float, Float> {
|
||||
val max = maxOf(red, green, blue)
|
||||
val min = minOf(red, green, blue)
|
||||
val delta = max - min
|
||||
|
||||
val hue: Float = if (max == min) {
|
||||
0f
|
||||
} else {
|
||||
when (max) {
|
||||
red -> (60 * ((green - blue) / delta) + 360) % 360
|
||||
green -> (60 * ((blue - red) / delta) + 120) % 360
|
||||
blue -> (60 * ((red - green) / delta) + 240) % 360
|
||||
else -> throw IllegalStateException("Unexpected max value: $max")
|
||||
}
|
||||
}
|
||||
val saturation: Float = if (max == 0f) 0f else delta / max
|
||||
|
||||
return Triple(hue, saturation, max)
|
||||
}
|
||||
|
||||
private fun Color.asHexString(): String {
|
||||
val alpha = (alpha * 255).toInt()
|
||||
val red = (red * 255).toInt()
|
||||
val green = (green * 255).toInt()
|
||||
val blue = (blue * 255).toInt()
|
||||
return String.format("#%02X%02X%02X%02X", alpha, red, green, blue)
|
||||
}
|
||||
|
||||
// 11x11 palette of Tailwind colours
|
||||
val palette = listOf(
|
||||
TailwindColourScheme.neutral[0],
|
||||
TailwindColourScheme.red[0],
|
||||
TailwindColourScheme.red[1],
|
||||
TailwindColourScheme.red[2],
|
||||
TailwindColourScheme.red[3],
|
||||
TailwindColourScheme.red[4],
|
||||
TailwindColourScheme.red[5],
|
||||
TailwindColourScheme.red[6],
|
||||
TailwindColourScheme.red[7],
|
||||
TailwindColourScheme.red[9],
|
||||
TailwindColourScheme.red[10],
|
||||
|
||||
TailwindColourScheme.neutral[1],
|
||||
TailwindColourScheme.orange[0],
|
||||
TailwindColourScheme.orange[1],
|
||||
TailwindColourScheme.orange[2],
|
||||
TailwindColourScheme.orange[3],
|
||||
TailwindColourScheme.orange[4],
|
||||
TailwindColourScheme.orange[5],
|
||||
TailwindColourScheme.orange[6],
|
||||
TailwindColourScheme.orange[7],
|
||||
TailwindColourScheme.orange[9],
|
||||
TailwindColourScheme.orange[10],
|
||||
|
||||
TailwindColourScheme.neutral[2],
|
||||
TailwindColourScheme.yellow[0],
|
||||
TailwindColourScheme.yellow[1],
|
||||
TailwindColourScheme.yellow[2],
|
||||
TailwindColourScheme.yellow[3],
|
||||
TailwindColourScheme.yellow[4],
|
||||
TailwindColourScheme.yellow[5],
|
||||
TailwindColourScheme.yellow[6],
|
||||
TailwindColourScheme.yellow[7],
|
||||
TailwindColourScheme.yellow[9],
|
||||
TailwindColourScheme.yellow[10],
|
||||
|
||||
TailwindColourScheme.neutral[3],
|
||||
TailwindColourScheme.green[0],
|
||||
TailwindColourScheme.green[1],
|
||||
TailwindColourScheme.green[2],
|
||||
TailwindColourScheme.green[3],
|
||||
TailwindColourScheme.green[4],
|
||||
TailwindColourScheme.green[5],
|
||||
TailwindColourScheme.green[6],
|
||||
TailwindColourScheme.green[7],
|
||||
TailwindColourScheme.green[9],
|
||||
TailwindColourScheme.green[10],
|
||||
|
||||
TailwindColourScheme.neutral[4],
|
||||
TailwindColourScheme.teal[0],
|
||||
TailwindColourScheme.teal[1],
|
||||
TailwindColourScheme.teal[2],
|
||||
TailwindColourScheme.teal[3],
|
||||
TailwindColourScheme.teal[4],
|
||||
TailwindColourScheme.teal[5],
|
||||
TailwindColourScheme.teal[6],
|
||||
TailwindColourScheme.teal[7],
|
||||
TailwindColourScheme.teal[9],
|
||||
TailwindColourScheme.teal[10],
|
||||
|
||||
TailwindColourScheme.neutral[5],
|
||||
TailwindColourScheme.sky[0],
|
||||
TailwindColourScheme.sky[1],
|
||||
TailwindColourScheme.sky[2],
|
||||
TailwindColourScheme.sky[3],
|
||||
TailwindColourScheme.sky[4],
|
||||
TailwindColourScheme.sky[5],
|
||||
TailwindColourScheme.sky[6],
|
||||
TailwindColourScheme.sky[7],
|
||||
TailwindColourScheme.sky[9],
|
||||
TailwindColourScheme.sky[10],
|
||||
|
||||
TailwindColourScheme.neutral[6],
|
||||
TailwindColourScheme.blue[0],
|
||||
TailwindColourScheme.blue[1],
|
||||
TailwindColourScheme.blue[2],
|
||||
TailwindColourScheme.blue[3],
|
||||
TailwindColourScheme.blue[4],
|
||||
TailwindColourScheme.blue[5],
|
||||
TailwindColourScheme.blue[6],
|
||||
TailwindColourScheme.blue[7],
|
||||
TailwindColourScheme.blue[9],
|
||||
TailwindColourScheme.blue[10],
|
||||
|
||||
TailwindColourScheme.neutral[7],
|
||||
TailwindColourScheme.violet[0],
|
||||
TailwindColourScheme.violet[1],
|
||||
TailwindColourScheme.violet[2],
|
||||
TailwindColourScheme.violet[3],
|
||||
TailwindColourScheme.violet[4],
|
||||
TailwindColourScheme.violet[5],
|
||||
TailwindColourScheme.violet[6],
|
||||
TailwindColourScheme.violet[7],
|
||||
TailwindColourScheme.violet[9],
|
||||
TailwindColourScheme.violet[10],
|
||||
|
||||
TailwindColourScheme.neutral[8],
|
||||
TailwindColourScheme.purple[0],
|
||||
TailwindColourScheme.purple[1],
|
||||
TailwindColourScheme.purple[2],
|
||||
TailwindColourScheme.purple[3],
|
||||
TailwindColourScheme.purple[4],
|
||||
TailwindColourScheme.purple[5],
|
||||
TailwindColourScheme.purple[6],
|
||||
TailwindColourScheme.purple[7],
|
||||
TailwindColourScheme.purple[9],
|
||||
TailwindColourScheme.purple[10],
|
||||
|
||||
TailwindColourScheme.neutral[9],
|
||||
TailwindColourScheme.pink[0],
|
||||
TailwindColourScheme.pink[1],
|
||||
TailwindColourScheme.pink[2],
|
||||
TailwindColourScheme.pink[3],
|
||||
TailwindColourScheme.pink[4],
|
||||
TailwindColourScheme.pink[5],
|
||||
TailwindColourScheme.pink[6],
|
||||
TailwindColourScheme.pink[7],
|
||||
TailwindColourScheme.pink[9],
|
||||
TailwindColourScheme.pink[10],
|
||||
|
||||
TailwindColourScheme.neutral[10],
|
||||
TailwindColourScheme.rose[0],
|
||||
TailwindColourScheme.rose[1],
|
||||
TailwindColourScheme.rose[2],
|
||||
TailwindColourScheme.rose[3],
|
||||
TailwindColourScheme.rose[4],
|
||||
TailwindColourScheme.rose[5],
|
||||
TailwindColourScheme.rose[6],
|
||||
TailwindColourScheme.rose[7],
|
||||
TailwindColourScheme.rose[9],
|
||||
TailwindColourScheme.rose[10],
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ColumnScope.ColourPickerSheet(
|
||||
initialValue: Int,
|
||||
onColourSelected: (Int) -> Unit,
|
||||
onUseDefaultColour: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
var color by remember { mutableStateOf(Color(initialValue)) }
|
||||
var mode by remember { mutableStateOf(ColourPickerMode.Sliders) }
|
||||
|
||||
val hueComponent by remember(selectedColour) { derivedStateOf { selectedColour shr 16 and 0xFF } }
|
||||
val saturationComponent by remember(selectedColour) { derivedStateOf { selectedColour shr 8 and 0xFF } }
|
||||
val valueComponent by remember(selectedColour) { derivedStateOf { selectedColour and 0xFF } }
|
||||
|
||||
val hueTrackColours = remember {
|
||||
(0..255).map {
|
||||
Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
it.toFloat(),
|
||||
1f,
|
||||
1f
|
||||
)
|
||||
)
|
||||
)
|
||||
(0..359).map {
|
||||
Color.hsv(it.toFloat(), 1f, 1f, 1f)
|
||||
}
|
||||
}
|
||||
|
||||
var pendingHexColourString by remember(selectedColour) {
|
||||
// First we convert the colour from HHSSVV to #RRGGBB.
|
||||
val asRgb = android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
saturationComponent / 255f,
|
||||
valueComponent / 255f
|
||||
)
|
||||
)
|
||||
mutableStateOf("#${asRgb.toString(16).padStart(6, '0')}")
|
||||
}
|
||||
val colorHsv by remember(color) { derivedStateOf { color.asHsv() } }
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
|
|
@ -132,7 +297,10 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
Text(
|
||||
hueComponent.toString(),
|
||||
stringResource(
|
||||
R.string.colour_picker_hue_value_fmt,
|
||||
colorHsv.first.toInt()
|
||||
),
|
||||
style = MaterialTheme.typography.labelLarge.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(
|
||||
alpha = 0.6f
|
||||
|
|
@ -142,22 +310,17 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
}
|
||||
|
||||
Slider(
|
||||
value = hueComponent.toFloat(),
|
||||
value = colorHsv.first,
|
||||
onValueChange = {
|
||||
selectedColour =
|
||||
(selectedColour and 0x00FFFF) or (it.toInt() shl 16)
|
||||
val (_, saturation, value) = colorHsv
|
||||
color = Color.hsv(it, saturation, value)
|
||||
},
|
||||
valueRange = 0f..255f,
|
||||
valueRange = 0f..359f,
|
||||
colors = SliderDefaults.colors().copy(
|
||||
// The thumb colour is the current hue at full saturation and value.
|
||||
thumbColor = Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
1f,
|
||||
1f
|
||||
)
|
||||
)
|
||||
thumbColor = Color.hsv(
|
||||
colorHsv.first,
|
||||
1f,
|
||||
1f
|
||||
)
|
||||
),
|
||||
track = {
|
||||
|
|
@ -167,7 +330,10 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
.height(4.dp)
|
||||
) {
|
||||
drawRect(
|
||||
Brush.horizontalGradient(hueTrackColours, endX = size.width)
|
||||
Brush.horizontalGradient(
|
||||
hueTrackColours,
|
||||
endX = size.width
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +348,7 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
Text(
|
||||
saturationComponent.toString(),
|
||||
(colorHsv.second * 100).toInt().toString(),
|
||||
style = MaterialTheme.typography.labelLarge.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(
|
||||
alpha = 0.6f
|
||||
|
|
@ -192,21 +358,17 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
}
|
||||
|
||||
Slider(
|
||||
value = saturationComponent.toFloat(),
|
||||
value = colorHsv.second,
|
||||
onValueChange = {
|
||||
selectedColour =
|
||||
(selectedColour and 0xFF00FF) or (it.toInt() shl 8)
|
||||
val (hue, _, value) = colorHsv
|
||||
color = Color.hsv(hue, it, value)
|
||||
},
|
||||
valueRange = 0f..255f,
|
||||
valueRange = 0f..1f,
|
||||
colors = SliderDefaults.colors().copy(
|
||||
thumbColor = Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
(selectedColour shr 8 and 0xFF) / 255f,
|
||||
1f
|
||||
)
|
||||
)
|
||||
thumbColor = Color.hsv(
|
||||
colorHsv.first,
|
||||
colorHsv.second,
|
||||
1f
|
||||
)
|
||||
),
|
||||
track = {
|
||||
|
|
@ -218,23 +380,15 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
drawRect(
|
||||
Brush.horizontalGradient(
|
||||
listOf(
|
||||
Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
0f,
|
||||
1f
|
||||
)
|
||||
)
|
||||
Color.hsv(
|
||||
colorHsv.first,
|
||||
0f,
|
||||
1f
|
||||
),
|
||||
Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
1f,
|
||||
1f
|
||||
)
|
||||
)
|
||||
Color.hsv(
|
||||
colorHsv.first,
|
||||
1f,
|
||||
1f
|
||||
)
|
||||
),
|
||||
endX = size.width
|
||||
|
|
@ -253,7 +407,7 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
Text(
|
||||
valueComponent.toString(),
|
||||
(colorHsv.third * 100).toInt().toString(),
|
||||
style = MaterialTheme.typography.labelLarge.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(
|
||||
alpha = 0.6f
|
||||
|
|
@ -263,21 +417,17 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
}
|
||||
|
||||
Slider(
|
||||
value = valueComponent.toFloat(),
|
||||
value = colorHsv.third,
|
||||
onValueChange = {
|
||||
selectedColour =
|
||||
(selectedColour and 0xFFFF00) or it.toInt()
|
||||
val (hue, saturation, _) = colorHsv
|
||||
color = Color.hsv(hue, saturation, it)
|
||||
},
|
||||
valueRange = 0f..255f,
|
||||
valueRange = 0f..1f,
|
||||
colors = SliderDefaults.colors().copy(
|
||||
thumbColor = Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
(selectedColour shr 8 and 0xFF) / 255f,
|
||||
(selectedColour and 0xFF) / 255f
|
||||
)
|
||||
)
|
||||
thumbColor = Color.hsv(
|
||||
colorHsv.first,
|
||||
colorHsv.second,
|
||||
colorHsv.third
|
||||
)
|
||||
),
|
||||
track = {
|
||||
|
|
@ -289,23 +439,15 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
drawRect(
|
||||
Brush.horizontalGradient(
|
||||
listOf(
|
||||
Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
(selectedColour shr 8 and 0xFF) / 255f,
|
||||
0f
|
||||
)
|
||||
)
|
||||
Color.hsv(
|
||||
colorHsv.first,
|
||||
colorHsv.second,
|
||||
0f
|
||||
),
|
||||
Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
(selectedColour shr 8 and 0xFF) / 255f,
|
||||
1f
|
||||
)
|
||||
)
|
||||
Color.hsv(
|
||||
colorHsv.first,
|
||||
colorHsv.second,
|
||||
1f
|
||||
)
|
||||
),
|
||||
endX = size.width
|
||||
|
|
@ -314,36 +456,120 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.colour_picker_alpha),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
Text(
|
||||
(color.alpha * 100).toInt().toString(),
|
||||
style = MaterialTheme.typography.labelLarge.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(
|
||||
alpha = 0.6f
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Slider(
|
||||
value = color.alpha,
|
||||
onValueChange = { color = color.copy(alpha = it) },
|
||||
valueRange = 0f..1f,
|
||||
colors = SliderDefaults.colors().copy(
|
||||
thumbColor = color
|
||||
),
|
||||
track = {
|
||||
Canvas(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(4.dp)
|
||||
) {
|
||||
drawRect(
|
||||
Brush.horizontalGradient(
|
||||
listOf(
|
||||
color.copy(alpha = 0f),
|
||||
color.copy(alpha = 1f)
|
||||
),
|
||||
endX = size.width
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ColourPickerMode.Palette -> {
|
||||
Text("TODO: Palette picker", Modifier.fillMaxWidth())
|
||||
BoxWithConstraints {
|
||||
val boxMaxWidth = this.maxWidth
|
||||
|
||||
FlowRow(
|
||||
maxItemsInEachRow = 11,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
for (colour in palette) {
|
||||
Box(
|
||||
Modifier
|
||||
.clip(CircleShape)
|
||||
.clickable { color = colour }
|
||||
.size((boxMaxWidth - 80.dp) / 11)
|
||||
.background(colour)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColourPickerMode.Hex -> {
|
||||
OutlinedTextField(
|
||||
value = pendingHexColourString,
|
||||
onValueChange = {
|
||||
pendingHexColourString = it
|
||||
var hex by remember(color) {
|
||||
mutableStateOf(color.asHexString())
|
||||
}
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if ("#[0-9a-fA-F]{6}".toRegex().matches(it)) {
|
||||
val newColour =
|
||||
it.substring(1).toIntOrNull(16) ?: return@OutlinedTextField
|
||||
val floatArr = FloatArray(3)
|
||||
android.graphics.Color.RGBToHSV(
|
||||
newColour shr 16 and 0xFF,
|
||||
newColour shr 8 and 0xFF,
|
||||
newColour and 0xFF,
|
||||
floatArr
|
||||
)
|
||||
selectedColour = floatArr.fold(0) { acc, f ->
|
||||
(acc shl 8) or (f.toInt() and 0xFF)
|
||||
BackHandler(enabled = isFocused) {
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = hex,
|
||||
onValueChange = {
|
||||
hex = if (it.isNotEmpty() && it[0] == '#') {
|
||||
it
|
||||
} else {
|
||||
"#$it"
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.colour_picker_hex_template)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
isFocused = it.isFocused
|
||||
},
|
||||
)
|
||||
|
||||
TextButton(
|
||||
onClick = {
|
||||
try {
|
||||
color = Color(android.graphics.Color.parseColor(hex))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Ignore
|
||||
}
|
||||
},
|
||||
enabled = (hex.length == 9 || hex.length == 7) && hex[0] == '#' && hex.substring(
|
||||
1
|
||||
).all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(R.string.colour_picker_hex_use))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -357,21 +583,43 @@ fun ColumnScope.ColourPickerSheet(initialValue: Int, onColourSelected: (Int) ->
|
|||
|
||||
Canvas(
|
||||
Modifier
|
||||
.clip(MaterialTheme.shapes.large)
|
||||
.fillMaxWidth()
|
||||
.height(64.dp)
|
||||
) {
|
||||
drawRect(
|
||||
Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hueComponent.toFloat(),
|
||||
saturationComponent / 255f,
|
||||
valueComponent / 255f
|
||||
)
|
||||
)
|
||||
),
|
||||
size = size
|
||||
)
|
||||
drawRect(color)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
Column(
|
||||
Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
TextButton(onClick = onUseDefaultColour, Modifier.fillMaxWidth()) {
|
||||
Icon(Icons.Default.CheckCircle, null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(stringResource(R.string.colour_picker_use_default))
|
||||
}
|
||||
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
TextButton(onClick = onDismiss, modifier = Modifier.weight(1f)) {
|
||||
Icon(Icons.Default.Close, null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(stringResource(R.string.colour_picker_cancel))
|
||||
}
|
||||
Button(
|
||||
onClick = { onColourSelected(color.toArgb()) },
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Icon(Icons.Default.Check, null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(stringResource(R.string.colour_picker_apply))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -456,15 +456,18 @@
|
|||
<string name="colour_picker_mode_sliders">Sliders</string>
|
||||
<string name="colour_picker_mode_palette">Palette</string>
|
||||
<string name="colour_picker_hue">Hue</string>
|
||||
<string name="colour_picker_hue_value_fmt">%1$d°</string>
|
||||
<string name="colour_picker_saturation">Saturation</string>
|
||||
<string name="colour_picker_value">Value</string>
|
||||
<string name="colour_picker_red">Red</string>
|
||||
<string name="colour_picker_green">Green</string>
|
||||
<string name="colour_picker_blue">Blue</string>
|
||||
<string name="colour_picker_hex">Hex</string>
|
||||
<string name="colour_picker_hex_template" translatable="false">#AARRGGBB</string>
|
||||
<string name="colour_picker_hex_use">Use</string>
|
||||
<string name="colour_picker_alpha">Alpha</string>
|
||||
<string name="colour_picker_preview">Preview</string>
|
||||
<string name="colour_picker_reset_to_default">Reset to default</string>
|
||||
<string name="colour_picker_use_default">Default</string>
|
||||
<string name="colour_picker_cancel">Cancel</string>
|
||||
<string name="colour_picker_apply">Apply</string>
|
||||
|
||||
|
|
@ -581,8 +584,6 @@
|
|||
<string name="settings_appearance_colour_overrides_outline">Outline</string>
|
||||
<string name="settings_appearance_colour_overrides_outline_variant">Outline Variant</string>
|
||||
<string name="settings_appearance_colour_overrides_scrim">Scrim</string>
|
||||
<string name="settings_appearance_colour_overrides_apply">Apply</string>
|
||||
<string name="settings_appearance_colour_overrides_reset">Reset</string>
|
||||
<string name="settings_appearance_colour_overrides_export">Export</string>
|
||||
<string name="settings_appearance_colour_overrides_import">Import</string>
|
||||
<string name="settings_appearance_colour_overrides_import_error">This file is not a valid colour override file.</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue