diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ead682e70..52fe98eb8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -242,6 +242,7 @@ dependencies { implementation(libs.diff) implementation(libs.aboutlibraries.core) + implementation(libs.aboutlibraries.compose.m3) implementation(libs.insetter) // Dead Dependency diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/LicenseScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/LicenseScreen.kt index f24740f6f..2c4bfece2 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/LicenseScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/LicenseScreen.kt @@ -1,9 +1,11 @@ package com.hippo.ehviewer.ui.settings +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack 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 @@ -11,9 +13,13 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import com.hippo.ehviewer.R -import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer +import com.hippo.ehviewer.ui.openBrowser +import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer +import com.mikepenz.aboutlibraries.ui.compose.m3.LibraryDefaults import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator @@ -21,6 +27,7 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator @Composable fun LicenseScreen(navigator: DestinationsNavigator) { val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val context = LocalContext.current Scaffold( topBar = { TopAppBar( @@ -33,7 +40,15 @@ fun LicenseScreen(navigator: DestinationsNavigator) { scrollBehavior = scrollBehavior, ) }, - ) { - LibrariesContainer(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), contentPadding = it) + ) { paddingValues -> + LibrariesContainer( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + contentPadding = paddingValues, + colors = LibraryDefaults.libraryColors(badgeBackgroundColor = MaterialTheme.colorScheme.tertiary), + padding = LibraryDefaults.libraryPadding(badgeContentPadding = PaddingValues(4.dp)), + onLibraryClick = { library -> + library.library.website?.let { context.openBrowser(it) } + }, + ) } } diff --git a/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/Libraries.kt b/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/Libraries.kt deleted file mode 100644 index 49bbc0208..000000000 --- a/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/Libraries.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.mikepenz.aboutlibraries.ui.compose - -import android.content.Context -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.produceState -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.hippo.ehviewer.R -import com.hippo.ehviewer.ui.openBrowser -import com.mikepenz.aboutlibraries.Libs -import com.mikepenz.aboutlibraries.entity.Library -import com.mikepenz.aboutlibraries.util.withJson -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -/** - * Displays all provided libraries in a simple list. - */ -@Composable -fun LibrariesContainer( - modifier: Modifier = Modifier, - lazyListState: LazyListState = rememberLazyListState(), - contentPadding: PaddingValues = PaddingValues(0.dp), - librariesBlock: (Context) -> Libs = { context -> - Libs.Builder().withJson(context, R.raw.aboutlibraries).build() - }, - showAuthor: Boolean = true, - showVersion: Boolean = true, - showLicenseBadges: Boolean = true, - padding: LibraryPadding = LibraryDefaults.libraryPadding(), - itemContentPadding: PaddingValues = LibraryDefaults.ContentPadding, - itemSpacing: Dp = LibraryDefaults.LibraryItemSpacing, - header: (LazyListScope.() -> Unit)? = null, - onLibraryClick: ((Library) -> Unit)? = null, -) { - val context = LocalContext.current - val libraries = produceState(null) { - value = withContext(Dispatchers.IO) { - librariesBlock(context) - } - } - - val libs = libraries.value?.libraries?.sortedBy { it.name.lowercase() } - if (libs != null) { - Libraries( - libraries = libs, - modifier = modifier, - lazyListState = lazyListState, - contentPadding = contentPadding, - showAuthor = showAuthor, - showVersion = showVersion, - showLicenseBadges = showLicenseBadges, - padding = padding, - itemContentPadding = itemContentPadding, - itemSpacing = itemSpacing, - header = header, - onLibraryClick = { library -> - if (onLibraryClick != null) { - onLibraryClick.invoke(library) - } else { - library.website?.let { context.openBrowser(it) } - } - }, - ) - } -} diff --git a/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/SharedLibraries.kt b/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/SharedLibraries.kt deleted file mode 100644 index 84e37bbf0..000000000 --- a/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/SharedLibraries.kt +++ /dev/null @@ -1,266 +0,0 @@ -package com.mikepenz.aboutlibraries.ui.compose - -import android.widget.TextView -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SuggestionChip -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.Typography -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.text.HtmlCompat -import androidx.core.text.parseAsHtml -import com.mikepenz.aboutlibraries.entity.Library -import com.mikepenz.aboutlibraries.ui.compose.util.author -import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent - -/** - * Displays all provided libraries in a simple list. - */ -@Composable -fun Libraries( - libraries: List, - modifier: Modifier = Modifier, - lazyListState: LazyListState = rememberLazyListState(), - contentPadding: PaddingValues = PaddingValues(0.dp), - showAuthor: Boolean = true, - showVersion: Boolean = true, - showLicenseBadges: Boolean = true, - padding: LibraryPadding = LibraryDefaults.libraryPadding(), - itemContentPadding: PaddingValues = LibraryDefaults.ContentPadding, - itemSpacing: Dp = LibraryDefaults.LibraryItemSpacing, - header: (LazyListScope.() -> Unit)? = null, - onLibraryClick: ((Library) -> Unit)? = null, -) { - LazyColumn( - modifier, - verticalArrangement = Arrangement.spacedBy(itemSpacing), - state = lazyListState, - contentPadding = contentPadding, - ) { - header?.invoke(this) - libraryItems( - libraries, - showAuthor, - showVersion, - showLicenseBadges, - padding, - itemContentPadding, - ) { - onLibraryClick?.invoke(it) - } - } -} - -internal inline fun LazyListScope.libraryItems( - libraries: List, - showAuthor: Boolean = true, - showVersion: Boolean = true, - showLicenseBadges: Boolean = true, - padding: LibraryPadding, - itemContentPadding: PaddingValues = LibraryDefaults.ContentPadding, - crossinline onLibraryClick: ((Library) -> Unit), -) { - items(libraries) { library -> - Library( - library, - showAuthor, - showVersion, - showLicenseBadges, - padding, - itemContentPadding, - ) { - onLibraryClick.invoke(library) - } - } -} - -@Composable -internal fun Library( - library: Library, - showAuthor: Boolean = true, - showVersion: Boolean = true, - showLicenseBadges: Boolean = true, - padding: LibraryPadding = LibraryDefaults.libraryPadding(), - contentPadding: PaddingValues = LibraryDefaults.ContentPadding, - typography: Typography = MaterialTheme.typography, - onClick: () -> Unit, -) { - Column(modifier = Modifier.fillMaxWidth().clickable { onClick.invoke() }.padding(contentPadding)) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Text( - text = library.name, - modifier = Modifier.padding(padding.namePadding).weight(1f), - style = typography.titleMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - val version = library.artifactVersion - if (version != null && showVersion) { - Text( - version, - modifier = Modifier.padding(padding.versionPadding), - style = typography.bodyMedium, - textAlign = TextAlign.Center, - ) - } - } - val author = library.author - if (showAuthor && author.isNotBlank()) { - Text( - text = author, - style = typography.bodyMedium, - ) - } - var openDialog by remember { mutableStateOf(false) } - if (openDialog) { - LicenseDialog(library = library) { - openDialog = false - } - } - if (showLicenseBadges && library.licenses.isNotEmpty()) { - Row { - library.licenses.forEach { - SuggestionChip( - onClick = { openDialog = true }, - label = { Text(text = it.name) }, - ) - } - } - } - } -} - -@Composable -fun LicenseDialog( - library: Library, - onDismiss: () -> Unit, -) { - val scrollState = rememberScrollState() - AlertDialog( - onDismissRequest = onDismiss, - confirmButton = { - TextButton(onClick = onDismiss) { - Text(stringResource(id = android.R.string.ok)) - } - }, - text = { - Column(modifier = Modifier.verticalScroll(scrollState)) { - HtmlText(html = library.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty()) - } - }, - ) -} - -@Composable -fun HtmlText( - html: String, - modifier: Modifier = Modifier, -) { - AndroidView( - modifier = modifier, - factory = { context -> TextView(context) }, - update = { it.text = html.parseAsHtml(flags = HtmlCompat.FROM_HTML_MODE_COMPACT) }, - ) -} - -/** - * Contains the default values used by [Library] - */ -object LibraryDefaults { - private val LibraryItemPadding = 16.dp - private val LibraryNamePaddingTop = 4.dp - private val LibraryVersionPaddingStart = 8.dp - private val LibraryBadgePaddingTop = 8.dp - private val LibraryBadgePaddingEnd = 4.dp - internal val LibraryItemSpacing = 0.dp - - /** - * The default content padding used by [Library] - */ - val ContentPadding = PaddingValues(LibraryItemPadding) - - /** - * Creates a [LibraryPadding] that represents the default paddings used in a [Library] - * - * @param namePadding the padding around the name shown as part of a [Library] - * @param versionPadding the padding around the version shown as part of a [Library] - * @param badgePadding the padding around a badge element shown as part of a [Library] - * @param badgeContentPadding the padding around the content of a badge element shown as part of a [Library] - */ - @Composable - fun libraryPadding( - namePadding: PaddingValues = PaddingValues(top = LibraryNamePaddingTop), - versionPadding: PaddingValues = PaddingValues(start = LibraryVersionPaddingStart), - badgePadding: PaddingValues = PaddingValues( - top = LibraryBadgePaddingTop, - end = LibraryBadgePaddingEnd, - ), - badgeContentPadding: PaddingValues = PaddingValues(4.dp), - ): LibraryPadding = DefaultLibraryPadding( - namePadding = namePadding, - versionPadding = versionPadding, - badgePadding = badgePadding, - badgeContentPadding = badgeContentPadding, - ) -} - -/** - * Represents the padding values used in a library. - */ -@Stable -interface LibraryPadding { - /** Represents the padding around the name shown as part of a [Library] */ - val namePadding: PaddingValues - - /** Represents the padding around the version shown as part of a [Library] */ - val versionPadding: PaddingValues - - /** Represents the padding around a badge element shown as part of a [Library] */ - val badgePadding: PaddingValues - - /** Represents the padding around the content of a badge element shown as part of a [Library] */ - val badgeContentPadding: PaddingValues -} - -/** - * Default [LibraryPadding]. - */ -@Immutable -private class DefaultLibraryPadding( - override val namePadding: PaddingValues, - override val versionPadding: PaddingValues, - override val badgePadding: PaddingValues, - override val badgeContentPadding: PaddingValues, -) : LibraryPadding diff --git a/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/util/Extensions.kt b/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/util/Extensions.kt deleted file mode 100644 index 7fa52d03b..000000000 --- a/app/src/main/kotlin/com/mikepenz/aboutlibraries/ui/compose/util/Extensions.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.mikepenz.aboutlibraries.ui.compose.util - -import com.mikepenz.aboutlibraries.entity.Library -import com.mikepenz.aboutlibraries.entity.License - -val Library.author: String - get() = developers.takeIf { it.isNotEmpty() }?.map { it.name }?.joinToString(", ") ?: organization?.name ?: "" - -val License.htmlReadyLicenseContent: String? - get() = licenseContent?.replace("\n", "
") - -val License.strippedLicenseContent: String? - get() = licenseContent?.replace("
", "\n")?.replace("
", "\n") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cf8583dd3..84da9ca33 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,6 +34,7 @@ splitties = "3.0.0" [libraries] aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlibraries" } +aboutlibraries-compose-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlibraries" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }