From 3c6bf4765712a74159e774acf705fcd0ddc5e004 Mon Sep 17 00:00:00 2001 From: linusmuema Date: Mon, 12 Jun 2023 06:31:26 +0200 Subject: [PATCH 01/30] - added the formatting classes --- .../screens/survey/SurveyViewmodel.kt | 13 +++-- .../survey/components/PersonalDetails.kt | 4 +- .../java/com/dsc/form_builder/BaseState.kt | 4 +- .../java/com/dsc/form_builder/ChoiceState.kt | 3 +- .../java/com/dsc/form_builder/Extensions.kt | 2 +- .../java/com/dsc/form_builder/FormState.kt | 4 +- .../java/com/dsc/form_builder/SelectState.kt | 4 +- .../com/dsc/form_builder/TextFieldState.kt | 28 ++++++++-- .../java/com/dsc/form_builder/Transform.kt | 5 +- .../java/com/dsc/form_builder/Validators.kt | 7 +-- .../dsc/form_builder/format/CardFormatter.kt | 15 +++++ .../com/dsc/form_builder/format/DateFormat.kt | 56 +++++++++++++++++++ .../com/dsc/form_builder/format/Formatter.kt | 29 ++++++++++ .../com/dsc/form_builder/FormStateTest.kt | 2 +- .../com/dsc/form_builder/FormatterTest.kt | 42 ++++++++++++++ .../form_builder/FormatterTestProviders.kt | 22 ++++++++ .../dsc/form_builder/SelectStateProviders.kt | 2 +- .../com/dsc/form_builder/SelectStateTest.kt | 2 +- .../dsc/form_builder/TextFieldProviders.kt | 2 +- .../dsc/form_builder/TextFieldStateTest.kt | 4 +- 20 files changed, 211 insertions(+), 39 deletions(-) create mode 100644 form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt create mode 100644 form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt create mode 100644 form-builder/src/main/java/com/dsc/form_builder/format/Formatter.kt create mode 100644 form-builder/src/test/java/com/dsc/form_builder/FormatterTest.kt create mode 100644 form-builder/src/test/java/com/dsc/form_builder/FormatterTestProviders.kt diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt index 4aca60c..8005b6c 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt @@ -5,7 +5,12 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel -import com.dsc.form_builder.* +import com.dsc.form_builder.BaseState +import com.dsc.form_builder.ChoiceState +import com.dsc.form_builder.FormState +import com.dsc.form_builder.SelectState +import com.dsc.form_builder.TextFieldState +import com.dsc.form_builder.Validators import com.dsc.formbuilder.screens.survey.components.SurveyModel class SurveyViewmodel : ViewModel() { @@ -26,7 +31,7 @@ class SurveyViewmodel : ViewModel() { message = "Username should have more than 4 characters" ), Validators.Required() - ) + ), ), TextFieldState( name = "email", @@ -34,14 +39,14 @@ class SurveyViewmodel : ViewModel() { Validators.Email(), Validators.Required(), ), - transform = { it.trim().lowercase() } + transform = { it.trim().lowercase() }, ), TextFieldState( name = "number", validators = listOf( Validators.Phone(), Validators.Required(), - ) + ), ), SelectState( name = "platform", diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt index be3eb9b..3adf793 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt @@ -5,10 +5,8 @@ import androidx.compose.foundation.layout.Arrangement.Center import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.dsc.form_builder.BaseState @@ -59,7 +57,7 @@ fun TextInput(label: String, state: TextFieldState) { errorBorderColor = MaterialTheme.colors.error, focusedBorderColor = MaterialTheme.colors.onPrimary, unfocusedBorderColor = MaterialTheme.colors.onPrimary - ) + ), ) if (state.hasError) { diff --git a/form-builder/src/main/java/com/dsc/form_builder/BaseState.kt b/form-builder/src/main/java/com/dsc/form_builder/BaseState.kt index 0d4d879..b28d575 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/BaseState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/BaseState.kt @@ -13,8 +13,6 @@ import androidx.compose.runtime.setValue * @param transform this function is used to change the data type in the field state. You can use it to convert the data in the field to your preferred type e.g [String] to [Int] * @param validators this is the list of [Validators] that are used to validate the field state. By default most states will have an empty list. You can override this and provide your own list of validators. * - * @author [Linus Muema](https://github.com/linusmuema) - * @created 05/04/2022 - 10:00 AM */ abstract class BaseState( val initial: T, @@ -76,4 +74,4 @@ abstract class BaseState( this.value = initial this.hideError() } -} \ No newline at end of file +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/ChoiceState.kt b/form-builder/src/main/java/com/dsc/form_builder/ChoiceState.kt index 40bd966..2ec1f2b 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/ChoiceState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/ChoiceState.kt @@ -15,7 +15,6 @@ package com.dsc.form_builder * * @param validators a list of [Validators] applied to the state's value. * - * @author [Samwel Otieno](https://github.com/otienosamwel) */ class ChoiceState( name: String, @@ -40,4 +39,4 @@ class ChoiceState( } return validations.all { it } } -} \ No newline at end of file +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/Extensions.kt b/form-builder/src/main/java/com/dsc/form_builder/Extensions.kt index 8f1d32f..dc9c851 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/Extensions.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/Extensions.kt @@ -2,4 +2,4 @@ package com.dsc.form_builder fun String.isNumeric(): Boolean { return this.toIntOrNull()?.let { true } ?: false -} \ No newline at end of file +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/FormState.kt b/form-builder/src/main/java/com/dsc/form_builder/FormState.kt index 499e0b0..edb8f98 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/FormState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/FormState.kt @@ -7,8 +7,6 @@ import kotlin.reflect.KParameter * This class represents the state of the whole form, i.e, the whole collection of fields. It is used to manage all of the states in terms of accessing data and validations. * @param fields this is a list of all fields in the form. We pass them as a parameter to the constructor for ease of management and access. * - * @author [Linus Muema](https://github.com/linusmuema) - * @created 05/04/2022 - 10:00 AM */ open class FormState>(val fields: List) { @@ -56,4 +54,4 @@ open class FormState>(val fields: List) { it.reset() } } -} \ No newline at end of file +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/SelectState.kt b/form-builder/src/main/java/com/dsc/form_builder/SelectState.kt index 8dffc5c..7ee18d7 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/SelectState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/SelectState.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.* * * @param validators a list of [Validators] applied to the state's value. * - * @author [Samwel Otieno](https://github.com/otienosamwel) */ class SelectState( name: String, @@ -127,5 +126,4 @@ class SelectState( override fun getData(): Any? { return if (transform == null) value.toList() else transform.transform(value) } - -} \ No newline at end of file +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt b/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt index f7965d6..d8c6390 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt @@ -1,6 +1,9 @@ package com.dsc.form_builder import androidx.compose.runtime.* +import androidx.compose.ui.text.input.VisualTransformation +import com.dsc.form_builder.format.Formatter +import com.dsc.form_builder.format.toVisualTransformation /** * This class represents the state of a single form field. @@ -10,18 +13,17 @@ import androidx.compose.runtime.* * * @param name The name of the field used to access the state when required in the form * @param initial The initial value/state of the field. By default it is an empty string. + * @param formatter The formatting option for the field. * @param transform The function used to change the [String] data type on the text field to a suitable type e.g [String] to [Int]. * @param validators This is the list of [Validators] that are used to validate the field state. By default the field states will have an empty list. You can override this and provide your own list of validators. * - * @author [Joy Kangangi](https://github.com/joykangangi) - * @created 06/04/2022 - 2:50 p.m. - * */ open class TextFieldState( name: String, initial: String = "", transform: Transform? = null, validators: List = listOf(), + private val formatter: Formatter? = null, ) : BaseState(initial = initial, name = name, transform = transform, validators = validators) { /** @@ -40,9 +42,24 @@ open class TextFieldState( this.value = update } + + /** + * This function is used to get a value transformation for a specified formatter. + * You need to first provide a [Formatter]. As the input value changes, the value is formatted. + */ + fun getTransformation(): VisualTransformation { + checkNotNull(this.formatter) { + """ + Missing formatter in the class. + You need to specify a formatter to use the getFormattedValue function. + """.trimIndent() + } + return formatter.toVisualTransformation() + } + /** - *This function is used to validate all text field inputs by checking against - *the corresponding validator from the list of [validators]. + * This function is used to validate all text field inputs by checking against + * the corresponding validator from the list of [validators]. * The validation checks are functions to validate the field values. * and returns true only if all fields are valid. * It is @@ -206,4 +223,3 @@ open class TextFieldState( return valid } } - diff --git a/form-builder/src/main/java/com/dsc/form_builder/Transform.kt b/form-builder/src/main/java/com/dsc/form_builder/Transform.kt index 330f0e5..38e3e23 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/Transform.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/Transform.kt @@ -2,9 +2,6 @@ package com.dsc.form_builder /** * This interface is used to allow change of data types to a suitable type when necessary. - * - *@author [Joy Kangangi](https://github.com/joykangangi) - * @created 06/04/2022 - 2:35 p.m. */ fun interface Transform { @@ -14,4 +11,4 @@ fun interface Transform { * For example to read 'age' value from a field [TextFieldState], it will be transformed from [String] to [Int]. */ fun transform(value: T): Any? -} \ No newline at end of file +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/Validators.kt b/form-builder/src/main/java/com/dsc/form_builder/Validators.kt index a01a112..c24963f 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/Validators.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/Validators.kt @@ -10,9 +10,6 @@ private const val CARD_NUMBER_MESSAGE = "Invalid card number" * * These are the types of validators available in the form builder library. * They all have the _message_ parameter to allow the developer to set their own custom error message. - * - * @author [Linus Muema](https://github.com/linusmuema) - * @created 05/04/2022 - 10:00 AM */ sealed interface Validators { @@ -80,7 +77,7 @@ sealed interface Validators { * Example: check if a string contains the word hello * ```kt * Validators.Custom( - * message = "value must have hello" + * message = "value must have hello", * function = { it.contains("hello") } * ) * ``` @@ -92,4 +89,4 @@ sealed interface Validators { * @param message the error message to display if the validation fails. */ class Custom(var message: String, var function: (value: Any) -> Boolean) : Validators -} \ No newline at end of file +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt b/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt new file mode 100644 index 0000000..518d168 --- /dev/null +++ b/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt @@ -0,0 +1,15 @@ +package com.dsc.form_builder.format + +/** + * + * This formatter is used for credit/debit card numbers. Can also be used for IBAN numbers. + * It groups the input value into chunks of four characters. + * The separator is an empty space as this is the most common option. + * + * Note: character limiting is not supported in the formatter. + */ +object CardFormatter: Formatter { + override fun format(value: String): String { + return value.replace(" ", "").chunked(4).joinToString(" ") + } +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt b/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt new file mode 100644 index 0000000..a5b5314 --- /dev/null +++ b/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt @@ -0,0 +1,56 @@ +package com.dsc.form_builder.format + +/** + * These are the formatting options for the [DateFormatter] class. + */ +enum class DateFormat { + DDMMYYYY, + MMDDYYYY, + YYYYDDMM, + DDMMYY, + MMDDYY, + YYMMDD, +} + +// Get the index where to place the separator +private fun DateFormat.separatorIndices(): MutableList { + val indices = mutableListOf() + val stringFormat = this.toString() + stringFormat.forEachIndexed { index, char -> + if (index > 0){ + val prev = stringFormat[index-1] + + if (prev != char) { + if (indices.isNotEmpty()) indices.add(index+1) + else indices.add(index) + } + } + } + return indices +} + + +/** + * + * This formatter is used for date inputs. You need to specify a [DateFormat] and a separator. + * The formatting function places the separator in the respective index as the user types. + * + * Note: character limiting is not supported in the formatter. + */ + +class DateFormatter(private val dateFormat: DateFormat, private val separator: String): Formatter { + override fun format(value: String): String { + var formatted = value + val indices = dateFormat.separatorIndices() + + // add first separator if user has exceeded that index + val first = indices.first() + if (value.length > first) formatted = formatted.replaceRange(first, first, separator) + + // add last separator if user has exceeded that index + val last = indices.last() + if (value.length >= last) formatted = formatted.replaceRange(last, last, separator) + + return formatted + } +} diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/Formatter.kt b/form-builder/src/main/java/com/dsc/form_builder/format/Formatter.kt new file mode 100644 index 0000000..3054854 --- /dev/null +++ b/form-builder/src/main/java/com/dsc/form_builder/format/Formatter.kt @@ -0,0 +1,29 @@ +package com.dsc.form_builder.format + +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation + + +/** + * + * These are the formatting interface for the [TextFieldState]. + * You can get the visual transformation to apply in your text input. + */ +interface Formatter { + fun format(value: String): String +} + +internal fun Formatter.toVisualTransformation(): VisualTransformation { + return VisualTransformation { + val output = format(it.text) + TransformedText( + AnnotatedString(output), + object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int = output.length + override fun transformedToOriginal(offset: Int): Int = it.text.length + } + ) + } +} diff --git a/form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt b/form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt index e545594..f95a377 100644 --- a/form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt +++ b/form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt @@ -39,4 +39,4 @@ internal class FormStateTest { assert(ageState.value == "34" && !ageState.hasError) } } -} \ No newline at end of file +} diff --git a/form-builder/src/test/java/com/dsc/form_builder/FormatterTest.kt b/form-builder/src/test/java/com/dsc/form_builder/FormatterTest.kt new file mode 100644 index 0000000..98190c2 --- /dev/null +++ b/form-builder/src/test/java/com/dsc/form_builder/FormatterTest.kt @@ -0,0 +1,42 @@ +package com.dsc.form_builder + +import com.dsc.form_builder.format.CardFormatter +import com.dsc.form_builder.format.DateFormat +import com.dsc.form_builder.format.DateFormatter +import com.dsc.form_builder.format.Formatter +import org.junit.jupiter.api.Nested +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ArgumentsSource + +internal class FormatterTest { + + @Nested + inner class DescribingFormatting { + + @ParameterizedTest + @ArgumentsSource(CreditCardFormatterProvider::class) + fun `credit card numbers are formatted correctly`(input: String, expected: String){ + // Given a formatting class + val classToTest: Formatter = CardFormatter + + // When formatting is applied + val formatted = classToTest.format(input) + + // then the value should be formatted correctly + assert(formatted == expected) + } + + @ParameterizedTest + @ArgumentsSource(DateFormatterProvider::class) + fun `dater inputs are formatted correctly`(format: DateFormat, separator: String, input: String, expected: String){ + // Given a formatting class + val classToTest: Formatter = DateFormatter(dateFormat = format, separator = separator) + + // When formatting is applied + val formatted = classToTest.format(input) + + // then the value should be formatted correctly + assert(formatted == expected) + } + } +} diff --git a/form-builder/src/test/java/com/dsc/form_builder/FormatterTestProviders.kt b/form-builder/src/test/java/com/dsc/form_builder/FormatterTestProviders.kt new file mode 100644 index 0000000..6f59718 --- /dev/null +++ b/form-builder/src/test/java/com/dsc/form_builder/FormatterTestProviders.kt @@ -0,0 +1,22 @@ +package com.dsc.form_builder + +import com.dsc.form_builder.format.DateFormat +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import java.util.stream.Stream + +object CreditCardFormatterProvider: ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream = Stream.of( + Arguments.of("40128888888818", "4012 8888 8888 18"), + Arguments.of("5555555555554444", "5555 5555 5555 4444"), + Arguments.of("DE89370400440532013000", "DE89 3704 0044 0532 0130 00") + ) +} + +object DateFormatterProvider: ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream = Stream.of( + Arguments.of(DateFormat.DDMMYY, ".", "010101", "01.01.01"), + Arguments.of(DateFormat.DDMMYYYY, "-", "01012001", "01-01-2001") + ) +} diff --git a/form-builder/src/test/java/com/dsc/form_builder/SelectStateProviders.kt b/form-builder/src/test/java/com/dsc/form_builder/SelectStateProviders.kt index 0905451..e0ae4f4 100644 --- a/form-builder/src/test/java/com/dsc/form_builder/SelectStateProviders.kt +++ b/form-builder/src/test/java/com/dsc/form_builder/SelectStateProviders.kt @@ -19,4 +19,4 @@ object MaxArgumentsProvider: ArgumentsProvider { Arguments.of(mutableListOf("item 1", "item 2"), 2, true), Arguments.of(mutableListOf("item 1", "item 2", "item 3"), 2, false), ) -} \ No newline at end of file +} diff --git a/form-builder/src/test/java/com/dsc/form_builder/SelectStateTest.kt b/form-builder/src/test/java/com/dsc/form_builder/SelectStateTest.kt index fef7037..7382f25 100644 --- a/form-builder/src/test/java/com/dsc/form_builder/SelectStateTest.kt +++ b/form-builder/src/test/java/com/dsc/form_builder/SelectStateTest.kt @@ -69,4 +69,4 @@ internal class SelectStateTest { assert(actual == expected) } } -} \ No newline at end of file +} diff --git a/form-builder/src/test/java/com/dsc/form_builder/TextFieldProviders.kt b/form-builder/src/test/java/com/dsc/form_builder/TextFieldProviders.kt index d4bcea1..cb0b323 100644 --- a/form-builder/src/test/java/com/dsc/form_builder/TextFieldProviders.kt +++ b/form-builder/src/test/java/com/dsc/form_builder/TextFieldProviders.kt @@ -75,4 +75,4 @@ object MaxValueArgumentsProvider : ArgumentsProvider { Arguments.of("20", 15, false), Arguments.of("test", 15, false), ) -} \ No newline at end of file +} diff --git a/form-builder/src/test/java/com/dsc/form_builder/TextFieldStateTest.kt b/form-builder/src/test/java/com/dsc/form_builder/TextFieldStateTest.kt index aba835d..61bafa4 100644 --- a/form-builder/src/test/java/com/dsc/form_builder/TextFieldStateTest.kt +++ b/form-builder/src/test/java/com/dsc/form_builder/TextFieldStateTest.kt @@ -1,5 +1,7 @@ package com.dsc.form_builder +import com.dsc.form_builder.format.CardFormatter +import com.dsc.form_builder.format.Formatter import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -125,4 +127,4 @@ internal class TextFieldStateTest { assert(actual == expected) } } -} \ No newline at end of file +} From e292fe72ab71fe1aefced7b1c7169fb2b6a4b10e Mon Sep 17 00:00:00 2001 From: linusmuema Date: Mon, 12 Jun 2023 09:09:51 +0200 Subject: [PATCH 02/30] - remove unnecessary empty string replacement --- .../src/main/java/com/dsc/form_builder/format/CardFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt b/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt index 518d168..839672a 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt @@ -10,6 +10,6 @@ package com.dsc.form_builder.format */ object CardFormatter: Formatter { override fun format(value: String): String { - return value.replace(" ", "").chunked(4).joinToString(" ") + return value.chunked(4).joinToString(" ") } } From 7fbe6819af3509e45f49cf90e3fcd6cbe702461b Mon Sep 17 00:00:00 2001 From: joykangangi <64706463+joykangangi@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:47:12 +0300 Subject: [PATCH 03/30] Implement Web-Url Validator --- .../screens/survey/SurveyViewmodel.kt | 21 ++++++++++--------- .../survey/components/PersonalDetails.kt | 10 +++++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt index 8005b6c..e1ffacc 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt @@ -23,16 +23,6 @@ class SurveyViewmodel : ViewModel() { val formState: FormState> = FormState( fields = listOf( - TextFieldState( - name = "username", - validators = listOf( - Validators.Min( - limit = 4, - message = "Username should have more than 4 characters" - ), - Validators.Required() - ), - ), TextFieldState( name = "email", validators = listOf( @@ -48,11 +38,22 @@ class SurveyViewmodel : ViewModel() { Validators.Required(), ), ), + TextFieldState( + name = "web_url", + validators = listOf( + Validators.WebUrl(), + Validators.Required() + ), + ), SelectState( name = "platform", validators = listOf( Validators.Required( message = "Select at least one platform" + ), + Validators.Max( + limit = 2, + message = "Select at most two platforms" ) ), ), diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt index 3adf793..3496956 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt @@ -16,7 +16,7 @@ import com.dsc.formbuilder.theme.FormBuilderTheme @Composable fun PersonalDetails(formState: FormState>) { - val usernameState: TextFieldState = formState.getState("username") + val webUrlState: TextFieldState = formState.getState("web_url") val emailState: TextFieldState = formState.getState("email") val numberState: TextFieldState = formState.getState("number") @@ -29,15 +29,17 @@ fun PersonalDetails(formState: FormState>) { Spacer(modifier = Modifier.height(30.dp)) - TextInput(label = "Username", state = usernameState) + TextInput(label = "Email", state = emailState) Spacer(modifier = Modifier.height(10.dp)) - TextInput(label = "Email", state = emailState) + TextInput(label = "Number", state = numberState) Spacer(modifier = Modifier.height(10.dp)) - TextInput(label = "Number", state = numberState) + TextInput(label = "Website Link", state = webUrlState) + + } } From 5a2a7907ab7a4010fbfe23e6824509729f7c36b4 Mon Sep 17 00:00:00 2001 From: joykangangi <64706463+joykangangi@users.noreply.github.com> Date: Fri, 16 Jun 2023 23:59:29 +0300 Subject: [PATCH 04/30] Add Date and Card Examples --- .../screens/survey/SurveyViewmodel.kt | 65 +++++++++++++++---- .../survey/components/PersonalDetails.kt | 60 +++++++++++++---- .../screens/survey/components/SurveyModel.kt | 5 +- 3 files changed, 103 insertions(+), 27 deletions(-) diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt index e1ffacc..cae63c8 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt @@ -11,7 +11,13 @@ import com.dsc.form_builder.FormState import com.dsc.form_builder.SelectState import com.dsc.form_builder.TextFieldState import com.dsc.form_builder.Validators +import com.dsc.form_builder.format.CardFormatter +import com.dsc.form_builder.format.DateFormat +import com.dsc.form_builder.format.DateFormatter import com.dsc.formbuilder.screens.survey.components.SurveyModel +import java.time.DateTimeException +import java.time.LocalDate +import java.time.format.DateTimeFormatter class SurveyViewmodel : ViewModel() { @@ -32,17 +38,31 @@ class SurveyViewmodel : ViewModel() { transform = { it.trim().lowercase() }, ), TextFieldState( - name = "number", + name = "card", + formatter = CardFormatter, + validators = listOf( + Validators.CardNumber(), + Validators.Required(), + ), + ), + TextFieldState( + name = "phone", validators = listOf( Validators.Phone(), Validators.Required(), ), ), TextFieldState( - name = "web_url", + name = "date", + formatter = DateFormatter(dateFormat = DateFormat.DDMMYYYY, separator = "/"), validators = listOf( - Validators.WebUrl(), - Validators.Required() + Validators.Required(), + Validators.Custom( + function = { + validDate(date = it.toString()) + }, + message = "Invalid date" + ) ), ), SelectState( @@ -50,10 +70,6 @@ class SurveyViewmodel : ViewModel() { validators = listOf( Validators.Required( message = "Select at least one platform" - ), - Validators.Max( - limit = 2, - message = "Select at most two platforms" ) ), ), @@ -100,13 +116,30 @@ class SurveyViewmodel : ViewModel() { ) ) + private fun validDate(date: String): Boolean { + val formatter = DateTimeFormatter.ofPattern("ddMMyyyy") + + return try { + LocalDate.parse(date, formatter) + true + } catch (e: DateTimeException) { + false + } + + } + fun navigate(screen: Int) { _screen.value = screen } fun validateSurvey() { - val pages: List> = (0..5).chunked(3) - if (!formState.validate()){ + val pages: List> = listOf( + (0..3).toList(), + (4..6).toList(), + (7..9).toList() + ) + + if (!formState.validate()) { val position = formState.fields.indexOfFirst { it.hasError } _screen.value = pages.indexOfFirst { it.contains(position) } } else { @@ -116,9 +149,15 @@ class SurveyViewmodel : ViewModel() { } fun validateScreen(screen: Int) { - val fields: List> = formState.fields.chunked(3)[screen] - if (fields.map { it.validate() }.all { it }){ // map is used so we can execute validate() on all fields in that screen - if (screen == 2){ + val fields: List> = when (screen) { + 0 -> formState.fields.subList(0, 4) + 1 -> formState.fields.subList(4, 7) + 2 -> formState.fields.subList(7, 9) + else -> emptyList() + } + + if (fields.map { it.validate() }.all { it }) { + if (screen == 2) { logData() _finish.value = true } diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt index 3496956..dc962c9 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt @@ -1,26 +1,42 @@ package com.dsc.formbuilder.screens.survey.components -import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement.Center +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.dsc.form_builder.BaseState import com.dsc.form_builder.FormState import com.dsc.form_builder.TextFieldState +import com.dsc.form_builder.format.CardFormatter +import com.dsc.form_builder.format.DateFormat +import com.dsc.form_builder.format.DateFormatter import com.dsc.formbuilder.theme.FormBuilderTheme @Composable fun PersonalDetails(formState: FormState>) { - val webUrlState: TextFieldState = formState.getState("web_url") val emailState: TextFieldState = formState.getState("email") - val numberState: TextFieldState = formState.getState("number") - - Column(verticalArrangement = Center, horizontalAlignment = CenterHorizontally) { + val cardState: TextFieldState = formState.getState("card") + val phoneState: TextFieldState = formState.getState("phone") + val dateState: TextFieldState = formState.getState("date") + + Column( + verticalArrangement = Center, + horizontalAlignment = CenterHorizontally + ) { Text( modifier = Modifier.fillMaxWidth(), text = "Personal Details", @@ -29,23 +45,37 @@ fun PersonalDetails(formState: FormState>) { Spacer(modifier = Modifier.height(30.dp)) - TextInput(label = "Email", state = emailState) + TextInput(label = "Email", state = emailState) Spacer(modifier = Modifier.height(10.dp)) - TextInput(label = "Number", state = numberState) + TextInput( + label = "Card Number", + state = cardState, + visualTransformation = cardState.getTransformation() + ) Spacer(modifier = Modifier.height(10.dp)) - TextInput(label = "Website Link", state = webUrlState) + TextInput(label = "Phone Number", state = phoneState) + Spacer(modifier = Modifier.height(10.dp)) + TextInput( + label = "Date of birth", + state = dateState, + visualTransformation = dateState.getTransformation() + ) } } @Composable -fun TextInput(label: String, state: TextFieldState) { +fun TextInput( + label: String, + state: TextFieldState, + visualTransformation: VisualTransformation = VisualTransformation.None, +) { Column { OutlinedTextField( @@ -60,6 +90,8 @@ fun TextInput(label: String, state: TextFieldState) { focusedBorderColor = MaterialTheme.colors.onPrimary, unfocusedBorderColor = MaterialTheme.colors.onPrimary ), + singleLine = true, + visualTransformation = visualTransformation, ) if (state.hasError) { @@ -80,9 +112,13 @@ fun TextInput(label: String, state: TextFieldState) { fun PersonalDetailsPreview() { val formState: FormState> = FormState( listOf( - TextFieldState("username"), TextFieldState("email"), - TextFieldState("number") + TextFieldState("phone"), + TextFieldState( + "date", + formatter = DateFormatter(dateFormat = DateFormat.DDMMYYYY, separator = "/"), + ), + TextFieldState("card", formatter = CardFormatter), ) ) diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/SurveyModel.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/SurveyModel.kt index 37e02a4..9e15115 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/SurveyModel.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/SurveyModel.kt @@ -3,8 +3,9 @@ package com.dsc.formbuilder.screens.survey.components data class SurveyModel( // First page val email: String, - val number: String, - val username: String, + val card: String, + val phone: String, + val date: String, // Second page val ide: List, From 448cf746ca3e9a6b7eb76643efd4448247b0d7d0 Mon Sep 17 00:00:00 2001 From: joykangangi <64706463+joykangangi@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:39:45 +0300 Subject: [PATCH 05/30] Add Date Validator --- .../screens/survey/SurveyViewmodel.kt | 22 +----------- form-builder/build.gradle | 4 +++ .../com/dsc/form_builder/TextFieldState.kt | 36 +++++++++++++++++-- .../java/com/dsc/form_builder/Validators.kt | 10 ++++++ .../com/dsc/form_builder/format/DateFormat.kt | 14 ++++---- .../dsc/form_builder/TextFieldProviders.kt | 12 +++++++ .../dsc/form_builder/TextFieldStateTest.kt | 12 +++++-- 7 files changed, 78 insertions(+), 32 deletions(-) diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt index cae63c8..d5c2894 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt @@ -15,9 +15,6 @@ import com.dsc.form_builder.format.CardFormatter import com.dsc.form_builder.format.DateFormat import com.dsc.form_builder.format.DateFormatter import com.dsc.formbuilder.screens.survey.components.SurveyModel -import java.time.DateTimeException -import java.time.LocalDate -import java.time.format.DateTimeFormatter class SurveyViewmodel : ViewModel() { @@ -57,12 +54,7 @@ class SurveyViewmodel : ViewModel() { formatter = DateFormatter(dateFormat = DateFormat.DDMMYYYY, separator = "/"), validators = listOf( Validators.Required(), - Validators.Custom( - function = { - validDate(date = it.toString()) - }, - message = "Invalid date" - ) + Validators.Date(format = DateFormat.DDMMYY) ), ), SelectState( @@ -116,18 +108,6 @@ class SurveyViewmodel : ViewModel() { ) ) - private fun validDate(date: String): Boolean { - val formatter = DateTimeFormatter.ofPattern("ddMMyyyy") - - return try { - LocalDate.parse(date, formatter) - true - } catch (e: DateTimeException) { - false - } - - } - fun navigate(screen: Int) { _screen.value = screen } diff --git a/form-builder/build.gradle b/form-builder/build.gradle index 0f91386..751f2df 100644 --- a/form-builder/build.gradle +++ b/form-builder/build.gradle @@ -26,6 +26,7 @@ android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 + coreLibraryDesugaringEnabled true } kotlinOptions { @@ -58,6 +59,9 @@ dependencies { // Kotlin reflection implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + + // For using modern java 8 classes with older versions of android + coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.2.2" } diff --git a/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt b/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt index 2f12020..225b1f7 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt @@ -2,8 +2,14 @@ package com.dsc.form_builder import androidx.compose.runtime.* import androidx.compose.ui.text.input.VisualTransformation +import com.dsc.form_builder.format.DateFormat +import com.dsc.form_builder.format.DateFormatter import com.dsc.form_builder.format.Formatter import com.dsc.form_builder.format.toVisualTransformation +import java.time.DateTimeException +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.format.ResolverStyle /** * This class represents the state of a single form field. @@ -24,7 +30,12 @@ open class TextFieldState( transform: Transform? = null, validators: List = listOf(), private val formatter: Formatter? = null, -) : BaseState(initial = initial, name = name, transform = transform, validators = validators) { +) : BaseState( + initial = initial, + name = name, + transform = transform, + validators = validators +) { /** * A mutable value holder that reads to the initial parameter during the execution of a [Composable] @@ -79,6 +90,7 @@ open class TextFieldState( is Validators.Custom -> validateCustom(it.function, it.message) is Validators.MinValue -> validateMinValue(it.limit, it.message) is Validators.MaxValue -> validateMaxValue(it.limit, it.message) + is Validators.Date -> validateDate(it.message, it.format) } } return validations.all { it } @@ -152,7 +164,27 @@ open class TextFieldState( checksum += if (n > 9) n - 9 else n } - val valid = checksum%10 == 0 + val valid = checksum % 10 == 0 + if (!valid) showError(message) + return valid + } + + /** + * This function validates a Date in [value]. + * It will return true if the string value is a valid date. + * This function makes use of the [java.time.format.DateTimeFormatter] and [java.time.LocalDate] to verify the validity of the date. + * @param message the error message passed to [showError] to display if the value is not a valid date. By default we use the [DATE_MESSAGE] constant. + * @param dateFormat the format pattern that specifies the expected format of the date [value] string. + */ + internal fun validateDate(message: String, dateFormat: DateFormat): Boolean { + val formatter = + DateTimeFormatter.ofPattern(dateFormat.pattern).withResolverStyle(ResolverStyle.STRICT) + val valid = try { + LocalDate.parse(value, formatter) + true + } catch (e: DateTimeException) { + false + } if (!valid) showError(message) return valid } diff --git a/form-builder/src/main/java/com/dsc/form_builder/Validators.kt b/form-builder/src/main/java/com/dsc/form_builder/Validators.kt index c24963f..1a83d36 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/Validators.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/Validators.kt @@ -1,10 +1,13 @@ package com.dsc.form_builder +import com.dsc.form_builder.format.DateFormat + private const val EMAIL_MESSAGE = "Invalid email address" private const val REQUIRED_MESSAGE = "This field is required" private const val PHONE_MESSAGE = "Invalid phone number" private const val WEB_URL_MESSAGE = "Invalid web url" private const val CARD_NUMBER_MESSAGE = "Invalid card number" +private const val DATE_MESSAGE = "Invalid date" /** * @@ -51,6 +54,13 @@ sealed interface Validators { */ class CardNumber(var message: String = CARD_NUMBER_MESSAGE) : Validators + /** + * This is a date validator. It will return true if the string value is a valid date. + * @param message the error message to display if the value is not a valid date. By default we use the [DATE_MESSAGE] constant. + * @param format the pattern that specifies the expected format of the date string. + */ + class Date(var message: String = DATE_MESSAGE, var format: DateFormat) : Validators + /** * This validator is used to check for numeric values. It will return true is the value is numeric and is greater than or equal to the specified limit. * @param limit the minimum value that the value can hold. diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt b/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt index a5b5314..83d30db 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt @@ -3,13 +3,13 @@ package com.dsc.form_builder.format /** * These are the formatting options for the [DateFormatter] class. */ -enum class DateFormat { - DDMMYYYY, - MMDDYYYY, - YYYYDDMM, - DDMMYY, - MMDDYY, - YYMMDD, +enum class DateFormat(val pattern: String) { + DDMMYYYY("ddMMuuuu"), + MMDDYYYY("MMdduuuu"), + YYYYDDMM("uuuuddMM"), + DDMMYY("ddMMuu"), + MMDDYY("MMdduu"), + YYMMDD("uuMMdd") } // Get the index where to place the separator diff --git a/form-builder/src/test/java/com/dsc/form_builder/TextFieldProviders.kt b/form-builder/src/test/java/com/dsc/form_builder/TextFieldProviders.kt index 6a924de..5e07e58 100644 --- a/form-builder/src/test/java/com/dsc/form_builder/TextFieldProviders.kt +++ b/form-builder/src/test/java/com/dsc/form_builder/TextFieldProviders.kt @@ -1,5 +1,6 @@ package com.dsc.form_builder +import com.dsc.form_builder.format.DateFormat import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.ArgumentsProvider @@ -78,3 +79,14 @@ object MaxValueArgumentsProvider : ArgumentsProvider { Arguments.of("test", 15, false), ) } + +object DateArgumentsProvider : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream = Stream.of( + Arguments.of("12122012", DateFormat.DDMMYYYY, true), + Arguments.of("06312023", DateFormat.MMDDYYYY, false), + Arguments.of("20231504", DateFormat.YYYYDDMM, true), + Arguments.of("290213", DateFormat.DDMMYY, false), // Non Leap Year + Arguments.of("121323", DateFormat.MMDDYY, true), + Arguments.of("070626", DateFormat.YYMMDD, true) + ) +} diff --git a/form-builder/src/test/java/com/dsc/form_builder/TextFieldStateTest.kt b/form-builder/src/test/java/com/dsc/form_builder/TextFieldStateTest.kt index 61bafa4..c1f947b 100644 --- a/form-builder/src/test/java/com/dsc/form_builder/TextFieldStateTest.kt +++ b/form-builder/src/test/java/com/dsc/form_builder/TextFieldStateTest.kt @@ -1,7 +1,6 @@ package com.dsc.form_builder -import com.dsc.form_builder.format.CardFormatter -import com.dsc.form_builder.format.Formatter +import com.dsc.form_builder.format.DateFormat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -126,5 +125,14 @@ internal class TextFieldStateTest { val actual = classToTest.validateMaxValue(limit, "expected validation: $expected") assert(actual == expected) } + + @ParameterizedTest + @ArgumentsSource(DateArgumentsProvider::class) + fun `Validators_Date works correctly`(date: String, pattern: DateFormat, expected: Boolean) { + classToTest.change(date) + + val actual = classToTest.validateDate("expected validation: $expected", pattern) + assert(actual == expected) + } } } From 988593342f91f8547be8813ade65e16b71288229 Mon Sep 17 00:00:00 2001 From: joykangangi <64706463+joykangangi@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:18:51 +0300 Subject: [PATCH 06/30] Remove Phone Field --- .../screens/survey/SurveyViewmodel.kt | 27 ++++--------------- .../screens/survey/components/Navigation.kt | 19 ++++++++----- .../survey/components/PersonalDetails.kt | 7 +---- .../screens/survey/components/SurveyModel.kt | 1 - .../com/dsc/form_builder/format/DateFormat.kt | 8 +++--- 5 files changed, 23 insertions(+), 39 deletions(-) diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt index d5c2894..a7530a5 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/SurveyViewmodel.kt @@ -42,19 +42,12 @@ class SurveyViewmodel : ViewModel() { Validators.Required(), ), ), - TextFieldState( - name = "phone", - validators = listOf( - Validators.Phone(), - Validators.Required(), - ), - ), TextFieldState( name = "date", formatter = DateFormatter(dateFormat = DateFormat.DDMMYYYY, separator = "/"), validators = listOf( + Validators.Date(format = DateFormat.DDMMYYYY), Validators.Required(), - Validators.Date(format = DateFormat.DDMMYY) ), ), SelectState( @@ -113,12 +106,7 @@ class SurveyViewmodel : ViewModel() { } fun validateSurvey() { - val pages: List> = listOf( - (0..3).toList(), - (4..6).toList(), - (7..9).toList() - ) - + val pages: List> = (0..5).chunked(3) if (!formState.validate()) { val position = formState.fields.indexOfFirst { it.hasError } _screen.value = pages.indexOfFirst { it.contains(position) } @@ -129,14 +117,9 @@ class SurveyViewmodel : ViewModel() { } fun validateScreen(screen: Int) { - val fields: List> = when (screen) { - 0 -> formState.fields.subList(0, 4) - 1 -> formState.fields.subList(4, 7) - 2 -> formState.fields.subList(7, 9) - else -> emptyList() - } - - if (fields.map { it.validate() }.all { it }) { + val fields: List> = formState.fields.chunked(3)[screen] + if (fields.map { it.validate() } + .all { it }) { // map is used so we can execute validate() on all fields in that screen if (screen == 2) { logData() _finish.value = true diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/Navigation.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/Navigation.kt index 5d1db5d..6b2b6a8 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/Navigation.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/Navigation.kt @@ -1,13 +1,20 @@ package com.dsc.formbuilder.screens.survey.components -import android.opengl.Matrix -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.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.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt index dc962c9..1f809ed 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/PersonalDetails.kt @@ -30,7 +30,6 @@ import com.dsc.formbuilder.theme.FormBuilderTheme fun PersonalDetails(formState: FormState>) { val emailState: TextFieldState = formState.getState("email") val cardState: TextFieldState = formState.getState("card") - val phoneState: TextFieldState = formState.getState("phone") val dateState: TextFieldState = formState.getState("date") Column( @@ -45,7 +44,7 @@ fun PersonalDetails(formState: FormState>) { Spacer(modifier = Modifier.height(30.dp)) - TextInput(label = "Email", state = emailState) + TextInput(label = "Email", state = emailState) Spacer(modifier = Modifier.height(10.dp)) @@ -57,10 +56,6 @@ fun PersonalDetails(formState: FormState>) { Spacer(modifier = Modifier.height(10.dp)) - TextInput(label = "Phone Number", state = phoneState) - - Spacer(modifier = Modifier.height(10.dp)) - TextInput( label = "Date of birth", state = dateState, diff --git a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/SurveyModel.kt b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/SurveyModel.kt index 9e15115..afa2f20 100644 --- a/example/src/main/java/com/dsc/formbuilder/screens/survey/components/SurveyModel.kt +++ b/example/src/main/java/com/dsc/formbuilder/screens/survey/components/SurveyModel.kt @@ -4,7 +4,6 @@ data class SurveyModel( // First page val email: String, val card: String, - val phone: String, val date: String, // Second page diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt b/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt index 83d30db..608bea8 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt @@ -17,11 +17,11 @@ private fun DateFormat.separatorIndices(): MutableList { val indices = mutableListOf() val stringFormat = this.toString() stringFormat.forEachIndexed { index, char -> - if (index > 0){ - val prev = stringFormat[index-1] + if (index > 0) { + val prev = stringFormat[index - 1] if (prev != char) { - if (indices.isNotEmpty()) indices.add(index+1) + if (indices.isNotEmpty()) indices.add(index + 1) else indices.add(index) } } @@ -38,7 +38,7 @@ private fun DateFormat.separatorIndices(): MutableList { * Note: character limiting is not supported in the formatter. */ -class DateFormatter(private val dateFormat: DateFormat, private val separator: String): Formatter { +class DateFormatter(private val dateFormat: DateFormat, private val separator: String) : Formatter { override fun format(value: String): String { var formatted = value val indices = dateFormat.separatorIndices() From d705d8615a510153c976b4e4996c506eea8c00d4 Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Fri, 23 Aug 2024 01:42:29 +0300 Subject: [PATCH 07/30] Migrating from using Gradle with Grovy to use Gradle with Kotlin. --- build.gradle | 19 -------- build.gradle.kts | 21 +++++++++ example/build.gradle | 64 ------------------------- example/build.gradle.kts | 67 +++++++++++++++++++++++++++ example/proguard-rules.pro | 2 +- form-builder/build.gradle | 77 ------------------------------- form-builder/build.gradle.kts | 82 +++++++++++++++++++++++++++++++++ form-builder/proguard-rules.pro | 2 +- settings.gradle | 10 ---- settings.gradle.kts | 10 ++++ 10 files changed, 182 insertions(+), 172 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 example/build.gradle create mode 100644 example/build.gradle.kts delete mode 100644 form-builder/build.gradle create mode 100644 form-builder/build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 7c66709..0000000 --- a/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -buildscript { - ext { - kotlin_version = '1.7.0' - compose_version = '1.2.0' - } - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' - classpath "org.jetbrains.dokka:dokka-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..291e6aa --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,21 @@ +buildscript { + extra.apply { + set( + "kotlin_version", + "1.7.0" + ) + set( + "compose_version", + "1.2.0" + ) + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:7.3.0") + classpath("org.jetbrains.dokka:dokka-gradle-plugin:${rootProject.extra["kotlin_version"]}") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${rootProject.extra["kotlin_version"]}") + } +} diff --git a/example/build.gradle b/example/build.gradle deleted file mode 100644 index 991b35c..0000000 --- a/example/build.gradle +++ /dev/null @@ -1,64 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' -} - -android { - compileSdk 33 - - defaultConfig { - applicationId "com.dsc.formbuilder" - minSdk 27 - targetSdk 33 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - buildFeatures { - compose true - } - composeOptions { - kotlinCompilerExtensionVersion compose_version - } - packagingOptions { - resources { - excludes += '/META-INF/{AL2.0,LGPL2.1}' - } - } -} - -dependencies { - - implementation project(':form-builder') - - implementation 'androidx.core:core-ktx:1.8.0' - implementation 'androidx.appcompat:appcompat:1.5.0' - implementation 'androidx.activity:activity-compose:1.5.1' - implementation 'com.google.android.material:material:1.6.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' - - implementation "androidx.compose.ui:ui:$compose_version" - implementation "androidx.compose.material:material:$compose_version" - implementation "androidx.compose.animation:animation:$compose_version" - implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - - debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" - - implementation("androidx.compose.material3:material3:1.0.0-beta01") - implementation("androidx.compose.material3:material3-window-size-class:1.0.0-beta01") -} \ No newline at end of file diff --git a/example/build.gradle.kts b/example/build.gradle.kts new file mode 100644 index 0000000..e6ec3ea --- /dev/null +++ b/example/build.gradle.kts @@ -0,0 +1,67 @@ +plugins { + id("com.android.application") + id("kotlin-android") +} + +android { + compileSdk = 33 + + defaultConfig { + applicationId = "com.dsc.formbuilder" + minSdk = 27 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String + } + packagingOptions { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + implementation(project(":form-builder")) + + implementation("androidx.core:core-ktx:1.8.0") + implementation("androidx.appcompat:appcompat:1.5.0") + implementation("androidx.activity:activity-compose:1.5.1") + implementation("com.google.android.material:material:1.6.1") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1") + + implementation("androidx.compose.ui:ui:${rootProject.extra["compose_version"]}") + implementation("androidx.compose.material:material:${rootProject.extra["compose_version"]}") + implementation("androidx.compose.animation:animation:${rootProject.extra["compose_version"]}") + implementation("androidx.compose.ui:ui-tooling-preview:${rootProject.extra["compose_version"]}") + + debugImplementation("androidx.compose.ui:ui-tooling:${rootProject.extra["compose_version"]}") + + implementation("androidx.compose.material3:material3:1.0.0-beta01") + implementation("androidx.compose.material3:material3-window-size-class:1.0.0-beta01") +} \ No newline at end of file diff --git a/example/proguard-rules.pro b/example/proguard-rules.pro index 481bb43..ff59496 100644 --- a/example/proguard-rules.pro +++ b/example/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/form-builder/build.gradle b/form-builder/build.gradle deleted file mode 100644 index 0f91386..0000000 --- a/form-builder/build.gradle +++ /dev/null @@ -1,77 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' - id 'maven-publish' - id 'org.jetbrains.dokka' -} - -android { - compileSdk 32 - - defaultConfig { - minSdk 21 - targetSdk 32 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - testOptions { - unitTests.all { - useJUnitPlatform() - } - } - - buildFeatures { - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion compose_version - } -} - -dependencies { - // Testing - testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' - testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.1' - - // Compose - implementation 'androidx.activity:activity-compose:1.5.1' - - // Kotlin reflection - implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" -} - - - -afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - - groupId = 'com.github.dsc-jkuat' - artifactId = 'form-builder' - version = '1.0.1' - } - } - } -} \ No newline at end of file diff --git a/form-builder/build.gradle.kts b/form-builder/build.gradle.kts new file mode 100644 index 0000000..80cfa9f --- /dev/null +++ b/form-builder/build.gradle.kts @@ -0,0 +1,82 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") + id("maven-publish") + id("org.jetbrains.dokka") +} + +android { + compileSdk = 32 + + defaultConfig { + minSdk = 21 + targetSdk = 32 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + } + + testOptions { + unitTests { + all { + it.useJUnitPlatform() + } + } + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String + } +} + +dependencies { + // Testing + testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") + testImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.1") + + // Compose + implementation("androidx.activity:activity-compose:1.5.1") + + // Kotlin reflection + implementation("org.jetbrains.kotlin:kotlin-reflect:${rootProject.extra["kotlin_version"]}") +} + + + +afterEvaluate { + publishing { + publications { + create("release") { + from(components["release"]) + + groupId = "com.github.dsc-jkuat" + artifactId = "form-builder" + version = "1.0.1" + } + } + } +} \ No newline at end of file diff --git a/form-builder/proguard-rules.pro b/form-builder/proguard-rules.pro index 481bb43..ff59496 100644 --- a/form-builder/proguard-rules.pro +++ b/form-builder/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 3375b63..0000000 --- a/settings.gradle +++ /dev/null @@ -1,10 +0,0 @@ -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } -} -rootProject.name = "Form Builder" -include ':example' -include ':form-builder' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ba3faa6 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,10 @@ +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "Form Builder" +include(":example") +include(":form-builder") From de58b95f492d801d18724134cda6de6dd32d8b68 Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Sat, 24 Aug 2024 01:19:07 +0300 Subject: [PATCH 08/30] Migrating from using Gradle with Grovy to use Gradle with Kotlin. --- build.gradle.kts | 14 +++++--------- settings.gradle.kts | 13 +++++++++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 291e6aa..9dd2545 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,13 +9,9 @@ buildscript { "1.2.0" ) } - repositories { - google() - mavenCentral() - } - dependencies { - classpath("com.android.tools.build:gradle:7.3.0") - classpath("org.jetbrains.dokka:dokka-gradle-plugin:${rootProject.extra["kotlin_version"]}") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${rootProject.extra["kotlin_version"]}") - } +} +plugins { + id("com.android.library") version "7.3.0" apply false + id("org.jetbrains.kotlin.android") version "1.7.0" apply false + id("org.jetbrains.dokka") version "1.7.0" apply false } diff --git a/settings.gradle.kts b/settings.gradle.kts index ba3faa6..31255e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,19 @@ dependencyResolutionManagement { mavenCentral() } } +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} rootProject.name = "Form Builder" include(":example") include(":form-builder") From 7a5dfd410e7ddb6d91113e21b9b9903c96291b23 Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Sun, 25 Aug 2024 12:24:25 +0300 Subject: [PATCH 09/30] Migrating to versions catalog system and unifing the compileSdk and targetSdk to 33 and minSdk to 21. --- build.gradle.kts | 19 +++----------- example/build.gradle.kts | 47 ++++++++++++++++++----------------- form-builder/build.gradle.kts | 32 ++++++++++++------------ gradle/libs.versions.toml | 43 ++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/build.gradle.kts b/build.gradle.kts index 9dd2545..a8bb772 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,6 @@ -buildscript { - extra.apply { - set( - "kotlin_version", - "1.7.0" - ) - set( - "compose_version", - "1.2.0" - ) - } -} plugins { - id("com.android.library") version "7.3.0" apply false - id("org.jetbrains.kotlin.android") version "1.7.0" apply false - id("org.jetbrains.dokka") version "1.7.0" apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.jetbrains.kotlin.android) apply false + alias(libs.plugins.jetbrains.dokka) apply false } diff --git a/example/build.gradle.kts b/example/build.gradle.kts index e6ec3ea..5f940a3 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -1,17 +1,17 @@ plugins { - id("com.android.application") - id("kotlin-android") + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) } android { - compileSdk = 33 + compileSdk = libs.versions.compileSdk.get().toInt() defaultConfig { applicationId = "com.dsc.formbuilder" - minSdk = 27 - targetSdk = 33 - versionCode = 1 - versionName = "1.0" + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() + versionCode = libs.versions.appVersionCode.get().toInt() + versionName = libs.versions.appVersionName.get() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -26,17 +26,18 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility(libs.versions.jvmTarget.get()) + targetCompatibility(libs.versions.jvmTarget.get()) } + kotlinOptions { - jvmTarget = "1.8" + jvmTarget = libs.versions.jvmTarget.get() } buildFeatures { compose = true } composeOptions { - kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String + kotlinCompilerExtensionVersion = libs.versions.compose.get() } packagingOptions { resources { @@ -49,19 +50,19 @@ dependencies { implementation(project(":form-builder")) - implementation("androidx.core:core-ktx:1.8.0") - implementation("androidx.appcompat:appcompat:1.5.0") - implementation("androidx.activity:activity-compose:1.5.1") - implementation("com.google.android.material:material:1.6.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1") + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.activity.compose) + implementation(libs.android.material) + implementation(libs.androidx.lifecycle.runtime) - implementation("androidx.compose.ui:ui:${rootProject.extra["compose_version"]}") - implementation("androidx.compose.material:material:${rootProject.extra["compose_version"]}") - implementation("androidx.compose.animation:animation:${rootProject.extra["compose_version"]}") - implementation("androidx.compose.ui:ui-tooling-preview:${rootProject.extra["compose_version"]}") + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.animation) + implementation(libs.androidx.compose.ui.tooling.preview) - debugImplementation("androidx.compose.ui:ui-tooling:${rootProject.extra["compose_version"]}") + debugImplementation(libs.androidx.compose.ui.tooling) - implementation("androidx.compose.material3:material3:1.0.0-beta01") - implementation("androidx.compose.material3:material3-window-size-class:1.0.0-beta01") + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.material3.window.size) } \ No newline at end of file diff --git a/form-builder/build.gradle.kts b/form-builder/build.gradle.kts index 80cfa9f..007bb84 100644 --- a/form-builder/build.gradle.kts +++ b/form-builder/build.gradle.kts @@ -1,16 +1,16 @@ plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.jetbrains.dokka) id("maven-publish") - id("org.jetbrains.dokka") } android { - compileSdk = 32 + compileSdk = libs.versions.compileSdk.get().toInt() defaultConfig { - minSdk = 21 - targetSdk = 32 + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") @@ -27,12 +27,12 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility(libs.versions.jvmTarget.get()) + targetCompatibility(libs.versions.jvmTarget.get()) } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = libs.versions.jvmTarget.get() } testOptions { @@ -48,21 +48,21 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String + kotlinCompilerExtensionVersion = libs.versions.compose.get() } } dependencies { // Testing - testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") - testImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0") - testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.1") + testImplementation(libs.junit.jupiter) + testImplementation(libs.mockito.kotlin) + testImplementation(libs.junit.jupiter.params) // Compose - implementation("androidx.activity:activity-compose:1.5.1") + implementation(libs.androidx.activity.compose) // Kotlin reflection - implementation("org.jetbrains.kotlin:kotlin-reflect:${rootProject.extra["kotlin_version"]}") + implementation(libs.jetbrains.kotlin.reflection) } @@ -75,7 +75,7 @@ afterEvaluate { groupId = "com.github.dsc-jkuat" artifactId = "form-builder" - version = "1.0.1" + version = libs.versions.libVersionName.get() } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..3a4401f --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,43 @@ +[versions] +activityCompose = "1.5.1" +agp = "7.3.0" +appcompat = "1.5.0" +appVersionCode = "1" +appVersionName = "1.0" +compileSdk = "33" +compose = "1.2.0" +coreKtx = "1.8.0" +junit = "5.8.1" +jvmTarget = "1.8" +kotlin = "1.7.0" +libVersionName = "1.0.1" +lifecycleRuntimeKtx = "2.5.1" +material = "1.6.1" +material3 = "1.0.0-beta01" +minSdk = "21" +mockitoKotlin = "4.0.0" +targetSdk = "33" + +[libraries] +android-material = { group = "com.google.android.material", name = "material", version.ref = "material" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +androidx-compose-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "compose" } +androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "compose" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } +androidx-compose-material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "material3" } +androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" } +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +jetbrains-kotlin-reflection = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } +junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } +junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" } +mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockitoKotlin" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" } \ No newline at end of file From be8d898e4cedb5fda74d8b219d77f7f48eb762db Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Sun, 25 Aug 2024 16:17:53 +0300 Subject: [PATCH 10/30] Upgrading dependencies & some adaptation and configurations. --- build.gradle.kts | 1 + example/build.gradle.kts | 12 +- example/src/main/AndroidManifest.xml | 3 +- .../screens/survey/components/OtherDetails.kt | 209 +++++++++++------- form-builder/build.gradle.kts | 12 +- form-builder/src/main/AndroidManifest.xml | 2 +- gradle.properties | 9 +- gradle/libs.versions.toml | 44 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 174 insertions(+), 120 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a8bb772..c524f0b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,4 +3,5 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false alias(libs.plugins.jetbrains.dokka) apply false + alias(libs.plugins.jetbrains.compose.compiler) apply false } diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 5f940a3..cff3649 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.jetbrains.compose.compiler) } android { @@ -30,26 +31,25 @@ android { targetCompatibility(libs.versions.jvmTarget.get()) } - kotlinOptions { - jvmTarget = libs.versions.jvmTarget.get() + kotlin { + jvmToolchain(libs.versions.jvmTarget.get().toInt()) } buildFeatures { compose = true } - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.compose.get() - } - packagingOptions { + packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } + namespace = "com.dsc.formbuilder" } dependencies { implementation(project(":form-builder")) + implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.androidx.activity.compose) diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml index 3a17566..a176e9a 100644 --- a/example/src/main/AndroidManifest.xml +++ b/example/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + >) { - - val genderState: ChoiceState = formState.getState("gender") - val experienceState: ChoiceState = formState.getState("experience") - val osState: ChoiceState = formState.getState("os") - - - val genderOptions = listOf("Non-binary", "Male", "Female") - val experienceOptions = listOf("1-3 Years", "3-5 Years", "5+ Years") - val osOptions = listOf("Mac OS", "Linux", "Windows") - - Column(horizontalAlignment = CenterHorizontally, verticalArrangement = Center) { - Text( - modifier = Modifier.fillMaxWidth(), - text = "Other Details", - style = MaterialTheme.typography.h6 - ) - - Spacer(modifier = Modifier.height(30.dp)) - - OtherDetailsRow(labelText = "Gender", items = genderOptions, genderState) - OtherDetailsRow(labelText = "Experience", items = experienceOptions, experienceState) - OtherDetailsRow(labelText = "Using OS", items = osOptions, osState) - } + + val genderState: ChoiceState = formState.getState("gender") + val experienceState: ChoiceState = formState.getState("experience") + val osState: ChoiceState = formState.getState("os") + + + val genderOptions = listOf( + "Non-binary", + "Male", + "Female" + ) + val experienceOptions = listOf( + "1-3 Years", + "3-5 Years", + "5+ Years" + ) + val osOptions = listOf( + "Mac OS", + "Linux", + "Windows" + ) + + Column( + horizontalAlignment = CenterHorizontally, + verticalArrangement = Center + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = "Other Details", + style = MaterialTheme.typography.h6 + ) + + Spacer(modifier = Modifier.height(30.dp)) + + OtherDetailsRow( + labelText = "Gender", + items = genderOptions, + genderState + ) + OtherDetailsRow( + labelText = "Experience", + items = experienceOptions, + experienceState + ) + OtherDetailsRow( + labelText = "Using OS", + items = osOptions, + osState + ) + } } @Composable -fun OtherDetailsRow(labelText: String, items: List, state: ChoiceState) { - Column { - Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { - Text(text = labelText, style = MaterialTheme.typography.body1) - - Spacer(modifier = Modifier.weight(1f)) - - items.forEach { item -> - Column( - modifier = Modifier.width(85.dp).selectableGroup(), - verticalArrangement = Center, - horizontalAlignment = CenterHorizontally - ) { - Text( - text = item, style = MaterialTheme.typography.caption - ) - RadioButton( - selected = state.value == item, - onClick = { state.change(item) }, - colors = RadioButtonDefaults.colors( - unselectedColor = MaterialTheme.colors.onPrimary, - selectedColor = MaterialTheme.colors.onPrimary - ) - ) - } - } - } - - if (state.hasError) { - Text( - text = state.errorMessage, - style = MaterialTheme.typography.caption.copy( - color = MaterialTheme.colors.error - ) - ) - } - } +fun OtherDetailsRow( + labelText: String, + items: List, + state: ChoiceState +) { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = labelText, + style = MaterialTheme.typography.body1 + ) + + Spacer(modifier = Modifier.weight(1f)) + + items.forEach { item -> + Column( + modifier = Modifier + .width(85.dp) + .selectableGroup(), + verticalArrangement = Center, + horizontalAlignment = CenterHorizontally + ) { + Text( + text = item, + style = MaterialTheme.typography.caption + ) + RadioButton( + selected = state.value == item, + onClick = { state.change(item) }, + colors = RadioButtonDefaults.colors( + unselectedColor = MaterialTheme.colors.onPrimary, + selectedColor = MaterialTheme.colors.onPrimary + ) + ) + } + } + } + + if (state.hasError) { + Text( + text = state.errorMessage, + style = MaterialTheme.typography.caption.copy( + color = MaterialTheme.colors.error + ) + ) + } + } } @Preview @Composable fun OtherDetailsPreview() { - val formState: FormState> = FormState( - listOf( - ChoiceState("gender", validators = listOf()), - ChoiceState("experience", validators = listOf()), - ChoiceState("os", validators = listOf()) - ) - ) - FormBuilderTheme { - Surface(color = MaterialTheme.colors.background) { - OtherDetails(formState) - } - } + val formState: FormState> = FormState( + listOf( + ChoiceState( + "gender", + validators = listOf() + ), + ChoiceState( + "experience", + validators = listOf() + ), + ChoiceState( + "os", + validators = listOf() + ) + ) + ) + FormBuilderTheme { + Surface(color = MaterialTheme.colors.background) { + OtherDetails(formState) + } + } } \ No newline at end of file diff --git a/form-builder/build.gradle.kts b/form-builder/build.gradle.kts index 007bb84..7de9358 100644 --- a/form-builder/build.gradle.kts +++ b/form-builder/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.jetbrains.dokka) + alias(libs.plugins.jetbrains.compose.compiler) id("maven-publish") } @@ -10,8 +11,6 @@ android { defaultConfig { minSdk = libs.versions.minSdk.get().toInt() - targetSdk = libs.versions.targetSdk.get().toInt() - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") } @@ -31,8 +30,8 @@ android { targetCompatibility(libs.versions.jvmTarget.get()) } - kotlinOptions { - jvmTarget = libs.versions.jvmTarget.get() + kotlin { + jvmToolchain(libs.versions.jvmTarget.get().toInt()) } testOptions { @@ -46,10 +45,7 @@ android { buildFeatures { compose = true } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.compose.get() - } + namespace = "com.dsc.form_builder" } dependencies { diff --git a/form-builder/src/main/AndroidManifest.xml b/form-builder/src/main/AndroidManifest.xml index f8a8341..44008a4 100644 --- a/form-builder/src/main/AndroidManifest.xml +++ b/form-builder/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 736c959..8dae799 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,17 +6,18 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true +org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app"s APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true -android.disableAutomaticComponentCreation=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a4401f..f42d8e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,34 +1,35 @@ [versions] -activityCompose = "1.5.1" -agp = "7.3.0" -appcompat = "1.5.0" +activityCompose = "1.9.1" +agp = "8.5.2" +appcompat = "1.7.0" appVersionCode = "1" appVersionName = "1.0" -compileSdk = "33" -compose = "1.2.0" -coreKtx = "1.8.0" +compileSdk = "34" +composeBom = "2024.08.00" +coreKtx = "1.13.1" +dokka = "1.9.20" junit = "5.8.1" -jvmTarget = "1.8" -kotlin = "1.7.0" +jvmTarget = "17" +kotlin = "2.0.0" libVersionName = "1.0.1" -lifecycleRuntimeKtx = "2.5.1" -material = "1.6.1" -material3 = "1.0.0-beta01" -minSdk = "21" +lifecycleRuntimeKtx = "2.8.4" +material = "1.12.0" +minSdk = "24" mockitoKotlin = "4.0.0" -targetSdk = "33" +targetSdk = "34" [libraries] android-material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } -androidx-compose-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "compose" } -androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "compose" } -androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } -androidx-compose-material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "material3" } -androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" } -androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose" } -androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose" } +androidx-compose-animation = { group = "androidx.compose.animation", name = "animation" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-compose-material = { group = "androidx.compose.material", name = "material" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-compose-material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class" } +androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } jetbrains-kotlin-reflection = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } @@ -39,5 +40,6 @@ mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", versio [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } +jetbrains-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" } \ No newline at end of file +jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e0120c0..483b6a7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Feb 17 16:08:59 EAT 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From 35845f11d994dbf8dd8cb166d6ae51837b8e83ac Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Sun, 25 Aug 2024 16:29:20 +0300 Subject: [PATCH 11/30] Upgrade the library version. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f42d8e2..cdcab8e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ dokka = "1.9.20" junit = "5.8.1" jvmTarget = "17" kotlin = "2.0.0" -libVersionName = "1.0.1" +libVersionName = "1.1.0" lifecycleRuntimeKtx = "2.8.4" material = "1.12.0" minSdk = "24" From a3053b3e393f5a5cf17c4b7c83482d42b53aa1dd Mon Sep 17 00:00:00 2001 From: Nabil Eldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:09:55 +0300 Subject: [PATCH 12/30] Update README.md Change the installation from using Groovy style to using Kotlin style Also, change the implementation to refer to my GitHub repo --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 331158c..cc8f30b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ support for a form state so the library is used to provide a custom implementati In the root `build.gradle` file add the following: -```groovy +```kotlin repositories { maven { url "https://jitpack.io" } } @@ -20,9 +20,9 @@ repositories { Then add the following to your module's `build.gradle` -```groovy +```kotlin dependencies { - implementation 'com.github.jkuatdsc:form-builder:${version}' + implementation("com.github.Nenoeldeeb:form-builder:${version}") } ``` From 2fd735f7704fc0963abee9ca7ea653540f25b05c Mon Sep 17 00:00:00 2001 From: Nabil Eldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:12:54 +0300 Subject: [PATCH 13/30] Update jitpack.yml Add openjdk17 instead of 11 for building the project. --- jitpack.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jitpack.yml b/jitpack.yml index 1bfc0d7..f82bf6a 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,4 +1,4 @@ jdk: - - openjdk11 + - openjdk17 before_install: - - ./scripts/prepareJitpackEnvironment.sh \ No newline at end of file + - ./scripts/prepareJitpackEnvironment.sh From 3cc2ad29eb63d90a3aecaeb29cc5edf37209797f Mon Sep 17 00:00:00 2001 From: Nabil Eldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:18:21 +0300 Subject: [PATCH 14/30] Update build.gradle.kts (form-builder) Change maven publish settings to get the library from my repo. --- form-builder/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/form-builder/build.gradle.kts b/form-builder/build.gradle.kts index 7de9358..69dd876 100644 --- a/form-builder/build.gradle.kts +++ b/form-builder/build.gradle.kts @@ -69,10 +69,10 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = "com.github.dsc-jkuat" + groupId = "com.github.Nenoeldeeb" artifactId = "form-builder" version = libs.versions.libVersionName.get() } } } -} \ No newline at end of file +} From 9c835788bab656d337b2cb61f0bab192d1a47291 Mon Sep 17 00:00:00 2001 From: Nabil Eldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:14:29 +0300 Subject: [PATCH 15/30] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc8f30b..4be22d2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ In the root `build.gradle` file add the following: ```kotlin repositories { - maven { url "https://jitpack.io" } + maven { url = uri("https://jitpack.io") } } ``` From cbeb5297b21ba2c9b552b10fb6f09d4ce4e775ac Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Sun, 1 Sep 2024 15:56:11 +0300 Subject: [PATCH 16/30] Add a new field of type "SwitchState" that handle the Switch Composable. --- .../java/com/dsc/form_builder/SwitchState.kt | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100755 form-builder/src/main/java/com/dsc/form_builder/SwitchState.kt diff --git a/form-builder/src/main/java/com/dsc/form_builder/SwitchState.kt b/form-builder/src/main/java/com/dsc/form_builder/SwitchState.kt new file mode 100755 index 0000000..aaefcfe --- /dev/null +++ b/form-builder/src/main/java/com/dsc/form_builder/SwitchState.kt @@ -0,0 +1,99 @@ +package com.dsc.form_builder + +import androidx.annotation.VisibleForTesting +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +/** + * This class represents the state of a switch form field. + * It extends [BaseState] and manages the state and validations for switch inputs. + * + * @param name The name of the field used to access the state when required in the form. + * @param initial The initial value/state of the switch. By default, it is false (off). + * @param transform An optional function used to change the [Boolean] data type of the switch to a suitable type. + * @param validators This is the list of [Validators] that are used to validate the switch state. + * By default, the switch states will have an empty list. You can override this + * and provide your own list of validators. + * + * @author [Nenoeldeeb] + * @created [01/09/2024] + */ +class SwitchState( + name: String, + initial: Boolean = false, + transform: Transform? = null, + validators: List = listOf(), +) : BaseState( + initial = initial, + name = name, + transform = transform, + validators = validators +) { + + /** + * The current value of the switch. It uses [mutableStateOf] to make it observable in Compose. + */ + override var value: Boolean by mutableStateOf(initial) + + /** + * Toggles the state of the switch between true (on) and false (off). + * This function also clears any previous error state by calling [hideError]. + */ + fun toggle() { + hideError() + value = !value + } + + /** + * Validates the switch state based on the provided validators. + * This function is called by [FormState] to confirm whether the switch field is valid. + * + * @return true if all validations pass, false otherwise. + */ + override fun validate(): Boolean { + val validations = validators.map { + when (it) { + is Validators.Required -> validateRequired(it.message) + is Validators.Custom -> validateCustom( + it.function, + it.message + ) + + else -> throw Exception("${it::class.simpleName} validator cannot be called on switch state. Did you mean Validators.Custom?") + } + } + return validations.all { it } + } + + /** + * Validates if the switch is in the required state (typically on/true). + * + * @param message The error message to display if the validation fails. + * @return true if the switch is in the required state, false otherwise. + */ + @VisibleForTesting + fun validateRequired(message: String): Boolean { + // For a switch, "required" typically means it must be turned on + val valid = value + if (!valid) showError(message) + return valid + } + + /** + * Allows for custom validation of the switch state. + * + * @param function A lambda that takes a Boolean and returns a Boolean. + * It should return true if the validation passes, false otherwise. + * @param message The error message to display if the validation fails. + * @return The result of the custom validation function. + */ + private fun validateCustom( + function: (Boolean) -> Boolean, + message: String + ): Boolean { + val valid = function(value) + if (!valid) showError(message) + return valid + } +} \ No newline at end of file From 9f7cdeecb36dbeafc67cf2c9650c253341772e05 Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Sun, 1 Sep 2024 15:57:44 +0300 Subject: [PATCH 17/30] Adding unit & integration tests to the newly added field. --- .../com/dsc/form_builder/FormStateTest.kt | 76 ++++++++++--------- .../com/dsc/form_builder/SwitchStateTest.kt | 53 +++++++++++++ 2 files changed, 95 insertions(+), 34 deletions(-) mode change 100644 => 100755 form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt create mode 100755 form-builder/src/test/java/com/dsc/form_builder/SwitchStateTest.kt diff --git a/form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt b/form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt old mode 100644 new mode 100755 index e545594..9a82c49 --- a/form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt +++ b/form-builder/src/test/java/com/dsc/form_builder/FormStateTest.kt @@ -5,38 +5,46 @@ import org.junit.jupiter.api.Test internal class FormStateTest { - @Nested - inner class DescribingFormState { - private val formState = FormState( - listOf( - TextFieldState(name = "email"), - SelectState(name = "hobbies"), - ChoiceState(name = "gender"), - TextFieldState(name = "age", initial = "34") - ) - ) - - private val emailState = formState.getState("email") - private val hobbyState = formState.getState("hobbies") - private val genderState = formState.getState("gender") - private val ageState = formState.getState("age") - - @Test - fun `state should be reset to initial values`() { - // Given a form state with values changed - emailState.change("buider@gmail.com") - hobbyState.select("Running") - genderState.change("male") - ageState.change("56") - - // When the form.reset is requested - formState.reset() - - // Then all values are reset to the original state - assert(emailState.value == "" && !emailState.hasError) - assert(hobbyState.value == mutableListOf() && !hobbyState.hasError) - assert(genderState.value == "" && !genderState.hasError) - assert(ageState.value == "34" && !ageState.hasError) - } - } + @Nested + inner class DescribingFormState { + + private val formState = FormState( + listOf( + TextFieldState(name = "email"), + SelectState(name = "hobbies"), + ChoiceState(name = "gender"), + TextFieldState( + name = "age", + initial = "34" + ), + SwitchState(name = "active") + ) + ) + + private val emailState = formState.getState("email") + private val hobbyState = formState.getState("hobbies") + private val genderState = formState.getState("gender") + private val ageState = formState.getState("age") + private val statusState = formState.getState("active") + + @Test + fun `state should be reset to initial values`() { + // Given a form state with values changed + emailState.change("buider@gmail.com") + hobbyState.select("Running") + genderState.change("male") + ageState.change("56") + statusState.toggle() + + // When the form.reset is requested + formState.reset() + + // Then all values are reset to the original state + assert(emailState.value == "" && !emailState.hasError) + assert(hobbyState.value == mutableListOf() && !hobbyState.hasError) + assert(genderState.value == "" && !genderState.hasError) + assert(ageState.value == "34" && !ageState.hasError) + assert(!statusState.value && !statusState.hasError) + } + } } \ No newline at end of file diff --git a/form-builder/src/test/java/com/dsc/form_builder/SwitchStateTest.kt b/form-builder/src/test/java/com/dsc/form_builder/SwitchStateTest.kt new file mode 100755 index 0000000..6094300 --- /dev/null +++ b/form-builder/src/test/java/com/dsc/form_builder/SwitchStateTest.kt @@ -0,0 +1,53 @@ +package com.dsc.form_builder + +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +internal class SwitchStateTest { + + @Nested + inner class DescribingStateChanges { + + private val classToTest: SwitchState = SwitchState(name = "test") + + @Test + fun `errors should be hidden when toggled`() { + // Simulate an existing validation error + classToTest.hasError = true + classToTest.errorMessage = "error message" + + classToTest.toggle() // When toggle is called + + assert(!classToTest.hasError) + assert(classToTest.errorMessage.isEmpty()) + } + + @Test + fun `state should be updated when toggled`() { + val initialState = classToTest.value + classToTest.toggle() // When toggle is called + + assert(classToTest.value != initialState) + + classToTest.toggle() // Toggle again + assert(classToTest.value == initialState) + } + } + + @Nested + inner class DescribingValidation { + + private val classToTest: SwitchState = SwitchState(name = "test") + + @Test + fun `Validators_Required works correctly`() { + // When state is false (off) + val firstValidation = classToTest.validateRequired("") + assert(!firstValidation) + + classToTest.toggle() // Turn on the switch + val secondValidation = classToTest.validateRequired("") + assert(secondValidation) + } + } +} \ No newline at end of file From 90a216ed804959e5b4fe65fb2ebcd802ae57d93c Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Sun, 1 Sep 2024 16:03:05 +0300 Subject: [PATCH 18/30] Update the library version to "1.2.0". --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cdcab8e..d2369c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ dokka = "1.9.20" junit = "5.8.1" jvmTarget = "17" kotlin = "2.0.0" -libVersionName = "1.1.0" +libVersionName = "1.2.0" lifecycleRuntimeKtx = "2.8.4" material = "1.12.0" minSdk = "24" From 757b7079b1a85e39fc875740148adf28c4d8f8db Mon Sep 17 00:00:00 2001 From: nenoeldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Sun, 1 Sep 2024 16:04:31 +0300 Subject: [PATCH 19/30] Update the Kotlin version to "2.2.20". --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d2369c4..bf2a005 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ coreKtx = "1.13.1" dokka = "1.9.20" junit = "5.8.1" jvmTarget = "17" -kotlin = "2.0.0" +kotlin = "2.0.20" libVersionName = "1.2.0" lifecycleRuntimeKtx = "2.8.4" material = "1.12.0" From faa0ee374ea545cca794ca2db87c1bff2654d205 Mon Sep 17 00:00:00 2001 From: Nabil Eldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:47:56 +0300 Subject: [PATCH 20/30] Update build.yaml workflow --- .github/workflows/build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 971da32..594cfba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -8,11 +8,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up JDK 12 - uses: actions/setup-java@v1 + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: 12 + java-version: 17 - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -24,4 +24,4 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages - FOLDER: ./form-builder/build/dokka/html \ No newline at end of file + FOLDER: ./form-builder/build/dokka/html From 01e8ca347d55b30b901b733b2659e762bb67d8e8 Mon Sep 17 00:00:00 2001 From: Nabil Eldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:52:21 +0300 Subject: [PATCH 21/30] Update build.yaml workflow --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 594cfba..8b3b710 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,6 +13,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: 17 + distribution: 'openjdk' - name: Grant execute permission for gradlew run: chmod +x gradlew From 8bfe169af89f0eb187011a71742771efd3623a91 Mon Sep 17 00:00:00 2001 From: Nabil Eldeeb <69523673+Nenoeldeeb@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:12:52 +0300 Subject: [PATCH 22/30] Update build.yaml workflow --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8b3b710..28eb99c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: 17 - distribution: 'openjdk' + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew From cde88ee94d4d8c55ead22a98522c22a8c96d7947 Mon Sep 17 00:00:00 2001 From: joykangangi <64706463+joykangangi@users.noreply.github.com> Date: Sun, 22 Sep 2024 23:23:46 +0300 Subject: [PATCH 23/30] resolve merge conflicts --- README.md | 2 +- example/build.gradle.kts | 3 +++ form-builder/build.gradle.kts | 6 +++++- .../src/main/java/com/dsc/form_builder/SwitchState.kt | 2 +- .../src/main/java/com/dsc/form_builder/TextFieldState.kt | 2 +- gradle/libs.versions.toml | 2 ++ 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4be22d2..5ccf33e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Then add the following to your module's `build.gradle` ```kotlin dependencies { - implementation("com.github.Nenoeldeeb:form-builder:${version}") + implementation("com.github.jkuatdsc:form-builder:${version}") } ``` diff --git a/example/build.gradle.kts b/example/build.gradle.kts index cff3649..34e1dc2 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -29,6 +29,7 @@ android { compileOptions { sourceCompatibility(libs.versions.jvmTarget.get()) targetCompatibility(libs.versions.jvmTarget.get()) + isCoreLibraryDesugaringEnabled = true } kotlin { @@ -48,6 +49,8 @@ android { dependencies { implementation(project(":form-builder")) + // For using modern java 8 classes with older versions of android + coreLibraryDesugaring(libs.core.java8) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.core.ktx) diff --git a/form-builder/build.gradle.kts b/form-builder/build.gradle.kts index 69dd876..fd215ff 100644 --- a/form-builder/build.gradle.kts +++ b/form-builder/build.gradle.kts @@ -28,6 +28,7 @@ android { compileOptions { sourceCompatibility(libs.versions.jvmTarget.get()) targetCompatibility(libs.versions.jvmTarget.get()) + isCoreLibraryDesugaringEnabled = true } kotlin { @@ -59,6 +60,9 @@ dependencies { // Kotlin reflection implementation(libs.jetbrains.kotlin.reflection) + + // For using modern java 8 classes with older versions of android + coreLibraryDesugaring(libs.core.java8) } @@ -69,7 +73,7 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = "com.github.Nenoeldeeb" + groupId = "com.github.dsc-jkuat" artifactId = "form-builder" version = libs.versions.libVersionName.get() } diff --git a/form-builder/src/main/java/com/dsc/form_builder/SwitchState.kt b/form-builder/src/main/java/com/dsc/form_builder/SwitchState.kt index aaefcfe..00f9171 100755 --- a/form-builder/src/main/java/com/dsc/form_builder/SwitchState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/SwitchState.kt @@ -73,7 +73,7 @@ class SwitchState( * @return true if the switch is in the required state, false otherwise. */ @VisibleForTesting - fun validateRequired(message: String): Boolean { + internal fun validateRequired(message: String): Boolean { // For a switch, "required" typically means it must be turned on val valid = value if (!valid) showError(message) diff --git a/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt b/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt index 225b1f7..10f1cd7 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt @@ -195,7 +195,7 @@ open class TextFieldState( * @param message the error message passed to [showError] to display if the [value] is empty. By default we use the [REQUIRED_MESSAGE] constant. */ internal fun validateRequired(message: String): Boolean { - val valid = value.isNotEmpty() + val valid = value.isNotBlank() if (!valid) showError(message) return valid } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf2a005..9dd615b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ appVersionName = "1.0" compileSdk = "34" composeBom = "2024.08.00" coreKtx = "1.13.1" +desugar = "2.1.2" dokka = "1.9.20" junit = "5.8.1" jvmTarget = "17" @@ -32,6 +33,7 @@ androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-toolin androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +core-java8 = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar" } jetbrains-kotlin-reflection = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" } From c429464638a10e634dabc560d743b6cdedc62c31 Mon Sep 17 00:00:00 2001 From: Joy Kangangi <64706463+joykangangi@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:48:54 +0300 Subject: [PATCH 24/30] Update build.yaml --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 28eb99c..8b3b710 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: 17 - distribution: 'temurin' + distribution: 'openjdk' - name: Grant execute permission for gradlew run: chmod +x gradlew From 86554139a21af740613f9c96294f03fe9032f5e2 Mon Sep 17 00:00:00 2001 From: Joy Kangangi <64706463+joykangangi@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:53:40 +0300 Subject: [PATCH 25/30] Update build.yaml --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8b3b710..594cfba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,6 @@ jobs: uses: actions/setup-java@v4 with: java-version: 17 - distribution: 'openjdk' - name: Grant execute permission for gradlew run: chmod +x gradlew From fc8cbb01b2456de1463e9f3365291fb70234425b Mon Sep 17 00:00:00 2001 From: Joy Kangangi <64706463+joykangangi@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:37:14 +0300 Subject: [PATCH 26/30] Update build.yaml --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 594cfba..28eb99c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,6 +13,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: 17 + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew From 9eb48ca183af3d21c41b6ae14024113a4947921c Mon Sep 17 00:00:00 2001 From: Joy Kangangi <64706463+joykangangi@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:47:37 +0300 Subject: [PATCH 27/30] Delete old form-builder/build.gradle --- form-builder/build.gradle | 81 --------------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 form-builder/build.gradle diff --git a/form-builder/build.gradle b/form-builder/build.gradle deleted file mode 100644 index 751f2df..0000000 --- a/form-builder/build.gradle +++ /dev/null @@ -1,81 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' - id 'maven-publish' - id 'org.jetbrains.dokka' -} - -android { - compileSdk 32 - - defaultConfig { - minSdk 21 - targetSdk 32 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - coreLibraryDesugaringEnabled true - } - - kotlinOptions { - jvmTarget = '1.8' - } - - testOptions { - unitTests.all { - useJUnitPlatform() - } - } - - buildFeatures { - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion compose_version - } -} - -dependencies { - // Testing - testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' - testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.1' - - // Compose - implementation 'androidx.activity:activity-compose:1.5.1' - - // Kotlin reflection - implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - - // For using modern java 8 classes with older versions of android - coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.2.2" -} - - - -afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - - groupId = 'com.github.dsc-jkuat' - artifactId = 'form-builder' - version = '1.0.1' - } - } - } -} \ No newline at end of file From 84d02e69a36ff3c5812e32d94007e062dad3d070 Mon Sep 17 00:00:00 2001 From: joykangangi <64706463+joykangangi@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:32:04 +0300 Subject: [PATCH 28/30] restore authors in docs --- form-builder/src/main/java/com/dsc/form_builder/BaseState.kt | 2 ++ .../src/main/java/com/dsc/form_builder/ChoiceState.kt | 1 + form-builder/src/main/java/com/dsc/form_builder/FormState.kt | 2 ++ .../src/main/java/com/dsc/form_builder/SelectState.kt | 1 + .../src/main/java/com/dsc/form_builder/TextFieldState.kt | 4 +++- form-builder/src/main/java/com/dsc/form_builder/Transform.kt | 3 +++ form-builder/src/main/java/com/dsc/form_builder/Validators.kt | 3 +++ 7 files changed, 15 insertions(+), 1 deletion(-) diff --git a/form-builder/src/main/java/com/dsc/form_builder/BaseState.kt b/form-builder/src/main/java/com/dsc/form_builder/BaseState.kt index b28d575..c7888b3 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/BaseState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/BaseState.kt @@ -13,6 +13,8 @@ import androidx.compose.runtime.setValue * @param transform this function is used to change the data type in the field state. You can use it to convert the data in the field to your preferred type e.g [String] to [Int] * @param validators this is the list of [Validators] that are used to validate the field state. By default most states will have an empty list. You can override this and provide your own list of validators. * + * @author [Linus Muema](https://github.com/linusmuema) + * @created 05/04/2022 - 10:00 AM */ abstract class BaseState( val initial: T, diff --git a/form-builder/src/main/java/com/dsc/form_builder/ChoiceState.kt b/form-builder/src/main/java/com/dsc/form_builder/ChoiceState.kt index 2ec1f2b..165eac1 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/ChoiceState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/ChoiceState.kt @@ -15,6 +15,7 @@ package com.dsc.form_builder * * @param validators a list of [Validators] applied to the state's value. * + * @author [Samwel Otieno](https://github.com/otienosamwel) */ class ChoiceState( name: String, diff --git a/form-builder/src/main/java/com/dsc/form_builder/FormState.kt b/form-builder/src/main/java/com/dsc/form_builder/FormState.kt index edb8f98..f2cfe5b 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/FormState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/FormState.kt @@ -7,6 +7,8 @@ import kotlin.reflect.KParameter * This class represents the state of the whole form, i.e, the whole collection of fields. It is used to manage all of the states in terms of accessing data and validations. * @param fields this is a list of all fields in the form. We pass them as a parameter to the constructor for ease of management and access. * + * @author [Linus Muema](https://github.com/linusmuema) + * @created 05/04/2022 - 10:00 AM */ open class FormState>(val fields: List) { diff --git a/form-builder/src/main/java/com/dsc/form_builder/SelectState.kt b/form-builder/src/main/java/com/dsc/form_builder/SelectState.kt index 7ee18d7..9838d61 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/SelectState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/SelectState.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.* * * @param validators a list of [Validators] applied to the state's value. * + * @author [Samwel Otieno](https://github.com/otienosamwel) */ class SelectState( name: String, diff --git a/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt b/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt index 10f1cd7..804b7a1 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/TextFieldState.kt @@ -19,10 +19,12 @@ import java.time.format.ResolverStyle * * @param name The name of the field used to access the state when required in the form * @param initial The initial value/state of the field. By default it is an empty string. - * @param formatter The formatting option for the field. * @param transform The function used to change the [String] data type on the text field to a suitable type e.g [String] to [Int]. * @param validators This is the list of [Validators] that are used to validate the field state. By default the field states will have an empty list. You can override this and provide your own list of validators. + * @param formatter The formatting option for the field. * + * @author [Joy Kangangi](https://github.com/joykangangi) + * @created 06/04/2022 */ open class TextFieldState( name: String, diff --git a/form-builder/src/main/java/com/dsc/form_builder/Transform.kt b/form-builder/src/main/java/com/dsc/form_builder/Transform.kt index 38e3e23..846ae11 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/Transform.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/Transform.kt @@ -2,6 +2,9 @@ package com.dsc.form_builder /** * This interface is used to allow change of data types to a suitable type when necessary. + * + * @author [Joy Kangangi](https://github.com/joykangangi) + * @created 06/04/2022 */ fun interface Transform { diff --git a/form-builder/src/main/java/com/dsc/form_builder/Validators.kt b/form-builder/src/main/java/com/dsc/form_builder/Validators.kt index 1a83d36..75aa396 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/Validators.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/Validators.kt @@ -13,6 +13,9 @@ private const val DATE_MESSAGE = "Invalid date" * * These are the types of validators available in the form builder library. * They all have the _message_ parameter to allow the developer to set their own custom error message. + * + * @author [Linus Muema](https://github.com/linusmuema) + * @created 05/04/2022 - 10:00 AM */ sealed interface Validators { From fdcbcf6d7945966b11417cb9e4305bbebdf234cf Mon Sep 17 00:00:00 2001 From: joykangangi <64706463+joykangangi@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:47:38 +0300 Subject: [PATCH 29/30] add formatters docs --- .../com/dsc/form_builder/format/CardFormatter.kt | 2 ++ .../com/dsc/form_builder/format/DateFormat.kt | 16 +++++++++++++++- .../com/dsc/form_builder/format/Formatter.kt | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt b/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt index 839672a..5f564a5 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/format/CardFormatter.kt @@ -7,6 +7,8 @@ package com.dsc.form_builder.format * The separator is an empty space as this is the most common option. * * Note: character limiting is not supported in the formatter. + * + * @author [Linus Muema](https://github.com/linusmuema) */ object CardFormatter: Formatter { override fun format(value: String): String { diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt b/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt index 608bea8..ffd22f3 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/format/DateFormat.kt @@ -2,6 +2,8 @@ package com.dsc.form_builder.format /** * These are the formatting options for the [DateFormatter] class. + * + * @author [Joy Kangangi](https://github.com/joykangangi) */ enum class DateFormat(val pattern: String) { DDMMYYYY("ddMMuuuu"), @@ -12,7 +14,17 @@ enum class DateFormat(val pattern: String) { YYMMDD("uuMMdd") } -// Get the index where to place the separator +/** + * Determines the indices where separators should be placed in a [DateFormat]. + * + * This function iterates over the characters of the [DateFormat] string representation + * and identifies the positions where the format changes, such as from one character type to another. + * These positions are used to insert separators between different segments of the date format. + * + * @return A mutable list of integers representing the positions where separators should be placed. + * + * @author [Linus Muema](https://github.com/linusmuema) + */ private fun DateFormat.separatorIndices(): MutableList { val indices = mutableListOf() val stringFormat = this.toString() @@ -36,6 +48,8 @@ private fun DateFormat.separatorIndices(): MutableList { * The formatting function places the separator in the respective index as the user types. * * Note: character limiting is not supported in the formatter. + * + * @author [Linus Muema](https://github.com/linusmuema) */ class DateFormatter(private val dateFormat: DateFormat, private val separator: String) : Formatter { diff --git a/form-builder/src/main/java/com/dsc/form_builder/format/Formatter.kt b/form-builder/src/main/java/com/dsc/form_builder/format/Formatter.kt index 3054854..2784832 100644 --- a/form-builder/src/main/java/com/dsc/form_builder/format/Formatter.kt +++ b/form-builder/src/main/java/com/dsc/form_builder/format/Formatter.kt @@ -10,11 +10,26 @@ import androidx.compose.ui.text.input.VisualTransformation * * These are the formatting interface for the [TextFieldState]. * You can get the visual transformation to apply in your text input. + * + * @author [Linus Muema](https://github.com/linusmuema) */ interface Formatter { fun format(value: String): String } + +/** + * Converts a [Formatter] to a [VisualTransformation] that can be applied to text input fields. + * This transformation modifies how the text is visually presented based on the provided [Formatter]. + * + * The function applies the [Formatter.format] method to the input text and returns a [TransformedText], + * ensuring that the visual representation of the text differs from the raw input, while keeping the cursor + * offset mapping consistent with the original text. + * + * @return A [VisualTransformation] that formats the input text based on the [Formatter]. + * + * @author [Linus Muema](https://github.com/linusmuema) + */ internal fun Formatter.toVisualTransformation(): VisualTransformation { return VisualTransformation { val output = format(it.text) From b47e463706289d5041a4732d18394b868c67364d Mon Sep 17 00:00:00 2001 From: Joy Kangangi <64706463+joykangangi@users.noreply.github.com> Date: Thu, 26 Sep 2024 01:04:07 +0300 Subject: [PATCH 30/30] Add Contribution Guideline --- README.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5ccf33e..0f8555b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![](https://jitpack.io/v/jkuatdsc/form-builder.svg)](https://jitpack.io/#jkuatdsc/form-builder) + ## Jetpack Compose FormBuilder A customisable android library used to provide an abstraction layer over form elements as well as provide a DRY code @@ -8,6 +9,13 @@ implementation of a form. The library is used to help in the state management of a `Form` in Jetpack compose. Currently, we don't have an official support for a form state so the library is used to provide a custom implementation of the same. + * [Installation](#installation) + * [Basic Usage](#basic-usage) + * [Demos](#demos) + * [Further Reading](#further-reading) + * [Contribution Guideline](#contribution-guideline) + + ### Installation In the root `build.gradle` file add the following: @@ -26,6 +34,7 @@ dependencies { } ``` + ### Basic Usage The library provides a [`FormState`](/form-builder/src/main/java/com/dsc/form_builder/FormState.kt) class that @@ -112,7 +121,8 @@ TextFieldState( ) ``` -### Demos + +### Demos **Demo 1** : In this demo, the validations are executed before the next screen is presented. @@ -124,12 +134,72 @@ https://user-images.githubusercontent.com/47350130/204372977-e8b66f71-0b61-4d93- You can find the sample apk [here](https://drive.google.com/file/d/1tMtDtJwuDZoQnxluiAPNC0dYs7aqXUjt/view?usp=sharing) -For more details about the library, check out the [references](https://jkuatdsc.github.io/form-builder/). - + ### Further Reading The links below provide a reinforced understanding to the library. +* [Form Builder Documentation](https://jkuatdsc.github.io/form-builder/). * [Introduction to Form Builder basics](https://www.section.io/engineering-education/jetpack-compose-forms/) * [Advanced Form Builder operations guide](https://www.section.io/engineering-education/making-jetpack-form-builder/) + +### Contribution Guideline + +We appreciate your interest in contributing to our project! To ensure a smooth and collaborative process, please follow these guidelines. + +#### 1. Fork the Repository +- Begin by **forking** the repository to your GitHub account. +- **Clone** the forked repository to your local machine. + +#### 2. Create a Feature Branch +- Create a new branch for your feature or bug fix. Branch names should follow this format: + ```bash + feature/ + fix/ + ``` +#### 3. Make Changes and commit +- Make your changes on the feature branch. +- Keep your commits small, focused, and meaningful. + +#### 4. Rebase with Develop Branch +- Before opening a pull request, rebase your feature branch with the latest `develop` branch + ```bash + git checkout develop + git pull origin develop + git checkout + git rebase develop + ``` +#### 5. Create a Pull Request (PR) +- Once your feature branch is up to date with `develop`, create a pull request (PR) to merge into the `develop` branch. +- Ensure that the PR description includes: + - A summary of the changes. + - Any relevant issue references (e.g., "Closes #issue-number") + - Details about any potential impact on existing functionality. + +#### 6. Code Review +- Wait for a code review from maintainers or team members. Address any feedback by making further changes and pushing them to the same feature branch. + +#### 7. Merge into Develop +- After approval and successful tests, your PR will be merged into the `develop` branch. + +#### 8. Merging to Main +- Once the changes in `develop` are stable, they will be merged into the `main` branch as part of a release cycle. + +#### 9. Updating Documentation +- If your changes require updates to the documentation, ensure you update the relevant files or create new ones before submitting your PR. + +#### 10. Best Practices +- Write tests for any new features or bug fixes. +- Follow the existing coding standards and style guides used in the project. +- Ensure that your changes pass any automated tests and that the project builds successfully. + +#### 11. Contribution Etiquette +- Do not maliciously delete any library functionality, change the name or package of the library, or amend any library information unless there is a well-justified reason for doing so. +- Be respectful and collaborative when communicating with other contributors or maintainers. +- When submitting a PR, please ensure that your comments and descriptions are clear and concise. +- Avoid large, unrelated changes in a single commit. Keep your work focused on the task at hand + +Your collaboration is key to the success of this project, and every contribution matters. +Thank you for being part of our journey!✨ + MIT [Licence](LICENSE)