feat: routine housekeeping

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-12-16 03:34:30 +01:00
parent b68267c1e7
commit 9e003897a5
13 changed files with 565 additions and 1 deletions

View File

@ -33,6 +33,7 @@ import chat.revolt.screens.about.AboutScreen
import chat.revolt.screens.about.AttributionScreen
import chat.revolt.screens.chat.ChatRouterScreen
import chat.revolt.screens.chat.dialogs.FeedbackDialog
import chat.revolt.screens.labs.LabsRootScreen
import chat.revolt.screens.login.LoginGreetingScreen
import chat.revolt.screens.login.LoginScreen
import chat.revolt.screens.login.MfaScreen
@ -159,6 +160,8 @@ fun AppEntrypoint(windowSizeClass: WindowSizeClass) {
composable("about") { AboutScreen(navController) }
composable("about/oss") { AttributionScreen(navController) }
composable("labs") { LabsRootScreen(navController) }
}
}
}

View File

@ -4,12 +4,14 @@ import android.content.Context
import android.graphics.RuntimeShader
import android.os.Build
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush as AndroidBrush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ShaderBrush
import org.intellij.lang.annotations.Language
import androidx.compose.ui.graphics.Brush as AndroidBrush
object SpecialUsers {
val JENNIFER = "01F1WKM5TK2V6KCZWR6DGBJDTZ"
val PLATFORM_MODERATION_USER = "01FC17E1WTM2BGE4F3ARN3FDAF"
val TRUSTED_MODERATION_BOTS = listOf(

View File

@ -4,6 +4,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import chat.revolt.api.RevoltAPI
import chat.revolt.api.internals.SpecialUsers
annotation class FeatureFlag(val name: String)
annotation class Treatment(val description: String)
@ -19,6 +20,14 @@ sealed class ClosedBetaAccessControlVariates {
data object Unrestricted : ClosedBetaAccessControlVariates()
}
@FeatureFlag("LabsAccessControl")
sealed class LabsAccessControlVariates {
@Treatment(
"Restrict access to Labs to users that meet certain or all criteria (implementation-specific)"
)
data class Restricted(val predicate: () -> Boolean) : LabsAccessControlVariates()
}
object FeatureFlags {
@FeatureFlag("ClosedBetaAccessControl")
var closedBetaAccessControl by mutableStateOf<ClosedBetaAccessControlVariates>(
@ -26,4 +35,11 @@ object FeatureFlags {
RevoltAPI.channelCache.containsKey("01H7X2KRB0CA4QDSMB4N7WGERF")
}
)
@FeatureFlag("LabsAccessControl")
var labsAccessControl by mutableStateOf<LabsAccessControlVariates>(
LabsAccessControlVariates.Restricted {
RevoltAPI.selfId == SpecialUsers.JENNIFER
}
)
}

View File

@ -0,0 +1,134 @@
package chat.revolt.screens.labs
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
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.filled.Home
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
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.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
enum class LabsHomeScreenTab {
Home,
Mockups,
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LabsHomeScreen(navController: NavController) {
val currentTab = rememberSaveable { mutableStateOf(LabsHomeScreenTab.Home) }
Scaffold(
topBar = {
TopAppBar(
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(),
title = {
Text("Labs")
}
)
},
bottomBar = {
NavigationBar {
NavigationBarItem(
selected = currentTab.value == LabsHomeScreenTab.Home,
onClick = { currentTab.value = LabsHomeScreenTab.Home },
icon = {
Icon(
imageVector = Icons.Default.Home,
contentDescription = null,
)
},
label = {
Text("Home")
}
)
NavigationBarItem(
selected = currentTab.value == LabsHomeScreenTab.Mockups,
onClick = { currentTab.value = LabsHomeScreenTab.Mockups },
icon = {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = null,
)
},
label = {
Text("UI Mockups")
}
)
}
}
) {
Box(Modifier.padding(it)) {
when (currentTab.value) {
LabsHomeScreenTab.Home -> {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
"Hey, this is kinda secret 🤫",
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center
)
Spacer(Modifier.height(8.dp))
Text(
"Remember, everything you see here can be broken and is not guaranteed to work.",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center
)
Spacer(Modifier.height(8.dp))
Text(
"Don't tell anyone about anything either, okay?",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center
)
}
}
LabsHomeScreenTab.Mockups -> {
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
ListItem(
headlineContent = {
Text("Call Screen")
},
modifier = Modifier.clickable {
navController.navigate("mockups/call")
}
)
Divider()
}
}
}
}
}
}

View File

@ -0,0 +1,71 @@
package chat.revolt.screens.labs
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import chat.revolt.api.settings.FeatureFlags
import chat.revolt.api.settings.LabsAccessControlVariates
import chat.revolt.screens.labs.ui.mockups.CallScreenMockup
annotation class LabsFeature
@Composable
fun LabsGuard(onTurnBack: () -> Unit = {}, content: @Composable () -> Unit) {
if (FeatureFlags.labsAccessControl is LabsAccessControlVariates.Restricted &&
(FeatureFlags.labsAccessControl as LabsAccessControlVariates.Restricted).predicate().not()
) {
AlertDialog(
onDismissRequest = { onTurnBack() },
confirmButton = {
TextButton(onClick = { onTurnBack() }) {
Text("Turn back")
}
},
title = {
Text("You don't have access to Labs.")
},
text = {
Text("Labs is where we test new features. However, these features may be unstable and may not work as expected. Hence, access to Labs is restricted.")
}
)
} else {
content()
}
}
@Composable
fun LabsRootScreen(topNav: NavController) {
val labsNav = rememberNavController()
Column(
modifier = Modifier
.fillMaxSize()
) {
LabsGuard(
onTurnBack = {
topNav.popBackStack()
}
) {
NavHost(
navController = labsNav,
startDestination = "home",
) {
composable("home") {
LabsHomeScreen(labsNav)
}
composable("mockups/call") {
CallScreenMockup()
}
}
}
}
}

View File

