--- a/ui/material3/build.gradle.kts Tue Apr 22 18:48:58 2025 -0400
+++ b/ui/material3/build.gradle.kts Tue Apr 22 19:01:42 2025 -0400
@@ -19,10 +19,13 @@
* You should have received a copy of the GNU General Public License
* along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
*/
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
id("com.android.library")
- kotlin("android")
+ kotlin("multiplatform")
alias(libs.plugins.org.jetbrains.kotlin.compose.compiler)
+ alias(libs.plugins.org.jetbrains.compose.multiplatform)
id("com.geekorum.build.source-license-checker")
`maven-publish`
}
@@ -30,6 +33,44 @@
group = "com.geekorum.aboutoss"
version = "0.1.0"
+kotlin {
+ androidTarget {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_17)
+ }
+ }
+
+ jvm("desktop")
+
+ listOf(
+ iosX64(),
+ iosArm64(),
+ iosSimulatorArm64(),
+ ).forEach { iosTarget ->
+ iosTarget.binaries.framework {
+ baseName = "aboutoss-ui-material3"
+ isStatic = true
+ }
+ }
+
+ sourceSets {
+ commonMain.dependencies {
+ api(project(":ui:common"))
+ implementation(project(":core"))
+ implementation(compose.material3)
+ implementation(compose.components.resources)
+ implementation(libs.org.jetbrains.androidx.navigation.compose)
+ }
+
+ androidMain.dependencies {
+ api(libs.androidx.activity)
+ implementation(dependencies.platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.activity.compose)
+ }
+ }
+}
+
+
android {
namespace = "com.geekorum.aboutoss.ui.material3"
compileSdk = 35
@@ -55,11 +96,8 @@
}
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = "1.8"
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
@@ -75,12 +113,6 @@
}
dependencies {
- api(project(":ui:common"))
- implementation(platform(libs.androidx.compose.bom))
- implementation(libs.androidx.compose.material3)
- implementation(libs.androidx.activity.compose)
- implementation(libs.androidx.navigation.compose)
-
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material3/src/androidMain/AndroidManifest.xml Tue Apr 22 19:01:42 2025 -0400
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ AboutOss is an utility library to retrieve and display
+ opensource licenses in Android applications.
+
+ Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
+
+ This file is part of AboutOss.
+
+ AboutOss is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ AboutOss is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
+
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <application>
+ <activity android:name="OpenSourceLicensesActivity"
+ android:theme="@android:style/Theme.Material.Light.NoActionBar"
+ android:label="@string/title_oss_licenses"
+ android:exported="false"
+ />
+ </application>
+</manifest>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material3/src/androidMain/kotlin/OpenSourceLicensesActivity.kt Tue Apr 22 19:01:42 2025 -0400
@@ -0,0 +1,130 @@
+/*
+ * AboutOss is an utility library to retrieve and display
+ * opensource licenses in Android applications.
+ *
+ * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
+ *
+ * This file is part of AboutOss.
+ *
+ * AboutOss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AboutOss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.ui.material3
+
+import android.app.Activity
+import android.net.Uri
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.geekorum.aboutoss.core.gms.GmsLicenseInfoRepository
+import com.geekorum.aboutoss.ui.common.BaseOpensourceLicenseActivity
+import com.geekorum.aboutoss.ui.common.Factory
+import com.geekorum.aboutoss.ui.common.OpenSourceLicensesViewModel
+import com.geekorum.aboutoss.ui.material3.OpenSourceLicensesActivity.Companion.themeProvider
+import kotlinx.coroutines.Dispatchers
+
+/**
+ * Activity to display opensource license information
+ *
+ * This activity use Material compose to create the UI.
+ * You can specify the Material theme to use by setting [themeProvider]
+ * before launching the activity
+ */
+open class OpenSourceLicensesActivity : BaseOpensourceLicenseActivity() {
+ override val viewModel: OpenSourceLicensesViewModel by viewModels(
+ factoryProducer = {
+ val gmsLicenseInfoRepository = GmsLicenseInfoRepository(
+ appContext = applicationContext,
+ mainCoroutineDispatcher = Dispatchers.Main,
+ ioCoroutineDispatcher = Dispatchers.IO,
+ )
+ OpenSourceLicensesViewModel.Factory(gmsLicenseInfoRepository)
+ }
+ )
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ setContent {
+ themeProvider {
+ DependencyNavHost(
+ openSourceLicensesViewModel = viewModel,
+ navigateUp = {
+ if (!onNavigateUp()) {
+ finish()
+ }
+ }
+ )
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * The composable Theme function to set the theme of the UI in [OpenSourceLicensesActivity]
+ * Default to base material theme [MaterialTheme]
+ */
+ var themeProvider: @Composable (@Composable () -> Unit) -> Unit = { content ->
+ val darkTheme: Boolean = isSystemInDarkTheme()
+ val colorScheme = MaterialTheme.colorScheme
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.primary.toArgb()
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+ }
+ }
+ MaterialTheme(content = content)
+ }
+ }
+}
+
+@Composable
+fun DependencyNavHost(
+ openSourceLicensesViewModel: OpenSourceLicensesViewModel,
+ navigateUp: () -> Unit
+) {
+ val navController = rememberNavController()
+ NavHost(navController, startDestination = "dependencies") {
+ composable("dependencies") {
+ OpenSourceDependenciesListScreen(
+ viewModel = openSourceLicensesViewModel,
+ onDependencyClick = {
+ navController.navigate("dependency_license/${Uri.encode(it)}")
+ },
+ onUpClick = navigateUp
+ )
+ }
+ composable("dependency_license/{dependency}") {
+ val dependency = requireNotNull(it.arguments?.getString("dependency"))
+ OpenSourceLicenseScreen(
+ viewModel = openSourceLicensesViewModel,
+ dependency = dependency,
+ onUpClick = {
+ navController.popBackStack()
+ },
+ )
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material3/src/commonMain/kotlin/com/geekorum/aboutoss/ui/material3/OpenSourceDependenciesListScreen.kt Tue Apr 22 19:01:42 2025 -0400
@@ -0,0 +1,126 @@
+/*
+ * AboutOss is an utility library to retrieve and display
+ * opensource licenses in Android applications.
+ *
+ * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
+ *
+ * This file is part of AboutOss.
+ *
+ * AboutOss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AboutOss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.ui.material3
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberTopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.geekorum.aboutoss.common.generated.resources.title_oss_licenses
+import com.geekorum.aboutoss.ui.common.OpenSourceLicensesViewModel
+import org.jetbrains.compose.resources.stringResource
+import com.geekorum.aboutoss.common.generated.resources.Res as CommonRes
+
+/**
+ * Display the list of dependencies used in the application
+ *
+ * @param viewModel the [OpenSourceLicensesViewModel] to use
+ * @param onDependencyClick lambda to execute on click on one dependency item
+ * @param onUpClick lambda to execute on click on the up arrow
+ */
+@Composable
+fun OpenSourceDependenciesListScreen(
+ viewModel: OpenSourceLicensesViewModel,
+ onDependencyClick: (String) -> Unit,
+ onUpClick: () -> Unit
+) {
+ val dependencies by viewModel.dependenciesList.collectAsState(initial = emptyList())
+ OpenSourceDependenciesListScreen(
+ dependencies = dependencies,
+ onDependencyClick = onDependencyClick,
+ onUpClick = onUpClick
+ )
+}
+
+/**
+ * Display the list of dependencies used in the application
+ *
+ * @param dependencies the list of dependencies
+ * @param onDependencyClick lambda to execute on click on one dependency item
+ * @param onUpClick lambda to execute on click on the up arrow
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun OpenSourceDependenciesListScreen(
+ dependencies: List<String>,
+ onDependencyClick: (String) -> Unit,
+ onUpClick: () -> Unit
+) {
+ val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
+ Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(CommonRes.string.title_oss_licenses)) },
+ navigationIcon = {
+ IconButton(onClick = onUpClick) {
+ Icon(
+ Icons.Default.ArrowBack,
+ contentDescription = null
+ )
+ }
+ },
+ scrollBehavior = scrollBehavior
+ )
+ }) {
+ LazyColumn(Modifier.fillMaxSize(), contentPadding = it) {
+ items(dependencies) {
+ Column {
+ ListItem(
+ modifier = Modifier
+ .height(64.dp)
+ .clickable(onClick = { onDependencyClick(it) }),
+ headlineContent = {
+ Text(
+ it, modifier = Modifier.padding(horizontal = 16.dp),
+ overflow = TextOverflow.Ellipsis, maxLines = 1
+ )
+ }
+ )
+ Divider(Modifier.padding(horizontal = 16.dp))
+ }
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material3/src/commonMain/kotlin/com/geekorum/aboutoss/ui/material3/OpenSourceLicenseScreen.kt Tue Apr 22 19:01:42 2025 -0400
@@ -0,0 +1,192 @@
+/*
+ * AboutOss is an utility library to retrieve and display
+ * opensource licenses in Android applications.
+ *
+ * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
+ *
+ * This file is part of AboutOss.
+ *
+ * AboutOss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AboutOss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.ui.material3
+
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.consumeWindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberTopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.UrlAnnotation
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.withAnnotation
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.unit.dp
+import com.geekorum.aboutoss.ui.common.OpenSourceLicensesViewModel
+
+/**
+ * Display the opensource license of a dependency
+ *
+ * @param viewModel the [OpenSourceLicensesViewModel] to use
+ * @param dependency the dependency
+ * @param onUpClick lambda to execute on click on the up arrow
+ */
+@Composable
+fun OpenSourceLicenseScreen(
+ viewModel: OpenSourceLicensesViewModel,
+ dependency: String,
+ onUpClick: () -> Unit,
+) {
+ val license by viewModel.getLicenseDependency(dependency).collectAsState("")
+ OpenSourceLicenseScreen(
+ dependency = dependency,
+ license = license,
+ onUpClick = onUpClick,
+ onUrlClick = {
+ viewModel.openLinkInBrowser(it)
+ },
+ onUrlsFound = {
+ viewModel.mayLaunchUrl(*it.toTypedArray())
+ }
+ )
+}
+
+/**
+ * Display the opensource license of a dependency
+ *
+ * @param dependency the dependency
+ * @param license the opensource license text
+ * @param onUpClick lambda to execute on click on the up arrow
+ * @param onUrlClick lambda to execute on click on a url
+ * @param onUrlsFound lambda to execute when all urls in the license have been found
+ */
+@OptIn(ExperimentalLayoutApi::class, ExperimentalTextApi::class, ExperimentalMaterial3Api::class)
+@Composable
+fun OpenSourceLicenseScreen(
+ dependency: String,
+ license: String,
+ onUpClick: () -> Unit,
+ onUrlClick: (String) -> Unit,
+ onUrlsFound: (List<String>) -> Unit,
+) {
+ val linkifiedLicense = linkifyText(text = license)
+ LaunchedEffect(linkifiedLicense) {
+ val uris =
+ linkifiedLicense.getUrlAnnotations(0, linkifiedLicense.length).map { it.item.url }
+ onUrlsFound(uris)
+ }
+
+ val scrollState = rememberScrollState()
+
+ val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
+ Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = {
+ TopAppBar(title = { Text(dependency, overflow = TextOverflow.Ellipsis, maxLines = 1) },
+ navigationIcon = {
+ IconButton(onClick = onUpClick) {
+ Icon(
+ Icons.Default.ArrowBack,
+ contentDescription = null
+ )
+ }
+ },
+ )
+ }) { paddingValues ->
+ val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
+ val pressIndicator = Modifier.pointerInput(layoutResult, linkifiedLicense) {
+ detectTapGestures { pos ->
+ layoutResult.value?.let { layoutResult ->
+ val posWithScroll = pos.copy(y = pos.y + scrollState.value)
+ val offset = layoutResult.getOffsetForPosition(posWithScroll)
+ linkifiedLicense.getUrlAnnotations(start = offset, end = offset)
+ .firstOrNull()?.let { annotation ->
+ onUrlClick(annotation.item.url)
+ }
+ }
+ }
+ }
+
+ Text(linkifiedLicense,
+ modifier = Modifier
+ .padding(paddingValues)
+ .consumeWindowInsets(paddingValues)
+ .padding(horizontal = 16.dp)
+ .fillMaxSize()
+ .then(pressIndicator)
+ .verticalScroll(scrollState),
+ onTextLayout = {
+ layoutResult.value = it
+ }
+ )
+ }
+}
+
+/**
+ * https://regexr.com/37i6s
+ */
+private val urlRegexp = """https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)""".toRegex()
+
+@OptIn(ExperimentalTextApi::class)
+@Composable
+private fun linkifyText(text: String): AnnotatedString {
+ val style = SpanStyle(
+ color = MaterialTheme.colorScheme.primary,
+ textDecoration = TextDecoration.Underline
+ )
+ return remember(text, style) {
+ buildAnnotatedString {
+ var currentIdx = 0
+ for (match in urlRegexp.findAll(text)) {
+ if (currentIdx < match.range.first) {
+ append(text.substring(currentIdx, match.range.first))
+ }
+ val url = text.substring(match.range)
+ withAnnotation(UrlAnnotation(url)) {
+ withStyle(style) {
+ append(url)
+ }
+ }
+ currentIdx = match.range.last + 1
+ }
+ append(text.substring(currentIdx))
+ }
+ }
+}
--- a/ui/material3/src/main/AndroidManifest.xml Tue Apr 22 18:48:58 2025 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-
- AboutOss is an utility library to retrieve and display
- opensource licenses in Android applications.
-
- Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
-
- This file is part of AboutOss.
-
- AboutOss is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- AboutOss is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
-
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <application>
- <activity android:name=".OpenSourceLicensesActivity"
- android:theme="@style/Theme.AppCompat.NoActionBar"
- android:label="@string/title_oss_licenses"
- android:exported="false"
- />
- </application>
-</manifest>
\ No newline at end of file
--- a/ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceDependenciesListScreen.kt Tue Apr 22 18:48:58 2025 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/*
- * AboutOss is an utility library to retrieve and display
- * opensource licenses in Android applications.
- *
- * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
- *
- * This file is part of AboutOss.
- *
- * AboutOss is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AboutOss is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.ui.material3
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import com.geekorum.aboutoss.ui.common.OpenSourceLicensesViewModel
-import com.geekorum.aboutoss.ui.common.R as commonR
-
-/**
- * Display the list of dependencies used in the application
- *
- * @param viewModel the [OpenSourceLicensesViewModel] to use
- * @param onDependencyClick lambda to execute on click on one dependency item
- * @param onUpClick lambda to execute on click on the up arrow
- */
-@Composable
-fun OpenSourceDependenciesListScreen(
- viewModel: OpenSourceLicensesViewModel,
- onDependencyClick: (String) -> Unit,
- onUpClick: () -> Unit
-) {
- val dependencies by viewModel.dependenciesList.collectAsState(initial = emptyList())
- OpenSourceDependenciesListScreen(
- dependencies = dependencies,
- onDependencyClick = onDependencyClick,
- onUpClick = onUpClick
- )
-}
-
-/**
- * Display the list of dependencies used in the application
- *
- * @param dependencies the list of dependencies
- * @param onDependencyClick lambda to execute on click on one dependency item
- * @param onUpClick lambda to execute on click on the up arrow
- */
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun OpenSourceDependenciesListScreen(
- dependencies: List<String>,
- onDependencyClick: (String) -> Unit,
- onUpClick: () -> Unit
-) {
- val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
- Scaffold(
- modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
- topBar = {
- TopAppBar(
- title = { Text(stringResource(commonR.string.title_oss_licenses)) },
- navigationIcon = {
- IconButton(onClick = onUpClick) {
- Icon(
- Icons.Default.ArrowBack,
- contentDescription = null
- )
- }
- },
- scrollBehavior = scrollBehavior
- )
- }) {
- LazyColumn(Modifier.fillMaxSize(), contentPadding = it) {
- items(dependencies) {
- Column {
- ListItem(
- modifier = Modifier
- .height(64.dp)
- .clickable(onClick = { onDependencyClick(it) }),
- headlineContent = {
- Text(
- it, modifier = Modifier.padding(horizontal = 16.dp),
- overflow = TextOverflow.Ellipsis, maxLines = 1
- )
- }
- )
- Divider(Modifier.padding(horizontal = 16.dp))
- }
- }
- }
- }
-}
--- a/ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceLicenseScreen.kt Tue Apr 22 18:48:58 2025 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-/*
- * AboutOss is an utility library to retrieve and display
- * opensource licenses in Android applications.
- *
- * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
- *
- * This file is part of AboutOss.
- *
- * AboutOss is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AboutOss is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.ui.material3
-
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.text.*
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import androidx.core.net.toUri
-import com.geekorum.aboutoss.ui.common.OpenSourceLicensesViewModel
-
-/**
- * Display the opensource license of a dependency
- *
- * @param viewModel the [OpenSourceLicensesViewModel] to use
- * @param dependency the dependency
- * @param onUpClick lambda to execute on click on the up arrow
- */
-@Composable
-fun OpenSourceLicenseScreen(
- viewModel: OpenSourceLicensesViewModel,
- dependency: String,
- onUpClick: () -> Unit,
-) {
- val context = LocalContext.current
- val license by viewModel.getLicenseDependency(dependency).collectAsState("")
- OpenSourceLicenseScreen(
- dependency = dependency,
- license = license,
- onUpClick = onUpClick,
- onUrlClick = {
- viewModel.openLinkInBrowser(context, it)
- },
- onUrlsFound = {
- val uris = it.map { uri -> uri.toUri() }
- viewModel.mayLaunchUrl(*uris.toTypedArray())
- }
- )
-}
-
-/**
- * Display the opensource license of a dependency
- *
- * @param dependency the dependency
- * @param license the opensource license text
- * @param onUpClick lambda to execute on click on the up arrow
- * @param onUrlClick lambda to execute on click on a url
- * @param onUrlsFound lambda to execute when all urls in the license have been found
- */
-@OptIn(ExperimentalLayoutApi::class, ExperimentalTextApi::class, ExperimentalMaterial3Api::class)
-@Composable
-fun OpenSourceLicenseScreen(
- dependency: String,
- license: String,
- onUpClick: () -> Unit,
- onUrlClick: (String) -> Unit,
- onUrlsFound: (List<String>) -> Unit,
-) {
- val linkifiedLicense = linkifyText(text = license)
- LaunchedEffect(linkifiedLicense) {
- val uris =
- linkifiedLicense.getUrlAnnotations(0, linkifiedLicense.length).map { it.item.url }
- onUrlsFound(uris)
- }
-
- val scrollState = rememberScrollState()
-
- val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
- Scaffold(
- modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
- topBar = {
- TopAppBar(title = { Text(dependency, overflow = TextOverflow.Ellipsis, maxLines = 1) },
- navigationIcon = {
- IconButton(onClick = onUpClick) {
- Icon(
- Icons.Default.ArrowBack,
- contentDescription = null
- )
- }
- },
- )
- }) { paddingValues ->
- val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
- val pressIndicator = Modifier.pointerInput(layoutResult, linkifiedLicense) {
- detectTapGestures { pos ->
- layoutResult.value?.let { layoutResult ->
- val posWithScroll = pos.copy(y = pos.y + scrollState.value)
- val offset = layoutResult.getOffsetForPosition(posWithScroll)
- linkifiedLicense.getUrlAnnotations(start = offset, end = offset)
- .firstOrNull()?.let { annotation ->
- onUrlClick(annotation.item.url)
- }
- }
- }
- }
-
- Text(linkifiedLicense,
- modifier = Modifier
- .padding(paddingValues)
- .consumeWindowInsets(paddingValues)
- .padding(horizontal = 16.dp)
- .fillMaxSize()
- .then(pressIndicator)
- .verticalScroll(scrollState),
- onTextLayout = {
- layoutResult.value = it
- }
- )
- }
-}
-
-/**
- * https://regexr.com/37i6s
- */
-private val urlRegexp = """https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)""".toRegex()
-
-@OptIn(ExperimentalTextApi::class)
-@Composable
-private fun linkifyText(text: String): AnnotatedString {
- val style = SpanStyle(
- color = MaterialTheme.colorScheme.primary,
- textDecoration = TextDecoration.Underline
- )
- return remember(text, style) {
- buildAnnotatedString {
- var currentIdx = 0
- for (match in urlRegexp.findAll(text)) {
- if (currentIdx < match.range.first) {
- append(text.substring(currentIdx, match.range.first))
- }
- val url = text.substring(match.range)
- withAnnotation(UrlAnnotation(url)) {
- withStyle(style) {
- append(url)
- }
- }
- currentIdx = match.range.last + 1
- }
- append(text.substring(currentIdx))
- }
- }
-}
--- a/ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceLicensesActivity.kt Tue Apr 22 18:48:58 2025 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/*
- * AboutOss is an utility library to retrieve and display
- * opensource licenses in Android applications.
- *
- * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
- *
- * This file is part of AboutOss.
- *
- * AboutOss is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AboutOss is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AboutOss. If not, see <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.ui.material3
-
-import android.app.Activity
-import android.net.Uri
-import android.os.Bundle
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.platform.LocalView
-import androidx.core.view.WindowCompat
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import com.geekorum.aboutoss.ui.common.BaseOpensourceLicenseActivity
-import com.geekorum.aboutoss.ui.common.OpenSourceLicensesViewModel
-
-/**
- * Activity to display opensource license information
- *
- * This activity use Material compose to create the UI.
- * You can specify the Material theme to use by setting [themeProvider]
- * before launching the activity
- */
-open class OpenSourceLicensesActivity : BaseOpensourceLicenseActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- WindowCompat.setDecorFitsSystemWindows(window, false)
- setContent {
- themeProvider {
- DependencyNavHost(
- openSourceLicensesViewModel = viewModel,
- navigateUp = {
- if (!onNavigateUp()) {
- finish()
- }
- }
- )
- }
- }
- }
-
- companion object {
- /**
- * The composable Theme function to set the theme of the UI in [OpenSourceLicensesActivity]
- * Default to base material theme [MaterialTheme]
- */
- var themeProvider: @Composable (@Composable () -> Unit) -> Unit = { content ->
- val darkTheme: Boolean = isSystemInDarkTheme()
- val colorScheme = MaterialTheme.colorScheme
- val view = LocalView.current
- if (!view.isInEditMode) {
- SideEffect {
- val window = (view.context as Activity).window
- window.statusBarColor = colorScheme.primary.toArgb()
- WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
- }
- }
- MaterialTheme(content = content)
- }
- }
-}
-
-@Composable
-fun DependencyNavHost(
- openSourceLicensesViewModel: OpenSourceLicensesViewModel,
- navigateUp: () -> Unit
-) {
- val navController = rememberNavController()
- NavHost(navController, startDestination = "dependencies") {
- composable("dependencies") {
- OpenSourceDependenciesListScreen(
- viewModel = openSourceLicensesViewModel,
- onDependencyClick = {
- navController.navigate("dependency_license/${Uri.encode(it)}")
- },
- onUpClick = navigateUp
- )
- }
- composable("dependency_license/{dependency}") {
- val dependency = requireNotNull(it.arguments?.getString("dependency"))
- OpenSourceLicenseScreen(
- viewModel = openSourceLicensesViewModel,
- dependency = dependency,
- onUpClick = {
- navController.popBackStack()
- },
- )
- }
- }
-}