|
|
|
|
@@ -0,0 +1,147 @@
|
|
|
|
|
package de.jeanlucmakiola.floret.components
|
|
|
|
|
|
|
|
|
|
import android.content.Intent
|
|
|
|
|
import android.net.Uri
|
|
|
|
|
import androidx.annotation.XmlRes
|
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
|
import androidx.compose.foundation.layout.PaddingValues
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
|
import androidx.compose.foundation.layout.width
|
|
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
|
|
import androidx.compose.material3.FilledTonalButton
|
|
|
|
|
import androidx.compose.material3.Icon
|
|
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
|
|
import androidx.compose.material3.OutlinedButton
|
|
|
|
|
import androidx.compose.material3.Surface
|
|
|
|
|
import androidx.compose.material3.Text
|
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
|
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.graphics.vector.ImageVector
|
|
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
import de.jeanlucmakiola.floret.locale.AppLanguage
|
|
|
|
|
|
|
|
|
|
/** A labelled, icon-led external link rendered as a button in [AboutCard]. */
|
|
|
|
|
data class AboutLink(val icon: ImageVector, val label: String, val url: String)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The "about this app" card shared across the family: the app's [logo] beside
|
|
|
|
|
* its [appName] and [author], then [primaryLinks] as a row of equal-width
|
|
|
|
|
* outlined buttons (e.g. source, licence) and an optional full-width
|
|
|
|
|
* [highlightLink] tonal button (e.g. donate). Each link opens its URL in the
|
|
|
|
|
* browser. The app supplies its own logo, strings and links.
|
|
|
|
|
*/
|
|
|
|
|
@Composable
|
|
|
|
|
fun AboutCard(
|
|
|
|
|
logo: @Composable () -> Unit,
|
|
|
|
|
appName: String,
|
|
|
|
|
author: String,
|
|
|
|
|
primaryLinks: List<AboutLink>,
|
|
|
|
|
highlightLink: AboutLink? = null,
|
|
|
|
|
) {
|
|
|
|
|
val context = LocalContext.current
|
|
|
|
|
val open = { url: String ->
|
|
|
|
|
runCatching { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) }
|
|
|
|
|
Unit
|
|
|
|
|
}
|
|
|
|
|
Surface(
|
|
|
|
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
|
|
|
|
shape = RoundedCornerShape(24.dp),
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
) {
|
|
|
|
|
Column(Modifier.fillMaxWidth().padding(16.dp)) {
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
logo()
|
|
|
|
|
Spacer(Modifier.width(16.dp))
|
|
|
|
|
Column(Modifier.weight(1f)) {
|
|
|
|
|
Text(appName, style = MaterialTheme.typography.titleLarge)
|
|
|
|
|
Text(
|
|
|
|
|
text = author,
|
|
|
|
|
style = MaterialTheme.typography.bodyMedium,
|
|
|
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (primaryLinks.isNotEmpty()) {
|
|
|
|
|
Spacer(Modifier.height(12.dp))
|
|
|
|
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
|
|
|
primaryLinks.forEach { link ->
|
|
|
|
|
OutlinedButton(
|
|
|
|
|
onClick = { open(link.url) },
|
|
|
|
|
contentPadding = PaddingValues(horizontal = 12.dp),
|
|
|
|
|
modifier = Modifier.weight(1f),
|
|
|
|
|
) {
|
|
|
|
|
Icon(link.icon, contentDescription = null, modifier = Modifier.size(18.dp))
|
|
|
|
|
Spacer(Modifier.width(8.dp))
|
|
|
|
|
Text(link.label)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (highlightLink != null) {
|
|
|
|
|
Spacer(Modifier.height(8.dp))
|
|
|
|
|
FilledTonalButton(onClick = { open(highlightLink.url) }, modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
Icon(highlightLink.icon, contentDescription = null, modifier = Modifier.size(18.dp))
|
|
|
|
|
Spacer(Modifier.width(8.dp))
|
|
|
|
|
Text(highlightLink.label)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A settings row that opens a full-screen language picker, backed by
|
|
|
|
|
* [AppLanguage]. The choice is mirrored locally so the row updates instantly
|
|
|
|
|
* before the activity recreation lands. [autoLabel] names the follow-the-system
|
|
|
|
|
* option (the `null` tag); [localesConfig] is the app's `res/xml/locales_config`;
|
|
|
|
|
* [leading] is the row's icon.
|
|
|
|
|
*/
|
|
|
|
|
@Composable
|
|
|
|
|
fun LanguagePickerRow(
|
|
|
|
|
position: Position,
|
|
|
|
|
title: String,
|
|
|
|
|
autoLabel: String,
|
|
|
|
|
@XmlRes localesConfig: Int,
|
|
|
|
|
leading: @Composable () -> Unit,
|
|
|
|
|
) {
|
|
|
|
|
val context = LocalContext.current
|
|
|
|
|
var current by remember { mutableStateOf(AppLanguage.currentTag()) }
|
|
|
|
|
var showDialog by remember { mutableStateOf(false) }
|
|
|
|
|
// null = follow the system; the rest are BCP-47 tags from locales_config.xml.
|
|
|
|
|
val options = remember { listOf<String?>(null) + AppLanguage.supportedTags(context, localesConfig) }
|
|
|
|
|
val label: (String?) -> String = { tag -> tag?.let { AppLanguage.displayName(it) } ?: autoLabel }
|
|
|
|
|
|
|
|
|
|
GroupedRow(
|
|
|
|
|
title = title,
|
|
|
|
|
summary = label(current),
|
|
|
|
|
position = position,
|
|
|
|
|
leading = leading,
|
|
|
|
|
onClick = { showDialog = true },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (showDialog) {
|
|
|
|
|
OptionPicker(
|
|
|
|
|
title = title,
|
|
|
|
|
options = options,
|
|
|
|
|
selected = current,
|
|
|
|
|
label = { label(it) },
|
|
|
|
|
onSelect = {
|
|
|
|
|
current = it
|
|
|
|
|
AppLanguage.apply(it)
|
|
|
|
|
},
|
|
|
|
|
onDismiss = { showDialog = false },
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|