0
0
mirror of https://github.com/florisboard/florisboard.git synced 2024-09-20 12:02:19 +02:00

Add ExtensionViewerScreen Ui

This commit is contained in:
Patrick Goldinger 2021-10-07 23:47:10 +02:00
parent 02de2411e1
commit ba712a5417
6 changed files with 295 additions and 4 deletions

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import dev.patrickgold.florisboard.common.launchUrl
import dev.patrickgold.florisboard.res.FlorisRef
@Composable
fun FlorisHyperlinkText(
text: String,
url: FlorisRef,
modifier: Modifier = Modifier,
enabled: Boolean = true,
color: Color = MaterialTheme.colors.primary,
) {
val context = LocalContext.current
Text(
modifier = modifier
.clickable(enabled = enabled) {
launchUrl(context, url.toString())
},
text = text,
color = color,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.ext
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.app.ui.components.FlorisChip
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ExtensionKeywordChip(
keyword: String,
modifier: Modifier = Modifier,
) {
FlorisChip(
modifier = modifier,
text = keyword,
enabled = false,
shape = RoundedCornerShape(4.dp),
)
}

View File

@ -0,0 +1,183 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.ext
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.FlorisHyperlinkText
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionConfig
import dev.patrickgold.florisboard.res.FlorisRef
import dev.patrickgold.florisboard.res.ext.Extension
import dev.patrickgold.florisboard.res.ext.ExtensionAuthor
import dev.patrickgold.florisboard.res.ext.ExtensionMeta
import dev.patrickgold.florisboard.res.isNotNullAndValid
@Composable
fun ExtensionViewerScreen(ext: Extension) = FlorisScreen(
title = ext.meta.title,
) {
val navController = LocalNavController.current
val context = LocalContext.current
Column(
modifier = Modifier.padding(horizontal = 16.dp),
) {
ext.meta.description?.let { Text(it) }
Spacer(modifier = Modifier.height(16.dp))
ExtensionMetaRowScrollableChips(
label = stringRes(R.string.ext__meta__authors),
showDividerAbove = false
) {
for ((n, author) in ext.meta.authors.withIndex()) {
if (n > 0) {
Spacer(modifier = Modifier.width(8.dp))
}
ExtensionAuthorChip(author)
}
}
ExtensionMetaRowSimpleText(label = stringRes(R.string.ext__meta__id)) {
Text(text = ext.meta.id)
}
ExtensionMetaRowSimpleText(label = stringRes(R.string.ext__meta__version)) {
Text(text = ext.meta.version)
}
if (ext.meta.keywords != null && ext.meta.keywords!!.isNotEmpty()) {
ExtensionMetaRowScrollableChips(label = stringRes(R.string.ext__meta__keywords)) {
for ((n, keyword) in ext.meta.keywords!!.withIndex()) {
if (n > 0) {
Spacer(modifier = Modifier.width(8.dp))
}
ExtensionKeywordChip(keyword)
}
}
}
if (ext.meta.homepage.isNotNullAndValid()) {
ExtensionMetaRowSimpleText(label = stringRes(R.string.ext__meta__homepage)) {
FlorisHyperlinkText(
text = ext.meta.homepage!!.authority,
url = ext.meta.homepage!!,
)
}
}
if (ext.meta.issueTracker.isNotNullAndValid()) {
ExtensionMetaRowSimpleText(label = stringRes(R.string.ext__meta__issue_tracker)) {
FlorisHyperlinkText(
text = ext.meta.issueTracker!!.authority,
url = ext.meta.issueTracker!!,
)
}
}
ExtensionMetaRowSimpleText(label = stringRes(R.string.ext__meta__license)) {
// TODO: display human-readable License name instead of
// SPDX identifier
Text(text = ext.meta.license)
}
}
}
@Composable
private fun ExtensionMetaRowSimpleText(
label: String,
modifier: Modifier = Modifier,
showDividerAbove: Boolean = true,
content: @Composable RowScope.() -> Unit,
) {
if (showDividerAbove) {
Divider()
}
Row(
modifier = modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(modifier = Modifier.padding(end = 24.dp), text = label)
content()
}
}
@Composable
private fun ExtensionMetaRowScrollableChips(
label: String,
modifier: Modifier = Modifier,
showDividerAbove: Boolean = true,
content: @Composable RowScope.() -> Unit,
) {
if (showDividerAbove) {
Divider()
}
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(modifier = Modifier.padding(end = 24.dp), text = label)
Row(
modifier = Modifier
.weight(1.0f, fill = false)
.horizontalScroll(rememberScrollState()),
) {
content()
}
}
}
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun PreviewExtensionViewerScreen() {
val testExtension = ThemeExtension(
meta = ExtensionMeta(
id = "com.example.theme.test",
version = "2.4.3",
title = "Test theme",
description = "This is a test theme to preview the extension viewer screen UI.",
keywords = listOf("Beach", "Sea", "Sun"),
homepage = FlorisRef.from("https://example.com"),
issueTracker = FlorisRef.from("https://git.example.com/issues"),
authors = listOf(
"Max Mustermann <max.mustermann@example.com> (maxmustermann.example.com)",
).map { ExtensionAuthor.fromOrTakeRaw(it) },
license = "apache-2.0",
),
dependencies = null,
theme = ThemeExtensionConfig(stylesheet = "test.json"),
)
ExtensionViewerScreen(ext = testExtension)
}

View File

@ -199,7 +199,7 @@ value class FlorisRef private constructor(val uri: Uri) {
* Returns if this URI contains data for all valid parts of a FlorisRef.
*/
val isValid: Boolean
get() = scheme.isNotBlank() && authority.isNotBlank() && relativePath.isNotBlank()
get() = scheme.isNotBlank() && authority.isNotBlank()
/**
* Returns if this URI contains data for all valid parts of a FlorisRef.
@ -296,3 +296,11 @@ value class FlorisRef private constructor(val uri: Uri) {
}
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun FlorisRef?.isNotNullAndValid(): Boolean {
contract {
returns(true) implies (this@isNotNullAndValid != null)
}
return this != null && this.isValid
}

View File

@ -16,6 +16,7 @@
package dev.patrickgold.florisboard.res.ext
import dev.patrickgold.florisboard.res.FlorisRef
import kotlinx.serialization.Serializable
/**
@ -70,12 +71,12 @@ class ExtensionMeta(
/**
* A link to the homepage of this extension or author.
*/
val homepage: String? = null,
val homepage: FlorisRef? = null,
/**
* A link to this extension's issue tracker.
*/
val issueTracker: String? = null,
val issueTracker: FlorisRef? = null,
/**
* A list of authors who actively worked on the content of this extension.

View File

@ -478,7 +478,18 @@
<string name="pref__clipboard__limit_history_size__label">Limit history size</string>
<string name="pref__clipboard__max_history_size__label">Max history size</string>
<!-- General -->
<!-- Extension strings -->
<string name="ext__meta__authors">Authors</string>
<string name="ext__meta__description">Description</string>
<string name="ext__meta__homepage">Homepage</string>
<string name="ext__meta__id">ID</string>
<string name="ext__meta__issue_tracker">Issue tracker</string>
<string name="ext__meta__keywords">Keywords</string>
<string name="ext__meta__license">License</string>
<string name="ext__meta__title">Title</string>
<string name="ext__meta__version">Version</string>
<!-- General strings -->
<string name="general__no_browser_app_found_for_url">No browser app found for handling URL {url}</string>
<!-- Screen orientation strings -->