generate third-party licenses at build and show them from settings

Add AboutLibraries, whose Android Gradle plugin collects the licence and
attribution of every dependency into a generated resource at build time,
and surface it under a new About section in Settings.
This commit is contained in:
austinried
2026-10-04 00:30:05 +00:00
parent 32d2988670
commit edb1eb8a68
8 changed files with 213 additions and 0 deletions
@@ -77,6 +77,7 @@ import com.subtracks.ui.playback.MiniPlayer
import com.subtracks.ui.playback.NowPlayingRoute
import com.subtracks.ui.playback.QueueRoute
import com.subtracks.ui.settings.AddSourceRoute
import com.subtracks.ui.settings.LicensesRoute
import com.subtracks.ui.settings.SettingsRoute
import com.subtracks.ui.theme.ArtworkTheme
import com.subtracks.ui.theme.rememberArtworkColors
@@ -110,6 +111,7 @@ private object Routes {
const val LIBRARY = "library"
const val SETTINGS = "settings"
const val DOWNLOADS = "downloads"
const val LICENSES = "licenses"
const val ADD_SERVER = "add-server"
const val EDIT_SERVER = "edit-server/{sourceId}"
const val ALBUM_DETAIL = "album/{albumId}?coverArt={coverArt}"
@@ -358,6 +360,7 @@ private fun MainNavigation() {
SettingsRoute(
onAddServer = { navController.navigate(Routes.ADD_SERVER) },
onOpenDownloads = { navController.navigate(Routes.DOWNLOADS) },
onOpenLicenses = { navController.navigate(Routes.LICENSES) },
onEditServer = { id -> navController.navigate(Routes.editServer(id)) },
onBack = { navController.popBackStack() },
)
@@ -365,6 +368,9 @@ private fun MainNavigation() {
composable(Routes.DOWNLOADS) {
DownloadsRoute(onBack = { navController.popBackStack() })
}
composable(Routes.LICENSES) {
LicensesRoute(onBack = { navController.popBackStack() })
}
composable(
route = Routes.ALBUM_DETAIL,
arguments =
@@ -0,0 +1,55 @@
package com.subtracks.ui.settings
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import com.subtracks.R
@Composable
fun LicensesRoute(
onBack: () -> Unit,
modifier: Modifier = Modifier,
) {
val libs by produceLibraries(R.raw.aboutlibraries)
LicensesScreen(libs = libs, onBack = onBack, modifier = modifier)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LicensesScreen(
libs: Libs?,
onBack: () -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(
modifier = modifier,
topBar = {
TopAppBar(
title = { Text("Licenses") },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back")
}
},
)
},
) { padding ->
LibrariesContainer(
libraries = libs,
modifier = Modifier.fillMaxSize().padding(padding),
)
}
}
@@ -62,6 +62,7 @@ private enum class SettingsDialog { WifiQuality, MobileQuality, DownloadQuality,
fun SettingsRoute(
onAddServer: () -> Unit,
onOpenDownloads: () -> Unit,
onOpenLicenses: () -> Unit,
onEditServer: (Long) -> Unit,
onBack: () -> Unit,
viewModel: SettingsViewModel = koinViewModel(),
@@ -96,6 +97,7 @@ fun SettingsRoute(
onOfflineChange = viewModel::setOfflineMode,
onAddServer = onAddServer,
onOpenDownloads = onOpenDownloads,
onOpenLicenses = onOpenLicenses,
onBack = onBack,
)
}
@@ -123,6 +125,7 @@ fun SettingsScreen(
onOfflineChange: (Boolean) -> Unit = {},
onAddServer: () -> Unit,
onOpenDownloads: () -> Unit,
onOpenLicenses: () -> Unit,
onBack: () -> Unit,
modifier: Modifier = Modifier,
) {
@@ -277,6 +280,14 @@ fun SettingsScreen(
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}
item { SectionHeader("About") }
item {
ListItem(
headlineContent = { Text("Licenses") },
modifier = Modifier.clickable(onClick = onOpenLicenses),
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}
item { Spacer(Modifier.height(fill)) }
}
}