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 // Square Logcat
implementation("com.squareup.logcat:logcat:0.1") implementation("com.squareup.logcat:logcat:0.1")
// Librevolt
implementation("librevolt:librevolt-jvm:0.1.0")
// Testing // Testing
androidTestImplementation("androidx.test:runner:$androidXTestVersion") androidTestImplementation("androidx.test:runner:$androidXTestVersion")
androidTestImplementation("androidx.test:rules:$androidXTestVersion") androidTestImplementation("androidx.test:rules:$androidXTestVersion")
@ -315,7 +318,16 @@ aboutLibraries {
additionalLicenses += listOf("ofl") additionalLicenses += listOf("ofl")
includePlatform = true includePlatform = true
strictMode = StrictMode.FAIL 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" 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.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons 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.Home
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
@ -30,9 +32,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.ui.theme.FragmentMono
enum class LabsHomeScreenTab { enum class LabsHomeScreenTab {
Home, Home,
@ -42,7 +47,7 @@ enum class LabsHomeScreenTab {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun LabsHomeScreen(navController: NavController) { fun LabsHomeScreen(navController: NavController, topNav: NavController) {
val currentTab = rememberSaveable { mutableStateOf(LabsHomeScreenTab.Home) } val currentTab = rememberSaveable { mutableStateOf(LabsHomeScreenTab.Home) }
Scaffold( Scaffold(
@ -51,6 +56,16 @@ fun LabsHomeScreen(navController: NavController) {
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(),
title = { title = {
Text("Labs") Text("Labs")
},
navigationIcon = {
IconButton(onClick = {
topNav.popBackStack()
}) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = "Back"
)
}
} }
) )
}, },
@ -175,6 +190,29 @@ fun LabsHomeScreen(navController: NavController) {
} }
) )
HorizontalDivider() 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.api.settings.FeatureFlags
import chat.revolt.screens.labs.ui.mockups.CallScreenMockup import chat.revolt.screens.labs.ui.mockups.CallScreenMockup
import chat.revolt.screens.labs.ui.mockups.NewLoginExperienceMockup 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.JBMSandbox
import chat.revolt.screens.labs.ui.sandbox.SettingsDslSandbox import chat.revolt.screens.labs.ui.sandbox.SettingsDslSandbox
@ -59,7 +61,7 @@ fun LabsRootScreen(topNav: NavController) {
startDestination = "home", startDestination = "home",
) { ) {
composable("home") { composable("home") {
LabsHomeScreen(labsNav) LabsHomeScreen(labsNav, topNav)
} }
composable("mockups/call") { composable("mockups/call") {
@ -75,6 +77,12 @@ fun LabsRootScreen(topNav: NavController) {
composable("sandboxes/jbm") { composable("sandboxes/jbm") {
JBMSandbox(labsNav) 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.IconButton
import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -26,11 +30,14 @@ import androidx.navigation.NavController
import chat.revolt.R import chat.revolt.R
import chat.revolt.components.generic.ListHeader import chat.revolt.components.generic.ListHeader
import chat.revolt.components.generic.RadioItem import chat.revolt.components.generic.RadioItem
import kotlinx.coroutines.launch
import kotlin.enums.EnumEntries import kotlin.enums.EnumEntries
val SubcategoryContentInsets = PaddingValues(horizontal = 16.dp) val SubcategoryContentInsets = PaddingValues(horizontal = 16.dp)
interface SettingsPageScope { interface SettingsPageScope {
fun showSnackbar(message: String)
@Composable @Composable
fun Subcategory( fun Subcategory(
title: @Composable () -> Unit, title: @Composable () -> Unit,
@ -84,7 +91,9 @@ fun SettingsPage(
title: @Composable () -> Unit, title: @Composable () -> Unit,
content: @Composable SettingsPageScope.() -> Unit content: @Composable SettingsPageScope.() -> Unit
) { ) {
val snackbarHostState = remember { SnackbarHostState() }
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val scope = rememberCoroutineScope()
Scaffold( Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@ -106,6 +115,9 @@ fun SettingsPage(
}, },
) )
}, },
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
) { pv -> ) { pv ->
Column( Column(
Modifier Modifier
@ -115,7 +127,13 @@ fun SettingsPage(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
content( 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" name = "compose-dev"
url = uri("https://maven.pkg.jetbrains.space/public/p/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" rootProject.name = "Revolt"