feat: custom oss attribution screen

Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
Infi 2023-06-18 20:32:44 +02:00
parent e9f4259da5
commit 9490ff24d8
3 changed files with 236 additions and 14 deletions

View File

@ -0,0 +1,45 @@
package chat.revolt.components.screens.settings
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.revolt.R
import chat.revolt.screens.about.Library
@Composable
fun AttributionItem(
library: Library,
onClick: () -> Unit
) {
SelectionContainer {
Column(
modifier = Modifier
.clickable(onClick = onClick)
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = library.name,
style = MaterialTheme.typography.bodyLarge,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(id = R.string.oss_attribution_tap_to_view_license),
style = MaterialTheme.typography.bodySmall,
)
}
}
}

View File

@ -1,20 +1,149 @@
package chat.revolt.screens.about
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.surfaceColorAtElevation
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.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
import chat.revolt.components.screens.settings.AttributionItem
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
@Serializable
data class AboutLibraries(
val metadata: Metadata,
val libraries: List<Library>,
val licenses: Map<String, License>
)
@Serializable
data class Metadata(
val generated: String,
)
@Serializable
data class License(
val content: String? = null,
val hash: String,
val internalHash: String? = null,
val url: String,
val spdxId: String? = null,
val name: String,
)
@Serializable
data class Library(
val uniqueId: String,
val funding: JsonArray,
val developers: List<Developer>,
val artifactVersion: String,
val description: String,
val scm: Scm? = null,
val name: String,
val licenses: List<String>,
val website: String? = null,
val organization: Organization? = null,
)
@Serializable
data class Organization(
val url: String,
val name: String,
)
@Serializable
data class Developer(
val organisationUrl: String? = null,
val name: String? = null,
)
@Serializable
data class Scm(
val connection: String? = null,
val url: String,
val developerConnection: String? = null,
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AttributionScreen(navController: NavController) {
var libraries by remember { mutableStateOf<AboutLibraries?>(null) }
val context = LocalContext.current
var licenceSheetOpen by remember { mutableStateOf(false) }
var licenseSheetTarget by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
context.resources.openRawResource(R.raw.aboutlibraries).use { stream ->
val text = stream.bufferedReader().use { it.readText() }
libraries = Json.decodeFromString(AboutLibraries.serializer(), text)
}
}
if (licenceSheetOpen) {
val licenceSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
sheetState = licenceSheetState,
onDismissRequest = {
licenceSheetOpen = false
}
) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
Text(
text = licenseSheetTarget,
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(8.dp))
libraries?.let {
val license = it.licenses[licenseSheetTarget]
if (license != null) {
Text(text = license.content ?: "No license content found.")
} else {
Text(text = "No license found.")
}
}
}
}
}
Column(
modifier = Modifier
.safeDrawingPadding()
@ -24,17 +153,59 @@ fun AttributionScreen(navController: NavController) {
showBackButton = true,
onBackButtonClicked = { navController.popBackStack() })
LibrariesContainer(
modifier = Modifier
.fillMaxSize()
.weight(1f),
colors = LibraryDefaults.libraryColors(
backgroundColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
badgeBackgroundColor = MaterialTheme.colorScheme.primary,
badgeContentColor = MaterialTheme.colorScheme.onPrimary
)
)
libraries?.let {
LazyColumn {
item {
Column(
modifier = Modifier
.padding(16.dp)
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.oss_attribution_body)
)
Text(
text = stringResource(R.string.oss_attribution_body_2)
)
Text(
text = stringResource(R.string.oss_attribution_warning),
color = MaterialTheme.colorScheme.error
)
Text(
text = stringResource(
R.string.oss_attribution_generation_date,
libraries?.metadata?.generated ?: ""
),
color = LocalContentColor.current.copy(alpha = 0.6f)
)
}
}
items(
items = it.libraries.sortedBy { library -> library.name }
) { library ->
AttributionItem(library = library) {
licenceSheetOpen = true
licenseSheetTarget = library.licenses.first()
}
}
item(key = "cat") {
Text(
text = "🐈",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
textAlign = TextAlign.Center
)
}
}
}
}
}

View File

@ -72,7 +72,13 @@
<string name="about">About</string>
<string name="app_full_name">Revolt on Android</string>
<string name="oss_attribution">OSS Attribution</string>
<string name="oss_attribution_body">Revolt is built with the help of these awesome open-source projects.</string>
<string name="oss_attribution_body_2">We\'re grateful to the developers of these projects for making their work available to the world.</string>
<string name="oss_attribution_warning">The list below is automatically generated. It may be incomplete or inaccurate.</string>
<string name="oss_attribution_generation_date">Last updated: %1$s</string>
<string name="oss_attribution_tap_to_view_license">Tap to view license</string>
<string name="comingsoon_toast">Sorry, this feature is not ready yet.</string>