feat(jbm): remove jbm switches on every message
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
f4085c1c93
commit
f6d9800f15
|
|
@ -42,10 +42,11 @@ import chat.revolt.RevoltApplication
|
|||
import chat.revolt.api.HitRateLimitException
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.api.api
|
||||
import chat.revolt.api.routes.onboard.needsOnboarding
|
||||
import chat.revolt.api.settings.Experiments
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.api.settings.SyncedSettings
|
||||
import chat.revolt.api.api
|
||||
import chat.revolt.ndk.NativeLibraries
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import chat.revolt.screens.DefaultDestinationScreen
|
||||
|
|
@ -67,6 +68,7 @@ import chat.revolt.screens.settings.AppearanceSettingsScreen
|
|||
import chat.revolt.screens.settings.ChangelogsSettingsScreen
|
||||
import chat.revolt.screens.settings.ChatSettingsScreen
|
||||
import chat.revolt.screens.settings.DebugSettingsScreen
|
||||
import chat.revolt.screens.settings.ExperimentsSettingsScreen
|
||||
import chat.revolt.screens.settings.ProfileSettingsScreen
|
||||
import chat.revolt.screens.settings.SessionSettingsScreen
|
||||
import chat.revolt.screens.settings.SettingsScreen
|
||||
|
|
@ -125,6 +127,14 @@ class MainActivityViewModel @Inject constructor(
|
|||
isReady.emit(true)
|
||||
}
|
||||
|
||||
private fun doPreStartupTasks() {
|
||||
Log.d("MainActivity", "Performing pre-startup tasks")
|
||||
viewModelScope.launch {
|
||||
Log.d("MainActivity", "Hydrating Experiments from KV")
|
||||
Experiments.hydrateWithKv()
|
||||
}
|
||||
}
|
||||
|
||||
fun checkLoggedInState() {
|
||||
viewModelScope.launch {
|
||||
Log.d("MainActivity", "Checking logged in state")
|
||||
|
|
@ -211,6 +221,7 @@ class MainActivityViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
Log.d("MainActivity", "Starting up")
|
||||
doPreStartupTasks()
|
||||
checkLoggedInState()
|
||||
}
|
||||
}
|
||||
|
|
@ -400,6 +411,7 @@ fun AppEntrypoint(
|
|||
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
|
||||
composable("settings/chat") { ChatSettingsScreen(navController) }
|
||||
composable("settings/debug") { DebugSettingsScreen(navController) }
|
||||
composable("settings/experiments") { ExperimentsSettingsScreen(navController) }
|
||||
composable("settings/changelogs") { ChangelogsSettingsScreen(navController) }
|
||||
|
||||
composable("settings/channel/{channelId}") { backStackEntry ->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package chat.revolt.api.settings
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import chat.revolt.RevoltApplication
|
||||
import chat.revolt.persistence.KVStorage
|
||||
|
||||
class ExperimentInstance(default: Boolean) {
|
||||
private var _isEnabled by mutableStateOf(default)
|
||||
val isEnabled: Boolean
|
||||
get() = GlobalState.experimentsEnabled && _isEnabled
|
||||
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
_isEnabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Experiments are boolean feature flags that can be toggled by the user in a self-service manner.
|
||||
* Unlike regular feature flags they are created with the goal of going live in the future.
|
||||
* They come with multiple safeguards:
|
||||
* - Users must first enable experiments in the settings by performing a hidden action. They are then warned about potential instability.
|
||||
* - Experiment states are not persisted across devices or uninstalls.
|
||||
* - All experiments can be disabled at once with a single toggle.
|
||||
*/
|
||||
object Experiments {
|
||||
val useKotlinBasedMarkdownRenderer = ExperimentInstance(false)
|
||||
|
||||
suspend fun hydrateWithKv() {
|
||||
val kvStorage = KVStorage(RevoltApplication.instance)
|
||||
|
||||
GlobalState.experimentsEnabled = kvStorage.getBoolean("experimentsEnabled") ?: false
|
||||
|
||||
useKotlinBasedMarkdownRenderer.setEnabled(
|
||||
kvStorage.getBoolean("exp/useKotlinBasedMarkdownRenderer") ?: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ object GlobalState {
|
|||
var theme by mutableStateOf(getDefaultTheme())
|
||||
var messageReplyStyle by mutableStateOf(MessageReplyStyle.SwipeFromEnd)
|
||||
var avatarRadius by mutableIntStateOf(50)
|
||||
var experimentsEnabled by mutableStateOf(false)
|
||||
|
||||
fun hydrateWithSettings(settings: SyncedSettings) {
|
||||
this.theme = settings.android.theme?.let { Theme.valueOf(it) } ?: getDefaultTheme()
|
||||
|
|
|
|||
|
|
@ -34,17 +34,13 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
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.clip
|
||||
|
|
@ -73,6 +69,7 @@ import chat.revolt.api.routes.channel.unreact
|
|||
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
||||
import chat.revolt.api.schemas.AutumnResource
|
||||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.api.settings.Experiments
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.api.settings.MessageReplyStyle
|
||||
import chat.revolt.callbacks.Action
|
||||
|
|
@ -199,8 +196,6 @@ fun Message(
|
|||
|
||||
val authorIsBlocked = remember(author) { author.relationship == "Blocked" }
|
||||
|
||||
var __TEMPORARY_useJbm by remember { mutableStateOf(false) }
|
||||
|
||||
Column(Modifier.animateContentSize()) {
|
||||
if (message.tail == false) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
|
@ -370,12 +365,21 @@ fun Message(
|
|||
message.content?.let {
|
||||
if (message.content.isBlank()) return@let // if only an attachment is sent
|
||||
|
||||
Switch(
|
||||
checked = __TEMPORARY_useJbm,
|
||||
onCheckedChange = { __TEMPORARY_useJbm = it },
|
||||
)
|
||||
|
||||
if (__TEMPORARY_useJbm == false) {
|
||||
if (Experiments.useKotlinBasedMarkdownRenderer.isEnabled) {
|
||||
CompositionLocalProvider(
|
||||
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
|
||||
fontSizeMultiplier = Gigamoji.useGigamojiForMessage(
|
||||
message.content
|
||||
)
|
||||
.let {
|
||||
if (it) 2f else 1f
|
||||
}
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
JBMRenderer(message.content)
|
||||
}
|
||||
} else {
|
||||
CompositionLocalProvider(
|
||||
LocalMarkdownTreeConfig provides LocalMarkdownTreeConfig.current.copy(
|
||||
currentServer = RevoltAPI.channelCache[message.channel]?.server,
|
||||
|
|
@ -390,20 +394,6 @@ fun Message(
|
|||
Spacer(modifier = Modifier.height(2.dp))
|
||||
RichMarkdown(input = message.content)
|
||||
}
|
||||
} else {
|
||||
CompositionLocalProvider(
|
||||
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
|
||||
fontSizeMultiplier = Gigamoji.useGigamojiForMessage(
|
||||
message.content
|
||||
)
|
||||
.let {
|
||||
if (it) 2f else 1f
|
||||
}
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
JBMRenderer(message.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package chat.revolt.screens.settings
|
||||
|
||||
import androidx.compose.material3.ElevatedButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.api.settings.Experiments
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import chat.revolt.settings.dsl.SettingsPage
|
||||
import chat.revolt.settings.dsl.SubcategoryContentInsets
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ExperimentsSettingsScreen(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
val kv = remember { KVStorage(context) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var useKotlinMdRendererChecked by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
useKotlinMdRendererChecked = kv.getBoolean("exp/useKotlinBasedMarkdownRenderer") ?: false
|
||||
}
|
||||
|
||||
SettingsPage(
|
||||
navController,
|
||||
title = {
|
||||
Text("Experiments", maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text("New Message Markdown Renderer")
|
||||
},
|
||||
supportingContent = {
|
||||
Text("Use a Kotlin-based Markdown renderer for messages rather than the C++ one. Missing features may be present.")
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = useKotlinMdRendererChecked,
|
||||
onCheckedChange = { isChecked ->
|
||||
scope.launch {
|
||||
kv.set("exp/useKotlinBasedMarkdownRenderer", isChecked)
|
||||
Experiments.useKotlinBasedMarkdownRenderer.setEnabled(isChecked)
|
||||
useKotlinMdRendererChecked = isChecked
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Subcategory(
|
||||
title = {
|
||||
Text("Disable experiments")
|
||||
},
|
||||
contentInsets = SubcategoryContentInsets
|
||||
) {
|
||||
ElevatedButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
kv.remove("experimentsEnabled")
|
||||
GlobalState.experimentsEnabled = false
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text("Disable")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -261,6 +261,27 @@ fun SettingsScreen(
|
|||
)
|
||||
}
|
||||
|
||||
if (GlobalState.experimentsEnabled) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = "Experiments"
|
||||
)
|
||||
},
|
||||
leadingContent = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_flask_24dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.testTag("settings_view_experiments")
|
||||
.clickable {
|
||||
navController.navigate("settings/experiments")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ListHeader {
|
||||
Text(
|
||||
stringResource(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package chat.revolt.sheets
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -12,22 +14,28 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
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.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.PlatformTextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
|
@ -37,15 +45,18 @@ import androidx.compose.ui.unit.sp
|
|||
import chat.revolt.R
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.internals.text.MessageProcessor
|
||||
import chat.revolt.api.internals.isUlid
|
||||
import chat.revolt.api.routes.custom.fetchEmoji
|
||||
import chat.revolt.api.routes.user.fetchUser
|
||||
import chat.revolt.api.schemas.Emoji
|
||||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.components.chat.MemberListItem
|
||||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.components.generic.SheetEnd
|
||||
import chat.revolt.internals.text.MessageProcessor
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
|
|
@ -55,6 +66,9 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) {
|
|||
val reactions = message.reactions
|
||||
val reactionEmoji = reactions?.keys?.toList()
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val extendedEmojiInfo = remember(emoji) { mutableStateListOf<Emoji>() }
|
||||
|
||||
LaunchedEffect(reactionEmoji) {
|
||||
|
|
@ -120,6 +134,71 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) {
|
|||
item("info") {
|
||||
val current = reactionEmoji[selectedReactionIndex]
|
||||
|
||||
// Code related to enabling of experimental features
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val canBeUsedForTapCountIncrement =
|
||||
remember(selectedReactionIndex) {
|
||||
MessageProcessor.emoji.unicodeAsShortcode(
|
||||
current
|
||||
) == ":trolleybus:"
|
||||
}
|
||||
var tapCount by remember { mutableIntStateOf(0) }
|
||||
var showEnabledConfirmAlert by remember { mutableStateOf(false) }
|
||||
var showEnabledAlreadyAlert by remember { mutableStateOf(false) }
|
||||
val incrementTapCount = remember {
|
||||
{
|
||||
if (canBeUsedForTapCountIncrement) {
|
||||
tapCount++
|
||||
if (tapCount > 9) {
|
||||
tapCount = 0
|
||||
if (GlobalState.experimentsEnabled) {
|
||||
showEnabledAlreadyAlert = true
|
||||
} else {
|
||||
showEnabledConfirmAlert = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showEnabledAlreadyAlert) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
title = { Text("Traveller, you may not unsee your knowledge...") },
|
||||
text = { Text("Experimental features are already unlocked.") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showEnabledAlreadyAlert = false }) {
|
||||
Text("OK")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showEnabledConfirmAlert) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
title = { Text("You hear a faint whisper in the wind...") },
|
||||
text = { Text("Would you like to enable experimental features? They may be unstable.") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showEnabledConfirmAlert = false
|
||||
GlobalState.experimentsEnabled = true
|
||||
scope.launch {
|
||||
KVStorage(context).set("experimentsEnabled", true)
|
||||
}
|
||||
}) {
|
||||
Text("I dare to try!")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(onClick = { showEnabledConfirmAlert = false }) {
|
||||
Text("I shall not risk it.")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
// End of code related to enabling of experimental features
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.padding(
|
||||
|
|
@ -158,6 +237,12 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) {
|
|||
)
|
||||
),
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
) {
|
||||
incrementTapCount()
|
||||
}
|
||||
.size(64.dp)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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="M6,22A3,3 0 0,1 3,19C3,18.4 3.18,17.84 3.5,17.37L9,7.81V6A1,1 0 0,1 8,5V4A2,2 0 0,1 10,2H14A2,2 0 0,1 16,4V5A1,1 0 0,1 15,6V7.81L20.5,17.37C20.82,17.84 21,18.4 21,19A3,3 0 0,1 18,22H6M5,19A1,1 0 0,0 6,20H18A1,1 0 0,0 19,19C19,18.79 18.93,18.59 18.82,18.43L16.53,14.47L14,17L8.93,11.93L5.18,18.43C5.07,18.59 5,18.79 5,19M13,10A1,1 0 0,0 12,11A1,1 0 0,0 13,12A1,1 0 0,0 14,11A1,1 0 0,0 13,10Z" />
|
||||
</vector>
|
||||
Loading…
Reference in New Issue