feat(labs): new sandboxes

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2025-02-23 16:20:05 +01:00
parent 32398e8d99
commit 6918f7b9fc
8 changed files with 252 additions and 4 deletions

View File

@ -305,6 +305,9 @@ dependencies {
// Square Logcat
implementation("com.squareup.logcat:logcat:0.1")
// Librevolt
implementation("librevolt:librevolt-jvm:0.1.0")
// Testing
androidTestImplementation("androidx.test:runner:$androidXTestVersion")
androidTestImplementation("androidx.test:rules:$androidXTestVersion")
@ -315,7 +318,16 @@ aboutLibraries {
additionalLicenses += listOf("ofl")
includePlatform = true
strictMode = StrictMode.FAIL
allowedLicenses += listOf("Apache-2.0", "OFL", "MIT", "ASDKL", "BSD-2-Clause", "cmark")
allowedLicenses += listOf(
"Apache-2.0",
"OFL",
"MIT",
"ASDKL",
"BSD-2-Clause",
"cmark",
"EPL-1.0",
"BSD-3-Clause"
)
configPath = "compliance"
}

View File

@ -0,0 +1,57 @@
package chat.revolt.components.generic
import android.annotation.SuppressLint
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import logcat.logcat
@SuppressLint("UnusedBoxWithConstraintsScope")
@Composable
fun GradientStopEditor(
segments: Int,
stops: List<Pair<Float, Color>>,
onStopsChanged: (List<Pair<Float, Color>>) -> Unit,
_onClickSegment: (Int) -> Unit,
modifier: Modifier = Modifier
) {
// Our gradient stop editor uses zero-width "segments" to represent points where the stops can be placed.
// There are two segments, one at the start and one at the end, that are not visible to the user.
// They only exist so that the user can place stops at the very beginning and end of the gradient without
// having to tap at the edge of the screen.
Canvas(
modifier
.padding(horizontal = 16.dp)
.pointerInput(segments) {
detectTapGestures { offset ->
val segment = (offset.x / size.width * segments).toInt()
logcat { "Tapped segment $segment" }
}
}
.fillMaxWidth()
) {
// Debug code for now
val debugStops = mutableListOf(Color.Transparent)
for (i in 1 until segments + 1) {
if (i % 2 == 0) {
debugStops.add(Color(0xff366fd1))
} else {
debugStops.add(Color(0xff36d1af))
}
}
debugStops.add(Color.Transparent)
drawRect(
brush = Brush.linearGradient(debugStops, Offset(0f, 0f), Offset(size.width, 0f)),
size = size,
)
}
}

View File

@ -11,12 +11,14 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
@ -30,9 +32,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.revolt.ui.theme.FragmentMono
enum class LabsHomeScreenTab {
Home,
@ -42,7 +47,7 @@ enum class LabsHomeScreenTab {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LabsHomeScreen(navController: NavController) {
fun LabsHomeScreen(navController: NavController, topNav: NavController) {
val currentTab = rememberSaveable { mutableStateOf(LabsHomeScreenTab.Home) }
Scaffold(
@ -51,6 +56,16 @@ fun LabsHomeScreen(navController: NavController) {
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(),
title = {
Text("Labs")
},
navigationIcon = {
IconButton(onClick = {
topNav.popBackStack()
}) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = "Back"
)
}
}
)
},
@ -175,6 +190,29 @@ fun LabsHomeScreen(navController: NavController) {
}
)
HorizontalDivider()
ListItem(
headlineContent = {
Text("Gradient Editor")
},
modifier = Modifier.clickable {
navController.navigate("sandboxes/gradienteditor")
}
)
HorizontalDivider()
ListItem(
headlineContent = {
Text(buildAnnotatedString {
pushStyle(SpanStyle(fontFamily = FragmentMono))
append("librevolt")
pop()
append(" Sample")
})
},
modifier = Modifier.clickable {
navController.navigate("sandboxes/librevolt")
}
)
HorizontalDivider()
}
}
}

View File

