From 6918f7b9fcf3eb9383aa7c729a9cddc1c23e0145 Mon Sep 17 00:00:00 2001 From: Infi Date: Sun, 23 Feb 2025 16:20:05 +0100 Subject: [PATCH] feat(labs): new sandboxes Signed-off-by: Infi --- app/build.gradle.kts | 14 +++- .../components/generic/GradientStopEditor.kt | 57 ++++++++++++++++ .../revolt/screens/labs/LabsHomeScreen.kt | 40 ++++++++++- .../revolt/screens/labs/LabsRootScreen.kt | 10 ++- .../screens/labs/ui/sandbox/CoreLibSandbox.kt | 43 ++++++++++++ .../labs/ui/sandbox/GradientEditorSandbox.kt | 68 +++++++++++++++++++ .../chat/revolt/settings/dsl/SettingsPage.kt | 20 +++++- settings.gradle.kts | 4 ++ 8 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/chat/revolt/components/generic/GradientStopEditor.kt create mode 100644 app/src/main/java/chat/revolt/screens/labs/ui/sandbox/CoreLibSandbox.kt create mode 100644 app/src/main/java/chat/revolt/screens/labs/ui/sandbox/GradientEditorSandbox.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fd79b140..f71bc04a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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" } diff --git a/app/src/main/java/chat/revolt/components/generic/GradientStopEditor.kt b/app/src/main/java/chat/revolt/components/generic/GradientStopEditor.kt new file mode 100644 index 00000000..f3ab159c --- /dev/null +++ b/app/src/main/java/chat/revolt/components/generic/GradientStopEditor.kt @@ -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>, + onStopsChanged: (List>) -> 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, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/labs/LabsHomeScreen.kt b/app/src/main/java/chat/revolt/screens/labs/LabsHomeScreen.kt index 414ed204..567a4910 100644 --- a/app/src/main/java/chat/revolt/screens/labs/LabsHomeScreen.kt +++ b/app/src/main/java/chat/revolt/screens/labs/LabsHomeScreen.kt @@ -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() } } } diff --git a/app/src/main/java/chat/revolt/screens/labs/LabsRootScreen.kt b/app/src/main/java/chat/revolt/screens/labs/LabsRootScreen.kt index 497fe782..bcaf0b6f 100644 --- a/app/src/main/java/chat/revolt/screens/labs/LabsRootScreen.kt +++ b/app/src/main/java/chat/revolt/screens/labs/LabsRootScreen.kt @@ -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) + } } } } diff --git a/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/CoreLibSandbox.kt b/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/CoreLibSandbox.kt new file mode 100644 index 00000000..ddee7d68 --- /dev/null +++ b/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/CoreLibSandbox.kt @@ -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("") } + + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/GradientEditorSandbox.kt b/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/GradientEditorSandbox.kt new file mode 100644 index 00000000..af014f73 --- /dev/null +++ b/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/GradientEditorSandbox.kt @@ -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) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/settings/dsl/SettingsPage.kt b/app/src/main/java/chat/revolt/settings/dsl/SettingsPage.kt index 50aacc8c..334d58e4 100644 --- a/app/src/main/java/chat/revolt/settings/dsl/SettingsPage.kt +++ b/app/src/main/java/chat/revolt/settings/dsl/SettingsPage.kt @@ -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) + } + } + } ) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0c23441f..88c92ff9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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"