# HG changeset patch # User Da Risk # Date 1683054413 14400 # Node ID bb30a0b89177b7e776c24a1dace9cc28fff3d95b # Parent 84df85e84619acd5e84865e8b8e48cde14862bcc add a material3 ui module diff -r 84df85e84619 -r bb30a0b89177 settings.gradle.kts --- a/settings.gradle.kts Sun Apr 16 15:04:57 2023 -0400 +++ b/settings.gradle.kts Tue May 02 15:06:53 2023 -0400 @@ -21,3 +21,4 @@ include(":core") include(":ui:common") include(":ui:material2") +include(":ui:material3") diff -r 84df85e84619 -r bb30a0b89177 ui/material3/.gitignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/.gitignore Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,1 @@ +/build \ No newline at end of file diff -r 84df85e84619 -r bb30a0b89177 ui/material3/build.gradle.kts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/build.gradle.kts Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,115 @@ +/* + * AboutOss is an utility library to retrieve and display + * opensource licenses in Android applications. + * + * Copyright (C) 2023 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 . + */ +plugins { + id("com.android.library") + kotlin("android") + id("com.geekorum.build.source-license-checker") + `maven-publish` +} + +group = "com.geekorum.aboutoss" +version = "0.0.1" + +android { + namespace = "com.geekorum.aboutoss.ui.material3" + compileSdk = 33 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + + aarMetadata { + minCompileSdk = 24 + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + publishing { + singleVariant("release") { + withJavadocJar() + withSourcesJar() + } + } +} + +dependencies { + implementation(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) +} + +publishing { + publications { + val pomConfiguration: (MavenPom).() -> Unit = { + name.set("ui-material3") + description.set("A library to retrieve and display opensource licenses in Android applications") + licenses { + license { + name.set("GPL-3.0-or-later") + url.set("https://www.gnu.org/licenses/gpl-3.0.html") + distribution.set("repo") + } + } + inceptionYear.set("2023") + } + + register("release") { + afterEvaluate { + from(components["release"]) + } + artifactId = "ui-material3" + pom(pomConfiguration) + } + } +} diff -r 84df85e84619 -r bb30a0b89177 ui/material3/consumer-rules.pro diff -r 84df85e84619 -r bb30a0b89177 ui/material3/proguard-rules.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/proguard-rules.pro Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff -r 84df85e84619 -r bb30a0b89177 ui/material3/src/androidTest/java/com/geekorum/aboutoss/ui/material3/ExampleInstrumentedTest.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/src/androidTest/java/com/geekorum/aboutoss/ui/material3/ExampleInstrumentedTest.kt Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,45 @@ +/* + * AboutOss is an utility library to retrieve and display + * opensource licenses in Android applications. + * + * Copyright (C) 2023 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 . + */ +package com.geekorum.aboutoss.ui.material3 + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.geekorum.aboutoss.ui.material3.test", appContext.packageName) + } +} \ No newline at end of file diff -r 84df85e84619 -r bb30a0b89177 ui/material3/src/main/AndroidManifest.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/src/main/AndroidManifest.xml Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,33 @@ + + + + + + + \ No newline at end of file diff -r 84df85e84619 -r bb30a0b89177 ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceDependenciesListScreen.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceDependenciesListScreen.kt Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,116 @@ +/* + * AboutOss is an utility library to retrieve and display + * opensource licenses in Android applications. + * + * Copyright (C) 2023 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 . + */ +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, + 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)) + } + } + } + } +} diff -r 84df85e84619 -r bb30a0b89177 ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceLicenseScreen.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceLicenseScreen.kt Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,173 @@ +/* + * AboutOss is an utility library to retrieve and display + * opensource licenses in Android applications. + * + * Copyright (C) 2023 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 . + */ +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) -> 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(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)) + } + } +} diff -r 84df85e84619 -r bb30a0b89177 ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceLicensesActivity.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/src/main/java/com/geekorum/aboutoss/ui/material3/OpenSourceLicensesActivity.kt Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,115 @@ +/* + * AboutOss is an utility library to retrieve and display + * opensource licenses in Android applications. + * + * Copyright (C) 2023 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 . + */ +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 + */ +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() + }, + ) + } + } +} diff -r 84df85e84619 -r bb30a0b89177 ui/material3/src/test/java/com/geekorum/aboutoss/ui/material3/ExampleUnitTest.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/material3/src/test/java/com/geekorum/aboutoss/ui/material3/ExampleUnitTest.kt Tue May 02 15:06:53 2023 -0400 @@ -0,0 +1,37 @@ +/* + * AboutOss is an utility library to retrieve and display + * opensource licenses in Android applications. + * + * Copyright (C) 2023 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 . + */ +package com.geekorum.aboutoss.ui.material3 + +import org.junit.Assert +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + Assert.assertEquals(4, 2 + 2) + } +} \ No newline at end of file