--- a/settings.gradle.kts Thu Apr 13 17:28:56 2023 -0400
+++ b/settings.gradle.kts Thu Apr 13 17:37:13 2023 -0400
@@ -19,5 +19,5 @@
rootProject.name = "AboutOss"
include(":core")
-include(":ui")
include(":ui:common")
+include(":ui:material2")
--- a/ui/build.gradle.kts Thu Apr 13 17:28:56 2023 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-plugins {
- id("com.android.library")
- kotlin("android")
- id("com.geekorum.build.source-license-checker")
-}
-
-android {
- namespace = "com.geekorum.aboutoss.ui"
- compileSdk = 33
-
- defaultConfig {
- minSdk = 28
-
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles("consumer-rules.pro")
- }
-
- 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()
- }
-}
-
-dependencies {
- implementation(project(":ui:common"))
- implementation(platform(libs.androidx.compose.bom))
- implementation(libs.androidx.compose.material)
- implementation(libs.androidx.lifecycle.viewmodel)
- implementation(libs.androidx.activity)
- implementation(libs.androidx.activity.compose)
- implementation(libs.androidx.navigation.compose)
- implementation(libs.geekdroid) {
- //TODO get rid of dagger platform in geekdroid
- exclude("com.google.dagger", "dagger-platform")
- }
-
- implementation(libs.appcompat)
- testImplementation(libs.junit)
- androidTestImplementation(libs.androidx.test.ext.junit)
- androidTestImplementation(libs.espresso.core)
-}
\ No newline at end of file
--- a/ui/common/build.gradle.kts Thu Apr 13 17:28:56 2023 -0400
+++ b/ui/common/build.gradle.kts Thu Apr 13 17:37:13 2023 -0400
@@ -35,7 +35,7 @@
dependencies {
implementation(project(":core"))
- implementation(libs.appcompat)
+ api(libs.appcompat)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.activity)
implementation(libs.geekdroid) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material2/build.gradle.kts Thu Apr 13 17:37:13 2023 -0400
@@ -0,0 +1,54 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ id("com.geekorum.build.source-license-checker")
+}
+
+android {
+ namespace = "com.geekorum.aboutoss.ui.material"
+ compileSdk = 33
+
+ defaultConfig {
+ minSdk = 28
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ 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()
+ }
+}
+
+dependencies {
+ implementation(project(":ui:common"))
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.compose.material)
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.androidx.navigation.compose)
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.ext.junit)
+ androidTestImplementation(libs.espresso.core)
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material2/proguard-rules.pro Thu Apr 13 17:37:13 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material2/src/androidTest/java/com/geekorum/aboutoss/ui/material/ExampleInstrumentedTest.kt Thu Apr 13 17:37:13 2023 -0400
@@ -0,0 +1,45 @@
+/*
+ * AboutOss is a 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 <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.ui.material
+
+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.material.test", appContext.packageName)
+ }
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material2/src/main/java/com/geekorum/aboutoss/ui/material/OpenSourceDependenciesListScreen.kt Thu Apr 13 17:37:13 2023 -0400
@@ -0,0 +1,116 @@
+/*
+ * AboutOss is a 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 <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.ui.material
+
+import androidx.compose.animation.core.animateDpAsState
+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.foundation.lazy.rememberLazyListState
+import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.ListItem
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+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
+
+@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
+ )
+}
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun OpenSourceDependenciesListScreen(
+ dependencies: List<String>,
+ onDependencyClick: (String) -> Unit,
+ onUpClick: () -> Unit
+) {
+ val lazyListState = rememberLazyListState()
+ val hasScrolled by remember {
+ derivedStateOf {
+ lazyListState.firstVisibleItemIndex != 0 || lazyListState.firstVisibleItemScrollOffset > 0
+ }
+ }
+ val topBarElevation by animateDpAsState(
+ if (hasScrolled) 4.dp else 0.dp
+ )
+ Scaffold(topBar = {
+ TopAppBar(title = { Text(stringResource(commonR.string.title_oss_licenses)) },
+ navigationIcon = {
+ IconButton(onClick = onUpClick) {
+ Icon(
+ Icons.Default.ArrowBack,
+ contentDescription = null
+ )
+ }
+ },
+ elevation = topBarElevation
+ )
+ }) {
+ LazyColumn(Modifier.fillMaxSize(), state = lazyListState, contentPadding = it) {
+ items(dependencies) {
+ Column {
+ ListItem(
+ Modifier
+ .height(64.dp)
+ .clickable(onClick = { onDependencyClick(it) })
+ ) {
+ 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/material2/src/main/java/com/geekorum/aboutoss/ui/material/OpenSourceLicenseScreen.kt Thu Apr 13 17:37:13 2023 -0400
@@ -0,0 +1,178 @@
+/*
+ * AboutOss is a 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 <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.ui.material
+
+import androidx.compose.animation.core.animateDpAsState
+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.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+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.pointer.pointerInput
+import androidx.compose.ui.platform.LocalContext
+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 androidx.core.net.toUri
+import com.geekorum.aboutoss.ui.common.OpenSourceLicensesViewModel
+
+@Composable
+fun OpenSourceLicenseScreen(
+ viewModel: OpenSourceLicensesViewModel,
+ dependency: String,
+ onBackClick: () -> Unit,
+) {
+ val context = LocalContext.current
+ val license by viewModel.getLicenseDependency(dependency).collectAsState("")
+ OpenSourceLicenseScreen(
+ dependency = dependency,
+ license = license,
+ onBackClick = onBackClick,
+ onUrlClick = {
+ viewModel.openLinkInBrowser(context, it)
+ },
+ onUrlsFound = {
+ val uris = it.map { uri -> uri.toUri() }
+ viewModel.mayLaunchUrl(*uris.toTypedArray())
+ }
+ )
+}
+
+@OptIn(ExperimentalLayoutApi::class, ExperimentalTextApi::class)
+@Composable
+fun OpenSourceLicenseScreen(
+ dependency: String,
+ license: String,
+ onBackClick: () -> 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 hasScrolled by remember {
+ derivedStateOf { scrollState.value > 0 }
+ }
+ val topBarElevation by animateDpAsState(
+ if (hasScrolled) 4.dp else 0.dp
+ )
+ Scaffold(topBar = {
+ TopAppBar(title = { Text(dependency, overflow = TextOverflow.Ellipsis, maxLines = 1) },
+ navigationIcon = {
+ IconButton(onClick = onBackClick) {
+ Icon(
+ Icons.Default.ArrowBack,
+ contentDescription = null
+ )
+ }
+ },
+ elevation = topBarElevation
+ )
+ }) { 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(horizontal = 16.dp)
+ .fillMaxSize()
+ .then(pressIndicator)
+ .verticalScroll(scrollState)
+ .padding(paddingValues)
+ .consumeWindowInsets(paddingValues),
+ 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.colors.secondary,
+ 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))
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material2/src/main/java/com/geekorum/aboutoss/ui/material/OpenSourceLicensesActivity.kt Thu Apr 13 17:37:13 2023 -0400
@@ -0,0 +1,81 @@
+/*
+ * AboutOss is a 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 <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.ui.material
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.Composable
+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
+
+class OpenSourceLicensesActivity : BaseOpensourceLicenseActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ MaterialTheme {
+ DependencyNavHost(
+ openSourceLicensesViewModel = viewModel,
+ navigateUp = {
+ onNavigateUp()
+ }
+ )
+ }
+ }
+ }
+}
+
+
+@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,
+ onBackClick = {
+ navController.popBackStack()
+ },
+ )
+ }
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/material2/src/test/java/com/geekorum/aboutoss/ui/material/ExampleUnitTest.kt Thu Apr 13 17:37:13 2023 -0400
@@ -0,0 +1,38 @@
+/*
+ * AboutOss is a 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 <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.ui.material
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * 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() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
--- a/ui/proguard-rules.pro Thu Apr 13 17:28:56 2023 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-# 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
--- a/ui/src/androidTest/java/com/geekorum/aboutoss/ui/ExampleInstrumentedTest.kt Thu Apr 13 17:28:56 2023 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * AboutOss is a 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 <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.ui
-
-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.test", appContext.packageName)
- }
-}
\ No newline at end of file
--- a/ui/src/main/java/com/geekorum/aboutoss/ui/OpenSourceDependenciesListScreen.kt Thu Apr 13 17:28:56 2023 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/*
- * AboutOss is a 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 <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.ui
-
-import androidx.compose.animation.core.animateDpAsState
-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.foundation.lazy.rememberLazyListState
-import androidx.compose.material.Divider
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.ListItem
-import androidx.compose.material.Scaffold
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-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
-
-@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
- )
-}
-
-@OptIn(ExperimentalMaterialApi::class)
-@Composable
-fun OpenSourceDependenciesListScreen(
- dependencies: List<String>,
- onDependencyClick: (String) -> Unit,
- onUpClick: () -> Unit
-) {
- val lazyListState = rememberLazyListState()
- val hasScrolled by remember {
- derivedStateOf {
- lazyListState.firstVisibleItemIndex != 0 || lazyListState.firstVisibleItemScrollOffset > 0
- }
- }
- val topBarElevation by animateDpAsState(
- if (hasScrolled) 4.dp else 0.dp
- )
- Scaffold(topBar = {
- TopAppBar(title = { Text(stringResource(commonR.string.title_oss_licenses)) },
- navigationIcon = {
- IconButton(onClick = onUpClick) {
- Icon(
- Icons.Default.ArrowBack,
- contentDescription = null
- )
- }
- },
- elevation = topBarElevation
- )
- }) {
- LazyColumn(Modifier.fillMaxSize(), state = lazyListState, contentPadding = it) {
- items(dependencies) {
- Column {
- ListItem(
- Modifier
- .height(64.dp)
- .clickable(onClick = { onDependencyClick(it) })
- ) {
- Text(
- it, modifier = Modifier.padding(horizontal = 16.dp),
- overflow = TextOverflow.Ellipsis, maxLines = 1
- )
- }
- Divider(Modifier.padding(horizontal = 16.dp))
- }
- }
- }
- }
-}
--- a/ui/src/main/java/com/geekorum/aboutoss/ui/OpenSourceLicenseScreen.kt Thu Apr 13 17:28:56 2023 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-/*
- * AboutOss is a 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 <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.ui
-
-import androidx.compose.animation.core.animateDpAsState
-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.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Scaffold
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.derivedStateOf
-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.pointer.pointerInput
-import androidx.compose.ui.platform.LocalContext
-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 androidx.core.net.toUri
-import com.geekorum.aboutoss.ui.common.OpenSourceLicensesViewModel
-
-@Composable
-fun OpenSourceLicenseScreen(
- viewModel: OpenSourceLicensesViewModel,
- dependency: String,
- onBackClick: () -> Unit,
-) {
- val context = LocalContext.current
- val license by viewModel.getLicenseDependency(dependency).collectAsState("")
- OpenSourceLicenseScreen(
- dependency = dependency,
- license = license,
- onBackClick = onBackClick,
- onUrlClick = {
- viewModel.openLinkInBrowser(context, it)
- },
- onUrlsFound = {
- val uris = it.map { uri -> uri.toUri() }
- viewModel.mayLaunchUrl(*uris.toTypedArray())
- }
- )
-}
-
-@OptIn(ExperimentalLayoutApi::class, ExperimentalTextApi::class)
-@Composable
-fun OpenSourceLicenseScreen(
- dependency: String,
- license: String,
- onBackClick: () -> 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 hasScrolled by remember {
- derivedStateOf { scrollState.value > 0 }
- }
- val topBarElevation by animateDpAsState(
- if (hasScrolled) 4.dp else 0.dp
- )
- Scaffold(topBar = {
- TopAppBar(title = { Text(dependency, overflow = TextOverflow.Ellipsis, maxLines = 1) },
- navigationIcon = {
- IconButton(onClick = onBackClick) {
- Icon(
- Icons.Default.ArrowBack,
- contentDescription = null
- )
- }
- },
- elevation = topBarElevation
- )
- }) { 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(horizontal = 16.dp)
- .fillMaxSize()
- .then(pressIndicator)
- .verticalScroll(scrollState)
- .padding(paddingValues)
- .consumeWindowInsets(paddingValues),
- 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.colors.secondary,
- 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/src/main/java/com/geekorum/aboutoss/ui/OpenSourceLicensesActivity.kt Thu Apr 13 17:28:56 2023 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/*
- * AboutOss is a 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 <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.ui
-
-import android.net.Uri
-import android.os.Bundle
-import androidx.activity.compose.setContent
-import androidx.compose.material.MaterialTheme
-import androidx.compose.runtime.Composable
-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
-
-class OpenSourceLicensesActivity : BaseOpensourceLicenseActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- MaterialTheme {
- DependencyNavHost(
- openSourceLicensesViewModel = viewModel,
- navigateUp = {
- onNavigateUp()
- }
- )
- }
- }
- }
-}
-
-
-@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,
- onBackClick = {
- navController.popBackStack()
- },
- )
- }
- }
-}
-
--- a/ui/src/test/java/com/geekorum/aboutoss/ui/ExampleUnitTest.kt Thu Apr 13 17:28:56 2023 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-/*
- * AboutOss is a 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 <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.ui
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * 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() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file