From 43346f934d60c7bf234641e645909147653a3247 Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 11 Aug 2025 18:21:55 +0200 Subject: [PATCH 01/17] Total update --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 8 ++++---- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 8 ++++---- gradlew.bat | 4 ++-- .../com/ucasoft/kcron/BuildAndParseTests.kt | 4 +++- .../kotlinx/datetime/CronLocalDateTime.kt | 7 +++++-- .../datetime/CronLocalDateTimeExtensions.kt | 5 ++++- settings.gradle.kts | 2 +- 10 files changed, 25 insertions(+), 17 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 742c600..369eb13 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ allprojects { group = "com.ucasoft.kcron" - version = "0.23.0" + version = "0.26.0" repositories { mavenCentral() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b15a5ea..2d72237 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -kotlin = "2.1.0" -kotlinx-datetime = "0.6.1" +kotlin = "2.2.0" +kotlinx-datetime = "0.7.1" kotest = "5.9.1" -kover = "0.9.0" -benchmark = "0.4.13" +kover = "0.9.1" +benchmark = "0.4.14" [libraries] kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinx-datetime" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..8bdaf60c75ab801e22807dde59e12a8735a34077 100755 GIT binary patch delta 37330 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a79..2a84e18 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f3b75f3..ef07e01 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -205,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019..5eed7ee 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/kcron-common/src/commonTest/kotlin/com/ucasoft/kcron/BuildAndParseTests.kt b/kcron-common/src/commonTest/kotlin/com/ucasoft/kcron/BuildAndParseTests.kt index a901cfa..f5fc94e 100644 --- a/kcron-common/src/commonTest/kotlin/com/ucasoft/kcron/BuildAndParseTests.kt +++ b/kcron-common/src/commonTest/kotlin/com/ucasoft/kcron/BuildAndParseTests.kt @@ -5,11 +5,12 @@ import com.ucasoft.kcron.core.settings.Version import io.kotest.assertions.throwables.shouldThrowWithMessage import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe -import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.time.Clock +import kotlin.time.ExperimentalTime class BuildAndParseTests { @@ -17,6 +18,7 @@ class BuildAndParseTests { private val modernCronExpression = "30 * * ? * * 2050" + @OptIn(ExperimentalTime::class) @BeforeTest fun setupOnce() { currentYear = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).year diff --git a/kcron-kotlinx-datetime/src/commonMain/kotlin/com/ucasoft/kcron/kotlinx/datetime/CronLocalDateTime.kt b/kcron-kotlinx-datetime/src/commonMain/kotlin/com/ucasoft/kcron/kotlinx/datetime/CronLocalDateTime.kt index 7020f16..c596be7 100644 --- a/kcron-kotlinx-datetime/src/commonMain/kotlin/com/ucasoft/kcron/kotlinx/datetime/CronLocalDateTime.kt +++ b/kcron-kotlinx-datetime/src/commonMain/kotlin/com/ucasoft/kcron/kotlinx/datetime/CronLocalDateTime.kt @@ -2,12 +2,15 @@ package com.ucasoft.kcron.kotlinx.datetime import com.ucasoft.kcron.abstractions.CronDateTime import kotlinx.datetime.* +import kotlin.time.Clock +import kotlin.time.ExperimentalTime class CronLocalDateTime: CronDateTime { private val dateTime: LocalDateTime + @OptIn(ExperimentalTime::class) constructor() { dateTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) } @@ -20,10 +23,10 @@ class CronLocalDateTime: CronDateTime { get() = dateTime.year override val month: Int - get() = dateTime.monthNumber + get() = dateTime.month.number override val dayOfMonth: Int - get() = dateTime.dayOfMonth + get() = dateTime.day override val isoDayOfWeek: Int get() = dateTime.dayOfWeek.isoDayNumber diff --git a/kcron-kotlinx-datetime/src/commonMain/kotlin/com/ucasoft/kcron/kotlinx/datetime/CronLocalDateTimeExtensions.kt b/kcron-kotlinx-datetime/src/commonMain/kotlin/com/ucasoft/kcron/kotlinx/datetime/CronLocalDateTimeExtensions.kt index 6ba7c54..db3a1ec 100644 --- a/kcron-kotlinx-datetime/src/commonMain/kotlin/com/ucasoft/kcron/kotlinx/datetime/CronLocalDateTimeExtensions.kt +++ b/kcron-kotlinx-datetime/src/commonMain/kotlin/com/ucasoft/kcron/kotlinx/datetime/CronLocalDateTimeExtensions.kt @@ -1,9 +1,11 @@ package com.ucasoft.kcron.kotlinx.datetime import kotlinx.datetime.* +import kotlinx.datetime.number +import kotlin.time.ExperimentalTime fun LocalDateTime.toCronLocalDateTime() = - CronLocalDateTime(this.year, this.monthNumber, this.dayOfMonth, this.hour, this.minute, this.second) + CronLocalDateTime(this.year, this.month.number, this.day, this.hour, this.minute, this.second) fun LocalDateTime.plusHours(hours: Int, timeZone: TimeZone = TimeZone.currentSystemDefault()): LocalDateTime { return plus(this, hours, DateTimeUnit.HOUR, timeZone) @@ -17,6 +19,7 @@ fun LocalDateTime.minusDays(days: Int, timeZone: TimeZone = TimeZone.currentSyst return plus(this, if (days > 0) { days * -1 } else { days }, DateTimeUnit.DAY, timeZone) } +@OptIn(ExperimentalTime::class) private fun plus(self: LocalDateTime, value: Int, unit: DateTimeUnit, timeZone: TimeZone) : LocalDateTime { return self.toInstant(timeZone).plus(value, unit, timeZone).toLocalDateTime(timeZone) } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a83cd1e..43c10ab 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "kcron" From 34d71424865751c7fb9c26fc670cbd97fd2a00f1 Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 11 Aug 2025 21:26:24 +0200 Subject: [PATCH 02/17] Start implementation --- build.gradle.kts | 1 + gradle.properties | 2 + gradle/libs.versions.toml | 5 +- kcron-ui-builder/build.gradle.kts | 48 ++++++++ .../composeResources/values/strings.xml | 7 ++ .../ucasoft/kcron/ui/builder/CronUiBuilder.kt | 104 ++++++++++++++++++ .../kcron/ui/builder/CronUiBuilderPreview.kt | 10 ++ settings.gradle.kts | 3 +- 8 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 kcron-ui-builder/build.gradle.kts create mode 100644 kcron-ui-builder/src/commonMain/composeResources/values/strings.xml create mode 100644 kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt create mode 100644 kcron-ui-builder/src/jvmMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt diff --git a/build.gradle.kts b/build.gradle.kts index 369eb13..7a16c0d 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,7 @@ allprojects { version = "0.26.0" repositories { + google() mavenCentral() } diff --git a/gradle.properties b/gradle.properties index 7fc6f1f..47c1aa5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,3 @@ kotlin.code.style=official +org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.macos.enabled=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2d72237..246509b 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ kotlinx-datetime = "0.7.1" kotest = "5.9.1" kover = "0.9.1" benchmark = "0.4.14" +compose = "1.8.2" [libraries] kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinx-datetime" } @@ -14,4 +15,6 @@ kotest-assetions = { group = "io.kotest", name = "kotest-assertions-core", versi [plugins] multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } -benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "benchmark" } \ No newline at end of file +benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "benchmark" } +compose = { id = "org.jetbrains.compose", version.ref = "compose" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } \ No newline at end of file diff --git a/kcron-ui-builder/build.gradle.kts b/kcron-ui-builder/build.gradle.kts new file mode 100644 index 0000000..5537214 --- /dev/null +++ b/kcron-ui-builder/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + alias(libs.plugins.multiplatform) + id("publish") + alias(libs.plugins.compose) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kover) +} + +kotlin { + jvmToolchain(11) + jvm() + macosX64() + macosArm64() + js(IR) { + browser() + nodejs() + } + @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) + wasmJs { + browser() + nodejs() + } + iosX64() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + } + } + commonTest { + dependencies { + @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) + implementation(compose.uiTest) + } + } + jvmMain { + dependencies { + implementation(compose.uiTooling) + } + } + } +} \ No newline at end of file diff --git a/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml b/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 0000000..8fabb84 --- /dev/null +++ b/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,7 @@ + + Minute + Hour + Day of Month + Month + Day of Week + \ No newline at end of file diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt new file mode 100644 index 0000000..9e75f33 --- /dev/null +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt @@ -0,0 +1,104 @@ +package com.ucasoft.kcron.ui.builder + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.ucasoft.kcron.kcron_ui_builder.generated.resources.* +import org.jetbrains.compose.resources.stringResource + + +@Composable +fun CronUiBuilder( + modifier: Modifier = Modifier +) { + Card ( + modifier = modifier.padding(12.dp) + ) { + Column( + modifier = Modifier.fillMaxWidth().padding(12.dp) + ) { + CronField( + stringResource(Res.string.minute), + listOf( + "Every" to "*" + ) + ) + CronField( + stringResource(Res.string.hour) + ) + CronField( + stringResource(Res.string.day_of_month) + ) + CronField( + stringResource(Res.string.month) + ) + CronField( + stringResource(Res.string.day_of_week) + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CronField( + label: String, + options: List> = emptyList() +) { + var open by remember { mutableStateOf(false) } + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + Text( + label, + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold) + ) + ExposedDropdownMenuBox( + expanded = open, + onExpandedChange = { open = false } + ) { + TextField( + "Label", + onValueChange = {} + ) + if (options.isNotEmpty()) { + ExposedDropdownMenu( + expanded = open, + onDismissRequest = { open = false } + ) { + options.map { + DropdownMenuItem( + onClick = { + open = false + }, + text = { + Text(it.first) + } + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/kcron-ui-builder/src/jvmMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt b/kcron-ui-builder/src/jvmMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt new file mode 100644 index 0000000..8be5a34 --- /dev/null +++ b/kcron-ui-builder/src/jvmMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt @@ -0,0 +1,10 @@ +package com.ucasoft.kcron.ui.builder + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.runtime.Composable + +@Composable +@Preview +fun PreviewCronUiBuilder() { + CronUiBuilder() +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 43c10ab..f7824fb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,5 +9,6 @@ include( "kcron-common", "kcron-common-benchmark", "kcron-core", - "kcron-kotlinx-datetime" + "kcron-kotlinx-datetime", + "kcron-ui-builder" ) \ No newline at end of file From e56fa5d03811fea3a530fe967d6617ea2eda1baa Mon Sep 17 00:00:00 2001 From: Sergey Date: Tue, 12 Aug 2025 21:36:28 +0200 Subject: [PATCH 03/17] Work on UI --- kcron-ui-builder/build.gradle.kts | 1 + .../ucasoft/kcron/ui/builder/CronUiBuilder.kt | 203 +++++++++++++++--- 2 files changed, 171 insertions(+), 33 deletions(-) diff --git a/kcron-ui-builder/build.gradle.kts b/kcron-ui-builder/build.gradle.kts index 5537214..c605af5 100644 --- a/kcron-ui-builder/build.gradle.kts +++ b/kcron-ui-builder/build.gradle.kts @@ -31,6 +31,7 @@ kotlin { implementation(compose.foundation) implementation(compose.material3) implementation(compose.components.resources) + implementation(project(":kcron-common")) } } commonTest { diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt index 9e75f33..6763b52 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt @@ -1,57 +1,162 @@ package com.ucasoft.kcron.ui.builder +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Card -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.ucasoft.kcron.core.common.WeekDays +import com.ucasoft.kcron.core.exceptions.WrongPartExpression +import com.ucasoft.kcron.core.parsers.Parser +import com.ucasoft.kcron.core.settings.Version import com.ucasoft.kcron.kcron_ui_builder.generated.resources.* import org.jetbrains.compose.resources.stringResource +private data class Expression( + val minutes: String = "*", + val hours: String = "*", + val dayOfMonth: String = "?", + val month: String = "*", + val dayOfWeek: String = "*", +) { + override fun toString(): String { + return "$minutes $hours $dayOfMonth $month $dayOfWeek" + } +} @Composable fun CronUiBuilder( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + allowCustom: Boolean = true, + firstDayOfWeek: WeekDays = WeekDays.Monday ) { - Card ( + val parser = Parser() + var expression by remember { mutableStateOf(Expression()) } + + Card( modifier = modifier.padding(12.dp) ) { Column( - modifier = Modifier.fillMaxWidth().padding(12.dp) + modifier = Modifier.fillMaxWidth().padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { CronField( stringResource(Res.string.minute), listOf( - "Every" to "*" - ) - ) + "Every" to "*", + "Hour start" to "0", + "15" to "15", + "30" to "30", + "45" to "45", + "Every 5" to "0/5", + "Every 15" to "0/15", + "Every 30" to "0/30" + ), + allowCustom + ) { + try { + val copy = expression.copy(minutes = it) + parser.parse(copy.toString(), Version.Classic) + expression = copy + } + catch (wrong: WrongPartExpression) { + println(wrong) + } + } CronField( - stringResource(Res.string.hour) - ) + stringResource(Res.string.hour), + listOf( + "Every" to "*", + "Midnight" to "0", + "6 AM" to "6", + "9 AM" to "9", + "Noon" to "12", + "6 PM" to "18", + "9 PM" to "21", + "Every 2" to "0/2", + "Every 6" to "0/6", + "Every 12" to "0/12" + ), + allowCustom + ) { + try { + val copy = expression.copy(hours = it) + parser.parse(copy.toString(), Version.Classic) + expression = copy + } + catch (wrong: WrongPartExpression) { + println(wrong) + } + } CronField( - stringResource(Res.string.day_of_month) + stringResource(Res.string.day_of_month), + listOf( + "Every" to "*", + "1st" to "1", + "15th" to "15", + "Last" to "L", + "First Weekday" to "1W", + "Last Weekday" to "LW", + "Every 2" to "*/2", + "Every 7" to "*/7", + "Every 14" to "*/14" + ), + allowCustom ) CronField( - stringResource(Res.string.month) + stringResource(Res.string.month), + listOf( + "Every" to "*", + "January" to "1", + "February" to "2", + "March" to "3", + "April" to "4", + "May" to "5", + "June" to "6", + "July" to "7", + "August" to "8", + "September" to "9", + "October" to "10", + "November" to "11", + "December" to "12" + ), + allowCustom ) CronField( - stringResource(Res.string.day_of_week) + stringResource(Res.string.day_of_week), + listOf( + "Every" to "*", + "Monday" to "1", + "Tuesday" to "2", + "Wednesday" to "3", + "Thursday" to "4", + "Friday" to "5", + "Saturday" to "6", + "Sunday" to "7", + "Weekdays" to "1-5", + "Weekends" to "6-7" + ), + allowCustom + ) + Spacer( + Modifier.weight(1f) + ) + OutlinedTextField( + expression.toString(), + onValueChange = {}, + readOnly = true, + modifier = Modifier.background(MaterialTheme.colorScheme.primary), + colors = OutlinedTextFieldDefaults.colors(focusedTextColor = MaterialTheme.colorScheme.onPrimary, unfocusedTextColor = MaterialTheme.colorScheme.onPrimary), + textStyle = TextStyle.Default.copy(textAlign = TextAlign.Center) ) } } @@ -61,9 +166,15 @@ fun CronUiBuilder( @Composable fun CronField( label: String, - options: List> = emptyList() + options: List> = emptyList(), + allowCustom: Boolean = false, + customPlaceholder: String = "Enter custom value", + onValueChanged: (String) -> Unit = {} ) { var open by remember { mutableStateOf(false) } + var prebuildPattern by remember { mutableStateOf(options.firstOrNull()) } + val customPattern = "Custom" to "" + var selectedPattern by remember { mutableStateOf("") } Column( verticalArrangement = Arrangement.spacedBy(8.dp), @@ -76,29 +187,55 @@ fun CronField( ) ExposedDropdownMenuBox( expanded = open, - onExpandedChange = { open = false } + onExpandedChange = { open = it }, ) { - TextField( - "Label", - onValueChange = {} + OutlinedTextField( + prebuildPattern?.first ?: "", + onValueChange = {}, + readOnly = true, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(open) + }, + modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable) ) - if (options.isNotEmpty()) { + if(options.isNotEmpty()) { ExposedDropdownMenu( expanded = open, onDismissRequest = { open = false } ) { options.map { DropdownMenuItem( + text = { Text(it.first) }, onClick = { + prebuildPattern = it + selectedPattern = it.second + onValueChanged(it.second) + open = false + } + ) + } + if (allowCustom) { + DropdownMenuItem( + text = { Text(customPattern.first) }, + onClick = { + prebuildPattern = customPattern + selectedPattern = customPattern.second open = false - }, - text = { - Text(it.first) } ) } } } } + if (prebuildPattern == customPattern) { + OutlinedTextField( + selectedPattern, + onValueChange = { + selectedPattern = it + onValueChanged(it) + }, + placeholder = { Text(customPlaceholder) } + ) + } } } \ No newline at end of file From 63c853b1935c481ce568fed37840360ad86dd72c Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 15 Aug 2025 14:37:57 +0200 Subject: [PATCH 04/17] Make ParsResult Data Class --- .../kcron/core/exceptions/WrongPartCombination.kt | 2 +- .../com/ucasoft/kcron/core/parsers/ParseResult.kt | 5 +---- .../com/ucasoft/kcron/core/parsers/Parser.kt | 14 ++++++++------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/exceptions/WrongPartCombination.kt b/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/exceptions/WrongPartCombination.kt index 0e573fe..20c99a2 100644 --- a/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/exceptions/WrongPartCombination.kt +++ b/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/exceptions/WrongPartCombination.kt @@ -5,7 +5,7 @@ import com.ucasoft.kcron.core.common.PartValue import com.ucasoft.kcron.core.parsers.CombinationRule class WrongPartCombination( - private val part: MutableMap.MutableEntry, + private val part: Map.Entry, private val dependency: CombinationRule, secondPart: PartValue ) : Throwable("Wrong part combination: part ${part.key.partName} with ${part.value.type} type requires that part ${dependency.part.partName} has ${dependency.type} type but it was ${secondPart.type}!") { diff --git a/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/parsers/ParseResult.kt b/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/parsers/ParseResult.kt index 81f5960..19d21f3 100644 --- a/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/parsers/ParseResult.kt +++ b/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/parsers/ParseResult.kt @@ -3,7 +3,4 @@ package com.ucasoft.kcron.core.parsers import com.ucasoft.kcron.core.common.CronPart import com.ucasoft.kcron.core.common.PartValue -class ParseResult { - - val parts = mutableMapOf() -} \ No newline at end of file +data class ParseResult(val parts: Map) \ No newline at end of file diff --git a/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/parsers/Parser.kt b/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/parsers/Parser.kt index 9772a77..6b12048 100644 --- a/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/parsers/Parser.kt +++ b/kcron-core/src/commonMain/kotlin/com/ucasoft/kcron/core/parsers/Parser.kt @@ -70,14 +70,16 @@ class Parser { throw WrongPartsExpression(partExceptions) } - val result = ParseResult() - for ((index, parser) in partParsers.withIndex()) { - result.parts[parser.part] = PartValue(parser.group as CronGroups, expressionParts[index]) - } - return result + return ParseResult( + partParsers.withIndex().associate { (index, part) -> + part.part to PartValue( + part.group as CronGroups, + expressionParts[index] + ) + }) } - private fun ensureCombinationRules(parts: MutableMap) { + private fun ensureCombinationRules(parts: Map) { val combinationExceptions = mutableListOf() for (part in parts) { val rule = combinationRules.firstOrNull { r -> r.part == part.key && r.type == part.value.type } From 536a5cbe623ee5e2050bbc16a8727d8fb8a19372 Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 15 Aug 2025 14:54:02 +0200 Subject: [PATCH 05/17] Make it works --- .../com/ucasoft/kcron/ui/builder/CronField.kt | 93 ++++++++ .../ucasoft/kcron/ui/builder/CronUiBuilder.kt | 211 ++++++------------ 2 files changed, 166 insertions(+), 138 deletions(-) create mode 100644 kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt new file mode 100644 index 0000000..154783c --- /dev/null +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt @@ -0,0 +1,93 @@ +package com.ucasoft.kcron.ui.builder + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuAnchorType +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun CronField( + label: String, + value: String, + options: List> = emptyList(), + customPlaceholder: String = "Enter custom value", + isError: Boolean = false, + onValueChanged: (String) -> Unit = {} +) { + var open by remember { mutableStateOf(false) } + var prebuildPattern by remember { mutableStateOf(options.firstOrNull { it.second == value } ?: options.last()) } + var selectedPattern by remember { mutableStateOf(value) } + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.Companion.CenterHorizontally, + modifier = Modifier.Companion.fillMaxWidth() + ) { + Text( + label, + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Companion.Bold) + ) + ExposedDropdownMenuBox( + expanded = open, + onExpandedChange = { open = it }, + ) { + OutlinedTextField( + prebuildPattern.first, + onValueChange = {}, + readOnly = true, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(open) + }, + modifier = Modifier.Companion.menuAnchor(MenuAnchorType.Companion.PrimaryNotEditable) + ) + if (options.isNotEmpty()) { + ExposedDropdownMenu( + expanded = open, + onDismissRequest = { open = false } + ) { + options.map { + DropdownMenuItem( + text = { Text(it.first) }, + onClick = { + prebuildPattern = it + if (it.second.isNotEmpty()) { + selectedPattern = it.second + onValueChanged(it.second) + } + open = false + } + ) + } + } + } + } + if (prebuildPattern.first == "Custom") { + OutlinedTextField( + selectedPattern, + onValueChange = { + selectedPattern = it + onValueChanged(it) + }, + placeholder = { Text(customPlaceholder) }, + isError = isError + ) + } + } +} \ No newline at end of file diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt index 6763b52..eec6234 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt @@ -1,7 +1,6 @@ package com.ucasoft.kcron.ui.builder import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -11,36 +10,26 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.ucasoft.kcron.core.common.CronPart +import com.ucasoft.kcron.core.common.PartValue import com.ucasoft.kcron.core.common.WeekDays +import com.ucasoft.kcron.core.exceptions.WrongPartCombinations import com.ucasoft.kcron.core.exceptions.WrongPartExpression import com.ucasoft.kcron.core.parsers.Parser -import com.ucasoft.kcron.core.settings.Version import com.ucasoft.kcron.kcron_ui_builder.generated.resources.* import org.jetbrains.compose.resources.stringResource -private data class Expression( - val minutes: String = "*", - val hours: String = "*", - val dayOfMonth: String = "?", - val month: String = "*", - val dayOfWeek: String = "*", -) { - override fun toString(): String { - return "$minutes $hours $dayOfMonth $month $dayOfWeek" - } -} - @Composable fun CronUiBuilder( + expression: String = "* * * * *", modifier: Modifier = Modifier, allowCustom: Boolean = true, firstDayOfWeek: WeekDays = WeekDays.Monday ) { val parser = Parser() - var expression by remember { mutableStateOf(Expression()) } + var parts by remember { mutableStateOf(parser.parse(expression).parts) } Card( modifier = modifier.padding(12.dp) @@ -49,9 +38,8 @@ fun CronUiBuilder( modifier = Modifier.fillMaxWidth().padding(12.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - CronField( - stringResource(Res.string.minute), - listOf( + listOf( + Res.string.minute to listOf( "Every" to "*", "Hour start" to "0", "15" to "15", @@ -61,20 +49,7 @@ fun CronUiBuilder( "Every 15" to "0/15", "Every 30" to "0/30" ), - allowCustom - ) { - try { - val copy = expression.copy(minutes = it) - parser.parse(copy.toString(), Version.Classic) - expression = copy - } - catch (wrong: WrongPartExpression) { - println(wrong) - } - } - CronField( - stringResource(Res.string.hour), - listOf( + Res.string.hour to listOf( "Every" to "*", "Midnight" to "0", "6 AM" to "6", @@ -86,35 +61,18 @@ fun CronUiBuilder( "Every 6" to "0/6", "Every 12" to "0/12" ), - allowCustom - ) { - try { - val copy = expression.copy(hours = it) - parser.parse(copy.toString(), Version.Classic) - expression = copy - } - catch (wrong: WrongPartExpression) { - println(wrong) - } - } - CronField( - stringResource(Res.string.day_of_month), - listOf( + Res.string.day_of_month to listOf( "Every" to "*", "1st" to "1", "15th" to "15", "Last" to "L", "First Weekday" to "1W", "Last Weekday" to "LW", - "Every 2" to "*/2", - "Every 7" to "*/7", - "Every 14" to "*/14" + "Every 2" to "1/2", + "Every 7" to "1/7", + "Every 14" to "1/14" ), - allowCustom - ) - CronField( - stringResource(Res.string.month), - listOf( + Res.string.month to listOf( "Every" to "*", "January" to "1", "February" to "2", @@ -129,11 +87,7 @@ fun CronUiBuilder( "November" to "11", "December" to "12" ), - allowCustom - ) - CronField( - stringResource(Res.string.day_of_week), - listOf( + Res.string.day_of_week to listOf( "Every" to "*", "Monday" to "1", "Tuesday" to "2", @@ -144,14 +98,53 @@ fun CronUiBuilder( "Sunday" to "7", "Weekdays" to "1-5", "Weekends" to "6-7" - ), - allowCustom - ) + ) + ).map { + it.first to if (allowCustom) + it.second + ("Custom" to "") + else + it.second + }.map { + (labelRes, options) -> + var isError by remember { mutableStateOf(false) } + CronField( + stringResource(labelRes), + when(labelRes) { + Res.string.minute -> parts[CronPart.Minutes]!!.value + Res.string.hour -> parts[CronPart.Hours]!!.value + Res.string.day_of_month -> parts[CronPart.Days]!!.value + Res.string.month -> parts[CronPart.Months]!!.value + Res.string.day_of_week -> parts[CronPart.DaysOfWeek]!!.value + else -> "" + }, + options, + isError = isError + ) { + try { + val copy = when (labelRes) { + Res.string.minute -> copyParts(parts, minutes = it) + Res.string.hour -> copyParts(parts, hours = it) + Res.string.day_of_month -> copyParts(parts, dayOfMonth = it) + Res.string.month -> copyParts(parts, month = it) + Res.string.day_of_week -> copyParts(parts, dayOfWeek = it) + else -> parts + } + parser.parse(copy.entries.joinToString(" ") { it.value.value }) + parts = copy + isError = false + } catch (wrong: WrongPartExpression) { + println(wrong) + isError = true + } catch (wrong: WrongPartCombinations) { + throw wrong + } + } + } Spacer( Modifier.weight(1f) ) OutlinedTextField( - expression.toString(), + parts.entries.drop(1).take(5).joinToString(" ") { it.value.value }, onValueChange = {}, readOnly = true, modifier = Modifier.background(MaterialTheme.colorScheme.primary), @@ -162,80 +155,22 @@ fun CronUiBuilder( } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun CronField( - label: String, - options: List> = emptyList(), - allowCustom: Boolean = false, - customPlaceholder: String = "Enter custom value", - onValueChanged: (String) -> Unit = {} -) { - var open by remember { mutableStateOf(false) } - var prebuildPattern by remember { mutableStateOf(options.firstOrNull()) } - val customPattern = "Custom" to "" - var selectedPattern by remember { mutableStateOf("") } - - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth() - ) { - Text( - label, - style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold) - ) - ExposedDropdownMenuBox( - expanded = open, - onExpandedChange = { open = it }, - ) { - OutlinedTextField( - prebuildPattern?.first ?: "", - onValueChange = {}, - readOnly = true, - trailingIcon = { - ExposedDropdownMenuDefaults.TrailingIcon(open) - }, - modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable) - ) - if(options.isNotEmpty()) { - ExposedDropdownMenu( - expanded = open, - onDismissRequest = { open = false } - ) { - options.map { - DropdownMenuItem( - text = { Text(it.first) }, - onClick = { - prebuildPattern = it - selectedPattern = it.second - onValueChanged(it.second) - open = false - } - ) - } - if (allowCustom) { - DropdownMenuItem( - text = { Text(customPattern.first) }, - onClick = { - prebuildPattern = customPattern - selectedPattern = customPattern.second - open = false - } - ) - } - } - } - } - if (prebuildPattern == customPattern) { - OutlinedTextField( - selectedPattern, - onValueChange = { - selectedPattern = it - onValueChanged(it) - }, - placeholder = { Text(customPlaceholder) } - ) +private fun copyParts( + parts: Map, + minutes: String? = null, + hours: String? = null, + dayOfMonth: String? = null, + month: String? = null, + dayOfWeek: String? = null +) : Map { + return parts.entries.associate { + it.key to when (it.key) { + CronPart.Minutes -> PartValue(it.value.type, minutes ?: it.value.value) + CronPart.Hours -> PartValue(it.value.type, hours ?: it.value.value) + CronPart.Days -> PartValue(it.value.type, dayOfMonth ?: it.value.value) + CronPart.Months -> PartValue(it.value.type, month ?: it.value.value) + CronPart.DaysOfWeek -> PartValue(it.value.type, dayOfWeek ?: it.value.value) + else -> it.value } } } \ No newline at end of file From 698bd9ed4ac5eaf5a3e81c147d246a5748354595 Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 15 Aug 2025 16:40:02 +0200 Subject: [PATCH 06/17] Finish with builder --- .../ucasoft/kcron/ui/builder/CronUiBuilder.kt | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt index eec6234..a08fb8d 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt @@ -1,7 +1,9 @@ package com.ucasoft.kcron.ui.builder import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -12,6 +14,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.ucasoft.kcron.Cron +import com.ucasoft.kcron.core.builders.Builder import com.ucasoft.kcron.core.common.CronPart import com.ucasoft.kcron.core.common.PartValue import com.ucasoft.kcron.core.common.WeekDays @@ -26,7 +30,8 @@ fun CronUiBuilder( expression: String = "* * * * *", modifier: Modifier = Modifier, allowCustom: Boolean = true, - firstDayOfWeek: WeekDays = WeekDays.Monday + firstDayOfWeek: WeekDays = WeekDays.Monday, + onBuild: (Builder<*, *, *>) -> Unit = {}, ) { val parser = Parser() var parts by remember { mutableStateOf(parser.parse(expression).parts) } @@ -151,6 +156,35 @@ fun CronUiBuilder( colors = OutlinedTextFieldDefaults.colors(focusedTextColor = MaterialTheme.colorScheme.onPrimary, unfocusedTextColor = MaterialTheme.colorScheme.onPrimary), textStyle = TextStyle.Default.copy(textAlign = TextAlign.Center) ) + Spacer( + Modifier.weight(1f) + ) + Row { + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.Center + ) { + Button( + onClick = {}, + ) { + Text("Cancel") + } + } + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.Center + ) { + Button( + onClick = { + onBuild(Cron.parseAndBuild(parts.entries.joinToString(" ") { it.value.value }) { + it.firstDayOfWeek = firstDayOfWeek + }) + }, + ) { + Text("Ok") + } + } + } } } } From 5e7fec267c966eeeae318cb742a330b6aa1c43ff Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 15 Aug 2025 17:43:09 +0200 Subject: [PATCH 07/17] Work with string resources --- .../composeResources/values-ru/strings.xml | 37 +++++++++++++ .../composeResources/values/strings.xml | 30 ++++++++++ .../com/ucasoft/kcron/ui/builder/CronField.kt | 8 +-- .../ucasoft/kcron/ui/builder/CronUiBuilder.kt | 55 +++++++------------ 4 files changed, 92 insertions(+), 38 deletions(-) create mode 100644 kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml diff --git a/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml b/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml new file mode 100644 index 0000000..27184c0 --- /dev/null +++ b/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml @@ -0,0 +1,37 @@ + + Минута + Час + День месяца + Месяц + День недели + + + Каждый + Каждые %1$d + + + + Январь + Февраль + Март + Апрель + Май + Июнь + Июль + Август + Сентябрь + Октябрь + Ноябрь + Декабрь + + + + Понедельник + Вторник + Среда + Четверг + Пятница + Суббота + Воскресенье + + \ No newline at end of file diff --git a/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml b/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml index 8fabb84..126a9d2 100644 --- a/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml +++ b/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml @@ -4,4 +4,34 @@ Day of Month Month Day of Week + + + Every + Every %1$d + + + + January + February + March + April + May + June + July + August + September + October + November + December + + + + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday + Sunday + \ No newline at end of file diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt index 154783c..f62c7de 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt @@ -37,12 +37,12 @@ internal fun CronField( Column( verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalAlignment = Alignment.Companion.CenterHorizontally, - modifier = Modifier.Companion.fillMaxWidth() + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() ) { Text( label, - style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Companion.Bold) + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold) ) ExposedDropdownMenuBox( expanded = open, @@ -55,7 +55,7 @@ internal fun CronField( trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(open) }, - modifier = Modifier.Companion.menuAnchor(MenuAnchorType.Companion.PrimaryNotEditable) + modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable) ) if (options.isNotEmpty()) { ExposedDropdownMenu( diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt index a08fb8d..e0e7388 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt @@ -23,6 +23,8 @@ import com.ucasoft.kcron.core.exceptions.WrongPartCombinations import com.ucasoft.kcron.core.exceptions.WrongPartExpression import com.ucasoft.kcron.core.parsers.Parser import com.ucasoft.kcron.kcron_ui_builder.generated.resources.* +import org.jetbrains.compose.resources.pluralStringResource +import org.jetbrains.compose.resources.stringArrayResource import org.jetbrains.compose.resources.stringResource @Composable @@ -43,64 +45,49 @@ fun CronUiBuilder( modifier = Modifier.fillMaxWidth().padding(12.dp), horizontalAlignment = Alignment.CenterHorizontally ) { + var daysOfWeek = stringArrayResource(Res.array.days_of_week).withIndex().map { it.value to it.index } + daysOfWeek = daysOfWeek.drop(firstDayOfWeek.ordinal) + daysOfWeek.take(firstDayOfWeek.ordinal) listOf( Res.string.minute to listOf( - "Every" to "*", + pluralStringResource(Res.plurals.every, 1) to "*", "Hour start" to "0", "15" to "15", "30" to "30", "45" to "45", - "Every 5" to "0/5", - "Every 15" to "0/15", - "Every 30" to "0/30" + pluralStringResource(Res.plurals.every, 5, 5) to "0/5", + pluralStringResource(Res.plurals.every, 15, 15) to "0/15", + pluralStringResource(Res.plurals.every, 30, 30) to "0/30" ), Res.string.hour to listOf( - "Every" to "*", + pluralStringResource(Res.plurals.every, 1) to "*", "Midnight" to "0", "6 AM" to "6", "9 AM" to "9", "Noon" to "12", "6 PM" to "18", "9 PM" to "21", - "Every 2" to "0/2", - "Every 6" to "0/6", - "Every 12" to "0/12" + pluralStringResource(Res.plurals.every, 2, 2) to "0/2", + pluralStringResource(Res.plurals.every, 6, 6) to "0/6", + pluralStringResource(Res.plurals.every, 12, 12) to "0/12" ), Res.string.day_of_month to listOf( - "Every" to "*", + pluralStringResource(Res.plurals.every, 1) to "*", "1st" to "1", "15th" to "15", "Last" to "L", "First Weekday" to "1W", "Last Weekday" to "LW", - "Every 2" to "1/2", - "Every 7" to "1/7", - "Every 14" to "1/14" + pluralStringResource(Res.plurals.every, 2, 2) to "1/2", + pluralStringResource(Res.plurals.every, 7, 7) to "1/7", + pluralStringResource(Res.plurals.every, 14, 14) to "1/14" ), Res.string.month to listOf( - "Every" to "*", - "January" to "1", - "February" to "2", - "March" to "3", - "April" to "4", - "May" to "5", - "June" to "6", - "July" to "7", - "August" to "8", - "September" to "9", - "October" to "10", - "November" to "11", - "December" to "12" - ), + pluralStringResource(Res.plurals.every, 1) to "*" + ) + stringArrayResource(Res.array.months).withIndex().map { it.value to (it.index + 1).toString() }, Res.string.day_of_week to listOf( - "Every" to "*", - "Monday" to "1", - "Tuesday" to "2", - "Wednesday" to "3", - "Thursday" to "4", - "Friday" to "5", - "Saturday" to "6", - "Sunday" to "7", + pluralStringResource(Res.plurals.every, 1) to "*" + ) + daysOfWeek.withIndex().map { (index, name) -> name.first to (index + 1).toString() } + + listOf( "Weekdays" to "1-5", "Weekends" to "6-7" ) From 2eb3116a6f589a2d5752fcd40d50ca460a07651b Mon Sep 17 00:00:00 2001 From: Sergey Date: Sat, 16 Aug 2025 13:29:09 +0200 Subject: [PATCH 08/17] Finish with dropdowns resources --- .../composeResources/values-ru/strings.xml | 17 ++++++++++ .../composeResources/values/strings.xml | 17 ++++++++++ .../com/ucasoft/kcron/ui/builder/CronField.kt | 2 +- .../ucasoft/kcron/ui/builder/CronUiBuilder.kt | 32 ++++++++----------- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml b/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml index 27184c0..351dbbf 100644 --- a/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml +++ b/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml @@ -5,11 +5,28 @@ Месяц День недели + Вручную + Каждый Каждые %1$d + Начало часа + + Полночь + 6 + 9 + Полдень + 18 + 21 + + 1-е + 15-е + Последний + Первый будний день + Последний будний день + Январь Февраль diff --git a/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml b/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml index 126a9d2..c997a4b 100644 --- a/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml +++ b/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml @@ -5,11 +5,28 @@ Month Day of Week + Custom + Every Every %1$d + Hour Start + + Midnight + 6 AM + 9 AM + Noon + 6 PM + 9 PM + + 1st + 15th + Last + First Weekday + Last Weekday + January February diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt index f62c7de..c4ea244 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronField.kt @@ -78,7 +78,7 @@ internal fun CronField( } } } - if (prebuildPattern.first == "Custom") { + if (prebuildPattern.second == "") { OutlinedTextField( selectedPattern, onValueChange = { diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt index e0e7388..4819e70 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt @@ -50,7 +50,7 @@ fun CronUiBuilder( listOf( Res.string.minute to listOf( pluralStringResource(Res.plurals.every, 1) to "*", - "Hour start" to "0", + stringResource(Res.string.hour_start) to "0", "15" to "15", "30" to "30", "45" to "45", @@ -60,23 +60,23 @@ fun CronUiBuilder( ), Res.string.hour to listOf( pluralStringResource(Res.plurals.every, 1) to "*", - "Midnight" to "0", - "6 AM" to "6", - "9 AM" to "9", - "Noon" to "12", - "6 PM" to "18", - "9 PM" to "21", + stringResource(Res.string.midnight) to "0", + stringResource(Res.string._6_am) to "6", + stringResource(Res.string._9_am) to "9", + stringResource(Res.string.noon) to "12", + stringResource(Res.string._6_pm) to "18", + stringResource(Res.string._9_pm) to "21", pluralStringResource(Res.plurals.every, 2, 2) to "0/2", pluralStringResource(Res.plurals.every, 6, 6) to "0/6", pluralStringResource(Res.plurals.every, 12, 12) to "0/12" ), Res.string.day_of_month to listOf( pluralStringResource(Res.plurals.every, 1) to "*", - "1st" to "1", - "15th" to "15", - "Last" to "L", - "First Weekday" to "1W", - "Last Weekday" to "LW", + stringResource(Res.string.first) to "1", + stringResource(Res.string.fifteenth) to "15", + stringResource(Res.string.last) to "L", + stringResource(Res.string.first_weekday) to "1W", + stringResource(Res.string.last_weekday) to "LW", pluralStringResource(Res.plurals.every, 2, 2) to "1/2", pluralStringResource(Res.plurals.every, 7, 7) to "1/7", pluralStringResource(Res.plurals.every, 14, 14) to "1/14" @@ -86,14 +86,10 @@ fun CronUiBuilder( ) + stringArrayResource(Res.array.months).withIndex().map { it.value to (it.index + 1).toString() }, Res.string.day_of_week to listOf( pluralStringResource(Res.plurals.every, 1) to "*" - ) + daysOfWeek.withIndex().map { (index, name) -> name.first to (index + 1).toString() } + - listOf( - "Weekdays" to "1-5", - "Weekends" to "6-7" - ) + ) + daysOfWeek.withIndex().map { (index, name) -> name.first to (index + 1).toString() } ).map { it.first to if (allowCustom) - it.second + ("Custom" to "") + it.second + (stringResource(Res.string.custom) to "") else it.second }.map { From dc0af2693a601ac0642e65adc3ebd15e7c02cfe8 Mon Sep 17 00:00:00 2001 From: Sergey Date: Sun, 17 Aug 2025 12:11:17 +0200 Subject: [PATCH 09/17] Finish with resources --- .../src/commonMain/composeResources/values-ru/strings.xml | 6 ++++++ .../src/commonMain/composeResources/values/strings.xml | 7 +++++++ .../kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml b/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml index 351dbbf..90fb818 100644 --- a/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml +++ b/kcron-ui-builder/src/commonMain/composeResources/values-ru/strings.xml @@ -12,6 +12,10 @@ Каждые %1$d + + Каждая + + Начало часа Полночь @@ -51,4 +55,6 @@ Суббота Воскресенье + + Отмена \ No newline at end of file diff --git a/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml b/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml index c997a4b..7414a24 100644 --- a/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml +++ b/kcron-ui-builder/src/commonMain/composeResources/values/strings.xml @@ -12,6 +12,10 @@ Every %1$d + + Every + + Hour Start Midnight @@ -51,4 +55,7 @@ Saturday Sunday + + Cancel + OK \ No newline at end of file diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt index 4819e70..20716e6 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt @@ -49,7 +49,7 @@ fun CronUiBuilder( daysOfWeek = daysOfWeek.drop(firstDayOfWeek.ordinal) + daysOfWeek.take(firstDayOfWeek.ordinal) listOf( Res.string.minute to listOf( - pluralStringResource(Res.plurals.every, 1) to "*", + pluralStringResource(Res.plurals.every_feminine, 1) to "*", stringResource(Res.string.hour_start) to "0", "15" to "15", "30" to "30", @@ -150,7 +150,7 @@ fun CronUiBuilder( Button( onClick = {}, ) { - Text("Cancel") + Text(stringResource(Res.string.cancel)) } } Box( @@ -164,7 +164,7 @@ fun CronUiBuilder( }) }, ) { - Text("Ok") + Text(stringResource(Res.string.ok)) } } } From 21b1245807d55da309300a1181b67ad0adea3c8f Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 22 Aug 2025 10:49:22 +0200 Subject: [PATCH 10/17] Add simple test --- kcron-ui-builder/build.gradle.kts | 6 +----- .../kcron/ui/builder/CronUIBuilderTests.kt | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 kcron-ui-builder/src/commonTest/kotlin/com/ucasoft/kcron/ui/builder/CronUIBuilderTests.kt diff --git a/kcron-ui-builder/build.gradle.kts b/kcron-ui-builder/build.gradle.kts index c605af5..f6f9fed 100644 --- a/kcron-ui-builder/build.gradle.kts +++ b/kcron-ui-builder/build.gradle.kts @@ -36,14 +36,10 @@ kotlin { } commonTest { dependencies { + implementation(kotlin("test")) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation(compose.uiTest) } } - jvmMain { - dependencies { - implementation(compose.uiTooling) - } - } } } \ No newline at end of file diff --git a/kcron-ui-builder/src/commonTest/kotlin/com/ucasoft/kcron/ui/builder/CronUIBuilderTests.kt b/kcron-ui-builder/src/commonTest/kotlin/com/ucasoft/kcron/ui/builder/CronUIBuilderTests.kt new file mode 100644 index 0000000..db2889d --- /dev/null +++ b/kcron-ui-builder/src/commonTest/kotlin/com/ucasoft/kcron/ui/builder/CronUIBuilderTests.kt @@ -0,0 +1,20 @@ +@file:OptIn(ExperimentalTestApi::class) + +package com.ucasoft.kcron.ui.builder + +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.runComposeUiTest +import kotlin.test.Test + +internal class CronUIBuilderTests { + + @Test + fun initialExpressionTest() = runComposeUiTest { + setContent { + CronUiBuilder("0 9 29 10 *") + } + + onNodeWithText("0 9 29 10 *").assertExists() + } +} \ No newline at end of file From e0f85f697c9811aa4e5dbdb5fefac3db0bce0a8c Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 22 Aug 2025 10:50:42 +0200 Subject: [PATCH 11/17] Change publishing approach --- build.gradle.kts | 5 +- buildSrc/settings.gradle.kts | 7 ++ .../src/main/kotlin/PublishingExtension.kt | 33 +++++++-- buildSrc/src/main/kotlin/publish.gradle.kts | 67 ------------------- gradle/libs.versions.toml | 4 +- kcron-abstractions/build.gradle.kts | 12 ++-- kcron-common/build.gradle.kts | 12 ++-- kcron-core/build.gradle.kts | 12 ++-- kcron-kotlinx-datetime/build.gradle.kts | 12 ++-- kcron-ui-builder/build.gradle.kts | 11 ++- 10 files changed, 82 insertions(+), 93 deletions(-) create mode 100644 buildSrc/settings.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/publish.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts index 7a16c0d..d473ff1 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { alias(libs.plugins.multiplatform) apply false - id("publish") apply false + alias(libs.plugins.maven.publish) apply false alias(libs.plugins.kover) apply false alias(libs.plugins.benchmark) apply false } @@ -9,12 +9,11 @@ allprojects { group = "com.ucasoft.kcron" - version = "0.26.0" - repositories { google() mavenCentral() } + version = "0.27.4" tasks.withType { reports { diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 0000000..fa8bc74 --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/PublishingExtension.kt b/buildSrc/src/main/kotlin/PublishingExtension.kt index 812c782..38329b7 100644 --- a/buildSrc/src/main/kotlin/PublishingExtension.kt +++ b/buildSrc/src/main/kotlin/PublishingExtension.kt @@ -1,7 +1,30 @@ -import org.gradle.api.model.ObjectFactory -import org.gradle.kotlin.dsl.property +import org.gradle.api.publish.maven.MavenPom -open class PublishingExtension(factory: ObjectFactory) { - val name = factory.property() - val description = factory.property() +fun configurePom(name: String, description: String, pom: MavenPom) { + pom.name.set(name) + pom.description.set(description) + pom.url.set("https://github.com/Scogun/kcron-common") + pom.licenses { + license { + this.name.set("The Apache License, Version 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + pom.developers { + developer { + id.set("Scogun") + this.name.set("Sergey Antonov") + email.set("SAntonov@ucasoft.com") + } + developer { + id.set("Myshkouski") + this.name.set("Alexei Myshkouski") + email.set("alexeimyshkouski@gmail.com") + } + } + pom.scm { + connection.set("scm:git:git://github.com/Scogun/kcron-common.git") + developerConnection.set("scm:git:ssh://github.com:Scogun/kcron-common.git") + url.set("https://github.com/Scogun/kcron-common") + } } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/publish.gradle.kts b/buildSrc/src/main/kotlin/publish.gradle.kts deleted file mode 100644 index ebdb19e..0000000 --- a/buildSrc/src/main/kotlin/publish.gradle.kts +++ /dev/null @@ -1,67 +0,0 @@ -plugins { - `maven-publish` - signing -} - -val libraryData = extensions.create("libraryData", PublishingExtension::class) - -val stubJavadoc by tasks.creating(Jar::class) { - archiveClassifier.set("javadoc") -} - -publishing { - publications.configureEach { - if (this is MavenPublication) { - if (name != "kotlinMultiplatform") { - artifact(stubJavadoc) - } - pom { - name.set(libraryData.name) - description.set(libraryData.description) - url.set("https://github.com/Scogun/kcron-common") - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - developers { - developer { - id.set("Scogun") - name.set("Sergey Antonov") - email.set("SAntonov@ucasoft.com") - } - developer { - id.set("Myshkouski") - name.set("Alexei Myshkouski") - email.set("alexeimyshkouski@gmail.com") - } - } - scm { - connection.set("scm:git:git://github.com/Scogun/kcron-common.git") - developerConnection.set("scm:git:ssh://github.com:Scogun/kcron-common.git") - url.set("https://github.com/Scogun/kcron-common") - } - } - } - } - repositories { - maven { - name = "MavenCentral" - url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") - credentials { - username = System.getenv("MAVEN_USERNAME") - password = System.getenv("MAVEN_PASSWORD") - } - } - } -} - -signing { - sign(publishing.publications) -} - -tasks.withType().configureEach { - val signingTasks = tasks.withType() - mustRunAfter(signingTasks) -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 246509b..dde2b28 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ kotest = "5.9.1" kover = "0.9.1" benchmark = "0.4.14" compose = "1.8.2" +maven-publish = "0.34.0" [libraries] kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinx-datetime" } @@ -17,4 +18,5 @@ multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotl kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } benchmark = { id = "org.jetbrains.kotlinx.benchmark", version.ref = "benchmark" } compose = { id = "org.jetbrains.compose", version.ref = "compose" } -compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } \ No newline at end of file +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } \ No newline at end of file diff --git a/kcron-abstractions/build.gradle.kts b/kcron-abstractions/build.gradle.kts index bec208b..17d82c4 100644 --- a/kcron-abstractions/build.gradle.kts +++ b/kcron-abstractions/build.gradle.kts @@ -1,6 +1,6 @@ plugins { alias(libs.plugins.multiplatform) - id("publish") + alias(libs.plugins.maven.publish) } kotlin { @@ -28,7 +28,11 @@ kotlin { } } -libraryData { - name.set("KCron Abstractions") - description.set("Abstractions for Kotlin Multiplatform Cron realization") +mavenPublishing { + publishToMavenCentral() + signAllPublications() + + pom { + configurePom("KCron Abstractions", "Abstractions for Kotlin Multiplatform Cron realization", this) + } } \ No newline at end of file diff --git a/kcron-common/build.gradle.kts b/kcron-common/build.gradle.kts index bc1dffe..2de30c6 100644 --- a/kcron-common/build.gradle.kts +++ b/kcron-common/build.gradle.kts @@ -1,6 +1,6 @@ plugins { alias(libs.plugins.multiplatform) - id("publish") + alias(libs.plugins.maven.publish) alias(libs.plugins.kover) } @@ -40,7 +40,11 @@ kotlin { } } -libraryData { - name.set("KCron Common") - description.set("Cron realization for Kotlin Multiplatform with Kotlinx DateTime Provider") +mavenPublishing { + publishToMavenCentral() + signAllPublications() + + pom { + configurePom("KCron Common", "Cron realization for Kotlin Multiplatform with Kotlinx DateTime Provider", this) + } } \ No newline at end of file diff --git a/kcron-core/build.gradle.kts b/kcron-core/build.gradle.kts index 97279b2..cd76e9d 100644 --- a/kcron-core/build.gradle.kts +++ b/kcron-core/build.gradle.kts @@ -1,6 +1,6 @@ plugins { alias(libs.plugins.multiplatform) - id("publish") + alias(libs.plugins.maven.publish) alias(libs.plugins.kover) } @@ -40,7 +40,11 @@ kotlin { } } -libraryData { - name.set("KCron Core") - description.set("Cron realization for Kotlin Multiplatform") +mavenPublishing { + publishToMavenCentral() + signAllPublications() + + pom { + configurePom("KCron Core", "Cron realization for Kotlin Multiplatform", this) + } } \ No newline at end of file diff --git a/kcron-kotlinx-datetime/build.gradle.kts b/kcron-kotlinx-datetime/build.gradle.kts index 8debe37..5977b32 100644 --- a/kcron-kotlinx-datetime/build.gradle.kts +++ b/kcron-kotlinx-datetime/build.gradle.kts @@ -1,6 +1,6 @@ plugins { alias(libs.plugins.multiplatform) - id("publish") + alias(libs.plugins.maven.publish) } kotlin { @@ -33,7 +33,11 @@ kotlin { } } -libraryData { - name.set("KCron Kotlinx DateTime") - description.set("Kotlinx DateTime Provider for Kotlin Multiplatform Cron realization") +mavenPublishing { + publishToMavenCentral() + signAllPublications() + + pom { + configurePom("KCron Kotlinx DateTime", "Kotlinx DateTime Provider for Kotlin Multiplatform Cron realization", this) + } } \ No newline at end of file diff --git a/kcron-ui-builder/build.gradle.kts b/kcron-ui-builder/build.gradle.kts index f6f9fed..f252a87 100644 --- a/kcron-ui-builder/build.gradle.kts +++ b/kcron-ui-builder/build.gradle.kts @@ -1,6 +1,6 @@ plugins { alias(libs.plugins.multiplatform) - id("publish") + alias(libs.plugins.maven.publish) alias(libs.plugins.compose) alias(libs.plugins.compose.compiler) alias(libs.plugins.kover) @@ -42,4 +42,13 @@ kotlin { } } } +} + +mavenPublishing { + publishToMavenCentral() + signAllPublications() + + pom { + configurePom("KCron UI Builder", "UI Builder for KCron using JetBrains Compose Multiplatform", this) + } } \ No newline at end of file From 3e9dba635eb1eccfee51d25b6aec00899de037e7 Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 22 Aug 2025 10:51:07 +0200 Subject: [PATCH 12/17] Fix action --- .github/workflows/publish.yml | 12 ++++++------ .github/workflows/tests.yml | 8 ++++---- .../ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt | 10 ---------- 3 files changed, 10 insertions(+), 20 deletions(-) delete mode 100644 kcron-ui-builder/src/jvmMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b924ba0..2d17c45 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,13 +15,13 @@ jobs: needs: tests runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set Up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin - java-version: 11 + java-version: 17 cache: gradle - name: Grant execute permission for gradlew @@ -38,7 +38,7 @@ jobs: shell: bash - name: Publish - run: ./gradlew publishAllPublicationsToMavenCentralRepository -Psigning.keyId=${{ secrets.SIGNING_KEY_ID }} -Psigning.password=${{ secrets.SIGNING_PASSWORD }} -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg) + run: ./gradlew publishToMavenCentral -Psigning.keyId=${{ secrets.SIGNING_KEY_ID }} -Psigning.password=${{ secrets.SIGNING_PASSWORD }} -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg) env: - MAVEN_USERNAME: ${{ secrets.MAVEN_TOKEN_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_TOKEN_PASSWORD }} \ No newline at end of file + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_TOKEN_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_TOKEN_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a98c0e4..65af971 100755 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,13 +14,13 @@ jobs: os: [macos-13, ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: "Set Up JDK" - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin - java-version: 11 + java-version: 17 cache: gradle - name: "Grant execute permission for gradlew" @@ -44,7 +44,7 @@ jobs: run: ./gradlew cleanMacosX64Test macosX64Test --tests "com.ucasoft.kcron.*" - name: "Tests Report" - uses: dorny/test-reporter@v1 + uses: dorny/test-reporter@v2 if: success() || failure() with: name: jUnit Tests diff --git a/kcron-ui-builder/src/jvmMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt b/kcron-ui-builder/src/jvmMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt deleted file mode 100644 index 8be5a34..0000000 --- a/kcron-ui-builder/src/jvmMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilderPreview.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.ucasoft.kcron.ui.builder - -import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.runtime.Composable - -@Composable -@Preview -fun PreviewCronUiBuilder() { - CronUiBuilder() -} \ No newline at end of file From c9c34c44eccabebb739e1c6eca63bd83e4a2aba5 Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 22 Aug 2025 13:02:23 +0200 Subject: [PATCH 13/17] Fix tests --- gradle.properties | 1 + kcron-ui-builder/build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/gradle.properties b/gradle.properties index 47c1aa5..4e77bd7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ kotlin.code.style=official org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.macos.enabled=true +org.gradle.jvmargs=-Xmx1024m -XX:MaxMetaspaceSize=512m diff --git a/kcron-ui-builder/build.gradle.kts b/kcron-ui-builder/build.gradle.kts index f252a87..943a366 100644 --- a/kcron-ui-builder/build.gradle.kts +++ b/kcron-ui-builder/build.gradle.kts @@ -39,6 +39,7 @@ kotlin { implementation(kotlin("test")) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation(compose.uiTest) + runtimeOnly(compose.desktop.currentOs) } } } From fb9068663a5f649f9aa375611ef00026623d0087 Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 22 Aug 2025 15:46:47 +0200 Subject: [PATCH 14/17] Remove tests --- gradle.properties | 1 - kcron-ui-builder/build.gradle.kts | 6 +++--- .../kcron/ui/builder/CronUIBuilderTests.kt | 20 ------------------- 3 files changed, 3 insertions(+), 24 deletions(-) delete mode 100644 kcron-ui-builder/src/commonTest/kotlin/com/ucasoft/kcron/ui/builder/CronUIBuilderTests.kt diff --git a/gradle.properties b/gradle.properties index 4e77bd7..47c1aa5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,3 @@ kotlin.code.style=official org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.macos.enabled=true -org.gradle.jvmargs=-Xmx1024m -XX:MaxMetaspaceSize=512m diff --git a/kcron-ui-builder/build.gradle.kts b/kcron-ui-builder/build.gradle.kts index 943a366..1fe2f02 100644 --- a/kcron-ui-builder/build.gradle.kts +++ b/kcron-ui-builder/build.gradle.kts @@ -34,14 +34,14 @@ kotlin { implementation(project(":kcron-common")) } } - commonTest { + // TODO UI Tests on Compose UI are unstable, enable when stable + /*commonTest { dependencies { implementation(kotlin("test")) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation(compose.uiTest) - runtimeOnly(compose.desktop.currentOs) } - } + }*/ } } diff --git a/kcron-ui-builder/src/commonTest/kotlin/com/ucasoft/kcron/ui/builder/CronUIBuilderTests.kt b/kcron-ui-builder/src/commonTest/kotlin/com/ucasoft/kcron/ui/builder/CronUIBuilderTests.kt deleted file mode 100644 index db2889d..0000000 --- a/kcron-ui-builder/src/commonTest/kotlin/com/ucasoft/kcron/ui/builder/CronUIBuilderTests.kt +++ /dev/null @@ -1,20 +0,0 @@ -@file:OptIn(ExperimentalTestApi::class) - -package com.ucasoft.kcron.ui.builder - -import androidx.compose.ui.test.ExperimentalTestApi -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.runComposeUiTest -import kotlin.test.Test - -internal class CronUIBuilderTests { - - @Test - fun initialExpressionTest() = runComposeUiTest { - setContent { - CronUiBuilder("0 9 29 10 *") - } - - onNodeWithText("0 9 29 10 *").assertExists() - } -} \ No newline at end of file From 11d9b2b368ba9ba6f7f63c6cf54b874a2cac68f4 Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 22 Aug 2025 16:32:56 +0200 Subject: [PATCH 15/17] Update documentation --- README.md | 10 +++++++--- docs/images/CronUiBuilderEn.png | Bin 0 -> 41281 bytes 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 docs/images/CronUiBuilderEn.png diff --git a/README.md b/README.md index d4a59e9..f2158b7 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # KCron Cron realization for Kotlin Multiplatform -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Scogun_kcron-common&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Scogun_kcron-common) ![GitHub](https://img.shields.io/github/license/Scogun/kcron-common?color=blue) ![Publish workflow](https://github.com/Scogun/kcron-common/actions/workflows/publish.yml/badge.svg) [![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/com.ucasoft.kcron/kcron-common/0.23.0?color=blue)](https://search.maven.org/artifact/com.ucasoft.kcron/kcron-common/0.23.0/jar) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Scogun_kcron-common&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Scogun_kcron-common) ![GitHub](https://img.shields.io/github/license/Scogun/kcron-common?color=blue) ![Publish workflow](https://github.com/Scogun/kcron-common/actions/workflows/publish.yml/badge.svg) [![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/com.ucasoft.kcron/kcron-common/0.27.4?color=blue)](https://search.maven.org/artifact/com.ucasoft.kcron/kcron-common/0.27.4/jar) ### Features * Kotlin Multiplatform library @@ -43,6 +43,10 @@ builder * Wasm * iOS * Support different DateTime libraries (via DateTime Provider Abstractions) +* Provide UI builder for JVM, macOS, JavaScript and iOS on Compose UI multiplatform + * Multilanguage support (English and Russian now) +

KCron Compose UI

+ ### Usage #### KCron-Common library as default implementation uses [Kotlinx-DateTime](https://github.com/Kotlin/kotlinx-datetime) library ***Add with Gradle*** @@ -51,7 +55,7 @@ kotlin { sourceSets { commonMain { dependencies { - implementation 'com.ucasoft.kcron:kcron-common:0.23.0' + implementation 'com.ucasoft.kcron:kcron-common:0.27.4' } } } @@ -116,6 +120,6 @@ builder.years(2021..2025) println(builder.expression) // 0/10 5-25 5,12 ? * SUN#5 2021-2025 ``` ### Current status -This library is on beta version `0.23.0`. +This library is on beta version `0.27.4`. It is continuing to develop. Check the news! diff --git a/docs/images/CronUiBuilderEn.png b/docs/images/CronUiBuilderEn.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7d09df3366fa4f843746805da04a0514ccd895 GIT binary patch literal 41281 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 Date: Fri, 22 Aug 2025 16:57:51 +0200 Subject: [PATCH 16/17] Refactoring --- buildSrc/settings.gradle.kts | 4 +- .../ucasoft/kcron/ui/builder/CronUiBuilder.kt | 66 +++++++++++-------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts index fa8bc74..a8779f7 100644 --- a/buildSrc/settings.gradle.kts +++ b/buildSrc/settings.gradle.kts @@ -4,4 +4,6 @@ dependencyResolutionManagement { from(files("../gradle/libs.versions.toml")) } } -} \ No newline at end of file +} + +rootProject.name = "kcron" \ No newline at end of file diff --git a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt index 20716e6..468921e 100644 --- a/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt +++ b/kcron-ui-builder/src/commonMain/kotlin/com/ucasoft/kcron/ui/builder/CronUiBuilder.kt @@ -23,6 +23,7 @@ import com.ucasoft.kcron.core.exceptions.WrongPartCombinations import com.ucasoft.kcron.core.exceptions.WrongPartExpression import com.ucasoft.kcron.core.parsers.Parser import com.ucasoft.kcron.kcron_ui_builder.generated.resources.* +import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.pluralStringResource import org.jetbrains.compose.resources.stringArrayResource import org.jetbrains.compose.resources.stringResource @@ -45,9 +46,10 @@ fun CronUiBuilder( modifier = Modifier.fillMaxWidth().padding(12.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - var daysOfWeek = stringArrayResource(Res.array.days_of_week).withIndex().map { it.value to it.index } - daysOfWeek = daysOfWeek.drop(firstDayOfWeek.ordinal) + daysOfWeek.take(firstDayOfWeek.ordinal) - listOf( + val daysOfWeek = stringArrayResource(Res.array.days_of_week).let { + it.drop(firstDayOfWeek.ordinal) + it.take(firstDayOfWeek.ordinal) + } + val fields = listOf( Res.string.minute to listOf( pluralStringResource(Res.plurals.every_feminine, 1) to "*", stringResource(Res.string.hour_start) to "0", @@ -86,37 +88,24 @@ fun CronUiBuilder( ) + stringArrayResource(Res.array.months).withIndex().map { it.value to (it.index + 1).toString() }, Res.string.day_of_week to listOf( pluralStringResource(Res.plurals.every, 1) to "*" - ) + daysOfWeek.withIndex().map { (index, name) -> name.first to (index + 1).toString() } - ).map { - it.first to if (allowCustom) - it.second + (stringResource(Res.string.custom) to "") - else - it.second - }.map { - (labelRes, options) -> + ) + daysOfWeek.withIndex().map { (index, name) -> name to (index + 1).toString() } + ).map { (label, options) -> + label to if (allowCustom) + options + (stringResource(Res.string.custom) to "") + else + options + } + + fields.map { (labelRes, options) -> var isError by remember { mutableStateOf(false) } CronField( stringResource(labelRes), - when(labelRes) { - Res.string.minute -> parts[CronPart.Minutes]!!.value - Res.string.hour -> parts[CronPart.Hours]!!.value - Res.string.day_of_month -> parts[CronPart.Days]!!.value - Res.string.month -> parts[CronPart.Months]!!.value - Res.string.day_of_week -> parts[CronPart.DaysOfWeek]!!.value - else -> "" - }, + getPartsValue(parts, labelRes), options, isError = isError ) { try { - val copy = when (labelRes) { - Res.string.minute -> copyParts(parts, minutes = it) - Res.string.hour -> copyParts(parts, hours = it) - Res.string.day_of_month -> copyParts(parts, dayOfMonth = it) - Res.string.month -> copyParts(parts, month = it) - Res.string.day_of_week -> copyParts(parts, dayOfWeek = it) - else -> parts - } + val copy = copyParts(parts, labelRes, it) parser.parse(copy.entries.joinToString(" ") { it.value.value }) parts = copy isError = false @@ -136,7 +125,10 @@ fun CronUiBuilder( onValueChange = {}, readOnly = true, modifier = Modifier.background(MaterialTheme.colorScheme.primary), - colors = OutlinedTextFieldDefaults.colors(focusedTextColor = MaterialTheme.colorScheme.onPrimary, unfocusedTextColor = MaterialTheme.colorScheme.onPrimary), + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = MaterialTheme.colorScheme.onPrimary, + unfocusedTextColor = MaterialTheme.colorScheme.onPrimary + ), textStyle = TextStyle.Default.copy(textAlign = TextAlign.Center) ) Spacer( @@ -172,6 +164,24 @@ fun CronUiBuilder( } } +private fun getPartsValue(parts: Map, labelRes: StringResource) = when (labelRes) { + Res.string.minute -> parts[CronPart.Minutes]!!.value + Res.string.hour -> parts[CronPart.Hours]!!.value + Res.string.day_of_month -> parts[CronPart.Days]!!.value + Res.string.month -> parts[CronPart.Months]!!.value + Res.string.day_of_week -> parts[CronPart.DaysOfWeek]!!.value + else -> "" +} + +private fun copyParts(parts: Map, labelRes: StringResource, newValue: String) = when (labelRes) { + Res.string.minute -> copyParts(parts, minutes = newValue) + Res.string.hour -> copyParts(parts, hours = newValue) + Res.string.day_of_month -> copyParts(parts, dayOfMonth = newValue) + Res.string.month -> copyParts(parts, month = newValue) + Res.string.day_of_week -> copyParts(parts, dayOfWeek = newValue) + else -> parts +} + private fun copyParts( parts: Map, minutes: String? = null, From 7a149fe524c6c8006ce555598aff690498e5ac68 Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 22 Aug 2025 17:13:34 +0200 Subject: [PATCH 17/17] Fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2158b7..e454d1f 100755 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ builder * Support different DateTime libraries (via DateTime Provider Abstractions) * Provide UI builder for JVM, macOS, JavaScript and iOS on Compose UI multiplatform * Multilanguage support (English and Russian now) -

KCron Compose UI

+

KCron Compose UI

### Usage #### KCron-Common library as default implementation uses [Kotlinx-DateTime](https://github.com/Kotlin/kotlinx-datetime) library