@ -0,0 +1,262 @@
package chat.revolt.screens.labs.ui.mockups
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.EaseInOutExpo
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import chat.revolt.R
import chat.revolt.api.schemas.ChannelType
import chat.revolt.components.screens.chat.ChannelIcon
import chat.revolt.screens.labs.LabsFeature
@LabsFeature
@Composable
fun CallScreenMockup() {
var showOptions by remember { mutableStateOf(false) }
var pushToTalk by remember { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }
val pushToTalkIsHeld by interactionSource.collectIsPressedAsState()
val pttBackground by animateColorAsState(
targetValue = if (pushToTalkIsHeld) {
MaterialTheme.colorScheme.primaryContainer
} else {
MaterialTheme.colorScheme.secondaryContainer
},
animationSpec = tween(200, easing = EaseInOutExpo),
label = "pttBackground"
)
val pttText by animateColorAsState(
targetValue = if (pushToTalkIsHeld) {
MaterialTheme.colorScheme.onPrimaryContainer
} else {
MaterialTheme.colorScheme.onSecondaryContainer
},
animationSpec = tween(200, easing = EaseInOutExpo),
label = "pttText"
)
if (showOptions) {
Dialog(
onDismissRequest = { showOptions = false }
) {
BoxWithConstraints {
Column(
modifier = Modifier
.clip(MaterialTheme.shapes.large)
.background(MaterialTheme.colorScheme.surface)
.padding(24.dp)
.width(maxWidth * 0.85f)
.heightIn(max = maxHeight * 0.85f)
) {
Row {
Checkbox(
checked = pushToTalk,
onCheckedChange = { pushToTalk = it },
modifier = Modifier
.padding(16.dp)
)
Text(
text = "Push to talk",
modifier = Modifier
.padding(16.dp)
)
}
}
}
}
}
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Row(
modifier = Modifier
.weight(1f)
.padding(vertical = 4.dp, horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(
modifier = Modifier
.height(48.dp)
.width(12.dp)
)
ChannelIcon(
channelType = ChannelType.VoiceChannel,
modifier = Modifier.alpha(0.6f)
)
Spacer(modifier = Modifier.width(8.dp))
Row(
modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Voice Channel",
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.width(4.dp))
Icon(
imageVector = Icons.Default.KeyboardArrowRight,
contentDescription = stringResource(R.string.menu),
modifier = Modifier
.size(18.dp)
.alpha(0.4f)
)
}
}
IconButton(onClick = {
showOptions = true
}) {
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null
)
}
Spacer(modifier = Modifier.width(4.dp))
}
Column(
modifier = Modifier
.weight(1f)
) {
}
if (pushToTalk) {
Row(
modifier = Modifier
.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
TextButton(
onClick = {},
) {
Icon(
painter = painterResource(R.drawable.ic_headphones_24dp),
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Galaxy Buds Live",
)
}
}
Row(
modifier = Modifier
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Button(
onClick = {},
colors = ButtonDefaults.buttonColors(
containerColor = pttBackground,
contentColor = pttText
),
interactionSource = interactionSource,
modifier = Modifier
.weight(1f)
) {
Icon(
painter = painterResource(R.drawable.ic_gesture_tap_button_24dp),
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Hold to talk",
)
}
}
}
Row(
modifier = Modifier
.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
if (!pushToTalk) {
Button(
onClick = {}
) {
Icon(
painter = painterResource(R.drawable.ic_microphone_off_24dp),
contentDescription = null
)
}
}
Button(
onClick = {},
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.onErrorContainer
),
modifier = Modifier
.weight(1f)
) {
Text(
text = "Leave call",
)
}
if (!pushToTalk) {
Button(
onClick = {}
) {
Icon(
painter = painterResource(R.drawable.ic_headphones_24dp),
contentDescription = null
)
}
}
}
}
}

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.DateRange
@ -27,7 +28,9 @@ import androidx.navigation.NavController
import chat.revolt.BuildConfig
import chat.revolt.R
import chat.revolt.api.RevoltAPI
import chat.revolt.api.settings.FeatureFlags
import chat.revolt.api.settings.GlobalState
import chat.revolt.api.settings.LabsAccessControlVariates
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.SheetClickable
import chat.revolt.components.screens.settings.SelfUserOverview
@ -189,6 +192,25 @@ fun SettingsScreen(
}
}
if (FeatureFlags.labsAccessControl is LabsAccessControlVariates.Restricted &&
(FeatureFlags.labsAccessControl as LabsAccessControlVariates.Restricted).predicate()
) {
SheetClickable(
icon = { modifier ->
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null,
modifier = modifier
)
},
label = { textStyle ->
Text(text = "Labs", style = textStyle)
},
) {
navController.navigate("labs")
}
}
SheetClickable(
icon = { modifier ->
Icon(

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M13 5C15.21 5 17 6.79 17 9C17 10.5 16.2 11.77 15 12.46V11.24C15.61 10.69 16 9.89 16 9C16 7.34 14.66 6 13 6S10 7.34 10 9C10 9.89 10.39 10.69 11 11.24V12.46C9.8 11.77 9 10.5 9 9C9 6.79 10.79 5 13 5M20 20.5C19.97 21.32 19.32 21.97 18.5 22H13C12.62 22 12.26 21.85 12 21.57L8 17.37L8.74 16.6C8.93 16.39 9.2 16.28 9.5 16.28H9.7L12 18V9C12 8.45 12.45 8 13 8S14 8.45 14 9V13.47L15.21 13.6L19.15 15.79C19.68 16.03 20 16.56 20 17.14V20.5M20 2H4C2.9 2 2 2.9 2 4V12C2 13.11 2.9 14 4 14H8V12L4 12L4 4H20L20 12H18V14H20V13.96L20.04 14C21.13 14 22 13.09 22 12V4C22 2.9 21.11 2 20 2Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M12,1C7,1 3,5 3,10V17A3,3 0 0,0 6,20H9V12H5V10A7,7 0 0,1 12,3A7,7 0 0,1 19,10V12H15V20H18A3,3 0 0,0 21,17V10C21,5 16.97,1 12,1Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M12,1A9,9 0 0,1 21,10V17C21,17.62 20.81,18.19 20.5,18.67L15,13.18V12H19V10A7,7 0 0,0 12,3C10,3 8.23,3.82 6.96,5.14L5.55,3.72C7.18,2.04 9.47,1 12,1M2.78,3.5L20.5,21.22L19.23,22.5L16.73,20H15V18.27L9,12.27V20H6A3,3 0 0,1 3,17V10C3,8.89 3.2,7.82 3.57,6.84L1.5,4.77L2.78,3.5M5.17,8.44C5.06,8.94 5,9.46 5,10V12H8.73L5.17,8.44Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M12,2A3,3 0 0,1 15,5V11A3,3 0 0,1 12,14A3,3 0 0,1 9,11V5A3,3 0 0,1 12,2M19,11C19,14.53 16.39,17.44 13,17.93V21H11V17.93C7.61,17.44 5,14.53 5,11H7A5,5 0 0,0 12,16A5,5 0 0,0 17,11H19Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M19,11C19,12.19 18.66,13.3 18.1,14.28L16.87,13.05C17.14,12.43 17.3,11.74 17.3,11H19M15,11.16L9,5.18V5A3,3 0 0,1 12,2A3,3 0 0,1 15,5V11L15,11.16M4.27,3L21,19.73L19.73,21L15.54,16.81C14.77,17.27 13.91,17.58 13,17.72V21H11V17.72C7.72,17.23 5,14.41 5,11H6.7C6.7,14 9.24,16.1 12,16.1C12.81,16.1 13.6,15.91 14.31,15.58L12.65,13.92L12,14A3,3 0 0,1 9,11V10.28L3,4.27L4.27,3Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M12,9C10.4,9 8.85,9.25 7.4,9.72V12.82C7.4,13.22 7.17,13.56 6.84,13.72C5.86,14.21 4.97,14.84 4.17,15.57C4,15.75 3.75,15.86 3.5,15.86C3.2,15.86 2.95,15.74 2.77,15.56L0.29,13.08C0.11,12.9 0,12.65 0,12.38C0,12.1 0.11,11.85 0.29,11.67C3.34,8.77 7.46,7 12,7C16.54,7 20.66,8.77 23.71,11.67C23.89,11.85 24,12.1 24,12.38C24,12.65 23.89,12.9 23.71,13.08L21.23,15.56C21.05,15.74 20.8,15.86 20.5,15.86C20.25,15.86 20,15.75 19.82,15.57C19.03,14.84 18.14,14.21 17.16,13.72C16.83,13.56 16.6,13.22 16.6,12.82V9.72C15.15,9.25 13.6,9 12,9Z" />
</vector>