From f6eaccff341b3f3a72665273942ba0198c53812d Mon Sep 17 00:00:00 2001 From: fflopsi Date: Wed, 14 Feb 2024 17:48:05 +0100 Subject: [PATCH] Update dependencies, remove moko-resources --- androidApp/build.gradle.kts | 10 +-- build.gradle.kts | 1 - common/build.gradle.kts | 31 ++++---- .../tournamentscompose/common/AndroidApp.kt | 30 +++++--- .../common/ui/AppSettings.kt | 66 ++++++++-------- .../common/ui/ImportExport.kt | 28 ++++--- .../common/ui/TournamentList.kt | 58 +++++++------- .../common/ui/TournamentViewer.kt | 75 ++++++++++--------- .../values-de}/strings.xml | 9 ++- .../values}/strings.xml | 25 ++++--- .../common/ui/GameEditor.kt | 69 +++++++++-------- .../common/ui/GameViewer.kt | 21 +++--- .../common/ui/PlayerViewer.kt | 25 ++++--- .../common/ui/PlayersEditor.kt | 28 ++++--- .../tournamentscompose/common/ui/Shared.kt | 58 +++++++------- .../common/ui/TournamentEditor.kt | 74 ++++++++++-------- .../common/ui/TournamentListContent.kt | 16 ++-- .../common/ui/TournamentViewerContent.kt | 23 +++--- .../commonMain/resources/MR/base/plurals.xml | 11 --- .../commonMain/resources/MR/de/plurals.xml | 11 --- .../common/ui/AppSettings.kt | 59 ++++++++------- .../common/ui/TournamentList.kt | 21 +++--- .../common/ui/TournamentViewer.kt | 52 ++++++------- desktopApp/build.gradle.kts | 1 - settings.gradle.kts | 8 +- 25 files changed, 430 insertions(+), 380 deletions(-) rename common/src/commonMain/{resources/MR/de => composeResources/values-de}/strings.xml (97%) rename common/src/commonMain/{resources/MR/base => composeResources/values}/strings.xml (97%) delete mode 100644 common/src/commonMain/resources/MR/base/plurals.xml delete mode 100644 common/src/commonMain/resources/MR/de/plurals.xml diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 00681a9..598b0a3 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -51,7 +51,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.4.8" + kotlinCompilerExtensionVersion = "1.5.9" } packaging { resources { @@ -61,13 +61,13 @@ android { } dependencies { - implementation("androidx.activity:activity-compose:1.7.2") + implementation("androidx.activity:activity-compose:1.8.2") implementation(project(":common")) testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") - androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.5.0") - debugImplementation("androidx.compose.ui:ui-tooling:1.5.0") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.5.0") + androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.1") + debugImplementation("androidx.compose.ui:ui-tooling:1.6.1") + debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.1") } diff --git a/build.gradle.kts b/build.gradle.kts index a8b4147..0926583 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,5 +5,4 @@ plugins { id("com.android.library") apply false id("org.jetbrains.kotlin.android") apply false id("org.jetbrains.compose") apply false - id("dev.icerock.mobile.multiplatform-resources") apply false } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 1d43d7d..3e2c3d0 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -2,17 +2,16 @@ plugins { kotlin("multiplatform") id("org.jetbrains.compose") id("com.android.library") - id("dev.icerock.mobile.multiplatform-resources") id("kotlin-kapt") id("kotlin-parcelize") } kotlin { jvmToolchain((property("tournamentscompose.versions.java") as String).toInt()) - android() + androidTarget() jvm() sourceSets { - val decomposeVersion = "2.0.1" + val decomposeVersion = "2.2.2" commonMain { dependencies { implementation(compose.runtime) @@ -22,9 +21,6 @@ kotlin { @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation( compose.components.resources ) - val resourcesVersion = "0.23.0" - implementation("dev.icerock.moko:resources:$resourcesVersion") - implementation("dev.icerock.moko:resources-compose:$resourcesVersion") implementation("com.google.code.gson:gson:2.10.1") implementation("com.arkivanov.decompose:decompose:$decomposeVersion") implementation("com.arkivanov.decompose:extensions-compose-jetbrains:$decomposeVersion") @@ -32,27 +28,26 @@ kotlin { } val androidMain by getting { dependencies { - val composeBom = platform("androidx.compose:compose-bom:2023.08.00") - implementation(composeBom) + implementation(project.dependencies.platform("androidx.compose:compose-bom:2024.02.00")) implementation("androidx.compose.ui:ui") //implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.runtime:runtime-livedata") implementation("androidx.compose.material3:material3") implementation("androidx.datastore:datastore-preferences:1.0.0") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1") - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") - implementation("androidx.navigation:navigation-compose:2.7.0") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") + implementation("androidx.navigation:navigation-compose:2.7.7") - val roomVersion = "2.5.2" + val roomVersion = "2.6.1" implementation("androidx.room:room-runtime:$roomVersion") implementation("androidx.room:room-ktx:$roomVersion") //annotationProcessor("androidx.room:room-compiler:$roomVersion") configurations["kapt"].dependencies.add(project.dependencies.create("androidx.room:room-compiler:$roomVersion")) implementation("com.google.accompanist:accompanist-systemuicontroller:0.30.1") - implementation("com.arkivanov.decompose:decompose:$decomposeVersion") - implementation("com.arkivanov.decompose:extensions-compose-jetbrains:$decomposeVersion") +// implementation("com.arkivanov.decompose:decompose:$decomposeVersion") +// implementation("com.arkivanov.decompose:extensions-compose-jetbrains:$decomposeVersion") } } val jvmMain by getting { @@ -63,14 +58,16 @@ kotlin { val multiplatformSettingsVersion = "1.0.0" implementation("com.russhwolf:multiplatform-settings:$multiplatformSettingsVersion") implementation("com.russhwolf:multiplatform-settings-coroutines:$multiplatformSettingsVersion") +// implementation("com.arkivanov.decompose:decompose:$decomposeVersion") +// implementation("com.arkivanov.decompose:extensions-compose-jetbrains:$decomposeVersion") } } } } -multiplatformResources { - multiplatformResourcesPackage = "me.frauenfelderflorian.tournamentscompose.common" -} +//multiplatformResources { +// multiplatformResourcesPackage = "me.frauenfelderflorian.tournamentscompose.common" +//} android { namespace = "me.frauenfelderflorian.tournamentscompose.common" diff --git a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/AndroidApp.kt b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/AndroidApp.kt index 18baadc..d480a4e 100644 --- a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/AndroidApp.kt +++ b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/AndroidApp.kt @@ -29,7 +29,7 @@ import androidx.room.Room import com.arkivanov.decompose.defaultComponentContext import com.arkivanov.decompose.router.stack.StackNavigation import com.google.accompanist.systemuicontroller.rememberSystemUiController -import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.launch import me.frauenfelderflorian.tournamentscompose.common.data.PlayersModel import me.frauenfelderflorian.tournamentscompose.common.data.Prefs import me.frauenfelderflorian.tournamentscompose.common.data.PrefsFactory @@ -39,6 +39,9 @@ import me.frauenfelderflorian.tournamentscompose.common.ui.ProvideComponentConte import me.frauenfelderflorian.tournamentscompose.common.ui.Screen import me.frauenfelderflorian.tournamentscompose.common.ui.importFromUri import me.frauenfelderflorian.tournamentscompose.common.ui.theme.TournamentsTheme +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res fun androidApp(activity: ComponentActivity) { WindowCompat.setDecorFitsSystemWindows(activity.window, false) @@ -48,6 +51,7 @@ fun androidApp(activity: ComponentActivity) { } } +@OptIn(ExperimentalResourceApi::class) @Composable fun AndroidAppContent(intent: Intent) { val context = LocalContext.current @@ -97,21 +101,23 @@ fun AndroidAppContent(intent: Intent) { showedImport = true }, icon = { Icon(Icons.Default.ArrowDownward, null) }, - title = { Text(stringResource(MR.strings.import_)) }, - text = { Text(stringResource(MR.strings.import_info)) }, + title = { Text(stringResource(Res.string.import)) }, + text = { Text(stringResource(Res.string.import_info)) }, confirmButton = { TextButton({ showImport = false - importFromUri( - uri = intent.data, - context = context, - scope = scope, - tournamentDao = tournamentDao, - gameDao = gameDao, - ) + scope.launch { + importFromUri( + uri = intent.data, + context = context, + scope = scope, + tournamentDao = tournamentDao, + gameDao = gameDao, + ) + } showedImport = true }) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { @@ -119,7 +125,7 @@ fun AndroidAppContent(intent: Intent) { showImport = false showedImport = true }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) diff --git a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/AppSettings.kt b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/AppSettings.kt index 9b03d2e..fdb4f98 100644 --- a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/AppSettings.kt +++ b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/AppSettings.kt @@ -21,10 +21,10 @@ import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.LightMode import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.outlined.Info -import androidx.compose.material3.Divider import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar @@ -52,13 +52,17 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontStyle import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.launch -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.PlayersModel import me.frauenfelderflorian.tournamentscompose.common.data.Prefs +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.getString +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, + ExperimentalResourceApi::class +) @Composable actual fun AppSettings( navigator: StackNavigation, @@ -85,11 +89,11 @@ actual fun AppSettings( Scaffold( topBar = { LargeTopAppBar( - title = { TopAppBarTitle(stringResource(MR.strings.settings), scrollBehavior) }, + title = { TopAppBarTitle(stringResource(Res.string.settings), scrollBehavior) }, navigationIcon = { BackButton(navigator) }, actions = { IconButton({ showInfo.value = true }) { - Icon(Icons.Outlined.Info, stringResource(MR.strings.about)) + Icon(Icons.Outlined.Info, stringResource(Res.string.about)) } }, scrollBehavior = scrollBehavior, @@ -105,12 +109,12 @@ actual fun AppSettings( Tab( selected = pagerState.currentPage == 0, onClick = { scope.launch { pagerState.animateScrollToPage(0) } }, - text = { Text(stringResource(MR.strings.app)) }, + text = { Text(stringResource(Res.string.app)) }, ) Tab( selected = pagerState.currentPage == 1, onClick = { scope.launch { pagerState.animateScrollToPage(1) } }, - text = { Text(stringResource(MR.strings.new_tournaments)) }, + text = { Text(stringResource(Res.string.new_tournaments)) }, ) } HorizontalPager(state = pagerState) { page -> @@ -126,15 +130,15 @@ actual fun AppSettings( ) { Column(Modifier.weight(2f)) { Text( - text = stringResource(MR.strings.choose_theme), + text = stringResource(Res.string.choose_theme), style = titleStyle ) Text( text = stringResource( when (prefs.theme) { - 1 -> MR.strings.light - 2 -> MR.strings.dark - else -> MR.strings.auto + 1 -> Res.string.light + 2 -> Res.string.dark + else -> Res.string.auto } ), style = detailsStyle, @@ -147,7 +151,7 @@ actual fun AppSettings( onDismissRequest = { themeSelectorExpanded = false }, ) { DropdownMenuItem( - text = { Text(stringResource(MR.strings.auto)) }, + text = { Text(stringResource(Res.string.auto)) }, onClick = { prefs.theme = 0 }, leadingIcon = { Icon( @@ -158,34 +162,34 @@ actual fun AppSettings( if (prefs.theme == 0) { Icon( Icons.Default.Check, - stringResource(MR.strings.active) + stringResource(Res.string.active) ) } }, ) - Divider() + HorizontalDivider() DropdownMenuItem( - text = { Text(stringResource(MR.strings.light)) }, + text = { Text(stringResource(Res.string.light)) }, onClick = { prefs.theme = 1 }, leadingIcon = { Icon(Icons.Default.LightMode, null) }, trailingIcon = { if (prefs.theme == 1) { Icon( Icons.Default.Check, - stringResource(MR.strings.active) + stringResource(Res.string.active) ) } }, ) DropdownMenuItem( - text = { Text(stringResource(MR.strings.dark)) }, + text = { Text(stringResource(Res.string.dark)) }, onClick = { prefs.theme = 2 }, leadingIcon = { Icon(Icons.Default.DarkMode, null) }, trailingIcon = { if (prefs.theme == 2) { Icon( Icons.Default.Check, - stringResource(MR.strings.active) + stringResource(Res.string.active) ) } }, @@ -205,15 +209,15 @@ actual fun AppSettings( ) { Column(Modifier.weight(2f)) { Text( - text = stringResource(MR.strings.use_dynamic_color), + text = stringResource(Res.string.use_dynamic_color), style = titleStyle ) Text( text = stringResource( if (prefs.dynamicColor) { - MR.strings.dynamic_color_on_desc + Res.string.dynamic_color_on_desc } else { - MR.strings.dynamic_color_off_desc + Res.string.dynamic_color_off_desc } ), style = detailsStyle, @@ -223,7 +227,7 @@ actual fun AppSettings( } } } - item { Divider() } + item { HorizontalDivider() } item { Row( horizontalArrangement = Arrangement.spacedBy(normalDp), @@ -234,18 +238,18 @@ actual fun AppSettings( ) { Column(Modifier.weight(2f)) { Text( - text = stringResource(MR.strings.experimental_features), + text = stringResource(Res.string.experimental_features), style = titleStyle, ) Text( - text = stringResource(MR.strings.experimental_features_desc), + text = stringResource(Res.string.experimental_features_desc), style = detailsStyle, ) } Switch(checked = prefs.experimentalFeatures, onCheckedChange = null) } } - item { Divider() } + item { HorizontalDivider() } item { Column(Modifier.clickable { context.startActivity( @@ -257,9 +261,9 @@ actual fun AppSettings( ) Runtime.getRuntime().exit(0) }.fillMaxWidth().padding(normalPadding)) { - Text(text = stringResource(MR.strings.restart), style = titleStyle) + Text(text = stringResource(Res.string.restart), style = titleStyle) Text( - text = stringResource(MR.strings.restart_desc), + text = stringResource(Res.string.restart_desc), style = detailsStyle, ) } @@ -269,13 +273,13 @@ actual fun AppSettings( LazyColumn(Modifier.fillMaxSize()) { item { Text( - text = stringResource(MR.strings.default_tournament_settings), + text = stringResource(Res.string.default_tournament_settings), fontStyle = FontStyle.Italic, modifier = Modifier.padding(normalPadding), ) } item { - val player = stringResource(MR.plurals.players, 1) + val player = stringResource(Res.string.player) PlayersSetting(prefs.players) { playersModel.players.clear() playersModel.players.addAll(prefs.players.ifEmpty { @@ -299,7 +303,7 @@ actual fun AppSettings( } catch (e: NumberFormatException) { scope.launch { hostState.showSnackbar( - MR.strings.invalid_number.getString(context) + getString(Res.string.invalid_number) ) } } diff --git a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/ImportExport.kt b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/ImportExport.kt index 4849e92..ca2f6e5 100644 --- a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/ImportExport.kt +++ b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/ImportExport.kt @@ -9,16 +9,19 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.GameDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.getString +import tournamentscompose.common.generated.resources.Res import java.io.BufferedReader import java.io.FileNotFoundException import java.io.IOException import java.io.InputStreamReader -fun exportToUri( +@OptIn(ExperimentalResourceApi::class) +suspend fun exportToUri( uri: Uri?, context: Context, content: Any, @@ -31,22 +34,23 @@ fun exportToUri( } } else { Toast.makeText( - context, MR.strings.exception_file.getString(context), Toast.LENGTH_SHORT + context, getString(Res.string.exception_file), Toast.LENGTH_SHORT ).show() } } catch (e: java.lang.Exception) { Toast.makeText( context, when (e) { - is FileNotFoundException -> MR.strings.exception_file.getString(context) - is IOException -> MR.strings.exception_io.getString(context) - else -> MR.strings.exception.getString(context) + is FileNotFoundException -> getString(Res.string.exception_file) + is IOException -> getString(Res.string.exception_io) + else -> getString(Res.string.exception) }, Toast.LENGTH_SHORT ).show() } } -fun importFromUri( +@OptIn(ExperimentalResourceApi::class) +suspend fun importFromUri( uri: Uri?, context: Context, scope: CoroutineScope, @@ -71,16 +75,16 @@ fun importFromUri( } } else { Toast.makeText( - context, MR.strings.exception_file.getString(context), Toast.LENGTH_SHORT + context, getString(Res.string.exception_file), Toast.LENGTH_SHORT ).show() } } catch (e: java.lang.Exception) { Toast.makeText( context, when (e) { - is FileNotFoundException -> MR.strings.exception_file.getString(context) - is JsonSyntaxException -> MR.strings.exception_json.getString(context) - is IOException -> MR.strings.exception_io.getString(context) - else -> MR.strings.exception.getString(context) + is FileNotFoundException -> getString(Res.string.exception_file) + is JsonSyntaxException -> getString(Res.string.exception_json) + is IOException -> getString(Res.string.exception_io) + else -> getString(Res.string.exception) }, Toast.LENGTH_SHORT ).show() } diff --git a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentList.kt b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentList.kt index dccb862..86df8fc 100644 --- a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentList.kt +++ b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentList.kt @@ -36,14 +36,16 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource -import me.frauenfelderflorian.tournamentscompose.common.MR +import kotlinx.coroutines.launch import me.frauenfelderflorian.tournamentscompose.common.data.GameDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res import java.util.UUID -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) @Composable actual fun TournamentList( navigator: StackNavigation, @@ -59,59 +61,65 @@ actual fun TournamentList( val hostState = remember { SnackbarHostState() } val context = LocalContext.current val exportToFile = rememberLauncherForActivityResult( - ActivityResultContracts.CreateDocument(stringResource(MR.strings.file_mime)) + ActivityResultContracts.CreateDocument(stringResource(Res.string.file_mime)) ) { - exportToUri( - uri = it, - context = context, - content = tournaments.values, - ) + scope.launch { + exportToUri( + uri = it, + context = context, + content = tournaments.values, + ) + } } val importFromFile = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { - importFromUri( - uri = it, - context = context, - scope = scope, - tournamentDao = tournamentDao, - gameDao = gameDao, - ) + scope.launch { + importFromUri( + uri = it, + context = context, + scope = scope, + tournamentDao = tournamentDao, + gameDao = gameDao, + ) + } } Scaffold( topBar = { LargeTopAppBar( - title = { TopAppBarTitle(stringResource(MR.strings.app_title), scrollBehavior) }, + title = { TopAppBarTitle(stringResource(Res.string.app_title), scrollBehavior) }, actions = { IconButton({ navigator.push(Screen.AppSettings) }) { - Icon(Icons.Default.Settings, stringResource(MR.strings.settings)) + Icon(Icons.Default.Settings, stringResource(Res.string.settings)) } IconButton({ showInfo.value = true }) { - Icon(Icons.Outlined.Info, stringResource(MR.strings.about)) + Icon(Icons.Outlined.Info, stringResource(Res.string.about)) } Box { var expanded by remember { mutableStateOf(false) } IconButton({ expanded = true }) { - Icon(Icons.Default.MoreVert, stringResource(MR.strings.more_actions)) + Icon(Icons.Default.MoreVert, stringResource(Res.string.more_actions)) } DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + val fileNameTournaments = stringResource(Res.string.file_name_tournaments) DropdownMenuItem( text = { - Text(stringResource(MR.strings.export_tournaments_to_file)) + Text(stringResource(Res.string.export_tournaments_to_file)) }, onClick = { expanded = false exportToFile.launch( - MR.strings.file_name_tournaments.getString(context) + fileNameTournaments ) }, leadingIcon = { Icon(Icons.Default.ArrowUpward, null) }, ) + val fileMime = stringResource(Res.string.file_mime) DropdownMenuItem( - text = { Text(stringResource(MR.strings.import_from_file)) }, + text = { Text(stringResource(Res.string.import_from_file)) }, onClick = { expanded = false importFromFile.launch( - arrayOf(MR.strings.file_mime.getString(context)) + arrayOf(fileMime) ) }, leadingIcon = { Icon(Icons.Default.ArrowDownward, null) }, @@ -125,7 +133,7 @@ actual fun TournamentList( floatingActionButton = { ExtendedFloatingActionButton( icon = { Icon(Icons.Default.Add, null) }, - text = { Text(stringResource(MR.strings.new_tournament)) }, + text = { Text(stringResource(Res.string.new_tournament)) }, expanded = scrollBehavior.state.collapsedFraction < 0.5f, onClick = { setCurrent(null) diff --git a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewer.kt b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewer.kt index 38e64da..fb75d0d 100644 --- a/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewer.kt +++ b/common/src/androidMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewer.kt @@ -40,18 +40,22 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.GameDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames import me.frauenfelderflorian.tournamentscompose.common.data.players import me.frauenfelderflorian.tournamentscompose.common.data.ranking +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, + ExperimentalResourceApi::class +) @Composable actual fun TournamentViewer( navigator: StackNavigation, @@ -73,13 +77,15 @@ actual fun TournamentViewer( val hostState = remember { SnackbarHostState() } val context = LocalContext.current val exportToFile = rememberLauncherForActivityResult( - ActivityResultContracts.CreateDocument(stringResource(MR.strings.file_mime)) + ActivityResultContracts.CreateDocument(stringResource(Res.string.file_mime)) ) { - exportToUri( - uri = it, - context = context, - content = setOf(tournament), - ) + scope.launch { + exportToUri( + uri = it, + context = context, + content = setOf(tournament), + ) + } } Scaffold( @@ -87,25 +93,22 @@ actual fun TournamentViewer( LargeTopAppBar( title = { TopAppBarTitle( - text = stringResource(MR.strings.tournament_title, tournament.t.name), + text = stringResource(Res.string.tournament_title, tournament.t.name), scrollBehavior = scrollBehavior, ) }, navigationIcon = { BackButton(navigator) }, actions = { IconButton({ navigator.push(Screen.TournamentEditor) }) { - Icon(Icons.Default.Edit, stringResource(MR.strings.edit_tournament)) + Icon(Icons.Default.Edit, stringResource(Res.string.edit_tournament)) } + val fileEndingTournament = stringResource(Res.string.file_ending_tournament) IconButton({ - exportToFile.launch( - "${tournament.t.name}${ - MR.strings.file_ending_tournament.getString(context) - }" - ) + exportToFile.launch("${tournament.t.name}${fileEndingTournament}") }) { Icon( Icons.Default.ArrowUpward, - stringResource(MR.strings.export_tournament_to_file) + stringResource(Res.string.export_tournament_to_file) ) } SettingsInfoMenu(navigator = navigator, showInfoDialog = showInfo) @@ -120,9 +123,9 @@ actual fun TournamentViewer( Text( stringResource( if (pagerState.currentPage == 0) { - MR.strings.new_game + Res.string.new_game } else { - MR.strings.add_new_player + Res.string.add_new_player } ) ) @@ -160,11 +163,11 @@ actual fun TournamentViewer( AlertDialog( onDismissRequest = { showNewPlayerDialog = false }, icon = { Icon(Icons.Default.Add, null) }, - title = { Text(stringResource(MR.strings.add_new_player)) }, + title = { Text(stringResource(Res.string.add_new_player)) }, text = { Column(verticalArrangement = Arrangement.spacedBy(normalDp)) { - Text(stringResource(MR.strings.add_new_player_info)) - val invalidName = stringResource(MR.strings.invalid_name) + Text(stringResource(Res.string.add_new_player_info)) + val invalidName = stringResource(Res.string.invalid_name) OutlinedTextField( value = newName, onValueChange = { value -> @@ -175,8 +178,8 @@ actual fun TournamentViewer( } }, singleLine = true, - label = { Text(stringResource(MR.strings.name)) }, - placeholder = { Text(stringResource(MR.strings.name_unique)) }, + label = { Text(stringResource(Res.string.name)) }, + placeholder = { Text(stringResource(Res.string.name_unique)) }, modifier = Modifier.fillMaxWidth(), ) } @@ -202,12 +205,12 @@ actual fun TournamentViewer( }, enabled = newName.isNotBlank() && !tournament.t.players.contains(newName), ) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ showNewPlayerDialog = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) @@ -217,9 +220,9 @@ actual fun TournamentViewer( AlertDialog( onDismissRequest = { showRenamePlayerDialog.value = false }, icon = { Icon(Icons.Default.Edit, null) }, - title = { Text("${stringResource(MR.strings.rename_player)} ${playerToBeRenamed.value}") }, + title = { Text("${stringResource(Res.string.rename_player)} ${playerToBeRenamed.value}") }, text = { - val invalidName = stringResource(MR.strings.invalid_name) + val invalidName = stringResource(Res.string.invalid_name) OutlinedTextField( value = newName, onValueChange = { value -> @@ -230,8 +233,8 @@ actual fun TournamentViewer( } }, singleLine = true, - label = { Text(stringResource(MR.strings.name)) }, - placeholder = { Text(stringResource(MR.strings.name_unique)) }, + label = { Text(stringResource(Res.string.name)) }, + placeholder = { Text(stringResource(Res.string.name_unique)) }, modifier = Modifier.fillMaxWidth(), ) }, @@ -259,12 +262,12 @@ actual fun TournamentViewer( }, enabled = newName.isNotBlank() && !tournament.t.players.contains(newName), ) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ showRenamePlayerDialog.value = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) @@ -274,9 +277,9 @@ actual fun TournamentViewer( onDismissRequest = { showDeletePlayerDialog.value = false }, icon = { Icon(Icons.Default.Delete, null) }, title = { - Text("${stringResource(MR.strings.delete_player)} ${playerToBeDeleted.value}?") + Text("${stringResource(Res.string.delete_player)} ${playerToBeDeleted.value}?") }, - text = { Text(stringResource(MR.strings.delete_player_hint)) }, + text = { Text(stringResource(Res.string.delete_player_hint)) }, confirmButton = { TextButton({ showDeletePlayerDialog.value = false @@ -296,12 +299,12 @@ actual fun TournamentViewer( } } }) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ showDeletePlayerDialog.value = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) diff --git a/common/src/commonMain/resources/MR/de/strings.xml b/common/src/commonMain/composeResources/values-de/strings.xml similarity index 97% rename from common/src/commonMain/resources/MR/de/strings.xml rename to common/src/commonMain/composeResources/values-de/strings.xml index 2ec33b4..5130744 100644 --- a/common/src/commonMain/resources/MR/de/strings.xml +++ b/common/src/commonMain/composeResources/values-de/strings.xml @@ -1,6 +1,9 @@ - Über + Spiel + Spiele + Spieler + Spieler Abwesend Abwesende Spieler Aktiv @@ -54,7 +57,7 @@ Tor erreicht vom besten Spieler Tore Gesamtzahl aller Tore - Importieren + Importieren Aus einer Datei importieren Willst du den Inhalt dieser Datei in die App importieren? Punkte für ersten Platz eingeben @@ -92,7 +95,7 @@ Einstellungen Alle Turniere teilen Startdatum - bis + bis Turnier "%1$s" Standardeinstellungen verwenden Standardeinstellungen können in den Einstellungen unter "Neue Turniere" angepasst werden diff --git a/common/src/commonMain/resources/MR/base/strings.xml b/common/src/commonMain/composeResources/values/strings.xml similarity index 97% rename from common/src/commonMain/resources/MR/base/strings.xml rename to common/src/commonMain/composeResources/values/strings.xml index b6996db..5490d35 100644 --- a/common/src/commonMain/resources/MR/base/strings.xml +++ b/common/src/commonMain/composeResources/values/strings.xml @@ -1,6 +1,9 @@ - About + game + games + player + players Absent Absent players Active @@ -10,6 +13,8 @@ Add a new player The player will be added to the tournament and marked as absent on all previous games App + TournamentsCompose + Tournaments Add at least two players Auto Back @@ -44,17 +49,22 @@ Add certain features that may not completely work or look like expected, but should not break anything Export this tournament to a file Export all tournaments to a file + .json + application/json + AllTournaments.json Points for first place Hoop %1$s of %2$s reached, with difficulty "%3$s" Game overview for Game from %1$s + https://github.com/fflopsi/tournaments + URL Give the tournament a meaningful name Hoop %1$s of %2$s reached Reached hoop Hoop reached by best player Hoops Total number of hoops - Import + Import Import from a file Do you want to import the contents of this file into the app? Input points for first place @@ -76,6 +86,7 @@ Number of reached hoop too small Number of hoops too small OK + players Recommended. Switch off for classic system Example: 5 players, first place gets 10 points, second place gets 10–3=7 points, third place gets 7–2=5 points, fourth place gets 5–2=3 points, fifth place gets 3–1=2 points. Absent players would get 0 points. In this system, absent players never get points. The last player always gets 2 points, the second-to-last 3 points, etc. Thus, there is no fixed amount of points for first/second/… place, but it varies based on the number of players present. However, second place gets 3 points less than first place, third place gets 2 points less than second place, and fourth place gets 2 points less than third place. After that, there is one point less for each place. @@ -92,17 +103,9 @@ Settings Share all tournaments Start date - to + to Tournament "%1$s" Use default settings Default settings can be changed in Settings under "New tournaments" Use Dynamic Color - TournamentsCompose - Tournaments - .json - application/json - AllTournaments.json - https://github.com/fflopsi/tournaments - URL - players diff --git a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/GameEditor.kt b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/GameEditor.kt index 9539d81..4d885f5 100644 --- a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/GameEditor.kt +++ b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/GameEditor.kt @@ -37,6 +37,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold +import androidx.compose.material3.SelectableDates import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Tab @@ -64,20 +65,23 @@ import androidx.compose.ui.unit.dp import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.popWhile -import dev.icerock.moko.resources.compose.stringResource import java.util.UUID import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.Game import me.frauenfelderflorian.tournamentscompose.common.data.GameDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames import me.frauenfelderflorian.tournamentscompose.common.data.players import me.frauenfelderflorian.tournamentscompose.common.data.playersByRank import me.frauenfelderflorian.tournamentscompose.common.data.ranking +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, + ExperimentalResourceApi::class +) @Composable fun GameEditor( navigator: StackNavigation, @@ -120,25 +124,25 @@ fun GameEditor( TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) val showInfo = remember { mutableStateOf(false) } var deleteDialogOpen by remember { mutableStateOf(false) } - val invalidNumber = stringResource(MR.strings.invalid_number) + val invalidNumber = stringResource(Res.string.invalid_number) Scaffold( topBar = { LargeTopAppBar( - title = { TopAppBarTitle(stringResource(MR.strings.edit_game), scrollBehavior) }, + title = { TopAppBarTitle(stringResource(Res.string.edit_game), scrollBehavior) }, navigationIcon = { BackButton(navigator) }, actions = { if (tournament.current != null) { IconButton({ deleteDialogOpen = true }) { - Icon(Icons.Default.Delete, stringResource(MR.strings.delete_game)) + Icon(Icons.Default.Delete, stringResource(Res.string.delete_game)) } } - val numberHoopsTooSmall = stringResource(MR.strings.number_hoops_too_small) + val numberHoopsTooSmall = stringResource(Res.string.number_hoops_too_small) val numberHoopReachedTooSmall = - stringResource(MR.strings.number_hoop_reached_too_small) + stringResource(Res.string.number_hoop_reached_too_small) val numberHoopReachedTooBig = - stringResource(MR.strings.number_hoop_reached_too_big) - val rankingInvalid = stringResource(MR.strings.ranking_invalid) + stringResource(Res.string.number_hoop_reached_too_big) + val rankingInvalid = stringResource(Res.string.ranking_invalid) IconButton({ try { if (hoopsString.toInt() < 1) { @@ -181,7 +185,7 @@ fun GameEditor( scope.launch { hostState.showSnackbar(invalidNumber) } } }) { - Icon(Icons.Default.Check, stringResource(MR.strings.save_and_exit)) + Icon(Icons.Default.Check, stringResource(Res.string.save_and_exit)) } SettingsInfoMenu(navigator = navigator, showInfoDialog = showInfo) }, @@ -203,12 +207,12 @@ fun GameEditor( Tab( selected = pagerState.currentPage == 0, onClick = { scope.launch { pagerState.animateScrollToPage(0) } }, - text = { Text(stringResource(MR.strings.details)) }, + text = { Text(stringResource(Res.string.details)) }, ) Tab( selected = pagerState.currentPage == 1, onClick = { scope.launch { pagerState.animateScrollToPage(1) } }, - text = { Text(stringResource(MR.strings.ranking)) }, + text = { Text(stringResource(Res.string.ranking)) }, ) } HorizontalPager(state = pagerState) { page -> @@ -226,7 +230,7 @@ fun GameEditor( modifier = Modifier.fillMaxWidth(), ) { Text( - text = "${stringResource(MR.strings.date)}: ${ + text = "${stringResource(Res.string.date)}: ${ formatDate(date) }", textAlign = TextAlign.Center, @@ -251,8 +255,8 @@ fun GameEditor( } }, singleLine = true, - label = { Text(stringResource(MR.strings.hoops)) }, - supportingText = { Text(stringResource(MR.strings.hoops_desc)) }, + label = { Text(stringResource(Res.string.hoops)) }, + supportingText = { Text(stringResource(Res.string.hoops_desc)) }, leadingIcon = { Icon(Icons.Default.Flag, null) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number @@ -270,9 +274,9 @@ fun GameEditor( } }, singleLine = true, - label = { Text(stringResource(MR.strings.hoop_reached)) }, + label = { Text(stringResource(Res.string.hoop_reached)) }, supportingText = { - Text(stringResource(MR.strings.hoop_reached_desc)) + Text(stringResource(Res.string.hoop_reached_desc)) }, trailingIcon = { Icon(Icons.Default.FlagCircle, null) }, keyboardOptions = KeyboardOptions( @@ -287,8 +291,8 @@ fun GameEditor( value = difficulty, onValueChange = { if (it.length < 100) difficulty = it }, singleLine = true, - label = { Text(stringResource(MR.strings.difficulty)) }, - placeholder = { Text(stringResource(MR.strings.difficulty_placeholder)) }, + label = { Text(stringResource(Res.string.difficulty)) }, + placeholder = { Text(stringResource(Res.string.difficulty_placeholder)) }, modifier = Modifier.fillMaxWidth().padding(normalPadding), ) } @@ -370,7 +374,13 @@ fun GameEditor( } } if (dateDialogOpen) { - val datePickerState = rememberDatePickerState(date) + val datePickerState = rememberDatePickerState( + initialSelectedDateMillis = date, + selectableDates = object : SelectableDates { + override fun isSelectableDate(utcTimeMillis: Long): Boolean = + utcTimeMillis in tournament.t.start..tournament.t.end + }, + ) val confirmEnabled by remember { derivedStateOf { datePickerState.selectedDateMillis != null } } @@ -384,19 +394,16 @@ fun GameEditor( }, enabled = confirmEnabled, ) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ dateDialogOpen = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) { - DatePicker( - state = datePickerState, - dateValidator = { it in tournament.t.start..tournament.t.end }, - ) + DatePicker(state = datePickerState) } } if (deleteDialogOpen) { @@ -410,17 +417,17 @@ fun GameEditor( } navigator.popWhile { top: Screen -> top !is Screen.TournamentViewer } }) { - Text(stringResource(MR.strings.delete_game)) + Text(stringResource(Res.string.delete_game)) } }, dismissButton = { TextButton({ deleteDialogOpen = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, icon = { Icon(Icons.Default.Delete, null) }, - title = { Text("${stringResource(MR.strings.delete_game)}?") }, - text = { Text(stringResource(MR.strings.delete_game_hint)) }, + title = { Text("${stringResource(Res.string.delete_game)}?") }, + text = { Text(stringResource(Res.string.delete_game_hint)) }, ) } InfoDialog(showInfo) diff --git a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/GameViewer.kt b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/GameViewer.kt index 160601a..cc4e707 100644 --- a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/GameViewer.kt +++ b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/GameViewer.kt @@ -11,8 +11,8 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.EmojiEvents -import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar @@ -31,14 +31,15 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.Game import me.frauenfelderflorian.tournamentscompose.common.data.absentPlayers import me.frauenfelderflorian.tournamentscompose.common.data.playersByRank import me.frauenfelderflorian.tournamentscompose.common.data.ranking +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) @Composable fun GameViewer( navigator: StackNavigation, @@ -53,14 +54,14 @@ fun GameViewer( LargeTopAppBar( title = { TopAppBarTitle( - text = stringResource(MR.strings.game_title, formatDate(game.date)), + text = stringResource(Res.string.game_title, formatDate(game.date)), scrollBehavior = scrollBehavior, ) }, navigationIcon = { BackButton(navigator) }, actions = { IconButton({ navigator.push(Screen.GameEditor) }) { - Icon(Icons.Default.Edit, stringResource(MR.strings.edit_game)) + Icon(Icons.Default.Edit, stringResource(Res.string.edit_game)) } SettingsInfoMenu(navigator = navigator, showInfoDialog = showInfo) }, @@ -73,11 +74,11 @@ fun GameViewer( Column(Modifier.padding(paddingValues)) { Text( text = stringResource( - MR.strings.game_details, game.hoopReached, game.hoops, game.difficulty + Res.string.game_details, game.hoopReached, game.hoops, game.difficulty ), modifier = Modifier.padding(normalPadding), ) - Divider() + HorizontalDivider() LazyColumn( verticalArrangement = Arrangement.spacedBy(normalDp), horizontalAlignment = Alignment.Start, @@ -116,8 +117,8 @@ fun GameViewer( ) } } - item { Divider() } - item { Text(stringResource(MR.strings.absent_players)) } + item { HorizontalDivider() } + item { Text(stringResource(Res.string.absent_players)) } items(game.absentPlayers.toList()) { Text(text = it, fontWeight = FontWeight.ExtraLight) } diff --git a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/PlayerViewer.kt b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/PlayerViewer.kt index b63980b..afb2583 100644 --- a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/PlayerViewer.kt +++ b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/PlayerViewer.kt @@ -7,8 +7,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items -import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -21,13 +21,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import com.arkivanov.decompose.router.stack.StackNavigation -import dev.icerock.moko.resources.compose.stringResource -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames import me.frauenfelderflorian.tournamentscompose.common.data.playersByRank import me.frauenfelderflorian.tournamentscompose.common.data.ranking +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) @Composable fun PlayerViewer( navigator: StackNavigation, @@ -43,7 +44,7 @@ fun PlayerViewer( LargeTopAppBar( title = { TopAppBarTitle( - "${stringResource(MR.strings.game_overview_for)} $player", scrollBehavior + "${stringResource(Res.string.game_overview_for)} $player", scrollBehavior ) }, navigationIcon = { BackButton(navigator) }, @@ -68,19 +69,25 @@ fun PlayerViewer( Text(formatDate(it.date)) Text( text = if (it.ranking[player] == 0) { - stringResource(MR.strings.absent) + stringResource(Res.string.absent) } else { stringResource( - MR.strings.rank_of, + Res.string.rank_of, it.ranking[player]!!, it.playersByRank.size, ) }, style = titleStyle, ) - Divider() + HorizontalDivider() Text(it.difficulty) - Text(stringResource(MR.strings.hoop_of_reached, it.hoopReached, it.hoops)) + Text( + stringResource( + Res.string.hoop_of_reached, + it.hoopReached, + it.hoops + ) + ) } } } diff --git a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/PlayersEditor.kt b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/PlayersEditor.kt index b2d9229..94a3822 100644 --- a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/PlayersEditor.kt +++ b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/PlayersEditor.kt @@ -37,12 +37,13 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.launch -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.PlayersModel +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) @Composable fun PlayersEditor( navigator: StackNavigation, @@ -61,11 +62,11 @@ fun PlayersEditor( Scaffold( topBar = { LargeTopAppBar( - title = { TopAppBarTitle(stringResource(MR.strings.edit_players), scrollBehavior) }, + title = { TopAppBarTitle(stringResource(Res.string.edit_players), scrollBehavior) }, navigationIcon = { BackButton(navigator) }, actions = { - val noNamelessPlayers = stringResource(MR.strings.no_nameless_players) - val noSameNamePlayers = stringResource(MR.strings.no_same_name_players) + val noNamelessPlayers = stringResource(Res.string.no_nameless_players) + val noSameNamePlayers = stringResource(Res.string.no_same_name_players) IconButton({ for (player1 in players) { if (player1.value.isBlank()) { @@ -88,7 +89,7 @@ fun PlayersEditor( playersModel.edited = true navigator.pop() }) { - Icon(Icons.Default.Check, stringResource(MR.strings.save_and_exit)) + Icon(Icons.Default.Check, stringResource(Res.string.save_and_exit)) } }, scrollBehavior = scrollBehavior, @@ -97,7 +98,7 @@ fun PlayersEditor( floatingActionButton = { ExtendedFloatingActionButton( icon = { Icon(Icons.Default.Add, null) }, - text = { Text(stringResource(MR.strings.add_new_player)) }, + text = { Text(stringResource(Res.string.add_new_player)) }, expanded = scrollBehavior.state.collapsedFraction < 0.5f, onClick = { players[playersIdCounter++] = "" }, ) @@ -113,7 +114,7 @@ fun PlayersEditor( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(normalPadding), ) { - val invalidName = stringResource(MR.strings.invalid_name) + val invalidName = stringResource(Res.string.invalid_name) OutlinedTextField( value = it.second, onValueChange = { value -> @@ -126,16 +127,19 @@ fun PlayersEditor( singleLine = true, label = { Text( - "${stringResource(MR.strings.name)} ${ + "${stringResource(Res.string.name)} ${ players.toList().indexOf(it) + 1 }" ) }, - placeholder = { Text(stringResource(MR.strings.name_unique)) }, + placeholder = { Text(stringResource(Res.string.name_unique)) }, modifier = Modifier.fillMaxWidth().weight(2f), ) IconButton({ players.remove(it.first) }) { - Icon(Icons.Default.Delete, stringResource(MR.strings.delete_player)) + Icon( + Icons.Default.Delete, + stringResource(Res.string.delete_player) + ) } } } diff --git a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/Shared.kt b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/Shared.kt index a797b17..78ebe85 100644 --- a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/Shared.kt +++ b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/Shared.kt @@ -16,17 +16,17 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Divider import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -64,9 +64,10 @@ import com.arkivanov.decompose.router.stack.push import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.ToNumberPolicy -import dev.icerock.moko.resources.compose.stringResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res import java.text.DateFormat -import me.frauenfelderflorian.tournamentscompose.common.MR val titleStyle @Composable get() = MaterialTheme.typography.titleLarge val detailsStyle @Composable get() = MaterialTheme.typography.bodyMedium @@ -92,11 +93,13 @@ fun TopAppBarTitle(text: String, scrollBehavior: TopAppBarScrollBehavior) { ) } +@OptIn(ExperimentalResourceApi::class) @Composable fun BackButton(navigator: StackNavigation) { - IconButton(navigator::pop) { Icon(Icons.Default.ArrowBack, stringResource(MR.strings.back)) } + IconButton(navigator::pop) { Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(Res.string.back)) } } +@OptIn(ExperimentalResourceApi::class) @Composable fun InfoDialog(showDialog: MutableState) { if (showDialog.value) { @@ -104,14 +107,14 @@ fun InfoDialog(showDialog: MutableState) { onDismissRequest = { showDialog.value = false }, icon = { Icon(Icons.Default.Info, null) }, title = { - Text("${stringResource(MR.strings.about)} ${stringResource(MR.strings.app_title)}") + Text("${stringResource(Res.string.about)} ${stringResource(Res.string.app_title)}") }, text = { Column { - Text(stringResource(MR.strings.built_by_info)) - val tag = stringResource(MR.strings.github_link_tag) + Text(stringResource(Res.string.built_by_info)) + val tag = stringResource(Res.string.github_link_tag) val linkString = buildAnnotatedString { - val string = stringResource(MR.strings.link_to_github) + val string = stringResource(Res.string.link_to_github) append(string) addStyle( style = SpanStyle( @@ -123,7 +126,7 @@ fun InfoDialog(showDialog: MutableState) { ) addStringAnnotation( tag = tag, - annotation = stringResource(MR.strings.github_link), + annotation = stringResource(Res.string.github_link), start = 0, end = string.length, ) @@ -139,31 +142,32 @@ fun InfoDialog(showDialog: MutableState) { } }, confirmButton = { - TextButton({ showDialog.value = false }) { Text(stringResource(MR.strings.ok)) } + TextButton({ showDialog.value = false }) { Text(stringResource(Res.string.ok)) } }, ) } } +@OptIn(ExperimentalResourceApi::class) @Composable fun SettingsInfoMenu(navigator: StackNavigation, showInfoDialog: MutableState) { Box { var expanded by remember { mutableStateOf(false) } IconButton({ expanded = true }) { - Icon(Icons.Default.MoreVert, stringResource(MR.strings.more_actions)) + Icon(Icons.Default.MoreVert, stringResource(Res.string.more_actions)) } DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { DropdownMenuItem( - text = { Text(stringResource(MR.strings.settings)) }, + text = { Text(stringResource(Res.string.settings)) }, onClick = { expanded = false navigator.push(Screen.AppSettings) }, leadingIcon = { Icon(Icons.Default.Settings, null) }, ) - Divider() + HorizontalDivider() DropdownMenuItem( - text = { Text(stringResource(MR.strings.about)) }, + text = { Text(stringResource(Res.string.about)) }, onClick = { expanded = false showInfoDialog.value = true @@ -174,6 +178,7 @@ fun SettingsInfoMenu(navigator: StackNavigation, showInfoDialog: Mutable } } +@OptIn(ExperimentalResourceApi::class) @Composable fun PlayersSetting(players: List, onClick: () -> Unit) { Row( @@ -183,23 +188,24 @@ fun PlayersSetting(players: List, onClick: () -> Unit) { ) { Column(Modifier.weight(2f)) { Text( - text = stringResource(MR.plurals.players, 2), + text = stringResource(Res.string.players), style = titleStyle, ) Text( text = if (players.isNotEmpty()) { players.joinToString(", ") } else { - stringResource(MR.strings.no_players) + stringResource(Res.string.no_players) }, style = detailsStyle, fontStyle = if (players.isNotEmpty()) FontStyle.Normal else FontStyle.Italic, ) } - Icon(Icons.Default.Edit, stringResource(MR.strings.edit_players)) + Icon(Icons.Default.Edit, stringResource(Res.string.edit_players)) } } +@OptIn(ExperimentalResourceApi::class) @Composable fun PointSystemSettings( adaptivePoints: Boolean, @@ -214,13 +220,13 @@ fun PointSystemSettings( modifier = Modifier.clickable(onClick = onClickAdaptivePoints).padding(normalPadding), ) { Column(Modifier.weight(2f)) { - Text(text = stringResource(MR.strings.adaptive_point_system), style = titleStyle) + Text(text = stringResource(Res.string.adaptive_point_system), style = titleStyle) Text( text = stringResource( if (adaptivePoints) { - MR.strings.point_system_adaptive_desc + Res.string.point_system_adaptive_desc } else { - MR.strings.point_system_classic_desc + Res.string.point_system_classic_desc } ), style = detailsStyle, @@ -237,7 +243,7 @@ fun PointSystemSettings( value = firstPoints?.toString() ?: "", onValueChange = onChangeFirstPoints, singleLine = true, - label = { Text(stringResource(MR.strings.first_points)) }, + label = { Text(stringResource(Res.string.first_points)) }, trailingIcon = { Icon(Icons.Default.Edit, null) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.fillMaxWidth().padding(normalPadding), @@ -246,9 +252,9 @@ fun PointSystemSettings( Text( text = stringResource( if (adaptivePoints) { - MR.strings.point_system_adaptive_info + Res.string.point_system_adaptive_info } else { - MR.strings.point_system_classic_info + Res.string.point_system_classic_info } ), fontStyle = FontStyle.Italic, @@ -258,9 +264,9 @@ fun PointSystemSettings( Text( text = stringResource( if (adaptivePoints) { - MR.strings.point_system_adaptive_expl + Res.string.point_system_adaptive_expl } else { - MR.strings.point_system_classic_expl + Res.string.point_system_classic_expl } ), fontStyle = FontStyle.Italic, diff --git a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentEditor.kt b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentEditor.kt index df38770..e3d1277 100644 --- a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentEditor.kt +++ b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentEditor.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.SelectableDates import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Switch @@ -51,12 +52,9 @@ import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.popTo import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource -import java.util.UUID import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.GameDao import me.frauenfelderflorian.tournamentscompose.common.data.PlayersModel import me.frauenfelderflorian.tournamentscompose.common.data.Prefs @@ -64,8 +62,12 @@ import me.frauenfelderflorian.tournamentscompose.common.data.Tournament import me.frauenfelderflorian.tournamentscompose.common.data.TournamentDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames import me.frauenfelderflorian.tournamentscompose.common.data.players +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res +import java.util.UUID -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) @Composable fun TournamentEditor( navigator: StackNavigation, @@ -114,8 +116,8 @@ fun TournamentEditor( } } - val atLeastTwoPlayers = stringResource(MR.strings.at_least_two_players) - val enterNumberFirstPoints = stringResource(MR.strings.enter_number_first_points) + val atLeastTwoPlayers = stringResource(Res.string.at_least_two_players) + val enterNumberFirstPoints = stringResource(Res.string.enter_number_first_points) fun save() { val t: Tournament if (current == null) { @@ -185,17 +187,17 @@ fun TournamentEditor( topBar = { LargeTopAppBar( title = { - TopAppBarTitle(stringResource(MR.strings.edit_tournament), scrollBehavior) + TopAppBarTitle(stringResource(Res.string.edit_tournament), scrollBehavior) }, navigationIcon = { BackButton(navigator) }, actions = { if (current != null) { IconButton({ deleteDialogOpen = true }) { - Icon(Icons.Default.Delete, stringResource(MR.strings.delete_tournament)) + Icon(Icons.Default.Delete, stringResource(Res.string.delete_tournament)) } } IconButton(::save) { - Icon(Icons.Default.Check, stringResource(MR.strings.save_and_exit)) + Icon(Icons.Default.Check, stringResource(Res.string.save_and_exit)) } SettingsInfoMenu(navigator = navigator, showInfoDialog = showInfo) }, @@ -216,8 +218,8 @@ fun TournamentEditor( value = name, onValueChange = { if (it.length < 100) name = it }, singleLine = true, - label = { Text(stringResource(MR.strings.name)) }, - placeholder = { Text(stringResource(MR.strings.give_meaningful_name)) }, + label = { Text(stringResource(Res.string.name)) }, + placeholder = { Text(stringResource(Res.string.give_meaningful_name)) }, trailingIcon = { Icon(Icons.Default.Edit, null) }, modifier = Modifier.fillMaxWidth().padding(normalPadding), ) @@ -230,13 +232,13 @@ fun TournamentEditor( onClick = { startDialogOpen = true }, modifier = Modifier.weight(1f), ) { - Text("${stringResource(MR.strings.start_date)}: ${formatDate(start)}") + Text("${stringResource(Res.string.start_date)}: ${formatDate(start)}") } OutlinedButton( onClick = { endDialogOpen = true }, modifier = Modifier.weight(1f), ) { - Text("${stringResource(MR.strings.end_date)}: ${formatDate(end)}") + Text("${stringResource(Res.string.end_date)}: ${formatDate(end)}") } } if (current == null) { @@ -247,9 +249,9 @@ fun TournamentEditor( .padding(normalPadding), ) { Column(Modifier.weight(2f)) { - Text(text = stringResource(MR.strings.use_defaults), style = titleStyle) + Text(text = stringResource(Res.string.use_defaults), style = titleStyle) Text( - text = stringResource(MR.strings.use_defaults_desc), + text = stringResource(Res.string.use_defaults_desc), style = detailsStyle, ) } @@ -260,7 +262,7 @@ fun TournamentEditor( enter = expandVertically(expandFrom = Alignment.Top), exit = shrinkVertically(shrinkTowards = Alignment.Top), ) { - val player = stringResource(MR.plurals.players, 1) + val player = stringResource(Res.string.player) PlayersSetting(players) { playersModel.players.clear() playersModel.players.addAll(players.ifEmpty { @@ -275,7 +277,7 @@ fun TournamentEditor( enter = expandVertically(expandFrom = Alignment.Top), exit = shrinkVertically(shrinkTowards = Alignment.Top), ) { - val invalidNumber = stringResource(MR.strings.invalid_number) + val invalidNumber = stringResource(Res.string.invalid_number) PointSystemSettings( adaptivePoints = adaptivePoints, onClickAdaptivePoints = { adaptivePoints = !adaptivePoints }, @@ -296,12 +298,18 @@ fun TournamentEditor( onClick = ::save, modifier = Modifier.padding(normalPadding).fillMaxWidth(), ) { - Text(stringResource(MR.strings.create_tournament)) + Text(stringResource(Res.string.create_tournament)) } } } if (startDialogOpen) { - val datePickerState = rememberDatePickerState(start) + val datePickerState = rememberDatePickerState( + initialSelectedDateMillis = start, + selectableDates = object : SelectableDates { + override fun isSelectableDate(utcTimeMillis: Long): Boolean = + utcTimeMillis < end + }, + ) val confirmEnabled by remember { derivedStateOf { datePickerState.selectedDateMillis != null } } @@ -315,20 +323,26 @@ fun TournamentEditor( }, enabled = confirmEnabled, ) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ startDialogOpen = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) { - DatePicker(state = datePickerState, dateValidator = { it < end }) + DatePicker(state = datePickerState) } } if (endDialogOpen) { - val datePickerState = rememberDatePickerState(end) + val datePickerState = rememberDatePickerState( + initialSelectedDateMillis = end, + selectableDates = object : SelectableDates { + override fun isSelectableDate(utcTimeMillis: Long): Boolean = + utcTimeMillis > start + }, + ) val confirmEnabled by remember { derivedStateOf { datePickerState.selectedDateMillis != null } } @@ -342,16 +356,16 @@ fun TournamentEditor( }, enabled = confirmEnabled, ) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ endDialogOpen = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) { - DatePicker(state = datePickerState, dateValidator = { it > start }) + DatePicker(state = datePickerState) } } if (deleteDialogOpen) { @@ -368,17 +382,17 @@ fun TournamentEditor( } navigator.popTo(0) }) { - Text(stringResource(MR.strings.delete_tournament)) + Text(stringResource(Res.string.delete_tournament)) } }, dismissButton = { TextButton({ deleteDialogOpen = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, icon = { Icon(Icons.Default.Delete, null) }, - title = { Text("${stringResource(MR.strings.delete_tournament)}?") }, - text = { Text(stringResource(MR.strings.delete_tournament_hint)) }, + title = { Text("${stringResource(Res.string.delete_tournament)}?") }, + text = { Text(stringResource(Res.string.delete_tournament_hint)) }, ) } InfoDialog(showInfo) diff --git a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentListContent.kt b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentListContent.kt index d3494dc..0634fd1 100644 --- a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentListContent.kt +++ b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentListContent.kt @@ -22,11 +22,13 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res import java.util.UUID +@OptIn(ExperimentalResourceApi::class) @Composable fun TournamentListContent( tournaments: Map, @@ -48,10 +50,12 @@ fun TournamentListContent( Column(Modifier.weight(2f)) { Text(text = it.t.name, style = titleStyle) Text( - text = "${formatDate(it.t.start)} ${stringResource(MR.strings.to_)} ${ + text = "${formatDate(it.t.start)} ${stringResource(Res.string.to)} ${ formatDate(it.t.end) }, ${it.games.size} ${ - stringResource(MR.plurals.games, it.games.size) + stringResource( + if (it.games.size == 1) Res.string.game else Res.string.games + ) }", style = detailsStyle, ) @@ -60,7 +64,7 @@ fun TournamentListContent( setCurrent(it.t.id) navigator.push(Screen.TournamentEditor) }) { - Icon(Icons.Default.Edit, stringResource(MR.strings.edit_tournament)) + Icon(Icons.Default.Edit, stringResource(Res.string.edit_tournament)) } } } @@ -68,7 +72,7 @@ fun TournamentListContent( } else { item { Text( - text = stringResource(MR.strings.add_first_tournament_hint), + text = stringResource(Res.string.add_first_tournament_hint), fontStyle = FontStyle.Italic, fontWeight = FontWeight.Light, modifier = Modifier.padding(normalPadding), diff --git a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewerContent.kt b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewerContent.kt index 0d0d0cb..8cec2fc 100644 --- a/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewerContent.kt +++ b/common/src/commonMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewerContent.kt @@ -39,15 +39,16 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames import me.frauenfelderflorian.tournamentscompose.common.data.getPoints import me.frauenfelderflorian.tournamentscompose.common.data.playersByPoints +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalResourceApi::class) @Composable fun TournamentViewerContent( navigator: StackNavigation, @@ -65,12 +66,12 @@ fun TournamentViewerContent( Tab( selected = pagerState.currentPage == 0, onClick = { scope.launch { pagerState.animateScrollToPage(0) } }, - text = { Text(stringResource(MR.strings.details)) }, + text = { Text(stringResource(Res.string.details)) }, ) Tab( selected = pagerState.currentPage == 1, onClick = { scope.launch { pagerState.animateScrollToPage(1) } }, - text = { Text(stringResource(MR.strings.ranking)) }, + text = { Text(stringResource(Res.string.ranking)) }, ) } HorizontalPager(state = pagerState) { page -> @@ -89,13 +90,13 @@ fun TournamentViewerContent( Column(Modifier.weight(2f)) { Text( text = stringResource( - MR.strings.game_title, formatDate(it.date) + Res.string.game_title, formatDate(it.date) ), style = titleStyle, ) Text( text = stringResource( - MR.strings.game_details, + Res.string.game_details, it.hoopReached, it.hoops, it.difficulty, @@ -108,7 +109,7 @@ fun TournamentViewerContent( navigator.push(Screen.GameEditor) }) { Icon( - Icons.Default.Edit, stringResource(MR.strings.edit_game) + Icons.Default.Edit, stringResource(Res.string.edit_game) ) } } @@ -117,7 +118,7 @@ fun TournamentViewerContent( } else { item { Text( - text = stringResource(MR.strings.add_first_game_hint), + text = stringResource(Res.string.add_first_game_hint), fontStyle = FontStyle.Italic, fontWeight = FontWeight.ExtraLight, modifier = Modifier.padding(normalPadding), @@ -150,7 +151,7 @@ fun TournamentViewerContent( onDismissRequest = { menuExpanded = false }, ) { DropdownMenuItem( - text = { Text(stringResource(MR.strings.rename_player)) }, + text = { Text(stringResource(Res.string.rename_player)) }, onClick = { playerToBeRenamed.value = it showRenamePlayerDialog.value = true @@ -159,7 +160,7 @@ fun TournamentViewerContent( leadingIcon = { Icon(Icons.Default.Edit, null) }, ) DropdownMenuItem( - text = { Text(stringResource(MR.strings.delete_player)) }, + text = { Text(stringResource(Res.string.delete_player)) }, onClick = { playerToBeDeleted.value = it showDeletePlayerDialog.value = true diff --git a/common/src/commonMain/resources/MR/base/plurals.xml b/common/src/commonMain/resources/MR/base/plurals.xml deleted file mode 100644 index 0cdb2d3..0000000 --- a/common/src/commonMain/resources/MR/base/plurals.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - game - games - - - player - players - - diff --git a/common/src/commonMain/resources/MR/de/plurals.xml b/common/src/commonMain/resources/MR/de/plurals.xml deleted file mode 100644 index 064b0b5..0000000 --- a/common/src/commonMain/resources/MR/de/plurals.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - Spiel - Spiele - - - Spieler - Spieler - - diff --git a/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/AppSettings.kt b/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/AppSettings.kt index 30af52f..b16e4d5 100644 --- a/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/AppSettings.kt +++ b/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/AppSettings.kt @@ -19,10 +19,10 @@ import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.LightMode import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.outlined.Info -import androidx.compose.material3.Divider import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar @@ -49,13 +49,16 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.text.font.FontStyle import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.launch -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.PlayersModel import me.frauenfelderflorian.tournamentscompose.common.data.Prefs +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, + ExperimentalResourceApi::class +) @Composable actual fun AppSettings( navigator: StackNavigation, @@ -81,11 +84,11 @@ actual fun AppSettings( Scaffold( topBar = { LargeTopAppBar( - title = { TopAppBarTitle(stringResource(MR.strings.settings), scrollBehavior) }, + title = { TopAppBarTitle(stringResource(Res.string.settings), scrollBehavior) }, navigationIcon = { BackButton(navigator) }, actions = { IconButton({ showInfo.value = true }) { - Icon(Icons.Outlined.Info, stringResource(MR.strings.about)) + Icon(Icons.Outlined.Info, stringResource(Res.string.about)) } }, scrollBehavior = scrollBehavior, @@ -101,12 +104,12 @@ actual fun AppSettings( Tab( selected = pagerState.currentPage == 0, onClick = { scope.launch { pagerState.animateScrollToPage(0) } }, - text = { Text(stringResource(MR.strings.app)) }, + text = { Text(stringResource(Res.string.app)) }, ) Tab( selected = pagerState.currentPage == 1, onClick = { scope.launch { pagerState.animateScrollToPage(1) } }, - text = { Text(stringResource(MR.strings.new_tournaments)) }, + text = { Text(stringResource(Res.string.new_tournaments)) }, ) } HorizontalPager(state = pagerState) { page -> @@ -122,15 +125,15 @@ actual fun AppSettings( ) { Column(Modifier.weight(2f)) { Text( - text = stringResource(MR.strings.choose_theme), + text = stringResource(Res.string.choose_theme), style = titleStyle ) Text( text = stringResource( when (prefs.theme) { - 1 -> MR.strings.light - 2 -> MR.strings.dark - else -> MR.strings.auto + 1 -> Res.string.light + 2 -> Res.string.dark + else -> Res.string.auto } ), style = detailsStyle, @@ -143,7 +146,7 @@ actual fun AppSettings( onDismissRequest = { themeSelectorExpanded = false }, ) { DropdownMenuItem( - text = { Text(stringResource(MR.strings.auto)) }, + text = { Text(stringResource(Res.string.auto)) }, onClick = { prefs.theme = 0 }, leadingIcon = { Icon( @@ -154,34 +157,34 @@ actual fun AppSettings( if (prefs.theme == 0) { Icon( Icons.Default.Check, - stringResource(MR.strings.active) + stringResource(Res.string.active) ) } }, ) - Divider() + HorizontalDivider() DropdownMenuItem( - text = { Text(stringResource(MR.strings.light)) }, + text = { Text(stringResource(Res.string.light)) }, onClick = { prefs.theme = 1 }, leadingIcon = { Icon(Icons.Default.LightMode, null) }, trailingIcon = { if (prefs.theme == 1) { Icon( Icons.Default.Check, - stringResource(MR.strings.active) + stringResource(Res.string.active) ) } }, ) DropdownMenuItem( - text = { Text(stringResource(MR.strings.dark)) }, + text = { Text(stringResource(Res.string.dark)) }, onClick = { prefs.theme = 2 }, leadingIcon = { Icon(Icons.Default.DarkMode, null) }, trailingIcon = { if (prefs.theme == 2) { Icon( Icons.Default.Check, - stringResource(MR.strings.active) + stringResource(Res.string.active) ) } }, @@ -190,7 +193,7 @@ actual fun AppSettings( } } } - item { Divider() } + item { HorizontalDivider() } item { Row( horizontalArrangement = Arrangement.spacedBy(normalDp), @@ -201,26 +204,26 @@ actual fun AppSettings( ) { Column(Modifier.weight(2f)) { Text( - text = stringResource(MR.strings.experimental_features), + text = stringResource(Res.string.experimental_features), style = titleStyle, ) Text( - text = stringResource(MR.strings.experimental_features_desc), + text = stringResource(Res.string.experimental_features_desc), style = detailsStyle, ) } Switch(checked = prefs.experimentalFeatures, onCheckedChange = null) } } - item { Divider() } + item { HorizontalDivider() } item { Column( Modifier.clickable { /*TODO*/ }.fillMaxWidth() .padding(normalPadding) ) { - Text(text = stringResource(MR.strings.restart), style = titleStyle) + Text(text = stringResource(Res.string.restart), style = titleStyle) Text( - text = stringResource(MR.strings.restart_desc), + text = stringResource(Res.string.restart_desc), style = detailsStyle, ) } @@ -230,13 +233,13 @@ actual fun AppSettings( LazyColumn(Modifier.fillMaxSize()) { item { Text( - text = stringResource(MR.strings.default_tournament_settings), + text = stringResource(Res.string.default_tournament_settings), fontStyle = FontStyle.Italic, modifier = Modifier.padding(normalPadding), ) } item { - val player = stringResource(MR.plurals.players, 1) + val player = stringResource(Res.string.player) PlayersSetting(prefs.players) { playersModel.players.clear() playersModel.players.addAll(prefs.players.ifEmpty { @@ -247,7 +250,7 @@ actual fun AppSettings( } item { val scope = rememberCoroutineScope() - val invalidNumber = stringResource(MR.strings.invalid_number) + val invalidNumber = stringResource(Res.string.invalid_number) PointSystemSettings( adaptivePoints = prefs.adaptivePoints, onClickAdaptivePoints = { diff --git a/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentList.kt b/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentList.kt index 10e9a30..ce40d8a 100644 --- a/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentList.kt +++ b/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentList.kt @@ -30,14 +30,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource import java.util.UUID -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.GameDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) @Composable actual fun TournamentList( navigator: StackNavigation, @@ -53,30 +54,30 @@ actual fun TournamentList( Scaffold( topBar = { LargeTopAppBar( - title = { TopAppBarTitle(stringResource(MR.strings.app_title), scrollBehavior) }, + title = { TopAppBarTitle(stringResource(Res.string.app_title), scrollBehavior) }, actions = { IconButton({ navigator.push(Screen.AppSettings) }) { - Icon(Icons.Default.Settings, stringResource(MR.strings.settings)) + Icon(Icons.Default.Settings, stringResource(Res.string.settings)) } IconButton({ showInfo.value = true }) { - Icon(Icons.Outlined.Info, stringResource(MR.strings.about)) + Icon(Icons.Outlined.Info, stringResource(Res.string.about)) } Box { var expanded by remember { mutableStateOf(false) } IconButton({ expanded = true }) { - Icon(Icons.Default.MoreVert, stringResource(MR.strings.more_actions)) + Icon(Icons.Default.MoreVert, stringResource(Res.string.more_actions)) } DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { DropdownMenuItem( text = { - Text(stringResource(MR.strings.export_tournaments_to_file)) + Text(stringResource(Res.string.export_tournaments_to_file)) }, onClick = { /*TODO*/ }, leadingIcon = { Icon(Icons.Default.ArrowUpward, null) }, enabled = false, ) DropdownMenuItem( - text = { Text(stringResource(MR.strings.import_from_file)) }, + text = { Text(stringResource(Res.string.import_from_file)) }, onClick = { /*TODO*/ }, leadingIcon = { Icon(Icons.Default.ArrowDownward, null) }, enabled = false, @@ -90,7 +91,7 @@ actual fun TournamentList( floatingActionButton = { ExtendedFloatingActionButton( icon = { Icon(Icons.Default.Add, null) }, - text = { Text(stringResource(MR.strings.new_tournament)) }, + text = { Text(stringResource(Res.string.new_tournament)) }, expanded = scrollBehavior.state.collapsedFraction < 0.5f, onClick = { setCurrent(null) diff --git a/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewer.kt b/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewer.kt index d3ba9f3..9f7f438 100644 --- a/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewer.kt +++ b/common/src/jvmMain/kotlin/me/frauenfelderflorian/tournamentscompose/common/ui/TournamentViewer.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.AlertDialog -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.ArrowUpward @@ -37,21 +36,22 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.frauenfelderflorian.tournamentscompose.common.MR import me.frauenfelderflorian.tournamentscompose.common.data.GameDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentDao import me.frauenfelderflorian.tournamentscompose.common.data.TournamentWithGames import me.frauenfelderflorian.tournamentscompose.common.data.players import me.frauenfelderflorian.tournamentscompose.common.data.ranking +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import tournamentscompose.common.generated.resources.Res @OptIn( ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, - ExperimentalMaterialApi::class + ExperimentalResourceApi::class ) @Composable actual fun TournamentViewer( @@ -78,19 +78,19 @@ actual fun TournamentViewer( LargeTopAppBar( title = { TopAppBarTitle( - text = stringResource(MR.strings.tournament_title, tournament.t.name), + text = stringResource(Res.string.tournament_title, tournament.t.name), scrollBehavior = scrollBehavior, ) }, navigationIcon = { BackButton(navigator) }, actions = { IconButton({ navigator.push(Screen.TournamentEditor) }) { - Icon(Icons.Default.Edit, stringResource(MR.strings.edit_tournament)) + Icon(Icons.Default.Edit, stringResource(Res.string.edit_tournament)) } IconButton(onClick = { /*TODO*/ }, enabled = false) { Icon( Icons.Default.ArrowUpward, - stringResource(MR.strings.export_tournament_to_file) + stringResource(Res.string.export_tournament_to_file) ) } SettingsInfoMenu(navigator = navigator, showInfoDialog = showInfo) @@ -105,9 +105,9 @@ actual fun TournamentViewer( Text( stringResource( if (pagerState.currentPage == 0) { - MR.strings.new_game + Res.string.new_game } else { - MR.strings.add_new_player + Res.string.add_new_player } ) ) @@ -144,11 +144,11 @@ actual fun TournamentViewer( var newName by rememberSaveable { mutableStateOf("") } AlertDialog( onDismissRequest = { showNewPlayerDialog = false }, - title = { Text(stringResource(MR.strings.add_new_player)) }, + title = { Text(stringResource(Res.string.add_new_player)) }, text = { Column(verticalArrangement = Arrangement.spacedBy(normalDp)) { - Text(stringResource(MR.strings.add_new_player_info)) - val invalidName = stringResource(MR.strings.invalid_name) + Text(stringResource(Res.string.add_new_player_info)) + val invalidName = stringResource(Res.string.invalid_name) OutlinedTextField( value = newName, onValueChange = { value -> @@ -159,8 +159,8 @@ actual fun TournamentViewer( } }, singleLine = true, - label = { Text(stringResource(MR.strings.name)) }, - placeholder = { Text(stringResource(MR.strings.name_unique)) }, + label = { Text(stringResource(Res.string.name)) }, + placeholder = { Text(stringResource(Res.string.name_unique)) }, modifier = Modifier.fillMaxWidth(), ) } @@ -186,12 +186,12 @@ actual fun TournamentViewer( }, enabled = newName.isNotBlank() && !tournament.t.players.contains(newName), ) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ showNewPlayerDialog = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) @@ -200,9 +200,9 @@ actual fun TournamentViewer( var newName by rememberSaveable { mutableStateOf(playerToBeRenamed.value) } AlertDialog( onDismissRequest = { showRenamePlayerDialog.value = false }, - title = { Text("${stringResource(MR.strings.rename_player)} ${playerToBeRenamed.value}") }, + title = { Text("${stringResource(Res.string.rename_player)} ${playerToBeRenamed.value}") }, text = { - val invalidName = stringResource(MR.strings.invalid_name) + val invalidName = stringResource(Res.string.invalid_name) OutlinedTextField( value = newName, onValueChange = { value -> @@ -213,8 +213,8 @@ actual fun TournamentViewer( } }, singleLine = true, - label = { Text(stringResource(MR.strings.name)) }, - placeholder = { Text(stringResource(MR.strings.name_unique)) }, + label = { Text(stringResource(Res.string.name)) }, + placeholder = { Text(stringResource(Res.string.name_unique)) }, modifier = Modifier.fillMaxWidth(), ) }, @@ -242,12 +242,12 @@ actual fun TournamentViewer( }, enabled = newName.isNotBlank() && !tournament.t.players.contains(newName), ) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ showRenamePlayerDialog.value = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) @@ -256,9 +256,9 @@ actual fun TournamentViewer( AlertDialog( onDismissRequest = { showDeletePlayerDialog.value = false }, title = { - Text("${stringResource(MR.strings.delete_player)} ${playerToBeDeleted.value}?") + Text("${stringResource(Res.string.delete_player)} ${playerToBeDeleted.value}?") }, - text = { Text(stringResource(MR.strings.delete_player_hint)) }, + text = { Text(stringResource(Res.string.delete_player_hint)) }, confirmButton = { TextButton({ showDeletePlayerDialog.value = false @@ -278,12 +278,12 @@ actual fun TournamentViewer( } } }) { - Text(stringResource(MR.strings.ok)) + Text(stringResource(Res.string.ok)) } }, dismissButton = { TextButton({ showDeletePlayerDialog.value = false }) { - Text(stringResource(MR.strings.cancel)) + Text(stringResource(Res.string.cancel)) } }, ) diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts index e1f8668..974028e 100644 --- a/desktopApp/build.gradle.kts +++ b/desktopApp/build.gradle.kts @@ -11,7 +11,6 @@ kotlin { withJava() } sourceSets { - @Suppress("UNUSED_VARIABLE") val jvmMain by getting { dependencies { implementation(compose.desktop.currentOs) diff --git a/settings.gradle.kts b/settings.gradle.kts index 72cc0d4..ec6e183 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,8 +7,8 @@ pluginManagement { } plugins { - val kotlinVersion = "1.8.22" - val agpVersion = "8.1.1" + val kotlinVersion = "1.9.22" + val agpVersion = "8.1.2" kotlin("jvm") version kotlinVersion kotlin("multiplatform") version kotlinVersion @@ -17,9 +17,7 @@ pluginManagement { id("com.android.application") version agpVersion id("com.android.library") version agpVersion - id("org.jetbrains.compose") version "1.5.0-rc02" - - id("dev.icerock.mobile.multiplatform-resources") version "0.23.0" + id("org.jetbrains.compose") version "1.6.0-rc01" } } dependencyResolutionManagement {