feat: changelog system
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
3a5098ecf1
commit
e625297718
|
|
@ -0,0 +1,90 @@
|
||||||
|
# 
|
||||||
|
|
||||||
|
## Welcome Beta Ring II 🎉
|
||||||
|
|
||||||
|
For many of you, this is the first time you're seeing this app. Welcome to the second beta ring!
|
||||||
|
You'll be helping us test the app and find bugs before we release it to the general public. Everyone
|
||||||
|
is so excited to have you here!
|
||||||
|
|
||||||
|
Crashes are expected, and we're working hard to fix them. If you find a crash, it will be reported
|
||||||
|
automatically. If you have anything else to say, use the **channels on Jenvolt** or the **Feedback**
|
||||||
|
button in settings.
|
||||||
|
|
||||||
|
## Server Identities are here
|
||||||
|
|
||||||
|
Server identities and role colours will now be shown across the app. This should help make the app
|
||||||
|
feel a lot more like the web client, which is our compatibility target. Servers look a lot closer
|
||||||
|
now!
|
||||||
|
|
||||||
|
We'll still exploring how to best show these identities in user sheets, but for now, they're only
|
||||||
|
shown inline in chat and in the member list.
|
||||||
|
|
||||||
|
## Changelogs
|
||||||
|
|
||||||
|
You're reading one right now! New releases will now come with changelogs, so you can see what's
|
||||||
|
been cooking in the kitchen. We'll also be posting these on the website after general availability,
|
||||||
|
so feel free to check them out there too.
|
||||||
|
|
||||||
|
## Updated to Android 14 SDK
|
||||||
|
|
||||||
|
We've updated the app to use the latest Android SDK, which is Android 14. This means we're now
|
||||||
|
ahead of the actual Android release schedule! 14 is still in beta, but the SDK is stable by now.
|
||||||
|
|
||||||
|
This will help us keep the
|
||||||
|
app [in the Play Store](https://support.google.com/googleplay/android-developer/answer/11926878?hl=en)
|
||||||
|
for longer, and it also means we can use the latest and greatest APIs. As always, the app's aim is
|
||||||
|
to make use of the Android platform as much as possible, and being on the newest SDK helps us do
|
||||||
|
that.
|
||||||
|
|
||||||
|
## Extended Markdown in Bios and Changelogs
|
||||||
|
|
||||||
|
There is now a separate **web-based** Markdown renderer, currently in use on user bios and
|
||||||
|
changelogs.
|
||||||
|
This renderer is more powerful than the one we use in chat, and it's also more accurate to the web
|
||||||
|
client. It is a little slower, but we're working on that. The aim will be to instantly render
|
||||||
|
Markdown in it, however the current placeholder implementation works alright. Look forward to KaTeX
|
||||||
|
and more!
|
||||||
|
|
||||||
|
Now, our focus will go back to the native chat Markdown renderer. It is quite a bit behind in terms
|
||||||
|
of feature support. Some features (such as KaTeX) will be impossible to implement natively, so we
|
||||||
|
will be looking at ways to fuse the two renderers together on-demand. As always, this is a long-term
|
||||||
|
goal, but we're getting there.
|
||||||
|
|
||||||
|
## Member List
|
||||||
|
|
||||||
|
Here it is, the member list sheet! This is equivalent to the right sidebar on the web client. It
|
||||||
|
shows all the members of the server, sorted by role and position, along with the correct role colour
|
||||||
|
and identity.
|
||||||
|
|
||||||
|
In the future, you will be able to filter this list by role, and search for members. For now, it's
|
||||||
|
just a list.
|
||||||
|
|
||||||
|
## The Small Things
|
||||||
|
|
||||||
|
- Latest and greatest dependency versions are now used, including Kotlin 1.9.10 and Compose 1.5.3.
|
||||||
|
- Audio player gained a "share URL" menu item.
|
||||||
|
- Message timestamps now use native time formatting APIs, which means they'll be formatted
|
||||||
|
correctly for your locale and will stay accurate in all cases.
|
||||||
|
- The disconnected/reconnected/connected banner is now using Material You colouring if your theme is
|
||||||
|
set to that.
|
||||||
|
- Roles in the user sheet are now sorted by position.
|
||||||
|
- If a users' profile is empty or fails to load, the fallback messages are now clearly
|
||||||
|
distinguishable as such.
|
||||||
|
- Debug builds now have "+debug" appended to their version string, an app ID of "chat.revolt.debug",
|
||||||
|
and a different name. This allows you to install the debug build alongside the release build.
|
||||||
|
|
||||||
|
## Squished Bug Showcase
|
||||||
|
|
||||||
|
- Fixed a crash when opening the user sheet for a user that has blocked you.
|
||||||
|
- Fixed a condition in which messages from yesterday would be shown as "Today" in the chat.
|
||||||
|
- Fixed a condition in which GIFs would not play in the chat.
|
||||||
|
- Fixed a condition in which animated WebP images would not play in the chat.
|
||||||
|
- Fixed a condition in which users were eagerly shown as offline when they were actually online.
|
||||||
|
- Fixed a condition in which the ripple area for server icons would be too small compared to the
|
||||||
|
icon.
|
||||||
|
|
||||||
|
## 🫡✨
|
||||||
|
|
||||||
|
That's all for now! We hope you enjoy this release, and we're looking forward to your feedback.
|
||||||
|
Please report any bugs you find, and let us know what you think of the app so far. Thank you for
|
||||||
|
testing!
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"list": {
|
||||||
|
"6000": {
|
||||||
|
"summary": "Beta Ring II, Server Identities, Changelogs, SDK34",
|
||||||
|
"version": "0.6.0",
|
||||||
|
"date": "2023-09-08"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latest": "6000"
|
||||||
|
}
|
||||||
|
|
@ -62,11 +62,8 @@
|
||||||
<script src="https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.5/dist/purify.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.5/dist/purify.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener("load", () => {
|
const renderMarkdown = () => {
|
||||||
const style = document.querySelector("style")
|
const markdown = document.querySelector("#markdown")
|
||||||
style.innerHTML = style.innerHTML
|
|
||||||
.replaceAll("{content}", Bridge.getContentColour())
|
|
||||||
.replaceAll("{primary}", Bridge.getPrimaryColour())
|
|
||||||
|
|
||||||
const converter = new showdown.Converter()
|
const converter = new showdown.Converter()
|
||||||
|
|
||||||
|
|
@ -75,14 +72,22 @@
|
||||||
converter.setOption("emoji", true)
|
converter.setOption("emoji", true)
|
||||||
converter.setOption("disableForced4SpacesIndentedSublists", true)
|
converter.setOption("disableForced4SpacesIndentedSublists", true)
|
||||||
converter.setOption("noHeaderId", true)
|
converter.setOption("noHeaderId", true)
|
||||||
converter.setOption("simpleLineBreaks", true)
|
converter.setOption("simpleLineBreaks", Bridge.shouldUseSimpleLineBreaks())
|
||||||
converter.setOption("strikethrough", true)
|
converter.setOption("strikethrough", true)
|
||||||
converter.setOption("tasklists", true)
|
converter.setOption("tasklists", true)
|
||||||
|
|
||||||
const markdown = document.querySelector("#markdown")
|
|
||||||
const html = converter.makeHtml(Bridge.getMarkdown())
|
const html = converter.makeHtml(Bridge.getMarkdown())
|
||||||
markdown.innerHTML = DOMPurify.sanitize(html)
|
markdown.innerHTML = DOMPurify.sanitize(html)
|
||||||
|
}
|
||||||
|
window.renderMarkdown = renderMarkdown
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
const style = document.querySelector("style")
|
||||||
|
style.innerHTML = style.innerHTML
|
||||||
|
.replaceAll("{content}", Bridge.getContentColour())
|
||||||
|
.replaceAll("{primary}", Bridge.getPrimaryColour())
|
||||||
|
|
||||||
|
renderMarkdown()
|
||||||
Bridge.onLoaded()
|
Bridge.onLoaded()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import chat.revolt.screens.register.RegisterDetailsScreen
|
||||||
import chat.revolt.screens.register.RegisterGreetingScreen
|
import chat.revolt.screens.register.RegisterGreetingScreen
|
||||||
import chat.revolt.screens.register.RegisterVerifyScreen
|
import chat.revolt.screens.register.RegisterVerifyScreen
|
||||||
import chat.revolt.screens.settings.AppearanceSettingsScreen
|
import chat.revolt.screens.settings.AppearanceSettingsScreen
|
||||||
|
import chat.revolt.screens.settings.ChangelogsSettingsScreen
|
||||||
import chat.revolt.screens.settings.ClosedBetaUpdaterScreen
|
import chat.revolt.screens.settings.ClosedBetaUpdaterScreen
|
||||||
import chat.revolt.screens.settings.DebugSettingsScreen
|
import chat.revolt.screens.settings.DebugSettingsScreen
|
||||||
import chat.revolt.screens.settings.SettingsScreen
|
import chat.revolt.screens.settings.SettingsScreen
|
||||||
|
|
@ -137,6 +138,7 @@ fun AppEntrypoint(windowSizeClass: WindowSizeClass) {
|
||||||
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
|
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
|
||||||
composable("settings/debug") { DebugSettingsScreen(navController) }
|
composable("settings/debug") { DebugSettingsScreen(navController) }
|
||||||
composable("settings/updater") { ClosedBetaUpdaterScreen(navController) }
|
composable("settings/updater") { ClosedBetaUpdaterScreen(navController) }
|
||||||
|
composable("settings/changelogs") { ChangelogsSettingsScreen(navController) }
|
||||||
dialog("settings/feedback") { FeedbackDialog(navController) }
|
dialog("settings/feedback") { FeedbackDialog(navController) }
|
||||||
|
|
||||||
composable("about") { AboutScreen(navController) }
|
composable("about") { AboutScreen(navController) }
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ private fun argbAsCssColour(argb: Int): String {
|
||||||
fun WebMarkdown(
|
fun WebMarkdown(
|
||||||
text: String,
|
text: String,
|
||||||
maskLoading: Boolean = false,
|
maskLoading: Boolean = false,
|
||||||
|
simpleLineBreaks: Boolean = true,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val contentColour = LocalContentColor.current
|
val contentColour = LocalContentColor.current
|
||||||
|
|
@ -128,7 +129,7 @@ fun WebMarkdown(
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUrl(
|
loadUrl(
|
||||||
"https://app.revolt.chat/_android_assets/webmarkdown/renderer.html",
|
"$REVOLT_APP/_android_assets/webmarkdown/renderer.html",
|
||||||
)
|
)
|
||||||
|
|
||||||
settings.apply {
|
settings.apply {
|
||||||
|
|
@ -164,6 +165,11 @@ fun WebMarkdown(
|
||||||
fun getPrimaryColour(): String {
|
fun getPrimaryColour(): String {
|
||||||
return argbAsCssColour(materialColourScheme.primary.toArgb())
|
return argbAsCssColour(materialColourScheme.primary.toArgb())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun shouldUseSimpleLineBreaks(): Boolean {
|
||||||
|
return simpleLineBreaks
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Bridge"
|
"Bridge"
|
||||||
)
|
)
|
||||||
|
|
@ -174,6 +180,9 @@ fun WebMarkdown(
|
||||||
LayoutParams.WRAP_CONTENT
|
LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
update = {
|
||||||
|
it.evaluateJavascript("renderMarkdown()", null)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package chat.revolt.internals
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import chat.revolt.api.RevoltJson
|
||||||
|
import chat.revolt.persistence.KVStorage
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Changelog(
|
||||||
|
val summary: String,
|
||||||
|
val version: String,
|
||||||
|
val date: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChangelogIndex(
|
||||||
|
val list: Map<String, Changelog>,
|
||||||
|
val latest: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class Changelogs(val context: Context, val kvStorage: KVStorage? = null) {
|
||||||
|
val index = context.assets.open("changelogs/index.json").use {
|
||||||
|
it.reader().readText()
|
||||||
|
}.let {
|
||||||
|
RevoltJson.decodeFromString(ChangelogIndex.serializer(), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getChangelog(version: String): String {
|
||||||
|
return context.assets.open("changelogs/${version}.md").use {
|
||||||
|
it.reader().readText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun hasSeenLatest(): Boolean {
|
||||||
|
if (kvStorage == null) throw IllegalStateException("Not supported for non-KVStorage instances of Changelogs")
|
||||||
|
|
||||||
|
return kvStorage.get("latestChangelogRead") == index.latest
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun markAsSeen() {
|
||||||
|
if (kvStorage == null) throw IllegalStateException("Not supported for non-KVStorage instances of Changelogs")
|
||||||
|
|
||||||
|
kvStorage.set("latestChangelogRead", index.latest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package chat.revolt.screens.chat
|
package chat.revolt.screens.chat
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
|
|
@ -73,12 +75,14 @@ import chat.revolt.components.screens.chat.drawer.channel.ChannelList
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerServer
|
import chat.revolt.components.screens.chat.drawer.server.DrawerServer
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon
|
import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon
|
||||||
import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator
|
import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator
|
||||||
|
import chat.revolt.internals.Changelogs
|
||||||
import chat.revolt.persistence.KVStorage
|
import chat.revolt.persistence.KVStorage
|
||||||
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
||||||
import chat.revolt.screens.chat.views.HomeScreen
|
import chat.revolt.screens.chat.views.HomeScreen
|
||||||
import chat.revolt.screens.chat.views.NoCurrentChannelScreen
|
import chat.revolt.screens.chat.views.NoCurrentChannelScreen
|
||||||
import chat.revolt.screens.chat.views.channel.ChannelScreen
|
import chat.revolt.screens.chat.views.channel.ChannelScreen
|
||||||
import chat.revolt.sheets.AddServerSheet
|
import chat.revolt.sheets.AddServerSheet
|
||||||
|
import chat.revolt.sheets.ChangelogSheet
|
||||||
import chat.revolt.sheets.ServerContextSheet
|
import chat.revolt.sheets.ServerContextSheet
|
||||||
import chat.revolt.sheets.StatusSheet
|
import chat.revolt.sheets.StatusSheet
|
||||||
import chat.revolt.sheets.UserContextSheet
|
import chat.revolt.sheets.UserContextSheet
|
||||||
|
|
@ -88,27 +92,41 @@ import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||||
import com.airbnb.lottie.compose.animateLottieCompositionAsState
|
import com.airbnb.lottie.compose.animateLottieCompositionAsState
|
||||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
class ChatRouterViewModel @Inject constructor(
|
class ChatRouterViewModel @Inject constructor(
|
||||||
private val kvStorage: KVStorage
|
private val kvStorage: KVStorage,
|
||||||
|
@ApplicationContext val context: Context
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
var currentServer by mutableStateOf("home")
|
var currentServer by mutableStateOf("home")
|
||||||
var currentChannel by mutableStateOf<String?>(null)
|
var currentChannel by mutableStateOf<String?>(null)
|
||||||
var sidebarSparkDisplayed by mutableStateOf(true)
|
var sidebarSparkDisplayed by mutableStateOf(true)
|
||||||
|
var latestChangelogRead by mutableStateOf(true)
|
||||||
|
var latestChangelog by mutableStateOf("")
|
||||||
|
|
||||||
|
private val changelogs = Changelogs(context, kvStorage)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
currentServer = kvStorage.get("currentServer") ?: "home"
|
currentServer = kvStorage.get("currentServer") ?: "home"
|
||||||
currentChannel = kvStorage.get("currentChannel")
|
currentChannel = kvStorage.get("currentChannel")
|
||||||
|
|
||||||
sidebarSparkDisplayed = if (kvStorage.getBoolean("sidebarSpark") == null) {
|
sidebarSparkDisplayed = if (kvStorage.getBoolean("sidebarSpark") == null) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
kvStorage.getBoolean("sidebarSpark")!!
|
kvStorage.getBoolean("sidebarSpark")!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
latestChangelogRead = changelogs.hasSeenLatest()
|
||||||
|
latestChangelog = changelogs.index.latest
|
||||||
|
if (!latestChangelogRead) {
|
||||||
|
changelogs.markAsSeen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,6 +309,22 @@ fun ChatRouterScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!viewModel.latestChangelogRead) {
|
||||||
|
val changelogSheetState = rememberModalBottomSheetState()
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
sheetState = changelogSheetState,
|
||||||
|
onDismissRequest = {
|
||||||
|
viewModel.latestChangelogRead = true
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ChangelogSheet(
|
||||||
|
version = viewModel.latestChangelog,
|
||||||
|
new = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showSidebarSpark.value) {
|
if (showSidebarSpark.value) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package chat.revolt.screens.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
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.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.components.generic.PageHeader
|
||||||
|
import chat.revolt.internals.Changelogs
|
||||||
|
import chat.revolt.persistence.KVStorage
|
||||||
|
import chat.revolt.sheets.ChangelogSheet
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class ChangelogsSettingsScreenViewModel @Inject constructor(
|
||||||
|
kvStorage: KVStorage,
|
||||||
|
@ApplicationContext context: Context
|
||||||
|
) : ViewModel() {
|
||||||
|
val index = Changelogs(context, kvStorage).index
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ChangelogsSettingsScreen(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: ChangelogsSettingsScreenViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
var currentChangelog by remember { mutableStateOf(viewModel.index.latest) }
|
||||||
|
var sheetOpen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (sheetOpen) {
|
||||||
|
val sheetState = rememberModalBottomSheetState()
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
sheetState = sheetState,
|
||||||
|
onDismissRequest = {
|
||||||
|
sheetOpen = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
ChangelogSheet(version = currentChangelog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.safeDrawingPadding()
|
||||||
|
) {
|
||||||
|
PageHeader(
|
||||||
|
text = stringResource(R.string.settings_changelogs),
|
||||||
|
showBackButton = true,
|
||||||
|
onBackButtonClicked = {
|
||||||
|
navController.popBackStack()
|
||||||
|
})
|
||||||
|
|
||||||
|
LazyColumn {
|
||||||
|
items(
|
||||||
|
viewModel.index.list.size,
|
||||||
|
key = { viewModel.index.list.keys.elementAt(it) }
|
||||||
|
) { index ->
|
||||||
|
val version = viewModel.index.list.keys.elementAt(index)
|
||||||
|
val changelog = viewModel.index.list[version]!!
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
currentChangelog = version
|
||||||
|
sheetOpen = true
|
||||||
|
}
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = changelog.version,
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = changelog.summary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,12 @@ class DebugSettingsScreenViewModel @Inject constructor(
|
||||||
fun forgetAllSparks() {
|
fun forgetAllSparks() {
|
||||||
this.forgetSidebarSparkShown()
|
this.forgetSidebarSparkShown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun forgetLatestChangelog() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
kvStorage.remove("latestChangelogRead")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -79,6 +85,19 @@ fun DebugSettingsScreen(
|
||||||
Text("Forget all sparks")
|
Text("Forget all sparks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Changelogs",
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
modifier = Modifier.padding(bottom = 10.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.horizontalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
ElevatedButton(onClick = { viewModel.forgetLatestChangelog() }) {
|
||||||
|
Text("Mark latest changelog as unread")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Build
|
import androidx.compose.material.icons.filled.Build
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.DateRange
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material.icons.filled.Star
|
import androidx.compose.material.icons.filled.Star
|
||||||
|
|
@ -154,6 +155,25 @@ fun SettingsScreen(
|
||||||
modifier = Modifier.padding(bottom = 10.dp, start = 10.dp, top = 20.dp)
|
modifier = Modifier.padding(bottom = 10.dp, start = 10.dp, top = 20.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SheetClickable(
|
||||||
|
icon = { modifier ->
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.DateRange,
|
||||||
|
contentDescription = stringResource(id = R.string.settings_changelogs),
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { textStyle ->
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.settings_changelogs),
|
||||||
|
style = textStyle
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.testTag("settings_view_changelogs")
|
||||||
|
) {
|
||||||
|
navController.navigate("settings/changelogs")
|
||||||
|
}
|
||||||
|
|
||||||
SheetClickable(
|
SheetClickable(
|
||||||
icon = { modifier ->
|
icon = { modifier ->
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package chat.revolt.sheets
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
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.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.components.generic.PageHeader
|
||||||
|
import chat.revolt.components.generic.WebMarkdown
|
||||||
|
import chat.revolt.internals.Changelog
|
||||||
|
import chat.revolt.internals.Changelogs
|
||||||
|
import chat.revolt.persistence.KVStorage
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
class ChangelogSheetViewModel @Inject constructor(
|
||||||
|
val kvStorage: KVStorage,
|
||||||
|
@ApplicationContext val context: Context
|
||||||
|
) : ViewModel() {
|
||||||
|
private val changelogs = Changelogs(context, kvStorage)
|
||||||
|
var changelogContents by mutableStateOf(null as String?)
|
||||||
|
var changelog by mutableStateOf(null as Changelog?)
|
||||||
|
|
||||||
|
private fun getContents(version: String): String {
|
||||||
|
return changelogs.getChangelog(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getChangelog(version: String): Changelog {
|
||||||
|
return changelogs.index.list[version] ?: throw IllegalStateException("Changelog not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun populate(version: String) {
|
||||||
|
changelogContents = getContents(version)
|
||||||
|
changelog = getChangelog(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChangelogSheet(
|
||||||
|
version: String,
|
||||||
|
new: Boolean = false,
|
||||||
|
viewModel: ChangelogSheetViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
LaunchedEffect(version) {
|
||||||
|
viewModel.populate(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
PageHeader(
|
||||||
|
if (new) {
|
||||||
|
stringResource(R.string.settings_changelogs_new_header)
|
||||||
|
} else {
|
||||||
|
stringResource(
|
||||||
|
R.string.settings_changelogs_historical_version_header,
|
||||||
|
viewModel.changelog?.version
|
||||||
|
?: stringResource(R.string.settings_changelogs_historical_version_header_placeholder)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (viewModel.changelogContents == null) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(200.dp)
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key(viewModel.changelogContents) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
WebMarkdown(
|
||||||
|
text = viewModel.changelogContents ?: "",
|
||||||
|
maskLoading = true,
|
||||||
|
simpleLineBreaks = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -359,4 +359,9 @@
|
||||||
|
|
||||||
<string name="settings_feedback_disabled_title">Feedback unavailable</string>
|
<string name="settings_feedback_disabled_title">Feedback unavailable</string>
|
||||||
<string name="settings_feedback_disabled_message">Feedback is not available on this build of Revolt. Support for this build is limited. (Build: %1$s %2$s)</string>
|
<string name="settings_feedback_disabled_message">Feedback is not available on this build of Revolt. Support for this build is limited. (Build: %1$s %2$s)</string>
|
||||||
|
|
||||||
|
<string name="settings_changelogs">Changelogs</string>
|
||||||
|
<string name="settings_changelogs_new_header">What\'s been cooking ✨</string>
|
||||||
|
<string name="settings_changelogs_historical_version_header">Changelog for %1$s</string>
|
||||||
|
<string name="settings_changelogs_historical_version_header_placeholder">that version</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue