Add LicenseFragment
This commit is contained in:
1 parent
92240c6c2c
commit
9ad933934f
11 files changed
+407
No files matched your search
@@ -59,6 +59,7 @@ open class BasePreferenceFragment :
|
||||
"mytags" -> MyTagsFragment()
|
||||
"uconfig" -> UConfigFragment()
|
||||
"hosts" -> HostsFragment()
|
||||
"license" -> LicenseFragment()
|
||||
else -> null
|
||||
}
|
||||
fragment?.let {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.hippo.ehviewer.ui.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.ui.compose.setMD3Content
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
|
||||
|
||||
class LicenseFragment : BaseFragment() {
|
||||
override fun getFragmentTitle(): Int {
|
||||
return R.string.license
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
return ComposeView(inflater.context).apply {
|
||||
setMD3Content {
|
||||
LibrariesContainer(modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.mikepenz.aboutlibraries.ui.compose
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.text.HtmlCompat
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent
|
||||
import com.mikepenz.aboutlibraries.util.withContext
|
||||
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().withContext(context).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<Libs?>(null) {
|
||||
value = withContext(Dispatchers.IO) {
|
||||
librariesBlock(context)
|
||||
}
|
||||
}
|
||||
|
||||
val libs = libraries.value?.libraries
|
||||
if (libs != null) {
|
||||
val openDialog = remember { mutableStateOf<Library?>(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(library)
|
||||
} else if (!library.licenses.firstOrNull()?.htmlReadyLicenseContent.isNullOrBlank()) {
|
||||
openDialog.value = library
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val library = openDialog.value
|
||||
if (library != null) {
|
||||
LicenseDialog(library = library) {
|
||||
openDialog.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT) })
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.mikepenz.aboutlibraries.ui.compose
|
||||
|
||||
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.material3.Badge
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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 com.mikepenz.aboutlibraries.entity.Library
|
||||
import com.mikepenz.aboutlibraries.ui.compose.util.author
|
||||
|
||||
/**
|
||||
* Displays all provided libraries in a simple list.
|
||||
*/
|
||||
@Composable
|
||||
fun Libraries(
|
||||
libraries: List<Library>,
|
||||
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<Library>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
if (showLicenseBadges && library.licenses.isNotEmpty()) {
|
||||
Row {
|
||||
library.licenses.forEach {
|
||||
Badge(
|
||||
modifier = Modifier.padding(padding.badgePadding),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(padding.badgeContentPadding),
|
||||
text = it.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(0.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
|
||||
@@ -0,0 +1,13 @@
|
||||
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", "<br />")
|
||||
|
||||
val License.strippedLicenseContent: String?
|
||||
get() = licenseContent?.replace("<br />", "\n")?.replace("<br/>", "\n")
|
||||
Reference in new issue
Block a user