feat: update aboutlibraries, refine oss screen

This commit is contained in:
infi 2026-05-10 18:52:44 +02:00
parent 02050172f2
commit 779275ce62
8 changed files with 92 additions and 99 deletions

View File

@ -9,6 +9,7 @@ plugins {
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
alias(libs.plugins.aboutlibraries) alias(libs.plugins.aboutlibraries)
alias(libs.plugins.aboutlibraries.android)
alias(libs.plugins.ksp) alias(libs.plugins.ksp)
alias(libs.plugins.sentry.android) alias(libs.plugins.sentry.android)
alias(libs.plugins.sqldelight) alias(libs.plugins.sqldelight)
@ -130,6 +131,7 @@ android {
viewBinding = true viewBinding = true
compose = true compose = true
buildConfig = true buildConfig = true
resValues = true
} }
packaging { packaging {
resources { resources {
@ -279,10 +281,9 @@ dependencies {
} }
aboutLibraries { aboutLibraries {
additionalLicenses += listOf("ofl") license {
includePlatform = true
strictMode = StrictMode.FAIL strictMode = StrictMode.FAIL
allowedLicenses += listOf( allowedLicenses.addAll(
"Apache-2.0", "Apache-2.0",
"ASDKL", "ASDKL",
"BSD-2-Clause", "BSD-2-Clause",
@ -295,7 +296,13 @@ aboutLibraries {
"OFL", "OFL",
"Public Domain" "Public Domain"
) )
configPath = "compliance" additionalLicenses.addAll("ofl")
}
collect {
includePlatform = true
configPath = file("../compliance")
}
} }
sqldelight { sqldelight {

View File

@ -1,16 +1,24 @@
package chat.stoat.composables.screens.settings package chat.stoat.composables.screens.settings
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.core.net.toUri
import chat.stoat.R import chat.stoat.R
import chat.stoat.screens.about.Library import chat.stoat.screens.about.Library
@Composable @Composable
fun AttributionItem(library: Library, onClick: () -> Unit) { fun AttributionItem(library: Library, onClick: () -> Unit) {
val context = LocalContext.current
ListItem( ListItem(
headlineContent = { headlineContent = {
Text( Text(
@ -22,6 +30,21 @@ fun AttributionItem(library: Library, onClick: () -> Unit) {
text = stringResource(id = R.string.oss_attribution_tap_to_view_license) text = stringResource(id = R.string.oss_attribution_tap_to_view_license)
) )
}, },
trailingContent = {
library.website?.let { website ->
IconButton(onClick = {
val customTab = CustomTabsIntent
.Builder()
.build()
customTab.launchUrl(context, website.toUri())
}) {
Icon(
painter = painterResource(id = R.drawable.ic_open_in_new_24dp),
contentDescription = stringResource(id = R.string.oss_attribution_open_library_website)
)
}
}
},
modifier = Modifier modifier = Modifier
.clickable(onClick = onClick) .clickable(onClick = onClick)
) )

View File

@ -1,5 +1,6 @@
package chat.stoat.screens.about package chat.stoat.screens.about
import android.content.ClipData
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@ -30,17 +31,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.ClipboardManager import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.platform.toClipEntry
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@ -134,22 +136,9 @@ fun DebugInfo(viewModel: AboutViewModel) {
@Composable @Composable
fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewModel()) { fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewModel()) {
val context = LocalContext.current val context = LocalContext.current
val clipboardManager: ClipboardManager = val resources = LocalResources.current
LocalClipboardManager.current val clipboard = LocalClipboard.current
val scope = rememberCoroutineScope()
fun copyDebugInformation() {
clipboardManager.setText(
AnnotatedString(StoatJson.encodeToString(viewModel.getDebugInformation()))
)
if (Platform.needsShowClipboardNotification()) {
Toast.makeText(
context,
context.getString(R.string.copied),
Toast.LENGTH_SHORT
).show()
}
}
Scaffold( Scaffold(
topBar = { topBar = {
@ -248,7 +237,24 @@ fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewMo
1 -> { 1 -> {
DebugInfo(viewModel) DebugInfo(viewModel)
TextButton(onClick = ::copyDebugInformation) { TextButton(onClick = {
scope.launch {
clipboard.setClipEntry(
ClipData.newPlainText(
"Stoat Debug Information",
StoatJson.encodeToString(viewModel.getDebugInformation())
).toClipEntry()
)
if (Platform.needsShowClipboardNotification()) {
Toast.makeText(
context,
resources.getString(R.string.copied),
Toast.LENGTH_SHORT
).show()
}
}
}) {
Text(text = stringResource(id = R.string.copy)) Text(text = stringResource(id = R.string.copy))
} }
} }

View File

@ -1,11 +1,8 @@
package chat.stoat.screens.about package chat.stoat.screens.about
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -16,7 +13,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -30,12 +26,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
@ -47,16 +41,10 @@ import kotlinx.serialization.json.JsonArray
@Serializable @Serializable
data class AboutLibraries( data class AboutLibraries(
val metadata: Metadata,
val libraries: List<Library>, val libraries: List<Library>,
val licenses: Map<String, License> val licenses: Map<String, License>
) )
@Serializable
data class Metadata(
val generated: String
)
@Serializable @Serializable
data class License( data class License(
val content: String? = null, val content: String? = null,
@ -105,13 +93,13 @@ data class Scm(
fun AttributionScreen(navController: NavController) { fun AttributionScreen(navController: NavController) {
var libraries by remember { mutableStateOf<AboutLibraries?>(null) } var libraries by remember { mutableStateOf<AboutLibraries?>(null) }
val context = LocalContext.current val resources = LocalResources.current
var licenceSheetOpen by remember { mutableStateOf(false) } var licenceSheetOpen by remember { mutableStateOf(false) }
var licenseSheetTarget by remember { mutableStateOf("") } var licenseSheetTarget by remember { mutableStateOf("") }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
context.resources.openRawResource(R.raw.aboutlibraries).use { stream -> resources.openRawResource(R.raw.aboutlibraries).use { stream ->
val text = stream.bufferedReader().use { it.readText() } val text = stream.bufferedReader().use { it.readText() }
libraries = Json.decodeFromString(AboutLibraries.serializer(), text) libraries = Json.decodeFromString(AboutLibraries.serializer(), text)
} }
@ -181,37 +169,6 @@ fun AttributionScreen(navController: NavController) {
Box(Modifier.padding(pv)) { Box(Modifier.padding(pv)) {
libraries?.let { libraries?.let {
LazyColumn { LazyColumn {
item {
Column(
modifier = Modifier
.padding(16.dp)
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceContainer)
.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(
items = it.libraries.sortedBy { library -> library.name } items = it.libraries.sortedBy { library -> library.name }
) { library -> ) { library ->
@ -220,16 +177,6 @@ fun AttributionScreen(navController: NavController) {
licenseSheetTarget = library.licenses.firstOrNull() ?: "" licenseSheetTarget = library.licenses.firstOrNull() ?: ""
} }
} }
item(key = "cat") {
Text(
text = "🐈",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
textAlign = TextAlign.Center
)
}
} }
} }
} }

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L480,120L480,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,480L840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM388,628L332,572L704,200L560,200L560,120L840,120L840,400L760,400L760,256L388,628Z"/>
</vector>

View File

@ -90,12 +90,9 @@
<string name="about_tab_version">Version</string> <string name="about_tab_version">Version</string>
<string name="about_tab_details">Details</string> <string name="about_tab_details">Details</string>
<string name="oss_attribution">OSS Attribution</string> <string name="oss_attribution">OSS Licenses</string>
<string name="oss_attribution_body">Stoat 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="oss_attribution_tap_to_view_license">Tap to view license</string>
<string name="oss_attribution_open_library_website">Open library website</string>
<string name="comingsoon_toast">Sorry, this feature is not ready yet.</string> <string name="comingsoon_toast">Sorry, this feature is not ready yet.</string>

View File

@ -6,6 +6,7 @@ plugins {
alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.aboutlibraries) apply false alias(libs.plugins.aboutlibraries) apply false
alias(libs.plugins.aboutlibraries.android) apply false
alias(libs.plugins.ksp) apply false alias(libs.plugins.ksp) apply false
alias(libs.plugins.google.services) apply false alias(libs.plugins.google.services) apply false
} }

View File

@ -16,7 +16,7 @@ lifecycle = "2.10.0"
accompanist = "0.37.3" accompanist = "0.37.3"
ktor = "3.4.1" ktor = "3.4.1"
glide = "5.0.5" glide = "5.0.5"
aboutlibraries = "11.3.0-rc02" aboutlibraries = "15.0.0-a02"
media3 = "1.9.2" media3 = "1.9.2"
telephoto = "1.0.0-alpha02" telephoto = "1.0.0-alpha02"
haze = "1.7.2" haze = "1.7.2"
@ -110,6 +110,7 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" } aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
aboutlibraries-android = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutlibraries" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
sentry-android = { id = "io.sentry.android.gradle", version = "6.5.0" } sentry-android = { id = "io.sentry.android.gradle", version = "6.5.0" }