@ -14,6 +14,8 @@ import androidx.navigation.compose.rememberNavController
import chat.revolt.api.settings.FeatureFlags
import chat.revolt.screens.labs.ui.mockups.CallScreenMockup
import chat.revolt.screens.labs.ui.mockups.NewLoginExperienceMockup
import chat.revolt.screens.labs.ui.sandbox.CoreLibSandbox
import chat.revolt.screens.labs.ui.sandbox.GradientEditorSandbox
import chat.revolt.screens.labs.ui.sandbox.JBMSandbox
import chat.revolt.screens.labs.ui.sandbox.SettingsDslSandbox
@ -59,7 +61,7 @@ fun LabsRootScreen(topNav: NavController) {
startDestination = "home",
) {
composable("home") {
LabsHomeScreen(labsNav)
LabsHomeScreen(labsNav, topNav)
}
composable("mockups/call") {
@ -75,6 +77,12 @@ fun LabsRootScreen(topNav: NavController) {
composable("sandboxes/jbm") {
JBMSandbox(labsNav)
}
composable("sandboxes/gradienteditor") {
GradientEditorSandbox(labsNav)
}
composable("sandboxes/librevolt") {
CoreLibSandbox(labsNav)
}
}
}
}

View File

@ -0,0 +1,43 @@
package chat.revolt.screens.labs.ui.sandbox
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.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.navigation.NavController
import chat.revolt.settings.dsl.SettingsPage
import chat.revolt.ui.theme.FragmentMono
@Composable
fun CoreLibSandbox(navController: NavController) {
var greeting by remember { mutableStateOf("<no greeting>") }
SettingsPage(
navController,
title = {
Text(
text = buildAnnotatedString {
pushStyle(SpanStyle(fontFamily = FragmentMono))
append("librevolt")
pop()
append(" Sample")
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
) {
Button(onClick = {
greeting = librevolt.greet()
}) {
Text("Greet")
}
Text(greeting)
}
}

View File

@ -0,0 +1,68 @@
package chat.revolt.screens.labs.ui.sandbox
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.revolt.components.generic.GradientStopEditor
import chat.revolt.settings.dsl.SettingsPage
@Composable
fun GradientEditorSandbox(navController: NavController) {
var segmentCount by remember { mutableIntStateOf(10) }
SettingsPage(
navController,
title = {
Text(
text = "Gradient Editor Sandbox",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
TextButton(
onClick = {
if (segmentCount >= 2)
segmentCount--
else showSnackbar("Minimum of 2 segments required.")
},
modifier = Modifier.weight(1f)
) {
Text("-")
}
Text(
"$segmentCount segments",
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center
)
TextButton(
onClick = { segmentCount++ },
modifier = Modifier.weight(1f)
) {
Text("+")
}
}
GradientStopEditor(
segments = segmentCount,
stops = emptyList(),
onStopsChanged = {},
_onClickSegment = { showSnackbar("Clicked segment $it") },
modifier = Modifier.height(24.dp)
)
}
}

View File

@ -15,9 +15,13 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
@ -26,11 +30,14 @@ import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.components.generic.ListHeader
import chat.revolt.components.generic.RadioItem
import kotlinx.coroutines.launch
import kotlin.enums.EnumEntries
val SubcategoryContentInsets = PaddingValues(horizontal = 16.dp)
interface SettingsPageScope {
fun showSnackbar(message: String)
@Composable
fun Subcategory(
title: @Composable () -> Unit,
@ -84,7 +91,9 @@ fun SettingsPage(
title: @Composable () -> Unit,
content: @Composable SettingsPageScope.() -> Unit
) {
val snackbarHostState = remember { SnackbarHostState() }
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val scope = rememberCoroutineScope()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@ -106,6 +115,9 @@ fun SettingsPage(
},
)
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
) { pv ->
Column(
Modifier
@ -115,7 +127,13 @@ fun SettingsPage(
.verticalScroll(rememberScrollState())
) {
content(
object : SettingsPageScope {}
object : SettingsPageScope {
override fun showSnackbar(message: String) {
scope.launch {
snackbarHostState.showSnackbar(message)
}
}
}
)
}
}

View File

@ -21,6 +21,10 @@ dependencyResolutionManagement {
name = "compose-dev"
url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
maven {
name = "revolt"
url = uri("https://git.revolt.chat/api/packages/librevolt/maven")
}
}
}
rootProject.name = "Revolt"