Welcome Screen: Language selection

This commit is contained in:
Stef Tervelde
2025-02-08 22:47:28 +01:00
parent b0c61a4467
commit 3d81b02ffe
6 changed files with 471 additions and 234 deletions
+2
View File
@@ -21,6 +21,7 @@ tasks.withType<JavaExec> {
systemProperty("processing.contributions.source", "https://contributions-preview.processing.org/contribs.txt")
systemProperty("processing.download.page", "https://processing.org/download/")
systemProperty("processing.download.latest", "https://processing.org/download/latest.txt")
systemProperty("processing.tutorials", "https://processing.org/tutorials/")
}
@@ -95,6 +96,7 @@ dependencies {
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(compose.materialIconsExtended)
implementation(compose.desktop.currentOs)
+112 -221
View File
@@ -2,135 +2,90 @@ package processing.app.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Checkbox
import androidx.compose.material.*
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.material.MaterialTheme.typography
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.res.loadImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.formdev.flatlaf.util.SystemInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.decodeToImageBitmap
import processing.app.Base
import processing.app.LocalPreferences
import processing.app.Messages
import processing.app.Platform
import processing.app.*
import processing.app.ui.components.LanguageChip
import processing.app.ui.components.examples.examples
import processing.app.ui.theme.*
import java.awt.Cursor
import java.io.File
import java.awt.Desktop
import java.io.IOException
import java.net.URI
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
import java.util.*
import javax.swing.SwingUtilities
import kotlin.io.path.exists
import kotlin.io.path.inputStream
import kotlin.io.path.isDirectory
class Welcome @Throws(IOException::class) constructor(base: Base) {
init {
SwingUtilities.invokeLater {
PDEWindow("menu.help.welcome", fullWindowContent = true) {
welcome()
CompositionLocalProvider(LocalBase provides base) {
welcome()
}
}
}
}
companion object {
val LocalBase = compositionLocalOf<Base?> { null }
@Composable
fun welcome() {
Row(
Column (
modifier = Modifier
.background(
Brush.linearGradient(
colorStops = arrayOf(0f to Color.Transparent, 1f to Color("#C0D7FF".toColorInt())),
start = Offset(815f, 0f),
end = Offset(815f* 2, 450f)
end = Offset(815f * 2, 450f)
)
)
.size(815.dp, 500.dp)
.padding(32.dp)
.padding(top = if(SystemInfo.isMacFullWindowContentSupported) 22.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(32.dp)
.padding(horizontal = 32.dp)
.padding(bottom = 32.dp)
.padding(top = if (SystemInfo.isMacFullWindowContentSupported) 22.dp else 0.dp),
){
Box(modifier = Modifier
.weight(1f)
.fillMaxHeight()
){
intro()
Column(
horizontalAlignment = Alignment.End,
modifier = Modifier
.align(Alignment.End)
) {
LanguageChip()
}
Column(modifier = Modifier
.weight(1.25f)
.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween
){
examples()
val locale = LocalLocale.current
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
Row(
horizontalArrangement = Arrangement.spacedBy(32.dp),
modifier = Modifier
.size(815.dp, 450.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
) {
chip {
Text(
text = locale["welcome.action.examples"],
)
}
chip {
Text(
text = locale["welcome.action.tutorials"],
)
}
intro()
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom
Column(
modifier = Modifier
.weight(1.25f)
.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.offset (-32.dp)
) {
val preferences = LocalPreferences.current
Checkbox(
checked = preferences["welcome.four.show"]?.equals("true") ?: false,
onCheckedChange = {
preferences.setProperty("welcome.four.show",it.toString())
},
modifier = Modifier
.size(24.dp)
)
Text(
text = locale["welcome.action.startup"],
)
}
PDEButton(onClick = { println("Open") }) {
val locale = LocalLocale.current
Text(locale["welcome.action.go"])
}
examples()
actions()
}
}
}
@@ -193,146 +148,82 @@ class Welcome @Throws(IOException::class) constructor(base: Base) {
}
}
data class Example(
val folder: Path,
val library: Path,
val path: String = library.resolve("examples").relativize(folder).toString(),
val title: String = folder.fileName.toString(),
val image: Path = folder.resolve("$title.png")
)
@Composable
fun loadExamples(): List<Example> {
val sketchbook = rememberSketchbookPath()
val resources = File(System.getProperty("compose.application.resources.dir") ?: "")
var examples by remember { mutableStateOf(emptyList<Example>()) }
val settingsFolder = Platform.getSettingsFolder()
val examplesCache = settingsFolder.resolve("examples.cache")
LaunchedEffect(sketchbook, resources){
if (!examplesCache.exists()) return@LaunchedEffect
withContext(Dispatchers.IO) {
examples = examplesCache.readText().lines().map {
val (library, folder) = it.split(",")
Example(
folder = File(folder).toPath(),
library = File(library).toPath()
)
}
}
}
LaunchedEffect(sketchbook, resources){
withContext(Dispatchers.IO) {
// TODO: Optimize
Messages.log("Start scanning for examples in $sketchbook and $resources")
// Folders that can contain contributions with examples
val scanned = listOf("libraries", "examples", "modes")
.flatMap { listOf(sketchbook.resolve(it), resources.resolve(it)) }
.filter { it.exists() && it.isDirectory() }
// Find contributions within those folders
.flatMap { Files.list(it.toPath()).toList() }
.filter { Files.isDirectory(it) }
// Find examples within those contributions
.flatMap { library ->
val fs = FileSystems.getDefault()
val matcher = fs.getPathMatcher("glob:**/*.pde")
val exampleFolders = mutableListOf<Path>()
val examples = library.resolve("examples")
if (!Files.exists(examples) || !examples.isDirectory()) return@flatMap emptyList()
Files.walkFileTree(library, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
if (matcher.matches(file)) {
exampleFolders.add(file.parent)
}
return FileVisitResult.CONTINUE
}
})
return@flatMap exampleFolders.map { folder ->
Example(
folder,
library,
)
}
}
.filter { it.image.exists() }
Messages.log("Done scanning for examples in $sketchbook and $resources")
if(scanned.isEmpty()) return@withContext
examples = scanned
examplesCache.writeText(examples.joinToString("\n") { "${it.library},${it.folder}" })
}
}
return examples
}
@Composable
fun rememberSketchbookPath(): File {
val preferences = LocalPreferences.current
val sketchbookPath = remember(preferences["sketchbook.path.four"]) {
preferences["sketchbook.path.four"] ?: Platform.getDefaultSketchbookFolder().toString()
}
return File(sketchbookPath)
}
@OptIn(ExperimentalResourceApi::class, ExperimentalComposeUiApi::class)
@Composable
fun examples(){
val examples = loadExamples()
// grab 4 random ones
val randoms = examples.shuffled().take(4)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
){
items(randoms){ example ->
Column(
fun actions(){
val locale = LocalLocale.current
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
val base = LocalBase.current
PDEChip(onClick = {
base?.defaultMode?.showExamplesFrame()
}) {
Text(
text = locale["welcome.action.examples"],
)
Image(
imageVector = Icons.AutoMirrored.Default.ArrowForward,
contentDescription = locale["welcome.action.tutorials"],
modifier = Modifier
.onPointerEvent(PointerEventType.Press) {
}
.onPointerEvent(PointerEventType.Release) {
}
.onPointerEvent(PointerEventType.Enter) {
}
.onPointerEvent(PointerEventType.Exit) {
}
.pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR)))
) {
val imageBitmap: ImageBitmap = remember(example.image) {
example.image.inputStream().readAllBytes().decodeToImageBitmap()
}
Image(
painter = BitmapPainter(imageBitmap),
contentDescription = example.title,
modifier = Modifier
.background(colors.primary)
.width(185.dp)
.aspectRatio(16f / 9f)
)
Text(example.title)
}
.padding(start = 8.dp)
.size(typography.body1.fontSize.value.dp)
)
}
PDEChip(onClick = {
if (!Desktop.isDesktopSupported()) return@PDEChip
val desktop = Desktop.getDesktop()
if(!desktop.isSupported(Desktop.Action.BROWSE)) return@PDEChip
try {
desktop.browse(URI(System.getProperty("processing.tutorials")))
} catch (e: Exception) {
e.printStackTrace()
}
}) {
Text(
text = locale["welcome.action.tutorials"],
)
Image(
imageVector = Icons.AutoMirrored.Default.ArrowForward,
contentDescription = locale["welcome.action.tutorials"],
modifier = Modifier
.padding(start = 8.dp)
.size(typography.body1.fontSize.value.dp)
)
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.offset(-32.dp)
) {
val preferences = LocalPreferences.current
Checkbox(
checked = preferences["welcome.four.show"]?.equals("true") ?: false,
onCheckedChange = {
preferences.setProperty("welcome.four.show", it.toString())
},
modifier = Modifier
.size(24.dp)
)
Text(
text = locale["welcome.action.startup"],
)
}
PDEButton(onClick = { println("Open") }) {
val locale = LocalLocale.current
Text(locale["welcome.action.go"])
}
}
}
@Composable
fun chip(content: @Composable () -> Unit){
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(colors.surface)
.border(1.dp, colors.primary, RoundedCornerShape(12.dp))
.padding(vertical = 4.dp, horizontal = 12.dp)
){
content()
}
}
@JvmStatic
fun main(args: Array<String>) {
@@ -0,0 +1,113 @@
package processing.app.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.MaterialTheme.typography
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Language
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import processing.app.Platform
import processing.app.ui.theme.LocalLocale
import processing.app.ui.theme.PDEChip
import processing.app.watchFile
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Paths
import java.util.*
import kotlin.io.path.inputStream
data class Language(
val name: String,
val code: String,
val locale: Locale,
val properties: Properties
)
@Composable
fun LanguageChip(){
var expanded by remember { mutableStateOf(false) }
val settingsFolder = Platform.getSettingsFolder()
val languageFile = File(settingsFolder, "language.txt")
watchFile(languageFile)
val main = ClassLoader.getSystemResource("PDE.properties")?: return
val languages = remember {
val list = when(main.protocol){
"file" -> {
val path = Paths.get(main.toURI())
Files.list(path.parent)
}
"jar" -> {
val uri = main.toURI()
val fs = FileSystems.newFileSystem(uri, emptyMap<String, Any>())
Files.list(fs.getPath("/"))
}
else -> null
} ?: return@remember null
list.toList()
.map { Pair(it, it.fileName.toString()) }
.filter { (_, fileName) -> fileName.startsWith("PDE_") && fileName.endsWith(".properties") }
.map { (path, _) ->
path.inputStream().reader(Charsets.UTF_8).use {
val properties = Properties()
properties.load(it)
val code = path.fileName.toString().removeSuffix(".properties").replace("PDE_", "")
val locale = Locale.forLanguageTag(code)
val name = locale.getDisplayName(locale)
return@map Language(
name,
code,
locale,
properties
)
}
}
} ?: return
val current = languageFile.readText(Charsets.UTF_8).substring(0, 2)
val currentLanguage = remember(current) { languages.find { it.code.startsWith(current) } ?: languages.first()}
val locale = LocalLocale.current
PDEChip(onClick = { expanded = !expanded }, leadingIcon = {
Image(
imageVector = Icons.Outlined.Language,
contentDescription = "Language",
modifier = Modifier
.padding(start = 8.dp)
.size(typography.body1.fontSize.value.dp)
)
}) {
Text(currentLanguage.name)
DropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
},
){
for (language in languages){
DropdownMenuItem(onClick = {
locale.set(language.locale)
expanded = false
}) {
Text(language.name)
}
}
}
}
}
@@ -0,0 +1,166 @@
package processing.app.ui.components.examples
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.decodeToImageBitmap
import processing.app.LocalPreferences
import processing.app.Messages
import processing.app.Platform
import java.awt.Cursor
import java.io.File
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
import kotlin.io.path.exists
import kotlin.io.path.inputStream
import kotlin.io.path.isDirectory
data class Example(
val folder: Path,
val library: Path,
val path: String = library.resolve("examples").relativize(folder).toString(),
val title: String = folder.fileName.toString(),
val image: Path = folder.resolve("$title.png")
)
@Composable
fun loadExamples(): List<Example> {
val sketchbook = rememberSketchbookPath()
val resources = File(System.getProperty("compose.application.resources.dir") ?: "")
var examples by remember { mutableStateOf(emptyList<Example>()) }
val settingsFolder = Platform.getSettingsFolder()
val examplesCache = settingsFolder.resolve("examples.cache")
LaunchedEffect(sketchbook, resources){
if (!examplesCache.exists()) return@LaunchedEffect
withContext(Dispatchers.IO) {
examples = examplesCache.readText().lines().map {
val (library, folder) = it.split(",")
Example(
folder = File(folder).toPath(),
library = File(library).toPath()
)
}
}
}
LaunchedEffect(sketchbook, resources){
withContext(Dispatchers.IO) {
// TODO: Optimize
Messages.log("Start scanning for examples in $sketchbook and $resources")
// Folders that can contain contributions with examples
val scanned = listOf("libraries", "examples", "modes")
.flatMap { listOf(sketchbook.resolve(it), resources.resolve(it)) }
.filter { it.exists() && it.isDirectory() }
// Find contributions within those folders
.flatMap { Files.list(it.toPath()).toList() }
.filter { Files.isDirectory(it) }
// Find examples within those contributions
.flatMap { library ->
val fs = FileSystems.getDefault()
val matcher = fs.getPathMatcher("glob:**/*.pde")
val exampleFolders = mutableListOf<Path>()
val examples = library.resolve("examples")
if (!Files.exists(examples) || !examples.isDirectory()) return@flatMap emptyList()
Files.walkFileTree(library, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
if (matcher.matches(file)) {
exampleFolders.add(file.parent)
}
return FileVisitResult.CONTINUE
}
})
return@flatMap exampleFolders.map { folder ->
Example(
folder,
library,
)
}
}
.filter { it.image.exists() }
Messages.log("Done scanning for examples in $sketchbook and $resources")
if(scanned.isEmpty()) return@withContext
examples = scanned
examplesCache.writeText(examples.joinToString("\n") { "${it.library},${it.folder}" })
}
}
return examples
}
@Composable
fun rememberSketchbookPath(): File {
val preferences = LocalPreferences.current
val sketchbookPath = remember(preferences["sketchbook.path.four"]) {
preferences["sketchbook.path.four"] ?: Platform.getDefaultSketchbookFolder().toString()
}
return File(sketchbookPath)
}
@OptIn(ExperimentalResourceApi::class, ExperimentalComposeUiApi::class)
@Composable
fun examples(){
val examples = loadExamples()
// grab 4 random ones
val randoms = examples.shuffled().take(4)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
){
items(randoms){ example ->
Column(
modifier = Modifier
.onPointerEvent(PointerEventType.Press) {
}
.onPointerEvent(PointerEventType.Release) {
}
.onPointerEvent(PointerEventType.Enter) {
}
.onPointerEvent(PointerEventType.Exit) {
}
.pointerHoverIcon(PointerIcon(Cursor(Cursor.HAND_CURSOR)))
) {
val imageBitmap: ImageBitmap = remember(example.image) {
example.image.inputStream().readAllBytes().decodeToImageBitmap()
}
Image(
painter = BitmapPainter(imageBitmap),
contentDescription = example.title,
modifier = Modifier
.background(colors.primary)
.width(185.dp)
.aspectRatio(16f / 9f)
)
Text(example.title)
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
package processing.app.ui.theme
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Chip
import androidx.compose.material.ChipColors
import androidx.compose.material.ChipDefaults
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PDEChip(
onClick: () -> Unit = {},
leadingIcon: @Composable (() -> Unit)? = null,
content: @Composable RowScope.() -> Unit
){
Chip(
onClick = onClick,
border = BorderStroke(1.dp, colors.onSurface.copy(alpha = 0.12f)),
colors = ChipDefaults.chipColors(
backgroundColor = Color.Transparent,
contentColor = colors.onSurface
),
leadingIcon = leadingIcon,
modifier = Modifier,
content = content
)
}
+39 -13
View File
@@ -1,8 +1,8 @@
package processing.app.ui.theme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import processing.app.LocalPreferences
import processing.app.Messages
import processing.app.Platform
@@ -12,13 +12,20 @@ import java.io.File
import java.io.InputStream
import java.util.*
class Locale(language: String = "") : Properties() {
class Locale(language: String = "", val setLocale: (java.util.Locale) -> Unit) : Properties() {
var locale: java.util.Locale = java.util.Locale.getDefault()
init {
val locale = java.util.Locale.getDefault()
load(ClassLoader.getSystemResourceAsStream("PDE.properties"))
load(ClassLoader.getSystemResourceAsStream("PDE_${locale.language}.properties") ?: InputStream.nullInputStream())
load(ClassLoader.getSystemResourceAsStream("PDE_${locale.toLanguageTag()}.properties") ?: InputStream.nullInputStream())
load(ClassLoader.getSystemResourceAsStream("PDE_${language}.properties") ?: InputStream.nullInputStream())
fun loadResourceUTF8(path: String) {
val stream = ClassLoader.getSystemResourceAsStream(path)
stream?.reader(charset = Charsets.UTF_8)?.use { reader ->
load(reader)
}
}
loadResourceUTF8("PDE.properties")
loadResourceUTF8("PDE_${locale.language}.properties")
loadResourceUTF8("PDE_${locale.toLanguageTag()}.properties")
loadResourceUTF8("PDE_${language}.properties")
}
@Deprecated("Use get instead", ReplaceWith("get(key)"))
@@ -28,8 +35,11 @@ class Locale(language: String = "") : Properties() {
return value
}
operator fun get(key: String): String = getProperty(key, key)
fun set(locale: java.util.Locale) {
setLocale(locale)
}
}
val LocalLocale = compositionLocalOf { Locale() }
val LocalLocale = compositionLocalOf<Locale> { error("No Locale Set") }
@Composable
fun LocaleProvider(content: @Composable () -> Unit) {
PlatformStart()
@@ -37,9 +47,25 @@ fun LocaleProvider(content: @Composable () -> Unit) {
val settingsFolder = Platform.getSettingsFolder()
val languageFile = File(settingsFolder, "language.txt")
watchFile(languageFile)
var code by remember{ mutableStateOf(languageFile.readText().substring(0, 2)) }
val locale = Locale(languageFile.readText().substring(0, 2))
CompositionLocalProvider(LocalLocale provides locale) {
content()
fun setLocale(locale: java.util.Locale) {
java.util.Locale.setDefault(locale)
languageFile.writeText(locale.language)
code = locale.language
}
val locale = Locale(code, ::setLocale)
Messages.log("Locale: $code")
val dir = when(locale["locale.direction"]) {
"rtl" -> LayoutDirection.Rtl
else -> LayoutDirection.Ltr
}
CompositionLocalProvider(LocalLayoutDirection provides dir) {
CompositionLocalProvider(LocalLocale provides locale) {
content()
}
}
}