From d5b6cfb7ddeac8160962e6aeb1d2dc673769bdc3 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Thu, 14 May 2020 17:16:41 +0300 Subject: [PATCH 01/15] Optimize building views Signed-off-by: Elly Kitoto --- .../neatformcore/domain/view/NFormView.kt | 1 - .../neatformcore/form/json/JsonFormBuilder.kt | 70 +++++++++---------- .../neatformcore/rules/RulesFactory.kt | 1 - .../views/widgets/CheckBoxNFormView.kt | 1 - 4 files changed, 32 insertions(+), 41 deletions(-) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt index 2a88ab9b..a3e08f18 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt @@ -5,7 +5,6 @@ import com.nerdstone.neatformcore.domain.listeners.DataActionListener import com.nerdstone.neatformcore.domain.listeners.VisibilityChangeListener import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty -import com.nerdstone.neatformcore.views.handlers.ViewDispatcher interface NFormView { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index 35a7af4d..3c6c959c 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -95,7 +95,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { } override fun buildForm( - jsonFormStepBuilderModel: JsonFormStepBuilderModel?, viewList: List? + jsonFormStepBuilderModel: JsonFormStepBuilderModel?, viewList: List? ): FormBuilder { registerViews() launch(defaultContextProvider.main()) { @@ -107,27 +107,21 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { } } } - launch { - val rulesAsync = withContext(defaultContextProvider.io()) { - singleRunner.afterPrevious { - registerFormRulesFromFile(context, RulesFileType.YAML) - } + + val rulesAsync = withContext(defaultContextProvider.io()) { + singleRunner.afterPrevious { + registerFormRulesFromFile(context, RulesFileType.YAML) } - if (rulesAsync) { - launch { - withContext(defaultContextProvider.main()) { - if (viewList == null) - createFormViews( - context, - arrayListOf(), - jsonFormStepBuilderModel - ) - else - createFormViews(context, viewList, jsonFormStepBuilderModel) - } - } + } + if (rulesAsync) { + withContext(defaultContextProvider.main()) { + if (viewList == null) + createFormViews(context, arrayListOf(), jsonFormStepBuilderModel) + else + createFormViews(context, viewList, jsonFormStepBuilderModel) } } + } catch (throwable: Throwable) { Timber.e(throwable) } @@ -139,7 +133,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { return when { jsonString != null -> JsonFormParser.parseJson(jsonString) fileSource != null -> JsonFormParser.parseJson( - AssetFile.readAssetFileAsString(context, fileSource!!) + AssetFile.readAssetFileAsString(context, fileSource!!) ) else -> null } @@ -149,7 +143,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { * @param context android context */ override fun createFormViews( - context: Context, views: List?, jsonFormStepBuilderModel: JsonFormStepBuilderModel? + context: Context, views: List?, jsonFormStepBuilderModel: JsonFormStepBuilderModel? ) { if (form != null) { when { @@ -166,20 +160,20 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { rootView.formBuilder = this addViewsToVerticalRootView(views, index, formContent, rootView) val stepFragment = StepFragment.newInstance( - index, - StepModel.Builder() - .title(form!!.formName) - .subTitle(formContent.stepName as CharSequence) - .build(), - rootView + index, + StepModel.Builder() + .title(form!!.formName) + .subTitle(formContent.stepName as CharSequence) + .build(), + rootView ) fragmentsList.add(stepFragment) } neatStepperLayout.setUpViewWithAdapter( - StepperPagerAdapter( - (context as FragmentActivity).supportFragmentManager, - fragmentsList - ) + StepperPagerAdapter( + (context as FragmentActivity).supportFragmentManager, + fragmentsList + ) ) neatStepperLayout.showLoadingIndicators(false) } @@ -193,15 +187,15 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { mainLayout?.addView(formViews) } else -> Toast.makeText( - context, R.string.form_builder_error, Toast.LENGTH_LONG + context, R.string.form_builder_error, Toast.LENGTH_LONG ).show() } } } private fun addViewsToVerticalRootView( - customViews: List?, stepIndex: Int, - formContent: NFormContent, verticalRootView: VerticalRootView + customViews: List?, stepIndex: Int, + formContent: NFormContent, verticalRootView: VerticalRootView ) { val view = customViews?.getOrNull(stepIndex) @@ -235,8 +229,8 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { } override fun registerFormRulesFromFile( - context: Context, - rulesFileType: RulesFileType + context: Context, + rulesFileType: RulesFileType ): Boolean { form?.rulesFile?.also { rulesFactory.readRulesFromFile(context, it, rulesFileType) @@ -279,7 +273,7 @@ class StepFragment : Step { companion object { fun newInstance( - index: Int, stepModel: StepModel, verticalRootView: VerticalRootView + index: Int, stepModel: StepModel, verticalRootView: VerticalRootView ): StepFragment { val args = Bundle().apply { @@ -302,7 +296,7 @@ class StepFragment : Step { override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { if (formView != null && formView?.parent != null) { return formView?.parent as View diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/RulesFactory.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/RulesFactory.kt index fdfff084..b0dbb43c 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/RulesFactory.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/RulesFactory.kt @@ -22,7 +22,6 @@ import timber.log.Timber import java.io.BufferedReader import java.io.InputStreamReader import java.util.* -import kotlin.collections.HashSet class RulesFactory private constructor() : RuleListener { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt index 83ebfac0..5101f9d3 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt @@ -2,7 +2,6 @@ package com.nerdstone.neatformcore.views.widgets import android.content.Context import android.util.AttributeSet -import android.widget.CheckBox import androidx.appcompat.widget.AppCompatCheckBox import com.nerdstone.neatformcore.domain.listeners.DataActionListener import com.nerdstone.neatformcore.domain.listeners.VisibilityChangeListener From 26ba9b37da4eac85588382f411217fc01d2833b7 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Thu, 14 May 2020 22:14:35 +0300 Subject: [PATCH 02/15] Wrap DataViewModel details in a MutableLiveData We are wrapping the details in a MutableLiveData so we can begin observing for changes and updating field values accordingly Signed-off-by: Elly Kitoto --- .../domain/model/NFormViewData.kt | 2 +- .../neatformcore/domain/view/NFormView.kt | 25 +++++++++- .../neatformcore/domain/view/RulesHandler.kt | 4 -- .../neatformcore/form/json/JsonFormBuilder.kt | 12 ++--- .../neatformcore/rules/NFormRulesHandler.kt | 13 +---- .../neatformcore/rules/NeatFormValidator.kt | 49 +++++++++---------- .../neatformcore/viewmodel/DataViewModel.kt | 7 ++- .../views/containers/MultiChoiceCheckBox.kt | 4 ++ .../views/containers/RadioGroupView.kt | 4 ++ .../views/handlers/ViewDispatcher.kt | 49 ++++++++++++------- .../views/widgets/CheckBoxNFormView.kt | 4 ++ .../views/widgets/DateTimePickerNFormView.kt | 4 ++ .../views/widgets/EditTextNFormView.kt | 4 ++ .../views/widgets/NotificationNFormView.kt | 4 ++ .../views/widgets/NumberSelectorNFormView.kt | 4 ++ .../views/widgets/SpinnerNFormView.kt | 4 ++ .../widgets/TextInputEditTextNFormView.kt | 4 ++ .../neatform/custom/views/CustomImageView.kt | 3 ++ 18 files changed, 131 insertions(+), 69 deletions(-) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/NFormViewData.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/NFormViewData.kt index b17a2dfe..3c795d87 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/NFormViewData.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/NFormViewData.kt @@ -10,6 +10,6 @@ data class NFormViewData( @Expose var value: Any? = null, @Expose - @SerializedName("meta_data") + @SerializedName("meta_data", alternate = ["metadata"]) var metadata: Map? = null ) : Serializable \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt index a3e08f18..f784e4f8 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt @@ -6,6 +6,13 @@ import com.nerdstone.neatformcore.domain.listeners.VisibilityChangeListener import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty +/** + * @author Elly Nerdstone + * + * very view must implement this interface. This contract basically defines all the interactions + * between fields. The form builder needs to be aware of how the fields are going to handle + * validation, required values and how they shall pass their values. + */ interface NFormView { var dataActionListener: DataActionListener? @@ -20,9 +27,25 @@ interface NFormView { var formValidator: FormValidator + /** + * This function is called to reset the value of the field when it is hidden + */ fun resetValueWhenHidden() - fun validateValue() : Boolean + /** + * This function is to implement field validation and it returns a [Boolean] value. + */ + fun validateValue(): Boolean + /** + * This function is used by fields to handle their required status + */ fun trackRequiredField() + + /** + * This method sets [value] of the field. And it also accepts an optional parameter called + * [disabled] that is used to indicate whether you want the field to be disabled or not. Use the + * option if you want to make the field readonly. + */ + fun setValue(value: Any, disabled: Boolean = false) } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/RulesHandler.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/RulesHandler.kt index b1c0b6da..41ca4e07 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/RulesHandler.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/RulesHandler.kt @@ -12,10 +12,6 @@ interface RulesHandler { var formBuilder: FormBuilder - fun handleReadOnlyState() - - fun handleFilter(filterItems: List) - fun updateSkipLogicFactAfterEvaluate(evaluationResult: Boolean, rule: Rule?, facts: Facts?) fun handleSkipLogic(facts: Facts?) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index 3c6c959c..14e5e1d4 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -219,7 +219,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { it[JsonFormConstants.FORM_NAME] = form?.formName it[JsonFormConstants.FORM_METADATA] = form?.formMetadata it[JsonFormConstants.FORM_VERSION] = form?.formVersion - it[JsonFormConstants.FORM_DATA] = viewModel.details + it[JsonFormConstants.FORM_DATA] = viewModel.details.value } return Utils.getJsonFromModel(formDetails) @@ -240,7 +240,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { override fun getFormData(): HashMap { if (formValidator.invalidFields.isEmpty() && formValidator.requiredFields.isEmpty()) { - return viewModel.details + return viewModel.details.value!! } FormErrorDialog(context).show() return hashMapOf() @@ -314,12 +314,8 @@ class StepFragment : Step { return StepVerificationState(true, null) } - override fun onSelected() { - //Overridden not useful at the moment - } + override fun onSelected() = Unit - override fun onError(stepVerificationState: StepVerificationState) { - //Overridden not useful at the moment - } + override fun onError(stepVerificationState: StepVerificationState) = Unit } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt index efb7d4bd..e943fde6 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt @@ -32,14 +32,6 @@ class NFormRulesHandler private constructor() : RulesHandler { } } - override fun handleReadOnlyState() { - TODO("implement toggle enable/disable state on views") - } - - override fun handleFilter(filterItems: List) { - TODO("implement functionality for filtering") - } - override fun updateSkipLogicFactAfterEvaluate( evaluationResult: Boolean, rule: Rule?, facts: Facts? ) { @@ -77,10 +69,7 @@ class NFormRulesHandler private constructor() : RulesHandler { filterCurrentRules(Constants.RuleActions.CALCULATION) .forEach { key -> val value = facts?.asMap()?.get(key) - formBuilder.viewModel.details[key] = - NFormViewData( - type = "Calculation", value = value, metadata = null - ) + formBuilder.viewModel.saveValue(key, NFormViewData("Calculation", value, null)) updateCalculationListeners(Pair(key, value)) } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NeatFormValidator.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NeatFormValidator.kt index 2331b98d..a3bb863f 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NeatFormValidator.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NeatFormValidator.kt @@ -41,29 +41,32 @@ class NeatFormValidator private constructor() : FormValidator { * @return [Pair] of true if validation succeeds and a string of the error message */ override fun validateField(nFormView: NFormView): Pair { - if (nFormView.viewDetails.view.visibility == View.VISIBLE) { - if ((nFormView.viewDetails.value == null || nFormView.viewDetails.value is HashMap<*, *> - && (nFormView.viewDetails.value as HashMap<*, *>).isEmpty()) - && Utils.isFieldRequired(nFormView) + + val viewDetails = nFormView.viewDetails + + if (viewDetails.view.visibility == View.VISIBLE) { + if ((viewDetails.value == null || viewDetails.value is HashMap<*, *> + && (viewDetails.value as HashMap<*, *>).isEmpty()) + && Utils.isFieldRequired(nFormView) ) { - invalidFields.add(nFormView.viewDetails.name) + invalidFields.add(viewDetails.name) val errorMessage = - nFormView.viewProperties.requiredStatus?.let { - Utils.extractKeyValue(it).second - } + nFormView.viewProperties.requiredStatus?.let { + Utils.extractKeyValue(it).second + } return Pair(false, errorMessage) } if (nFormView.viewProperties.validations != null) { nFormView.viewProperties.validations?.forEach { validation -> - if (!performValidation(validation, nFormView.viewDetails.value)) { - invalidFields.add(nFormView.viewDetails.name) + if (!performValidation(validation, viewDetails.value)) { + invalidFields.add(viewDetails.name) return Pair(false, validation.message) } } } } - invalidFields.remove(nFormView.viewDetails.name) - requiredFields.remove(nFormView.viewDetails.name) + invalidFields.remove(viewDetails.name) + requiredFields.remove(viewDetails.name) return Pair(true, null) } @@ -76,16 +79,14 @@ class NeatFormValidator private constructor() : FormValidator { val validationPair = validateField(nFormView) val anchorView = nFormView.viewDetails.view as ViewGroup val labelTextView = - (anchorView.getChildAt(0) as LinearLayout).findViewById(R.id.labelTextView) + (anchorView.getChildAt(0) as LinearLayout).findViewById(R.id.labelTextView) if (!validationPair.first) { - labelTextView.apply { - error = validationPair.second - } + labelTextView.apply { error = validationPair.second } showErrorMessage(anchorView, validationPair.second) } else { labelTextView.error = null (anchorView.getChildAt(0) as LinearLayout).findViewById(R.id.errorMessageTextView) - .visibility = View.GONE + .visibility = View.GONE } return validationPair.first } @@ -108,10 +109,10 @@ class NeatFormValidator private constructor() : FormValidator { facts.put(VALUE, value) val customRule: Rule = MVELRule() - .name(UUID.randomUUID().toString()) - .description(validation.condition) - .`when`(validation.condition) - .then("$VALIDATION_RESULT = true") + .name(UUID.randomUUID().toString()) + .description(validation.condition) + .`when`(validation.condition) + .then("$VALIDATION_RESULT = true") val rules = Rules(customRule) rulesEngine.fire(rules, facts) @@ -120,10 +121,8 @@ class NeatFormValidator private constructor() : FormValidator { } private fun getFormData(): MutableMap { - val viewModel = ViewModelProvider(formBuilder.context as FragmentActivity)[DataViewModel::class.java] - return viewModel.details.mapValuesTo(mutableMapOf(), { entry -> - entry.value - }) + return ViewModelProvider(formBuilder.context as FragmentActivity)[DataViewModel::class.java] + .details.value?.mapValuesTo(mutableMapOf(), { entry -> entry.value })!! } companion object { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt index 9cd108f6..d2da4fde 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt @@ -1,5 +1,6 @@ package com.nerdstone.neatformcore.viewmodel +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.nerdstone.neatformcore.domain.model.NFormViewData @@ -10,5 +11,9 @@ import com.nerdstone.neatformcore.domain.model.NFormViewData */ class DataViewModel : ViewModel() { - var details: HashMap = HashMap() + var details: MutableLiveData> = MutableLiveData(HashMap()) + + fun saveValue(fieldName: String, fieldValue:NFormViewData){ + details.value?.set(fieldName, fieldValue) + } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt index 283965ed..64940ffb 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt @@ -45,6 +45,10 @@ class MultiChoiceCheckBox : LinearLayout, NFormView { viewBuilder.resetCheckBoxValues() } + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } + override fun validateValue(): Boolean = formValidator.validateLabeledField(this) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt index bdee149f..b8aea957 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt @@ -41,6 +41,10 @@ class RadioGroupView : LinearLayout, NFormView { override fun validateValue(): Boolean = formValidator.validateLabeledField(this) + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } + override fun setVisibility(visibility: Int) { super.setVisibility( visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt index 74422690..1436bb8a 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt @@ -12,6 +12,16 @@ import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.viewmodel.DataViewModel +/** + * @author Elly Nerdstone + * + * This Singleton class provides a listener method that is called when a field's value + * has been changed. Say for instance when the text of an EditText has been updated the listener + * will be triggered. At this point the fields value can be validated and then the rules are fired + * for the fields watching on the field that has passed its value. + * + * The rules are handled by the [RulesFactory] class and the validation + */ class ViewDispatcher private constructor() : DataActionListener { val rulesFactory: RulesFactory = RulesFactory.INSTANCE @@ -24,28 +34,33 @@ class ViewDispatcher private constructor() : DataActionListener { override fun onPassData(viewDetails: NFormViewDetails) { val context = viewDetails.view.context var activityContext = context - if (context is ContextThemeWrapper) { - activityContext = context.baseContext - } - val viewModel = ViewModelProvider(activityContext as FragmentActivity)[DataViewModel::class.java] + if (context is ContextThemeWrapper) activityContext = context.baseContext + val viewModel = + ViewModelProvider(activityContext as FragmentActivity)[DataViewModel::class.java] - if (viewModel.details[viewDetails.name] != viewDetails.value) { - viewModel.details[viewDetails.name] = - NFormViewData(viewDetails.view.javaClass.simpleName, viewDetails.value, viewDetails.metadata) + viewModel.details.value?.also { - val nFormView = viewDetails.view as NFormView - nFormView.validateValue() + if (it[viewDetails.name] != viewDetails.value) { + it[viewDetails.name] = + NFormViewData( + viewDetails.view.javaClass.simpleName, + viewDetails.value, viewDetails.metadata + ) - //Fire rules for calculations and other fields watching on this current field - val calculations = (viewDetails.view as NFormView).viewProperties.calculations - if (rulesFactory.subjectsRegistry.containsKey(viewDetails.name.trim()) || calculations != null) { - rulesFactory.updateFactsAndExecuteRules(viewDetails) - } + val nFormView = viewDetails.view as NFormView + nFormView.validateValue() - if (viewDetails.value == null && Utils.isFieldRequired(nFormView) + //Fire rules for calculations and other fields watching on this current field + val calculations = (viewDetails.view as NFormView).viewProperties.calculations + if (rulesFactory.subjectsRegistry.containsKey(viewDetails.name.trim()) || calculations != null) { + rulesFactory.updateFactsAndExecuteRules(viewDetails) + } + + if (viewDetails.value == null && Utils.isFieldRequired(nFormView) && viewDetails.view.visibility == View.VISIBLE - ) { - nFormView.formValidator.requiredFields.add(viewDetails.name) + ) { + nFormView.formValidator.requiredFields.add(viewDetails.name) + } } } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt index 5101f9d3..c41ece02 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt @@ -48,6 +48,10 @@ class CheckBoxNFormView : AppCompatCheckBox, NFormView { override fun trackRequiredField() = ViewUtils.handleRequiredStatus(this) + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } + override fun validateValue(): Boolean { val validationPair = formValidator.validateField(this) if (!validationPair.first) { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt index 3dfde050..d0d9b3bf 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt @@ -40,6 +40,10 @@ class DateTimePickerNFormView : TextInputLayout, NFormView { return validationPair.first } + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } + override fun setVisibility(visibility: Int) { super.setVisibility( visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt index 003d3123..0e70f483 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt @@ -58,6 +58,10 @@ class EditTextNFormView : AppCompatEditText, NFormView { return validationPair.first } + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } + override fun setVisibility(visibility: Int) { super.setVisibility( visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt index 7a52facb..a2eafd6d 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt @@ -46,4 +46,8 @@ class NotificationNFormView : FrameLayout, NFormView, CalculationChangeListener Timber.i("Updated calculation ${calculationField.first} -> ${calculationField.second}") viewBuilder.updateNotificationText(calculationField) } + + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt index db08d672..cd5fd138 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt @@ -36,6 +36,10 @@ class NumberSelectorNFormView : LinearLayout, NFormView { override fun trackRequiredField() = ViewUtils.handleRequiredStatus(this) + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } + override fun validateValue() = formValidator.validateLabeledField(this) override fun setVisibility(visibility: Int) { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt index c6a1c082..cfdbea3c 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt @@ -36,6 +36,10 @@ class SpinnerNFormView : LinearLayout, NFormView { override fun resetValueWhenHidden() = viewBuilder.resetSpinnerValue() + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } + override fun validateValue() = formValidator.validateField(this).first override fun setVisibility(visibility: Int) { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt index f5d816d4..9b3f28cc 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt @@ -34,6 +34,10 @@ class TextInputEditTextNFormView : TextInputLayout, NFormView { editText?.setText("") } + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } + override fun validateValue(): Boolean { val validationPair = formValidator.validateField(this) if (!validationPair.first) { diff --git a/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt b/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt index 2447436b..c1586fc2 100644 --- a/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt +++ b/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt @@ -29,4 +29,7 @@ class CustomImageView(context: Context) : CircleImageView(context), NFormView { override fun trackRequiredField() = ViewUtils.handleRequiredStatus(this) + override fun setValue(value: Any, disabled: Boolean) { + TODO("Not yet implemented") + } } \ No newline at end of file From ee50b65499d3f34a007b9797f71716cd912d6e46 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Fri, 15 May 2020 00:20:25 +0300 Subject: [PATCH 03/15] Rename test classes Signed-off-by: Elly Kitoto --- .../neatformcore/CoroutineTestRules.kt | 37 +++++++++---------- .../junit/parser/JsonFormParserTest.kt | 2 +- .../neatformcore/junit/utils/UtilsTest.kt | 2 +- .../neatformcore/junit/utils/ViewUtilsTest.kt | 2 +- .../builders/BaseJsonViewBuilderTest.kt | 3 +- .../builders/CheckBoxViewBuilderTest.kt | 2 +- .../builders/DateTimePickerViewBuilderTest.kt | 2 +- .../builders/EditTextViewBuilderTest.kt | 2 +- .../builders/JsonFormBuilderTest.kt | 17 ++++----- .../MultiChoiceCheckBoxViewBuilderTest.kt | 2 +- .../builders/NotificationViewBuilderTest.kt | 2 +- .../builders/NumberSelectorViewBuilderTest.kt | 2 +- .../builders/RadioGroupViewBuilderTest.kt | 2 +- .../builders/SpinnerViewBuilderTest.kt | 2 +- .../builders/TextInputEditTextBuilderTest.kt | 2 +- .../robolectric/datasource/AssetFileTest.kt | 2 +- .../handlers/ViewDispatcherTest.kt | 2 +- .../rules/NeatFormValidatorTest.kt | 28 +++++++------- .../robolectric/rules/RulesFactoryTest.kt | 2 +- .../robolectric/utils/UtilsTest.kt | 2 +- .../robolectric/utils/ViewUtilsTest.kt | 2 +- 21 files changed, 57 insertions(+), 62 deletions(-) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/CoroutineTestRules.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/CoroutineTestRules.kt index 9250f68c..86d26ea0 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/CoroutineTestRules.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/CoroutineTestRules.kt @@ -1,36 +1,35 @@ package com.nerdstone.neatformcore import com.nerdstone.neatformcore.utils.DispatcherProvider -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain -import org.junit.rules.TestWatcher +import org.junit.rules.TestRule import org.junit.runner.Description +import org.junit.runners.model.Statement @ExperimentalCoroutinesApi class CoroutineTestRule(val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : - TestWatcher() { + TestRule, TestCoroutineScope by TestCoroutineScope(testDispatcher) { val testDispatcherProvider = object : DispatcherProvider { - override fun default(): CoroutineDispatcher = testDispatcher - override fun io(): CoroutineDispatcher = testDispatcher - override fun main(): CoroutineDispatcher = testDispatcher - override fun unconfined(): CoroutineDispatcher = testDispatcher + override fun default() = testDispatcher + override fun io() = testDispatcher + override fun main() = testDispatcher + override fun unconfined() = testDispatcher } - override fun starting(description: Description?) { - super.starting(description) - Dispatchers.setMain(testDispatcher) - } - - override fun finished(description: Description?) { - super.finished(description) - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - testDispatcher.cancelChildren() - } + override fun apply(base: Statement?, description: Description?) = + object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + Dispatchers.setMain(testDispatcher) + base?.evaluate() + cleanupTestCoroutines() + Dispatchers.resetMain() + } + } } \ No newline at end of file diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonFormParserTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonFormParserTest.kt index 9995790b..fadb14a3 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonFormParserTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonFormParserTest.kt @@ -5,7 +5,7 @@ import org.junit.Assert.* import org.junit.Before import org.junit.Test -class `Test passing JSON files` { +class JsonFormParserTest { private var json: String? = null diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/UtilsTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/UtilsTest.kt index 4f415b05..59df797d 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/UtilsTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/UtilsTest.kt @@ -8,7 +8,7 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -class `Test Utils` { +class UtilsTest { private enum class TestEnum { THREE, TWO, ONE, ZERO diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/ViewUtilsTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/ViewUtilsTest.kt index 0623211d..82536e29 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/ViewUtilsTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/ViewUtilsTest.kt @@ -8,7 +8,7 @@ import org.junit.Assert import org.junit.Before import org.junit.Test -class `Test View Utils` { +class ViewUtilsTest { @Before fun `Before doing anything else`() { diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt index d170f82f..8622ab13 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt @@ -9,11 +9,10 @@ import com.nerdstone.neatformcore.rules.NeatFormValidator import org.robolectric.Robolectric import org.robolectric.android.controller.ActivityController - open class BaseJsonViewBuilderTest { protected val activity: ActivityController = Robolectric.buildActivity(AppCompatActivity::class.java).setup() - private val mainLayout: ViewGroup = LinearLayout(activity.get()) + val mainLayout: ViewGroup = LinearLayout(activity.get()) var formBuilder = JsonFormBuilder(TestConstants.SAMPLE_JSON, activity.get(), mainLayout) val formValidator = NeatFormValidator.INSTANCE diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt index a98ec4bc..a2e80667 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt @@ -18,7 +18,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test building CheckBox view` : BaseJsonViewBuilderTest(){ +class CheckBoxViewBuilderTest : BaseJsonViewBuilderTest(){ private val viewProperty = spyk(NFormViewProperty()) private val checkBoxNFormView = CheckBoxNFormView(activity.get()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt index f4b14e51..4218583e 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt @@ -17,7 +17,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test building DateTimePicker view` : BaseJsonViewBuilderTest(){ +class DateTimePickerViewBuilderTest : BaseJsonViewBuilderTest(){ private val viewProperty = spyk(NFormViewProperty()) private val dateTimePickerNFormView = DateTimePickerNFormView(activity.get()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt index b8c9158f..7786577f 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt @@ -20,7 +20,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test building EditText view` : BaseJsonViewBuilderTest() { +class EditTextViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) private val editTextNFormView = EditTextNFormView(activity.get()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt index f92c21d4..10db571e 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt @@ -26,10 +26,9 @@ import com.nerdstone.neatformcore.views.containers.VerticalRootView import com.nerdstone.neatformcore.views.widgets.* import io.mockk.spyk import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel import kotlinx.coroutines.test.runBlockingTest -import org.junit.Assert -import org.junit.Rule -import org.junit.Test +import org.junit.* import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner @@ -38,7 +37,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) @ExperimentalCoroutinesApi -class `Test building form with JSON` { +class JsonFormBuilderTest { private val activity = Robolectric.buildActivity(AppCompatActivity::class.java).setup() private val mainLayout: LinearLayout = LinearLayout(activity.get()) @@ -48,7 +47,7 @@ class `Test building form with JSON` { @Test fun `Should parse json from file source, create views and register form rules`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE, mainLayout) ) @@ -85,7 +84,7 @@ class `Test building form with JSON` { @Test fun `Should parse json from json string, create views and register form rules`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( JsonFormBuilder(TestConstants.SAMPLE_JSON.trimIndent(), activity.get(), mainLayout) ) @@ -120,7 +119,7 @@ class `Test building form with JSON` { @Test fun `Should parse json from file source, update views from provided layout view with form rules`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE, mainLayout) ) @@ -146,7 +145,7 @@ class `Test building form with JSON` { @Test fun `Should build default form (in vertical layout) with using stepper library`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( objToCopy = JsonFormBuilder(activity.get(), TestConstants.SAMPLE_TWO_FORM_FILE, mainLayout), @@ -199,7 +198,7 @@ class `Test building form with JSON` { @Test fun `Should build customized form (using provided layout) using stepper library`() { - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( objToCopy = diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt index f769f51f..7285924f 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt @@ -24,7 +24,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test building MultiChoiceCheckBox view` : BaseJsonViewBuilderTest() { +class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) private val checkBoxOption1 = spyk(NFormSubViewProperty()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt index b017c9fd..48619607 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt @@ -26,7 +26,7 @@ import org.robolectric.shadows.ShadowTextView @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class, shadows = [ShadowTextView::class]) -class `Test building toast notification` : BaseJsonViewBuilderTest() { +class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { private val notificationNFormView = NotificationNFormView(activity.get()) private val viewProperty = spyk(NFormViewProperty()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt index ad196ba5..4fc97186 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt @@ -21,7 +21,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test building NumberSelector view` : BaseJsonViewBuilderTest() { +class NumberSelectorViewBuilderTest : BaseJsonViewBuilderTest() { private val numberSelector = NumberSelectorNFormView(activity.get()) private val numberSelectorViewBuilder = spyk(NumberSelectorViewBuilder(numberSelector)) private val viewProperty = spyk(NFormViewProperty()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt index 073f527c..b700df46 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt @@ -23,7 +23,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test building RadioGroup view` : BaseJsonViewBuilderTest() { +class RadioGroupViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) private val radioOption1 = spyk(NFormSubViewProperty()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt index 3b471e0e..ded77e12 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt @@ -20,7 +20,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test building Spinner view` : BaseJsonViewBuilderTest(){ +class SpinnerViewBuilderTest : BaseJsonViewBuilderTest(){ private val viewProperty = spyk(NFormViewProperty()) private val spinnerOption1 = spyk(NFormSubViewProperty()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt index 91df2b21..24571fd2 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt @@ -24,7 +24,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test building TextInputEditText View` : BaseJsonViewBuilderTest(){ +class TextInputEditTextBuilderTest : BaseJsonViewBuilderTest(){ private val viewProperty = spyk(NFormViewProperty()) private val textInputEditTextNFormView = TextInputEditTextNFormView(activity.get()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/datasource/AssetFileTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/datasource/AssetFileTest.kt index e6458a98..3f53cdc9 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/datasource/AssetFileTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/datasource/AssetFileTest.kt @@ -14,7 +14,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test working with Assets` { +class AssetFileTest { private lateinit var context: Context diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt index a56a5b86..7604bbdb 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt @@ -17,7 +17,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test View Dispacher action` : BaseJsonViewBuilderTest(){ +class ViewDispatcherTest : BaseJsonViewBuilderTest(){ private val viewProperty = NFormViewProperty() private val viewDispatcher = spyk() diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt index 55a20f4e..7a608439 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt @@ -11,6 +11,7 @@ import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.form.json.JsonFormBuilder import com.nerdstone.neatformcore.form.json.JsonFormConstants +import com.nerdstone.neatformcore.robolectric.builders.BaseJsonViewBuilderTest import com.nerdstone.neatformcore.views.containers.MultiChoiceCheckBox import com.nerdstone.neatformcore.views.containers.RadioGroupView import com.nerdstone.neatformcore.views.containers.VerticalRootView @@ -20,7 +21,6 @@ import com.nerdstone.neatformcore.views.widgets.SpinnerNFormView import com.nerdstone.neatformcore.views.widgets.TextInputEditTextNFormView import io.mockk.spyk import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch import kotlinx.coroutines.test.runBlockingTest import org.junit.Assert import org.junit.Ignore @@ -184,23 +184,20 @@ const val VALIDATION_FORM = """ @Config(application = TestNeatFormApp::class) @ExperimentalCoroutinesApi @Ignore("I still don't understand how this test passes individually but fails when run with others") -class `Test form validation ` { +class NeatFormValidatorTest: BaseJsonViewBuilderTest() { @get:Rule var coroutinesTestRule = CoroutineTestRule() - private val activity = Robolectric.buildActivity(AppCompatActivity::class.java).setup() - private val mainLayout: LinearLayout = LinearLayout(activity.get()) - private lateinit var formBuilder: JsonFormBuilder @Test fun `Should display error message and return empty map when required fields are missing`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { formBuilder = spyk( JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get(), mainLayout) ) formBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider - launch { formBuilder.buildForm() }.join() + formBuilder.buildForm() Assert.assertTrue(formBuilder.getFormData().isEmpty()) Assert.assertTrue(formBuilder.getFormDataAsJson() == "") @@ -209,13 +206,13 @@ class `Test form validation ` { @Test fun `Should display error message when there is invalid input`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { formBuilder = spyk( JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get(), mainLayout) ) formBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider - launch { formBuilder.buildForm() }.join() + formBuilder.buildForm() val scrollView = mainLayout.getChildAt(0) as ScrollView val verticalRootView = scrollView.getChildAt(0) as VerticalRootView @@ -231,7 +228,8 @@ class `Test form validation ` { Assert.assertTrue(formBuilder.formValidator.requiredFields.size == 5) Assert.assertTrue(formBuilder.getFormData().isEmpty()) - val multiChoiceCheckBoxErrorMessage = multiChoiceCheckBox.findViewById(R.id.errorMessageTextView) + val multiChoiceCheckBoxErrorMessage = + multiChoiceCheckBox.findViewById(R.id.errorMessageTextView) val checkBox1 = multiChoiceCheckBox.getChildAt(1) as CheckBox checkBox1.isChecked = true multiChoiceCheckBox.viewDetails.view.visibility = View.VISIBLE @@ -245,7 +243,7 @@ class `Test form validation ` { @Test fun `Should return valid form data when validation succeeds`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { updateViewValues() Assert.assertTrue(formBuilder.getFormData().isNotEmpty()) Assert.assertTrue(formBuilder.getFormData().size == 6) @@ -259,7 +257,7 @@ class `Test form validation ` { @Test fun `Should return field values with their metadata`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { updateViewValues() val formData = formBuilder.getFormData() Assert.assertTrue(formData["adult"]?.metadata != null && formData["adult"]?.value == "1234567890") @@ -280,7 +278,7 @@ class `Test form validation ` { @Test fun `Should return json string of form values`() = - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { updateViewValues() Assert.assertNotNull(formBuilder.getFormDataAsJson()) Assert.assertTrue(formBuilder.getFormDataAsJson().contains(JsonFormConstants.FORM_NAME)) @@ -291,13 +289,13 @@ class `Test form validation ` { * Update values of the views and trigger the trackRequireFields method by changing their visibility */ private fun updateViewValues() { - coroutinesTestRule.testDispatcher.runBlockingTest { + coroutinesTestRule.runBlockingTest { formBuilder = spyk( JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get(), mainLayout) ) formBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider - launch { formBuilder.buildForm() }.join() + formBuilder.buildForm() val scrollView = mainLayout.getChildAt(0) as ScrollView val verticalRootView = scrollView.getChildAt(0) as VerticalRootView diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt index 4d601123..86fc1dab 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt @@ -32,7 +32,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test Rules Engine functionality` { +class RulesFactoryTest { private val activity = Robolectric.buildActivity(AppCompatActivity::class.java).setup() private val view = EditTextNFormView(activity.get()) private val mainLayout: ViewGroup = LinearLayout(activity.get()) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/utils/UtilsTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/utils/UtilsTest.kt index 88e6fac8..73f6946b 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/utils/UtilsTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/utils/UtilsTest.kt @@ -18,7 +18,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test Utils with Robolectric` { +class UtilsTest { @Before fun `Before everything else`() { mockkObject(Utils) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/utils/ViewUtilsTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/utils/ViewUtilsTest.kt index caf875cc..9f28b64e 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/utils/ViewUtilsTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/utils/ViewUtilsTest.kt @@ -17,7 +17,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class `Test View Utils with Robolectric` { +class ViewUtilsTest { @Before fun `Before doing anything else`() { From 60d4835e04ac535650ec6db3d172785d7802868b Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Fri, 15 May 2020 22:39:35 +0300 Subject: [PATCH 04/15] Implement functionality for updating forms This involves providing a JSON representation of the previous form data to the formbuilder which it uses to update the views. The update function also includes a list of fields to make read only Signed-off-by: Elly Kitoto --- README.md | 4 +- neat-form-core/build.gradle | 5 +- .../domain/builders/FormBuilder.kt | 67 +++++++++- .../listeners/CalculationChangeListener.kt | 4 +- .../neatformcore/domain/view/NFormView.kt | 4 +- .../neatformcore/form/json/JsonFormBuilder.kt | 70 ++++++++--- .../neatformcore/form/json/JsonFormParser.kt | 22 ---- .../neatformcore/form/json/JsonParser.kt | 24 ++++ .../neatformcore/rules/NFormRulesHandler.kt | 16 +-- .../nerdstone/neatformcore/utils/ViewUtils.kt | 119 +++++++++++++----- .../neatformcore/viewmodel/DataViewModel.kt | 7 +- .../views/containers/MultiChoiceCheckBox.kt | 2 +- .../views/containers/RadioGroupView.kt | 2 +- .../views/handlers/ViewDispatcher.kt | 10 +- .../handlers/ViewVisibilityChangeHandler.kt | 2 +- .../views/widgets/CheckBoxNFormView.kt | 2 +- .../views/widgets/DateTimePickerNFormView.kt | 2 +- .../views/widgets/EditTextNFormView.kt | 7 +- .../views/widgets/NotificationNFormView.kt | 2 +- .../views/widgets/NumberSelectorNFormView.kt | 2 +- .../views/widgets/SpinnerNFormView.kt | 2 +- .../widgets/TextInputEditTextNFormView.kt | 5 +- ...sonFormParserTest.kt => JsonParserTest.kt} | 17 +-- .../neatform/custom/views/CustomImageView.kt | 2 +- .../nerdstone/neatform/form/FormActivity.kt | 55 ++++++-- 25 files changed, 330 insertions(+), 124 deletions(-) delete mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormParser.kt create mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonParser.kt rename neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/{JsonFormParserTest.kt => JsonParserTest.kt} (96%) diff --git a/README.md b/README.md index 4b60a0af..76bd57b8 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Add the library as a dependency to your app's `build.gradle` file ```groovy dependencies { //.... - implementation "com.nerdstone:neat-form-core:1.0.12" + implementation "com.nerdstone:neat-form-core:1.0.13" //.... } @@ -82,7 +82,7 @@ Add the library in the dependency section of your application's `build.gradle` f ```groovy dependencies { //consume library - use the latest version available on github packages - implementation "com.nerdstone:neat-form-core:1.0.12" + implementation "com.nerdstone:neat-form-core:1.0.13" //.... } diff --git a/neat-form-core/build.gradle b/neat-form-core/build.gradle index 093a7f72..c9748716 100644 --- a/neat-form-core/build.gradle +++ b/neat-form-core/build.gradle @@ -11,7 +11,7 @@ jacoco { def properties = new Properties() properties.load(new FileInputStream(rootProject.file("local.properties"))) -version = "1.0.12" +version = "1.0.13" android { compileSdkVersion 28 @@ -98,6 +98,9 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + // debugImplementation because LeakCanary should only run in debug builds. + // debugImplementation "com.squareup.leakcanary:leakcanary-android:2.3" } repositories { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt index c65a9aa2..91b52f6d 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt @@ -10,9 +10,30 @@ import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.viewmodel.DataViewModel import kotlin.reflect.KClass +/** + * [FormBuilder] contract is implemented by any builder using any preferred Schema. + * + * [formString] is the string representation of the form depending on the schema which can be JSON + * YAML or even XML + * + * The form builder requires a FragmentActivity [context] which it will use to create its views. If the form is + * generated with a stepper. That is when the generated form is not embedded into an existing view. The + * [neatStepperLayout] is the view holding the form steps. + * + * The form builder also uses the [formValidator] to validate fields. + * + * Before building views, views are first registered withing the [registeredViews] map. This is a map + * that holds the view type against the actual class type of the view that will used to create the view for example + * for an EditText inside the view type map you will have + * ["edit_text" -> EditTextNFormView::class] + * + * This map can be overridden if you have custom views that you also want rendered. Just remember to register the views + * before building the form. + * + */ interface FormBuilder { - var jsonString: String? + var formString: String? val context: Context @@ -24,25 +45,69 @@ interface FormBuilder { var registeredViews: HashMap> + /** + * THis is the method that hooks up everything on the form builder. It parses the file say JSON file that it has + * been provided with, reads the rules from Assets directory and generate the actual views. The functionality + * for generating views is delegated to [createFormViews] + */ fun buildForm( jsonFormStepBuilderModel: JsonFormStepBuilderModel? = null, viewList: List? = null ): FormBuilder + /** + * This method creates within [context]. It also has an optional [jsonFormStepBuilderModel] parameter + * that can be used to customize the stepper that will be shown on the [neatStepperLayout] + * + * When using custom layout to build the form you need to provide the layouts to the [views] list. + * The form builder will generate the views for steps in the order that the layouts are added to the list + * i.e. for step one views will be on the layout in position 0 of the [views] + * + */ fun createFormViews( context: Context, views: List? = null, jsonFormStepBuilderModel: JsonFormStepBuilderModel? = null ) + /** + * This method reads the rules file available withing the given [context] and returns true when done + * false otherwise. You also setup the [rulesFileType] in order for this to work. The form builder + * uses Rules engine that allows you to write rules either in JSON or YML format. + */ fun registerFormRulesFromFile( context: Context, rulesFileType: RulesFactory.RulesFileType ): Boolean + /** + * This method returns a map of the form data. Where the key is the field name and the value is of + * type [NFormViewData] + */ fun getFormData(): HashMap + /** + * This method returns Form metadata as specified on the form + */ fun getFormMetaData(): Map + /** + * This method returns a JSON string representation of the form data including the metadata specified on the + * form. + */ fun getFormDataAsJson(): String + /** + * Use this method to add view types to the [registeredViews] property + */ fun registerViews() + /** + * Use this method to update the view model which will in turn update its observers (views) + * This method expect [formDataJson] to comply with the json string that was returned by the form builder. + * The string will be parsed into a map of [NFormViewData] with the key being the name of the field + * then use the new map as the data for the view model. + * + * Any field that exists in the list of [readOnlyFields] will be disabled. + */ + + fun updateFormData(formDataJson: String, readOnlyFields: MutableSet = mutableSetOf()) + } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/CalculationChangeListener.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/CalculationChangeListener.kt index dba6b736..c4905c89 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/CalculationChangeListener.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/CalculationChangeListener.kt @@ -1,8 +1,10 @@ package com.nerdstone.neatformcore.domain.listeners /** + * @author Elly Nerdstone + * * Calculation listener. This listener listens for changes in calculations. - * Any views implementing this can make use of the updated value + * Any views implementing this can make use of the updated calculation value */ interface CalculationChangeListener { /** diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt index f784e4f8..32546c77 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt @@ -44,8 +44,8 @@ interface NFormView { /** * This method sets [value] of the field. And it also accepts an optional parameter called - * [disabled] that is used to indicate whether you want the field to be disabled or not. Use the + * [enabled] that is used to indicate whether you want the field to be disabled or not. Use the * option if you want to make the field readonly. */ - fun setValue(value: Any, disabled: Boolean = false) + fun setValue(value: Any, enabled: Boolean = true) } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index 14e5e1d4..ec0b261f 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -8,7 +8,9 @@ import android.view.ViewGroup import android.widget.ScrollView import android.widget.Toast import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import com.google.gson.Gson import com.nerdstone.neatandroidstepper.core.model.StepModel import com.nerdstone.neatandroidstepper.core.stepper.Step import com.nerdstone.neatandroidstepper.core.stepper.StepVerificationState @@ -22,15 +24,14 @@ import com.nerdstone.neatformcore.domain.model.NForm import com.nerdstone.neatformcore.domain.model.NFormContent import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.FormValidator +import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.form.common.FormErrorDialog +import com.nerdstone.neatformcore.form.json.JsonParser.parseJson import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.rules.RulesFactory.RulesFileType +import com.nerdstone.neatformcore.utils.* import com.nerdstone.neatformcore.utils.Constants.ViewType -import com.nerdstone.neatformcore.utils.DefaultDispatcherProvider -import com.nerdstone.neatformcore.utils.DispatcherProvider -import com.nerdstone.neatformcore.utils.SingleRunner -import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.viewmodel.DataViewModel import com.nerdstone.neatformcore.views.containers.MultiChoiceCheckBox import com.nerdstone.neatformcore.views.containers.RadioGroupView @@ -53,6 +54,19 @@ object JsonFormConstants { /*** * @author Elly Nerdstone + * + * This class implements the [FormBuilder] and is used to build the form using JSON specification. + * This form builder works in 3 sequential steps. First it parses the JSON into a model, secondly it + * reads the rules file and finally creates the actual views. All these three tasks run one after the + * other but within a [CoroutineScope]. + * + * To ensure that the the form builder will not attempt to create the views without first parsing + * the JSON a mutex [SingleRunner] is used to guarantee that the tasks run sequentially. This + * does not mean however that the tasks are running on the main thread. + * + * To parse the JSON for example is handled by [DispatcherProvider.default] method since it is a CPU intensive task. + * Reading of rules file however is run by [DispatcherProvider.io] whereas creation of views is done on the + * main thread using [DispatcherProvider.main] scope. */ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { private var mainLayout: ViewGroup? = null @@ -63,12 +77,13 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { var defaultContextProvider: DispatcherProvider var form: NForm? = null var fileSource: String? = null - override var jsonString: String? = null + override var formString: String? = null override lateinit var neatStepperLayout: NeatStepperLayout override lateinit var context: Context override lateinit var viewModel: DataViewModel override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var registeredViews = hashMapOf>() + private var readOnlyFields = mutableSetOf() constructor(context: Context, fileSource: String, mainLayout: ViewGroup?) : this() { @@ -81,7 +96,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { constructor(jsonString: String, context: Context, mainLayout: ViewGroup?) : this() { - this.jsonString = jsonString + this.formString = jsonString this.context = context this.mainLayout = mainLayout this.neatStepperLayout = NeatStepperLayout(context) @@ -131,8 +146,8 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { private fun parseJsonForm(): NForm? { return when { - jsonString != null -> JsonFormParser.parseJson(jsonString) - fileSource != null -> JsonFormParser.parseJson( + formString != null -> parseJson(formString) + fileSource != null -> parseJson( AssetFile.readAssetFileAsString(context, fileSource!!) ) else -> null @@ -191,6 +206,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { ).show() } } + observeViewModel() } private fun addViewsToVerticalRootView( @@ -228,9 +244,15 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { return "" } + override fun updateFormData(formDataJson: String, readOnlyFields: MutableSet) { + if (this::viewModel.isInitialized) { + viewModel.updateDetails(Gson().parseJson(formDataJson)) + this.readOnlyFields.addAll(readOnlyFields) + } + } + override fun registerFormRulesFromFile( - context: Context, - rulesFileType: RulesFileType + context: Context, rulesFileType: RulesFileType ): Boolean { form?.rulesFile?.also { rulesFactory.readRulesFromFile(context, it, rulesFileType) @@ -257,15 +279,32 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { registeredViews[ViewType.RADIO_GROUP] = RadioGroupView::class registeredViews[ViewType.TOAST_NOTIFICATION] = NotificationNFormView::class } -} + /** + * This method watches for changes in the view model by observing the mutable live data. + */ + private fun observeViewModel() { + this.viewModel = ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] + viewModel.details.observe(context as FragmentActivity, Observer { + it.filterNot { entry -> entry.key.endsWith(Constants.RuleActions.CALCULATION) } + .forEach { entry -> + val nFormView: NFormView? = + ViewUtils.findViewWithKey(entry.key, context) as NFormView + entry.value.value?.let { realValue -> + nFormView?.setValue(realValue, !readOnlyFields.contains(entry.key)) + } + Timber.d("Updated field %s : %s", entry.key, entry.value.value) + } + }) + } +} const val FRAGMENT_VIEW = "fragment_view" const val FRAGMENT_INDEX = "index" class StepFragment : Step { - var index: Int? = null - var formView: View? = null + private var index: Int? = null + private var formView: View? = null constructor() @@ -291,7 +330,6 @@ class StepFragment : Step { index = it.getInt(FRAGMENT_INDEX) formView = it.getSerializable(FRAGMENT_VIEW) as VerticalRootView? } - retainInstance = true } @@ -318,4 +356,8 @@ class StepFragment : Step { override fun onError(stepVerificationState: StepVerificationState) = Unit + override fun onDestroyView() { + super.onDestroyView() + formView = null + } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormParser.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormParser.kt deleted file mode 100644 index 916441f7..00000000 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormParser.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.nerdstone.neatformcore.form.json - -import com.google.gson.FieldNamingPolicy -import com.google.gson.GsonBuilder -import com.google.gson.stream.JsonReader -import com.nerdstone.neatformcore.domain.model.NForm -import java.io.StringReader - -object JsonFormParser { - - fun parseJson(json: String?): NForm? { - if (json != null && json.isNotEmpty()) { - val gson = GsonBuilder() - .setPrettyPrinting() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES) - .create() - return gson.fromJson(JsonReader(StringReader(json)), NForm::class.java) - } - return null - } - -} diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonParser.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonParser.kt new file mode 100644 index 00000000..7bb584cb --- /dev/null +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonParser.kt @@ -0,0 +1,24 @@ +package com.nerdstone.neatformcore.form.json + +import com.google.gson.FieldNamingPolicy +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import java.io.StringReader + +object JsonParser { + @Throws(Exception::class) + inline fun parseJson(json: String?): T? = + if (!json.isNullOrEmpty()) + GsonBuilder() + .setPrettyPrinting() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES) + .create() + .fromJson(JsonReader(StringReader(json)), T::class.java) + else null + + inline fun Gson.parseJson(json: String): T = + fromJson(JsonReader(StringReader(json)), object : TypeToken() {}.type) + +} diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt index e943fde6..183e73f9 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt @@ -1,6 +1,5 @@ package com.nerdstone.neatformcore.rules -import android.app.Activity import android.view.View import com.nerdstone.neatformcore.domain.builders.FormBuilder import com.nerdstone.neatformcore.domain.listeners.CalculationChangeListener @@ -69,7 +68,7 @@ class NFormRulesHandler private constructor() : RulesHandler { filterCurrentRules(Constants.RuleActions.CALCULATION) .forEach { key -> val value = facts?.asMap()?.get(key) - formBuilder.viewModel.saveValue(key, NFormViewData("Calculation", value, null)) + formBuilder.viewModel.saveFieldValue(key, NFormViewData("Calculation", value, null)) updateCalculationListeners(Pair(key, value)) } } @@ -78,17 +77,12 @@ class NFormRulesHandler private constructor() : RulesHandler { calculationListeners.forEach { it.onCalculationChanged(calculation) } fun hideOrShowField(key: String, isVisible: Boolean?) { - if (findViewWithKey(key) != null) { - changeVisibility(isVisible, findViewWithKey(key)!!) + val view = ViewUtils.findViewWithKey(key, formBuilder.context) + if (view != null) { + changeVisibility(isVisible, view) } } - private fun findViewWithKey(key: String): View? { - val activity = formBuilder.context as Activity - val activityRootView = activity.findViewById(android.R.id.content).rootView - return activityRootView.findViewWithTag(key) - } - override fun refreshViews(allRules: Rules?) { allRules?.also { it.toMutableList() @@ -97,7 +91,7 @@ class NFormRulesHandler private constructor() : RulesHandler { .endsWith(Constants.RuleActions.VISIBILITY) }.forEach { item -> val key = ViewUtils.getKey(item.name, Constants.RuleActions.VISIBILITY) - val view = findViewWithKey(key) + val view = ViewUtils.findViewWithKey(key, formBuilder.context) if (view != null) changeVisibility(false, view) } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt index 1cadf953..588bcd83 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt @@ -1,5 +1,6 @@ package com.nerdstone.neatformcore.utils +import android.app.Activity import android.content.Context import android.graphics.Color import android.os.Build @@ -12,11 +13,16 @@ import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import android.widget.Toast.LENGTH_LONG +import androidx.appcompat.view.ContextThemeWrapper +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.ViewModelProvider import com.nerdstone.neatformcore.BuildConfig import com.nerdstone.neatformcore.R +import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.domain.view.RootView +import com.nerdstone.neatformcore.viewmodel.DataViewModel import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import timber.log.Timber import java.util.* @@ -29,52 +35,59 @@ const val VALIDATION_RESULT = "validationResults" object ViewUtils { fun createViews( - rootView: RootView, viewProperties: List, - viewDispatcher: ViewDispatcher, buildFromLayout: Boolean = false + rootView: RootView, viewProperties: List, + viewDispatcher: ViewDispatcher, buildFromLayout: Boolean = false ) { val registeredViews = rootView.formBuilder.registeredViews for (viewProperty in viewProperties) { - buildView(buildFromLayout, rootView, viewProperty, viewDispatcher, - registeredViews[viewProperty.type] as KClass<*>) + buildView( + buildFromLayout, rootView, viewProperty, viewDispatcher, + registeredViews[viewProperty.type] as KClass<*> + ) } } private fun buildView( - buildFromLayout: Boolean, rootView: RootView, - viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher, kClass: KClass<*> + buildFromLayout: Boolean, rootView: RootView, + viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher, kClass: KClass<*> ) { val androidView = rootView as View val context = rootView.context if (buildFromLayout) { val view = androidView.findViewById( - context.resources.getIdentifier(viewProperty.name, ID, context.packageName) + context.resources.getIdentifier(viewProperty.name, ID, context.packageName) ) try { getView(view as NFormView, viewProperty, viewDispatcher) } catch (e: Exception) { Timber.e(e) if (BuildConfig.DEBUG) - Toast.makeText(rootView.context, "ERROR: The view with name ${viewProperty.name} " + - "defined in json form is missing in custom layout", LENGTH_LONG).show() + Toast.makeText( + context, "ERROR: The view with name ${viewProperty.name} " + + "defined in json form is missing in custom layout", LENGTH_LONG + ).show() } } else { val constructor = kClass.constructors.minBy { it.parameters.size } rootView.addChild( - getView(constructor!!.call(context) as NFormView, viewProperty, viewDispatcher) + getView(constructor!!.call(context) as NFormView, viewProperty, viewDispatcher) ) } } private fun getView( - nFormView: NFormView, viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher + nFormView: NFormView, viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher ): NFormView { if (viewProperty.subjects != null) { viewDispatcher.rulesFactory - .registerSubjects(splitText(viewProperty.subjects, ","), viewProperty) + .registerSubjects(splitText(viewProperty.subjects, ","), viewProperty) val hasVisibilityRule = - viewDispatcher.rulesFactory.viewHasVisibilityRule(viewProperty) + viewDispatcher.rulesFactory.viewHasVisibilityRule(viewProperty) if (hasVisibilityRule) { - viewDispatcher.rulesFactory.rulesHandler.changeVisibility(false, nFormView.viewDetails.view) + viewDispatcher.rulesFactory.rulesHandler.changeVisibility( + false, + nFormView.viewDetails.view + ) } } return setupView(nFormView, viewProperty, viewDispatcher) @@ -83,15 +96,19 @@ object ViewUtils { fun splitText(text: String?, delimiter: String): List { return if (text == null || text.isEmpty()) { ArrayList() - } else listOf(*text.split(delimiter.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) + } else listOf(*text.split(delimiter.toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray()) } + /** + * This method appends a suffix '*' to the provided [text] + */ fun addRedAsteriskSuffix(text: String): SpannableString { if (text.isNotEmpty()) { val textWithSuffix = SpannableString("$text *") textWithSuffix.setSpan( - ForegroundColorSpan(Color.RED), textWithSuffix.length - 1, textWithSuffix.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ForegroundColorSpan(Color.RED), textWithSuffix.length - 1, textWithSuffix.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) return textWithSuffix } @@ -103,18 +120,21 @@ object ViewUtils { } fun setupView( - nFormView: NFormView, viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher + nFormView: NFormView, viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher ): NFormView { with(nFormView) { - //Set view properties - viewProperties = viewProperty - viewDetails.name = viewProperty.name - viewDetails.metadata = viewProperty.viewMetadata - //Add listener and build view - viewDetails.view.id = View.generateViewId() - viewDetails.view.tag = viewProperty.name + //Set view properties and view dispatcher + viewProperties = viewProperty dataActionListener = viewDispatcher + + with(viewDetails) { + name = viewProperty.name + metadata = viewProperty.viewMetadata + view.id = View.generateViewId() + view.tag = viewProperty.name + } + addRequiredFields(nFormView) trackRequiredField() viewBuilder.buildView() @@ -127,9 +147,14 @@ object ViewUtils { nFormView.formValidator.requiredFields.add(nFormView.viewDetails.name) } + /** + * This method is the one responsible for mapping the JSON string [acceptedAttributes] to actual + * view attributes to [nFormView]. [task] is a the method that is responsible for applying these + * properties + */ fun applyViewAttributes( - nFormView: NFormView, acceptedAttributes: HashSet, - task: (attribute: Map.Entry) -> Unit + nFormView: NFormView, acceptedAttributes: HashSet, + task: (attribute: Map.Entry) -> Unit ) { nFormView.viewProperties.viewAttributes?.forEach { attribute -> if (acceptedAttributes.contains(attribute.key.toUpperCase(Locale.getDefault()))) { @@ -145,6 +170,10 @@ object ViewUtils { } } + /** + * This method extracts the value from the provided [attribute] and uses it as the text that will + * be displayed on the top label of [nFormView] + */ fun addViewLabel(attribute: Pair, nFormView: NFormView): LinearLayout { val layout = View.inflate((nFormView as View).context, R.layout.custom_label_layout, null) as LinearLayout @@ -158,16 +187,21 @@ object ViewUtils { } error = null } - (nFormView as View).findViewById(R.id.errorMessageTextView)?.visibility = View.GONE + (nFormView as View).findViewById(R.id.errorMessageTextView)?.visibility = + View.GONE return layout } + /** + * When a field represented by this [nFormView] is required it must be filled otherwise + * data will not be valid and an empty map will be returned if you try to access the form data. + */ fun handleRequiredStatus(nFormView: NFormView) { (nFormView as View).tag?.also { val formValidator = nFormView.formValidator if (Utils.isFieldRequired(nFormView) && - nFormView.viewDetails.value == null && - nFormView.viewDetails.view.visibility == View.VISIBLE + nFormView.viewDetails.value == null && + nFormView.viewDetails.view.visibility == View.VISIBLE ) { formValidator.requiredFields.add(nFormView.viewDetails.name) } else { @@ -176,10 +210,35 @@ object ViewUtils { } } + /** + * This method is used to animate the [view] + */ fun animateView(view: View) { when (view.visibility) { View.VISIBLE -> view.animate().alpha(1.0f).duration = 800 View.GONE -> view.animate().alpha(0.0f).duration = 800 } } + + /** + * This method returns [DataViewModel] from the provided context of the [viewDetails] + */ + fun getDataViewModel(viewDetails: NFormViewDetails) = + ViewModelProvider(getViewContext(viewDetails) as FragmentActivity)[DataViewModel::class.java] + + /** + * This method is used to get the base context for [viewDetails]. Sometimes the context is + * wrapped inside a [ContextThemeWrapper] but we need its base context which is an [FragmentActivity] + */ + private fun getViewContext(viewDetails: NFormViewDetails): Context { + val context = viewDetails.view.context + var activityContext = context + if (context is ContextThemeWrapper) activityContext = context.baseContext + return activityContext + } + + fun findViewWithKey(key: String, context: Context): View? { + val activityRootView = (context as Activity).findViewById(android.R.id.content).rootView + return activityRootView.findViewWithTag(key) + } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt index d2da4fde..1fdb0060 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt @@ -11,9 +11,14 @@ import com.nerdstone.neatformcore.domain.model.NFormViewData */ class DataViewModel : ViewModel() { + var details: MutableLiveData> = MutableLiveData(HashMap()) - fun saveValue(fieldName: String, fieldValue:NFormViewData){ + fun saveFieldValue(fieldName: String, fieldValue: NFormViewData) { details.value?.set(fieldName, fieldValue) } + + fun updateDetails(newDetails: HashMap) { + details.value?.putAll(newDetails) + } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt index 64940ffb..2532a5c6 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt @@ -45,7 +45,7 @@ class MultiChoiceCheckBox : LinearLayout, NFormView { viewBuilder.resetCheckBoxValues() } - override fun setValue(value: Any, disabled: Boolean) { + override fun setValue(value: Any, enabled: Boolean) { TODO("Not yet implemented") } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt index b8aea957..22ee0cd2 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt @@ -41,7 +41,7 @@ class RadioGroupView : LinearLayout, NFormView { override fun validateValue(): Boolean = formValidator.validateLabeledField(this) - override fun setValue(value: Any, disabled: Boolean) { + override fun setValue(value: Any, enabled: Boolean) { TODO("Not yet implemented") } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt index 1436bb8a..949abcba 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt @@ -10,6 +10,7 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.utils.Utils +import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.viewmodel.DataViewModel /** @@ -32,13 +33,8 @@ class ViewDispatcher private constructor() : DataActionListener { * @param viewDetails the details of the view that has just dispatched a value */ override fun onPassData(viewDetails: NFormViewDetails) { - val context = viewDetails.view.context - var activityContext = context - if (context is ContextThemeWrapper) activityContext = context.baseContext - val viewModel = - ViewModelProvider(activityContext as FragmentActivity)[DataViewModel::class.java] - - viewModel.details.value?.also { + + ViewUtils.getDataViewModel(viewDetails).details.value?.also { if (it[viewDetails.name] != viewDetails.value) { it[viewDetails.name] = diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewVisibilityChangeHandler.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewVisibilityChangeHandler.kt index 5083c563..33018a86 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewVisibilityChangeHandler.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewVisibilityChangeHandler.kt @@ -10,7 +10,7 @@ class ViewVisibilityChangeHandler private constructor() : VisibilityChangeListen override fun onVisibilityChanged(changedView: View, visibility: Int) { ViewUtils.animateView(changedView) if (changedView is NFormView) { - if (visibility == View.GONE) { + if (visibility == View.GONE && changedView.viewDetails.value != null) { changedView.resetValueWhenHidden() } changedView.trackRequiredField() diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt index c41ece02..513a8ee7 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt @@ -48,7 +48,7 @@ class CheckBoxNFormView : AppCompatCheckBox, NFormView { override fun trackRequiredField() = ViewUtils.handleRequiredStatus(this) - override fun setValue(value: Any, disabled: Boolean) { + override fun setValue(value: Any, enabled: Boolean) { TODO("Not yet implemented") } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt index d0d9b3bf..03d512ed 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt @@ -40,7 +40,7 @@ class DateTimePickerNFormView : TextInputLayout, NFormView { return validationPair.first } - override fun setValue(value: Any, disabled: Boolean) { + override fun setValue(value: Any, enabled: Boolean) { TODO("Not yet implemented") } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt index 0e70f483..07bd8923 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt @@ -58,12 +58,13 @@ class EditTextNFormView : AppCompatEditText, NFormView { return validationPair.first } - override fun setValue(value: Any, disabled: Boolean) { - TODO("Not yet implemented") + override fun setValue(value: Any, enabled: Boolean) { + setText(value as String) + isEnabled = enabled } override fun setVisibility(visibility: Int) { - super.setVisibility( visibility) + super.setVisibility(visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt index a2eafd6d..8e13019c 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt @@ -47,7 +47,7 @@ class NotificationNFormView : FrameLayout, NFormView, CalculationChangeListener viewBuilder.updateNotificationText(calculationField) } - override fun setValue(value: Any, disabled: Boolean) { + override fun setValue(value: Any, enabled: Boolean) { TODO("Not yet implemented") } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt index cd5fd138..3d44b435 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt @@ -36,7 +36,7 @@ class NumberSelectorNFormView : LinearLayout, NFormView { override fun trackRequiredField() = ViewUtils.handleRequiredStatus(this) - override fun setValue(value: Any, disabled: Boolean) { + override fun setValue(value: Any, enabled: Boolean) { TODO("Not yet implemented") } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt index cfdbea3c..3775cf8b 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt @@ -36,7 +36,7 @@ class SpinnerNFormView : LinearLayout, NFormView { override fun resetValueWhenHidden() = viewBuilder.resetSpinnerValue() - override fun setValue(value: Any, disabled: Boolean) { + override fun setValue(value: Any, enabled: Boolean) { TODO("Not yet implemented") } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt index 9b3f28cc..d253790a 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt @@ -34,8 +34,9 @@ class TextInputEditTextNFormView : TextInputLayout, NFormView { editText?.setText("") } - override fun setValue(value: Any, disabled: Boolean) { - TODO("Not yet implemented") + override fun setValue(value: Any, enabled: Boolean) { + this.editText?.setText(value as String) + isEnabled = enabled } override fun validateValue(): Boolean { diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonFormParserTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonParserTest.kt similarity index 96% rename from neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonFormParserTest.kt rename to neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonParserTest.kt index fadb14a3..8a0e776a 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonFormParserTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/parser/JsonParserTest.kt @@ -1,11 +1,12 @@ package com.nerdstone.neatformcore.junit.parser -import com.nerdstone.neatformcore.form.json.JsonFormParser +import com.nerdstone.neatformcore.domain.model.NForm +import com.nerdstone.neatformcore.form.json.JsonParser import org.junit.Assert.* import org.junit.Before import org.junit.Test -class JsonFormParserTest { +class JsonParserTest { private var json: String? = null @@ -218,7 +219,7 @@ class JsonFormParserTest { @Test fun `Should parse a valid JSON into NForm model`() { - val nForm = JsonFormParser.parseJson(json) + val nForm = JsonParser.parseJson(json) assertEquals(nForm!!.formName, "Profile") assertEquals(nForm.steps.size.toLong(), 5) assertNotNull(nForm) @@ -226,7 +227,7 @@ class JsonFormParserTest { @Test fun `Should correctly parse form content`() { - val nForm = JsonFormParser.parseJson(json) + val nForm = JsonParser.parseJson(json) val nFormContent = nForm!!.steps[0] assertEquals(nFormContent.stepName, "Behaviour and counselling") assertEquals(nFormContent.fields.size.toLong(), 2) @@ -234,7 +235,7 @@ class JsonFormParserTest { @Test fun `Should properly parse view properties`() { - val nForm = JsonFormParser.parseJson(json) + val nForm = JsonParser.parseJson(json) val property = nForm!!.steps[0].fields[0] assertEquals(property.name, "username") assertEquals(2, property.validations?.size) @@ -245,7 +246,7 @@ class JsonFormParserTest { @Test fun `Should properly parse sub view properties`() { - val nForm = JsonFormParser.parseJson(json) + val nForm = JsonParser.parseJson(json) val property = nForm!!.steps[4].fields[1] assertEquals(property.options!!.size.toLong(), 2) assertEquals(property.options!![0].name, "female") @@ -258,7 +259,7 @@ class JsonFormParserTest { @Test fun `Should not parse empty JSON or null`() { - assertNull(JsonFormParser.parseJson(null)) - assertNull(JsonFormParser.parseJson("")) + assertNull(JsonParser.parseJson(null)) + assertNull(JsonParser.parseJson("")) } } diff --git a/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt b/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt index c1586fc2..76f73959 100644 --- a/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt +++ b/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt @@ -29,7 +29,7 @@ class CustomImageView(context: Context) : CircleImageView(context), NFormView { override fun trackRequiredField() = ViewUtils.handleRequiredStatus(this) - override fun setValue(value: Any, disabled: Boolean) { + override fun setValue(value: Any, enabled: Boolean) { TODO("Not yet implemented") } } \ No newline at end of file diff --git a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt index f4150a36..74664c97 100644 --- a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt +++ b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt @@ -32,6 +32,29 @@ class FormActivity : AppCompatActivity(), StepperActions { private lateinit var completeButton: ImageView private var formBuilder: FormBuilder? = null + private val previousFormData = """ + { + "age": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + }, + "type": "TextInputEditTextNFormView", + "value": "4" + }, + "child": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + }, + "type": "TextInputEditTextNFormView", + "value": "adult" + } + } + """.trimIndent() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.form_activity) @@ -78,7 +101,9 @@ class FormActivity : AppCompatActivity(), StepperActions { formBuilder?.also { it.registeredViews["custom_image"] = CustomImageView::class it.buildForm() + updateForm() } + } FormType.embeddableCustomized -> { formBuilder = JsonFormBuilder(this, formData.filePath, formLayout) @@ -90,11 +115,15 @@ class FormActivity : AppCompatActivity(), StepperActions { FormType.stepperDefault -> { sampleToolBar.visibility = View.GONE formBuilder = JsonFormBuilder(this, formData.filePath, null) - formBuilder?.also { - it.registeredViews["custom_image"] = CustomImageView::class - it.buildForm(JsonFormStepBuilderModel.Builder(this, - stepperModel).build()) - } + formBuilder?.also { + it.registeredViews["custom_image"] = CustomImageView::class + it.buildForm( + JsonFormStepBuilderModel.Builder( + this, + stepperModel + ).build() + ) + } replaceView(mainLayout, (formBuilder as JsonFormBuilder).neatStepperLayout) } FormType.stepperCustomized -> { @@ -104,6 +133,7 @@ class FormActivity : AppCompatActivity(), StepperActions { JsonFormStepBuilderModel.Builder(this, stepperModel).build(), views ) + updateForm() replaceView(mainLayout, (formBuilder as JsonFormBuilder).neatStepperLayout) } else -> Toast.makeText( @@ -114,14 +144,19 @@ class FormActivity : AppCompatActivity(), StepperActions { } } - override fun onStepError(stepVerificationState: StepVerificationState) { + private fun updateForm() { + formBuilder?.apply { + if (viewModel.details.value?.isEmpty()!!) { + updateFormData(previousFormData, mutableSetOf("age")) + } + } } - override fun onButtonNextClick(step: Step) { - } + override fun onStepError(stepVerificationState: StepVerificationState) = Unit - override fun onButtonPreviousClick(step: Step) { - } + override fun onButtonNextClick(step: Step) = Unit + + override fun onButtonPreviousClick(step: Step) = Unit override fun onStepComplete(step: Step) { if (formBuilder?.getFormDataAsJson() != "") { From b2d34e341b724abcb7d2087a53defba3efdb2b57 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Mon, 18 May 2020 00:07:10 +0300 Subject: [PATCH 05/15] Implement set value for more fields including: number selector, checkbox, spinner Signed-off-by: Elly Kitoto --- .../resources/jacoco-agent.properties | 1 + .../domain/model/NFormViewData.kt | 5 +- .../neatformcore/domain/view/NFormView.kt | 2 + .../neatformcore/form/json/JsonFormBuilder.kt | 16 +-- .../nerdstone/neatformcore/utils/ViewUtils.kt | 16 ++- .../builders/DateTimePickerViewBuilder.kt | 39 +++--- .../builders/NumberSelectorViewBuilder.kt | 118 ++++++++++++++---- .../views/builders/SpinnerViewBuilder.kt | 2 +- .../builders/TextInputEditTextBuilder.kt | 12 +- .../views/containers/MultiChoiceCheckBox.kt | 1 + .../views/containers/RadioGroupView.kt | 1 + .../views/handlers/ViewDispatcher.kt | 44 +++---- .../handlers/ViewVisibilityChangeHandler.kt | 4 +- .../views/widgets/CheckBoxNFormView.kt | 6 +- .../views/widgets/DateTimePickerNFormView.kt | 11 +- .../views/widgets/EditTextNFormView.kt | 4 +- .../views/widgets/NotificationNFormView.kt | 5 +- .../views/widgets/NumberSelectorNFormView.kt | 6 +- .../views/widgets/SpinnerNFormView.kt | 19 ++- .../widgets/TextInputEditTextNFormView.kt | 7 +- .../src/main/res/values/strings.xml | 1 + .../neatform/custom/views/CustomImageView.kt | 1 + .../nerdstone/neatform/form/FormActivity.kt | 50 +++++++- .../layout/sample_one_form_custom_layout.xml | 5 +- 24 files changed, 265 insertions(+), 111 deletions(-) create mode 100644 neat-form-core/src/androidTest/resources/jacoco-agent.properties diff --git a/neat-form-core/src/androidTest/resources/jacoco-agent.properties b/neat-form-core/src/androidTest/resources/jacoco-agent.properties new file mode 100644 index 00000000..54608565 --- /dev/null +++ b/neat-form-core/src/androidTest/resources/jacoco-agent.properties @@ -0,0 +1 @@ +output=none \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/NFormViewData.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/NFormViewData.kt index 3c795d87..e790cbfe 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/NFormViewData.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/NFormViewData.kt @@ -11,5 +11,8 @@ data class NFormViewData( var value: Any? = null, @Expose @SerializedName("meta_data", alternate = ["metadata"]) - var metadata: Map? = null + var metadata: Map? = null, + @Expose + var visible: Boolean = true + ) : Serializable \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt index 32546c77..6bb58151 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/NFormView.kt @@ -27,6 +27,8 @@ interface NFormView { var formValidator: FormValidator + var initialValue: Any? + /** * This function is called to reset the value of the field when it is hidden */ diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index ec0b261f..1f029945 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -246,8 +246,11 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { override fun updateFormData(formDataJson: String, readOnlyFields: MutableSet) { if (this::viewModel.isInitialized) { - viewModel.updateDetails(Gson().parseJson(formDataJson)) this.readOnlyFields.addAll(readOnlyFields) + val viewData = Gson() + .parseJson>(formDataJson) + .filter { it.value.value != null } + viewModel.updateDetails(viewData as HashMap) } } @@ -288,10 +291,11 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { viewModel.details.observe(context as FragmentActivity, Observer { it.filterNot { entry -> entry.key.endsWith(Constants.RuleActions.CALCULATION) } .forEach { entry -> - val nFormView: NFormView? = - ViewUtils.findViewWithKey(entry.key, context) as NFormView + val view: View? = ViewUtils.findViewWithKey(entry.key, context) entry.value.value?.let { realValue -> - nFormView?.setValue(realValue, !readOnlyFields.contains(entry.key)) + if (view != null) (view as NFormView).setValue( + realValue, !readOnlyFields.contains(entry.key) + ) } Timber.d("Updated field %s : %s", entry.key, entry.value.value) } @@ -356,8 +360,4 @@ class StepFragment : Step { override fun onError(stepVerificationState: StepVerificationState) = Unit - override fun onDestroyView() { - super.onDestroyView() - formView = null - } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt index 588bcd83..92d433c5 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt @@ -12,7 +12,7 @@ import android.widget.CheckBox import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast -import android.widget.Toast.LENGTH_LONG +import android.widget.Toast.LENGTH_SHORT import androidx.appcompat.view.ContextThemeWrapper import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModelProvider @@ -64,7 +64,7 @@ object ViewUtils { if (BuildConfig.DEBUG) Toast.makeText( context, "ERROR: The view with name ${viewProperty.name} " + - "defined in json form is missing in custom layout", LENGTH_LONG + "defined in json form is missing in custom layout", LENGTH_SHORT ).show() } } else { @@ -81,12 +81,10 @@ object ViewUtils { if (viewProperty.subjects != null) { viewDispatcher.rulesFactory .registerSubjects(splitText(viewProperty.subjects, ","), viewProperty) - val hasVisibilityRule = - viewDispatcher.rulesFactory.viewHasVisibilityRule(viewProperty) + val hasVisibilityRule = viewDispatcher.rulesFactory.viewHasVisibilityRule(viewProperty) if (hasVisibilityRule) { viewDispatcher.rulesFactory.rulesHandler.changeVisibility( - false, - nFormView.viewDetails.view + false, nFormView.viewDetails.view ) } } @@ -237,8 +235,14 @@ object ViewUtils { return activityContext } + @Throws(Throwable::class) fun findViewWithKey(key: String, context: Context): View? { val activityRootView = (context as Activity).findViewById(android.R.id.content).rootView return activityRootView.findViewWithTag(key) } + + fun View.setReadOnlyState(enabled: Boolean) { + isEnabled = enabled + isFocusable = enabled + } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/DateTimePickerViewBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/DateTimePickerViewBuilder.kt index 65c246fa..680a0ebd 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/DateTimePickerViewBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/DateTimePickerViewBuilder.kt @@ -12,6 +12,7 @@ import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.widgets.DateTimePickerNFormView import java.text.SimpleDateFormat import java.util.* +import java.util.Calendar.* open class DateTimePickerViewBuilder(final override val nFormView: NFormView) : DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener, ViewBuilder { @@ -19,8 +20,8 @@ open class DateTimePickerViewBuilder(final override val nFormView: NFormView) : private var dateDisplayFormat = "yyyy-MM-dd" private val dateTimePickerNFormView = nFormView as DateTimePickerNFormView override val acceptedAttributes = Utils.convertEnumToSet(DateTimePickerProperties::class.java) - private val selectedDate = Calendar.getInstance() - private val calendar = Calendar.getInstance() + private val calendar = getInstance() + val selectedDate: Calendar = getInstance() val textInputEditText = com.google.android.material.textfield.TextInputEditText( dateTimePickerNFormView.context @@ -37,9 +38,7 @@ open class DateTimePickerViewBuilder(final override val nFormView: NFormView) : override fun buildView() { ViewUtils.applyViewAttributes( - nFormView = dateTimePickerNFormView, - acceptedAttributes = acceptedAttributes, - task = this::setViewProperties + dateTimePickerNFormView, acceptedAttributes, this::setViewProperties ) dateTimePickerNFormView.addView(textInputEditText) @@ -91,9 +90,9 @@ open class DateTimePickerViewBuilder(final override val nFormView: NFormView) : } private fun launchDatePickerDialog() { - val year = selectedDate.get(Calendar.YEAR) - val month = selectedDate.get(Calendar.MONTH) - val dayOfMonth = selectedDate.get(Calendar.DAY_OF_MONTH) + val year = selectedDate.get(YEAR) + val month = selectedDate.get(MONTH) + val dayOfMonth = selectedDate.get(DAY_OF_MONTH) val datePickerDialog = DatePickerDialog(dateTimePickerNFormView.context, this, year, month, dayOfMonth) @@ -102,8 +101,8 @@ open class DateTimePickerViewBuilder(final override val nFormView: NFormView) : } private fun launchTimePickerDialog() { - val hour = selectedDate.get(Calendar.HOUR_OF_DAY) - val minute = selectedDate.get(Calendar.MINUTE) + val hour = selectedDate.get(HOUR_OF_DAY) + val minute = selectedDate.get(MINUTE) val isTwentyFourHour = false val timePickerDialog = TimePickerDialog(dateTimePickerNFormView.context, this, hour, minute, isTwentyFourHour) @@ -112,28 +111,24 @@ open class DateTimePickerViewBuilder(final override val nFormView: NFormView) : override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { selectedDate.set(year, month, dayOfMonth) - textInputEditText.setText(getFormattedDate()) - dateTimePickerNFormView.viewDetails.value = selectedDate.timeInMillis - dateTimePickerNFormView.dataActionListener?.onPassData(dateTimePickerNFormView.viewDetails) + updateViewData() } override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { selectedDate.set( - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH), - hourOfDay, - minute + calendar.get(YEAR), calendar.get(MONTH), calendar.get(DAY_OF_MONTH), hourOfDay, minute ) + updateViewData() + } + + fun updateViewData() { textInputEditText.setText(getFormattedDate()) dateTimePickerNFormView.viewDetails.value = selectedDate.timeInMillis dateTimePickerNFormView.dataActionListener?.onPassData(dateTimePickerNFormView.viewDetails) } - private fun getFormattedDate(): String = - SimpleDateFormat( - dateDisplayFormat, Locale.getDefault() - ).format(Date(selectedDate.timeInMillis)) + private fun getFormattedDate(): String = SimpleDateFormat(dateDisplayFormat, Locale.getDefault()) + .format(Date(selectedDate.timeInMillis)) fun resetDatetimePickerValue() { textInputEditText.setText("") diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/NumberSelectorViewBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/NumberSelectorViewBuilder.kt index ea0c3c47..e1404fd0 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/NumberSelectorViewBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/NumberSelectorViewBuilder.kt @@ -1,26 +1,30 @@ package com.nerdstone.neatformcore.views.builders +import android.graphics.drawable.GradientDrawable import android.os.Build import android.view.View import android.widget.LinearLayout import android.widget.PopupMenu import android.widget.TextView +import android.widget.Toast import androidx.core.content.ContextCompat import com.nerdstone.neatformcore.R import com.nerdstone.neatformcore.domain.builders.ViewBuilder import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.utils.getViewsByTagValue import com.nerdstone.neatformcore.views.widgets.NumberSelectorNFormView +import timber.log.Timber import java.util.* open class NumberSelectorViewBuilder(final override val nFormView: NFormView) : ViewBuilder { - private var visibleNumbers = 1 private var firstNumber = 1 - private var lastNumber = visibleNumbers - private var maxValue = visibleNumbers + var visibleNumbers = 1 + var lastNumber = visibleNumbers + var maxValue = visibleNumbers private val numberSelectorNFormView = nFormView as NumberSelectorNFormView override val acceptedAttributes = Utils.convertEnumToSet(NumberSelectorProperties::class.java) @@ -89,12 +93,12 @@ open class NumberSelectorViewBuilder(final override val nFormView: NFormView) : (firstNumber..this.visibleNumbers).forEach { number -> val item = getNumberSelectorItem(number) if (this.visibleNumbers == 1) { - setNumberSelectorBackground(item, false) + setNumberSelectorBackground(item, selected = false) numberSelectorLayout.addView(item) numberSelectorNFormView.addView(numberSelectorLayout) return } - setNumberSelectorBackground(item, false) + setNumberSelectorBackground(item, selected = false) numberSelectorLayout.addView(item) } numberSelectorNFormView.addView(numberSelectorLayout) @@ -120,7 +124,7 @@ open class NumberSelectorViewBuilder(final override val nFormView: NFormView) : setTag(R.id.is_number_selector, true) setPadding(0, 20, 0, 20) setOnClickListener { - setNumberSelectorBackground(this, true) + setNumberSelectorBackground(this, selected = true) resetNumberSelectorBackground(getTag(R.id.number_selector_key) as String) if (number == visibleNumbers && maxValue > visibleNumbers) { showPopupMenu(this) @@ -131,22 +135,55 @@ open class NumberSelectorViewBuilder(final override val nFormView: NFormView) : } } - private fun setNumberSelectorBackground(item: TextView, isSelected: Boolean) { + private fun setNumberSelectorBackground( + item: TextView, selected: Boolean, enabled: Boolean = true + ) { when { - isSelected && lastNumber > 1 -> when (getValue(item.text.toString())) { - firstNumber -> item.setBackgroundResource(R.drawable.num_selector_selected_left_bg) - lastNumber -> item.setBackgroundResource(R.drawable.num_selector_selected_right_bg) - else -> item.setBackgroundResource(R.drawable.num_selector_selected_flat_bg) + selected && lastNumber > 1 -> when (getValue(item.text.toString())) { + firstNumber -> { + item.setBackgroundResource(R.drawable.num_selector_selected_left_bg) + changeDrawableColor(item, enabled) + } + lastNumber -> { + item.setBackgroundResource(R.drawable.num_selector_selected_right_bg) + changeDrawableColor(item, enabled) + } + else -> { + item.setBackgroundResource(R.drawable.num_selector_selected_flat_bg) + changeDrawableColor(item, enabled) + } } - !isSelected && lastNumber > 1 -> when (getValue(item.text.toString())) { + !selected && lastNumber > 1 -> when (getValue(item.text.toString())) { firstNumber -> item.setBackgroundResource(R.drawable.num_selector_left_bg) lastNumber -> item.setBackgroundResource(R.drawable.num_selector_right_bg) else -> item.setBackgroundResource(R.drawable.num_selector_flat_bg) } - isSelected && lastNumber == 1 -> item.setBackgroundResource(R.drawable.num_selector_selected_round_bg) - !isSelected && lastNumber == 1 -> item.setBackgroundResource(R.drawable.num_selector_round_bg) + selected && lastNumber == 1 -> { + item.setBackgroundResource(R.drawable.num_selector_selected_round_bg) + changeDrawableColor(item, enabled) + } + !selected && lastNumber == 1 -> item.setBackgroundResource(R.drawable.num_selector_round_bg) + } + setNumberTextColor(item, selected) + } + + private fun changeDrawableColor(item: TextView, enabled: Boolean) { + with(item.background as GradientDrawable) { + when (enabled) { + true -> { + apply { + colors = intArrayOf( + R.color.numberSelectorItemSelected, R.color.numberSelectorItemSelected + ) + } + } + else -> { + apply { + colors = intArrayOf(R.color.colorDarkGrey, R.color.colorDarkGrey) + } + } + } } - setNumberTextColor(item, isSelected) } private fun setNumberTextColor(item: TextView, isSelected: Boolean) { @@ -156,8 +193,7 @@ open class NumberSelectorViewBuilder(final override val nFormView: NFormView) : ContextCompat.getColor(numberSelectorNFormView.context, R.color.colorWhite) ) else -> item.setTextColor( - numberSelectorNFormView.context.resources - .getColor(R.color.colorWhite) + numberSelectorNFormView.context.resources.getColor(R.color.colorWhite) ) } } else { @@ -166,24 +202,27 @@ open class NumberSelectorViewBuilder(final override val nFormView: NFormView) : ContextCompat.getColor(numberSelectorNFormView.context, R.color.colorBlack) ) else -> item.setTextColor( - numberSelectorNFormView.context.resources - .getColor(R.color.colorBlack) + numberSelectorNFormView.context.resources.getColor(R.color.colorBlack) ) } } } private fun resetNumberSelectorBackground(tag: String) { - (numberSelectorNFormView as View) - .getViewsByTagValue(R.id.is_number_selector, true) - .map { it as TextView } + getNumberSelectorTextViews() .forEach { view -> if (view.getTag(R.id.number_selector_key) as String != tag) { - setNumberSelectorBackground(view, false) + setNumberSelectorBackground(view, selected = false) } } } + private fun getNumberSelectorTextViews(): List { + return (numberSelectorNFormView as View) + .getViewsByTagValue(R.id.is_number_selector, true) + .map { it as TextView } + } + private fun getValue(text: String): Int { return text.replace("+", "").trim().toInt() } @@ -212,5 +251,38 @@ open class NumberSelectorViewBuilder(final override val nFormView: NFormView) : numberSelectorNFormView.dataActionListener?.onPassData(numberSelectorNFormView.viewDetails) resetNumberSelectorBackground(Constants.ANY_TAG) } + + fun setValue(value: Any, enabled: Boolean) { + val textViews = getNumberSelectorTextViews() + val index = when (value) { + is Double -> value.toInt() + is Int -> value + else -> -1 + } + if (index <= maxValue) { + var textView: TextView? = null + if (index in 0..visibleNumbers) { + textView = textViews[index] + } else if (index >= visibleNumbers) { + lastNumber = index + textView = textViews[visibleNumbers].apply { + text = index.toString() + } + } + textView?.also { + setNumberSelectorBackground(it, selected = true, enabled = enabled) + resetNumberSelectorBackground(it.getTag(R.id.number_selector_key) as String) + passValue(it) + } + textViews.forEach { it.setReadOnlyState(enabled) } + } else { + Toast.makeText( + numberSelectorNFormView.context, + "Error setting value for ${numberSelectorNFormView.viewDetails.name} -> index $index out of bounds.", + Toast.LENGTH_SHORT + ).show() + Timber.e("Error setting number selector value index ($index) > than max ($maxValue)") + } + } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/SpinnerViewBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/SpinnerViewBuilder.kt index 9e899dcb..f5f53ed1 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/SpinnerViewBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/SpinnerViewBuilder.kt @@ -17,8 +17,8 @@ open class SpinnerViewBuilder(final override val nFormView: NFormView) : ViewBui private val spinnerNFormView = nFormView as SpinnerNFormView private val spinnerOptions = mutableListOf() - private val materialSpinner = SmartMaterialSpinner(spinnerNFormView.context) private val optionsNamesMap = hashMapOf() + val materialSpinner = SmartMaterialSpinner(spinnerNFormView.context) enum class SpinnerProperties { TEXT, SEARCHABLE diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/TextInputEditTextBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/TextInputEditTextBuilder.kt index 9fde671b..efaeec57 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/TextInputEditTextBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/TextInputEditTextBuilder.kt @@ -23,7 +23,6 @@ open class TextInputEditTextBuilder(final override val nFormView: NFormView) : V override val acceptedAttributes get() = Utils.convertEnumToSet(TextInputEditTextViewProperties::class.java) - override fun buildView() { createEditText() ViewUtils.applyViewAttributes( @@ -85,17 +84,12 @@ open class TextInputEditTextBuilder(final override val nFormView: NFormView) : V } textInputEditText.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(p0: Editable?) { - //Implement - } + override fun afterTextChanged(p0: Editable?) = Unit - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { - //Implement - } + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit override fun onTextChanged( - text: CharSequence, start: Int, lengthBefore: Int, - lengthAfter: Int + text: CharSequence, start: Int, lengthBefore: Int, lengthAfter: Int ) { textInputEditTextNFormView.dataActionListener?.also { if (text.isNotEmpty()) { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt index 2532a5c6..dc1514d0 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt @@ -24,6 +24,7 @@ class MultiChoiceCheckBox : LinearLayout, NFormView { override val viewBuilder = MultiChoiceCheckBoxViewBuilder(this) override val viewDetails = NFormViewDetails(this) override var formValidator: FormValidator = NeatFormValidator.INSTANCE + override var initialValue: Any? = null private var checkBoxOptionsTextSize: Float = 0f private var labelTextSize: Float = 0f diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt index 22ee0cd2..936414a0 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt @@ -23,6 +23,7 @@ class RadioGroupView : LinearLayout, NFormView { override val viewBuilder = RadioGroupViewBuilder(this) override val viewDetails = NFormViewDetails(this) override var formValidator: FormValidator = NeatFormValidator.INSTANCE + override var initialValue: Any? = null init { orientation = VERTICAL diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt index 949abcba..07332556 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt @@ -1,9 +1,6 @@ package com.nerdstone.neatformcore.views.handlers import android.view.View -import androidx.appcompat.view.ContextThemeWrapper -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.ViewModelProvider import com.nerdstone.neatformcore.domain.listeners.DataActionListener import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewDetails @@ -11,7 +8,6 @@ import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.utils.ViewUtils -import com.nerdstone.neatformcore.viewmodel.DataViewModel /** * @author Elly Nerdstone @@ -33,29 +29,33 @@ class ViewDispatcher private constructor() : DataActionListener { * @param viewDetails the details of the view that has just dispatched a value */ override fun onPassData(viewDetails: NFormViewDetails) { - + ViewUtils.getDataViewModel(viewDetails).details.value?.also { - if (it[viewDetails.name] != viewDetails.value) { - it[viewDetails.name] = - NFormViewData( - viewDetails.view.javaClass.simpleName, - viewDetails.value, viewDetails.metadata - ) + with(viewDetails) { + if (it[this.name] != this.value) { + it[this.name] = + NFormViewData( + type = view.javaClass.simpleName, + value = value, + metadata = metadata, + visible = view.visibility == View.VISIBLE + ) - val nFormView = viewDetails.view as NFormView - nFormView.validateValue() + val nFormView = view as NFormView + nFormView.validateValue() - //Fire rules for calculations and other fields watching on this current field - val calculations = (viewDetails.view as NFormView).viewProperties.calculations - if (rulesFactory.subjectsRegistry.containsKey(viewDetails.name.trim()) || calculations != null) { - rulesFactory.updateFactsAndExecuteRules(viewDetails) - } + //Fire rules for calculations and other fields watching on this current field + val calculations = (view as NFormView).viewProperties.calculations + if (rulesFactory.subjectsRegistry.containsKey(name.trim()) || calculations != null) { + rulesFactory.updateFactsAndExecuteRules(this) + } - if (viewDetails.value == null && Utils.isFieldRequired(nFormView) - && viewDetails.view.visibility == View.VISIBLE - ) { - nFormView.formValidator.requiredFields.add(viewDetails.name) + if (value == null && Utils.isFieldRequired(nFormView) + && view.visibility == View.VISIBLE + ) { + nFormView.formValidator.requiredFields.add(name) + } } } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewVisibilityChangeHandler.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewVisibilityChangeHandler.kt index 33018a86..9e267eb5 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewVisibilityChangeHandler.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewVisibilityChangeHandler.kt @@ -10,7 +10,9 @@ class ViewVisibilityChangeHandler private constructor() : VisibilityChangeListen override fun onVisibilityChanged(changedView: View, visibility: Int) { ViewUtils.animateView(changedView) if (changedView is NFormView) { - if (visibility == View.GONE && changedView.viewDetails.value != null) { + if (visibility == View.GONE && changedView.viewDetails.value != null + && changedView.initialValue == null + ) { changedView.resetValueWhenHidden() } changedView.trackRequiredField() diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt index 513a8ee7..14cfdd62 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt @@ -11,6 +11,7 @@ import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.utils.removeAsterisk import com.nerdstone.neatformcore.views.builders.CheckBoxViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler @@ -24,6 +25,7 @@ class CheckBoxNFormView : AppCompatCheckBox, NFormView { override val viewBuilder = CheckBoxViewBuilder(this) override val viewDetails = NFormViewDetails(this) override var formValidator: FormValidator = NeatFormValidator.INSTANCE + override var initialValue: Any? = null constructor(context: Context) : super(context) @@ -49,7 +51,9 @@ class CheckBoxNFormView : AppCompatCheckBox, NFormView { override fun trackRequiredField() = ViewUtils.handleRequiredStatus(this) override fun setValue(value: Any, enabled: Boolean) { - TODO("Not yet implemented") + initialValue = value + if (value is Map<*, *>) isChecked = value.size == 1 && value.containsKey(viewDetails.name) + setReadOnlyState(enabled) } override fun validateValue(): Boolean { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt index 03d512ed..d0bda3c8 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt @@ -11,6 +11,7 @@ import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.views.builders.DateTimePickerViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler @@ -23,6 +24,7 @@ class DateTimePickerNFormView : TextInputLayout, NFormView { override val viewBuilder = DateTimePickerViewBuilder(this) override val viewDetails = NFormViewDetails(this) override var formValidator: FormValidator = NeatFormValidator.INSTANCE + override var initialValue: Any? = null constructor(context: Context) : super(context) @@ -41,11 +43,16 @@ class DateTimePickerNFormView : TextInputLayout, NFormView { } override fun setValue(value: Any, enabled: Boolean) { - TODO("Not yet implemented") + when (value) { + is Double -> viewBuilder.selectedDate.timeInMillis = value.toLong() + is Long -> viewBuilder.selectedDate.timeInMillis = value + } + viewBuilder.updateViewData() + setReadOnlyState(enabled) } override fun setVisibility(visibility: Int) { - super.setVisibility( visibility) + super.setVisibility(visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt index 07bd8923..79979758 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt @@ -11,6 +11,7 @@ import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.utils.removeAsterisk import com.nerdstone.neatformcore.views.builders.EditTextViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler @@ -24,6 +25,7 @@ class EditTextNFormView : AppCompatEditText, NFormView { override val viewBuilder = EditTextViewBuilder(this) override var viewDetails = NFormViewDetails(this) override var formValidator: FormValidator = NeatFormValidator.INSTANCE + override var initialValue: Any? = null constructor(context: Context) : super(context) @@ -60,7 +62,7 @@ class EditTextNFormView : AppCompatEditText, NFormView { override fun setValue(value: Any, enabled: Boolean) { setText(value as String) - isEnabled = enabled + setReadOnlyState(enabled) } override fun setVisibility(visibility: Int) { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt index 8e13019c..d844da8f 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt @@ -25,6 +25,7 @@ class NotificationNFormView : FrameLayout, NFormView, CalculationChangeListener override val viewBuilder = NotificationViewBuilder(this) override val viewDetails = NFormViewDetails(this) override var formValidator: FormValidator = NeatFormValidator.INSTANCE + override var initialValue: Any? = null private val rulesHandler = NFormRulesHandler.INSTANCE @@ -47,7 +48,5 @@ class NotificationNFormView : FrameLayout, NFormView, CalculationChangeListener viewBuilder.updateNotificationText(calculationField) } - override fun setValue(value: Any, enabled: Boolean) { - TODO("Not yet implemented") - } + override fun setValue(value: Any, enabled: Boolean) = Unit } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt index 3d44b435..50e73f37 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt @@ -23,6 +23,7 @@ class NumberSelectorNFormView : LinearLayout, NFormView { override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE + override var initialValue: Any? = null constructor(context: Context) : super(context) @@ -37,13 +38,14 @@ class NumberSelectorNFormView : LinearLayout, NFormView { override fun trackRequiredField() = ViewUtils.handleRequiredStatus(this) override fun setValue(value: Any, enabled: Boolean) { - TODO("Not yet implemented") + initialValue = value + viewBuilder.setValue(value, enabled) } override fun validateValue() = formValidator.validateLabeledField(this) override fun setVisibility(visibility: Int) { - super.setVisibility( visibility) + super.setVisibility(visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt index 3775cf8b..b30642f5 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt @@ -5,12 +5,15 @@ import android.util.AttributeSet import android.widget.LinearLayout import com.nerdstone.neatformcore.domain.listeners.DataActionListener import com.nerdstone.neatformcore.domain.listeners.VisibilityChangeListener +import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.rules.NeatFormValidator +import com.nerdstone.neatformcore.utils.VALUE import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.views.builders.SpinnerViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler @@ -23,6 +26,7 @@ class SpinnerNFormView : LinearLayout, NFormView { override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE + override var initialValue: Any? = null init { orientation = VERTICAL @@ -37,13 +41,24 @@ class SpinnerNFormView : LinearLayout, NFormView { override fun resetValueWhenHidden() = viewBuilder.resetSpinnerValue() override fun setValue(value: Any, enabled: Boolean) { - TODO("Not yet implemented") + initialValue = value + viewBuilder.materialSpinner.apply { + when (value) { + is Map<*, *> -> { + setSelection(item.indexOf(value[VALUE])) + } + is NFormViewData -> { + setSelection(item.indexOf(value.value as String)) + } + } + setReadOnlyState(enabled) + } } override fun validateValue() = formValidator.validateField(this).first override fun setVisibility(visibility: Int) { - super.setVisibility( visibility) + super.setVisibility(visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt index d253790a..b5a5c020 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt @@ -11,6 +11,7 @@ import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.views.builders.TextInputEditTextBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler @@ -23,6 +24,7 @@ class TextInputEditTextNFormView : TextInputLayout, NFormView { override val viewBuilder = TextInputEditTextBuilder(this) override var viewDetails = NFormViewDetails(this) override var formValidator: FormValidator = NeatFormValidator.INSTANCE + override var initialValue: Any? = null constructor(context: Context) : super(context) @@ -35,8 +37,9 @@ class TextInputEditTextNFormView : TextInputLayout, NFormView { } override fun setValue(value: Any, enabled: Boolean) { + initialValue = value this.editText?.setText(value as String) - isEnabled = enabled + setReadOnlyState(enabled) } override fun validateValue(): Boolean { @@ -48,7 +51,7 @@ class TextInputEditTextNFormView : TextInputLayout, NFormView { } override fun setVisibility(visibility: Int) { - super.setVisibility( visibility) + super.setVisibility(visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) } } \ No newline at end of file diff --git a/neat-form-core/src/main/res/values/strings.xml b/neat-form-core/src/main/res/values/strings.xml index 8c2516bf..ef380d84 100644 --- a/neat-form-core/src/main/res/values/strings.xml +++ b/neat-form-core/src/main/res/values/strings.xml @@ -6,4 +6,5 @@ notification info notification_type OK + Error setting value diff --git a/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt b/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt index 76f73959..1547568c 100644 --- a/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt +++ b/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt @@ -22,6 +22,7 @@ class CustomImageView(context: Context) : CircleImageView(context), NFormView { override val viewBuilder = CustomImageViewBuilder(this) override var viewDetails = NFormViewDetails(this) override var formValidator: FormValidator = NeatFormValidator.INSTANCE + override var initialValue: Any? = null override fun resetValueWhenHidden() = Unit diff --git a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt index 74664c97..6fc1189e 100644 --- a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt +++ b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt @@ -41,7 +41,7 @@ class FormActivity : AppCompatActivity(), StepperActions { "openmrs_entity_parent": "" }, "type": "TextInputEditTextNFormView", - "value": "4" + "value": "54" }, "child": { "meta_data": { @@ -50,7 +50,48 @@ class FormActivity : AppCompatActivity(), StepperActions { "openmrs_entity_parent": "" }, "type": "TextInputEditTextNFormView", - "value": "adult" + "value": "child" + }, + "dob": { + "type": "DateTimePickerNFormView", + "value": 1589555422331 + }, + "time": { + "type": "DateTimePickerNFormView", + "value": 1589555422335 + }, + "adult": { + "type": "TextInputEditTextNFormView", + "value": "0723721920" + }, + "email_subscription": { + "type": "CheckBoxNFormView", + "value": { + "email_subscription": "Subscribe to email notifications" + }, + "visible": true + }, + "no_prev_pregnancies": { + "type": "NumberSelectorNFormView", + "value": 1, + "visible": true + }, + "gender": { + "type": "SpinnerNFormView", + "value": { + "value": "Male" + } + }, + "country": { + "type": "SpinnerNFormView", + "value": { + "meta_data": { + "country_code": "+61" + }, + "value": "Australia", + "visible": true + }, + "visible": true } } """.trimIndent() @@ -124,6 +165,7 @@ class FormActivity : AppCompatActivity(), StepperActions { ).build() ) } + updateForm() replaceView(mainLayout, (formBuilder as JsonFormBuilder).neatStepperLayout) } FormType.stepperCustomized -> { @@ -147,7 +189,9 @@ class FormActivity : AppCompatActivity(), StepperActions { private fun updateForm() { formBuilder?.apply { if (viewModel.details.value?.isEmpty()!!) { - updateFormData(previousFormData, mutableSetOf("age")) + updateFormData( + previousFormData, mutableSetOf("dob", "time", "email_subscription", "gender", "country", "no_prev_pregnancies") + ) } } } diff --git a/sample/src/main/res/layout/sample_one_form_custom_layout.xml b/sample/src/main/res/layout/sample_one_form_custom_layout.xml index 322a1775..537137bf 100644 --- a/sample/src/main/res/layout/sample_one_form_custom_layout.xml +++ b/sample/src/main/res/layout/sample_one_form_custom_layout.xml @@ -11,6 +11,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" + android:weightSum="3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> @@ -33,7 +34,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_weight="1" - android:visibility="gone"> + android:visibility="visible"> + android:visibility="visible"> Date: Sun, 24 May 2020 00:31:48 +0300 Subject: [PATCH 06/15] Complete implementation of set value - handled implementation for checkbox and radio button Signed-off-by: Elly Kitoto --- README.md | 4 +- neat-form-core/build.gradle | 8 +- .../domain/builders/FormBuilder.kt | 31 +-- .../domain/listeners/FormDataListener.kt | 21 ++ .../neatformcore/form/json/JsonFormBuilder.kt | 192 ++++++++++-------- .../neatformcore/rules/NFormRulesHandler.kt | 11 +- .../neatformcore/utils/Extensions.kt | 10 +- .../nerdstone/neatformcore/utils/Helpers.kt | 53 ++++- .../com/nerdstone/neatformcore/utils/Utils.kt | 18 +- .../nerdstone/neatformcore/utils/ViewUtils.kt | 34 +++- .../neatformcore/viewmodel/DataViewModel.kt | 3 +- .../viewmodel/ReadOnlyFieldsViewModel.kt | 15 ++ .../MultiChoiceCheckBoxViewBuilder.kt | 47 ++--- .../views/builders/RadioGroupViewBuilder.kt | 35 ++-- .../views/containers/MultiChoiceCheckBox.kt | 30 +-- .../views/containers/RadioGroupView.kt | 9 +- .../nerdstone/neatform/form/FormActivity.kt | 64 ++++-- 17 files changed, 378 insertions(+), 207 deletions(-) create mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/FormDataListener.kt create mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/ReadOnlyFieldsViewModel.kt diff --git a/README.md b/README.md index 76bd57b8..9d0edbab 100644 --- a/README.md +++ b/README.md @@ -851,7 +851,7 @@ Custom `sample_one_form_custom_layout.xml` layout used for rendering views * [Easy Rules](https://github.com/j-easy/easy-rules) - Rules Engine library * [Mockk](https://mockk.io/) - Testing Framework Kotlin * [GSON](https://github.com/google/gson) - Parsing JSON files -* [Smart Material Spinner](https://github.com/Chivorns/SmartMaterialSpinner) - Powerful android spinner library +* [Smart Material Spinner](https://github.com/Chivorns/SmartMaterialSpinner) - Powerful android spinner library ## RoadMap @@ -862,6 +862,7 @@ Custom `sample_one_form_custom_layout.xml` layout used for rendering views | ✔️ |️ Form Fields Validation | | ✔️ |️ Support Multi-Step Forms | | ✔️ |️ Rules Engine integration - handle form skip logic and calculations | +| ❌ |️ Support image and location picker and barcode reader | | ❌ |️ Multi language support | | ❌ |️ Ability to obtain and render `JSON`form from server | @@ -869,7 +870,6 @@ Custom `sample_one_form_custom_layout.xml` layout used for rendering views ## Awesome Contributors -* [ellykits](https://github.com/ellykits) * [cozej4](https://github.com/cozej4) ## License diff --git a/neat-form-core/build.gradle b/neat-form-core/build.gradle index c9748716..ad66f194 100644 --- a/neat-form-core/build.gradle +++ b/neat-form-core/build.gradle @@ -70,9 +70,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation 'androidx.multidex:multidex:2.0.1' implementation "androidx.core:core-ktx:1.2.0" - api "androidx.appcompat:appcompat:1.1.0" - api 'androidx.lifecycle:lifecycle-extensions:2.2.0' - api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + implementation "androidx.appcompat:appcompat:1.1.0" + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" api("com.nerdstone:neat-android-stepper:1.0.6") { transitive = true exclude group: 'androidx.appcompat', module: 'appcompat' @@ -87,7 +87,7 @@ dependencies { api 'org.jeasy:easy-rules-core:3.3.0' api 'org.jeasy:easy-rules-mvel:3.3.0' api 'com.google.code.gson:gson:2.8.6' - api 'com.github.chivorns:smartmaterialspinner:1.1.6' + api 'com.github.chivorns:smartmaterialspinner:1.2.1' testImplementation 'junit:junit:4.12' testImplementation 'org.robolectric:robolectric:4.3.1' diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt index 91b52f6d..ad912cd8 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt @@ -8,6 +8,7 @@ import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.viewmodel.DataViewModel +import com.nerdstone.neatformcore.viewmodel.ReadOnlyFieldsViewModel import kotlin.reflect.KClass /** @@ -39,19 +40,21 @@ interface FormBuilder { var neatStepperLayout: NeatStepperLayout - var viewModel: DataViewModel + var dataViewModel: DataViewModel var formValidator: FormValidator var registeredViews: HashMap> + var readOnlyFieldsViewModel: ReadOnlyFieldsViewModel + /** * THis is the method that hooks up everything on the form builder. It parses the file say JSON file that it has * been provided with, reads the rules from Assets directory and generate the actual views. The functionality * for generating views is delegated to [createFormViews] */ fun buildForm( - jsonFormStepBuilderModel: JsonFormStepBuilderModel? = null, viewList: List? = null + jsonFormStepBuilderModel: JsonFormStepBuilderModel? = null, viewList: List? = null ): FormBuilder /** @@ -64,8 +67,8 @@ interface FormBuilder { * */ fun createFormViews( - context: Context, views: List? = null, - jsonFormStepBuilderModel: JsonFormStepBuilderModel? = null + context: Context, views: List? = null, + jsonFormStepBuilderModel: JsonFormStepBuilderModel? = null ) /** @@ -74,7 +77,7 @@ interface FormBuilder { * uses Rules engine that allows you to write rules either in JSON or YML format. */ fun registerFormRulesFromFile( - context: Context, rulesFileType: RulesFactory.RulesFileType + context: Context, rulesFileType: RulesFactory.RulesFileType ): Boolean /** @@ -100,14 +103,16 @@ interface FormBuilder { fun registerViews() /** - * Use this method to update the view model which will in turn update its observers (views) - * This method expect [formDataJson] to comply with the json string that was returned by the form builder. - * The string will be parsed into a map of [NFormViewData] with the key being the name of the field - * then use the new map as the data for the view model. - * - * Any field that exists in the list of [readOnlyFields] will be disabled. + * Call this method before building the form to supply the fields with data obtained from [formDataJson]. You + * can optionally pass [readOnlyFields] which will be disabled. */ + fun withFormData( + formDataJson: String, readOnlyFields: MutableSet = mutableSetOf() + ): FormBuilder - fun updateFormData(formDataJson: String, readOnlyFields: MutableSet = mutableSetOf()) - + /** + * This method is called right after the views have been created. The data provided will be delegated to + * the view model which will call the method for setting values on its observers (fields) + */ + fun preFillForm() } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/FormDataListener.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/FormDataListener.kt new file mode 100644 index 00000000..ae320678 --- /dev/null +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/FormDataListener.kt @@ -0,0 +1,21 @@ +package com.nerdstone.neatformcore.domain.listeners + +import com.nerdstone.neatformcore.domain.model.NFormViewData + +/** + * @author Elly Nerdstone + * + * This interface is used by the form builder to communicate with the steps. + * [onReceiveDataEvent] method is called when the values of the fields in the form are updated. + * + * This updated record is passed to the view model that the Steps(Fragments) are watching on. + */ + +interface FormDataListener { + /** + * When new form [formFieldsData] has been received by the form builder, it is passed to this method. + */ + fun onReceiveDataEvent(formFieldsData: FormFieldsData) +} + +class FormFieldsData(val data: HashMap) \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index 1f029945..84831f6a 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -8,9 +8,10 @@ import android.view.ViewGroup import android.widget.ScrollView import android.widget.Toast import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider -import com.google.gson.Gson +import com.nerdstone.neatandroidstepper.core.domain.StepperActions import com.nerdstone.neatandroidstepper.core.model.StepModel import com.nerdstone.neatandroidstepper.core.stepper.Step import com.nerdstone.neatandroidstepper.core.stepper.StepVerificationState @@ -19,12 +20,13 @@ import com.nerdstone.neatandroidstepper.core.widget.NeatStepperLayout import com.nerdstone.neatformcore.R import com.nerdstone.neatformcore.datasource.AssetFile import com.nerdstone.neatformcore.domain.builders.FormBuilder +import com.nerdstone.neatformcore.domain.listeners.FormDataListener +import com.nerdstone.neatformcore.domain.listeners.FormFieldsData import com.nerdstone.neatformcore.domain.model.JsonFormStepBuilderModel import com.nerdstone.neatformcore.domain.model.NForm import com.nerdstone.neatformcore.domain.model.NFormContent import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.FormValidator -import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.form.common.FormErrorDialog import com.nerdstone.neatformcore.form.json.JsonParser.parseJson import com.nerdstone.neatformcore.rules.NeatFormValidator @@ -33,6 +35,7 @@ import com.nerdstone.neatformcore.rules.RulesFactory.RulesFileType import com.nerdstone.neatformcore.utils.* import com.nerdstone.neatformcore.utils.Constants.ViewType import com.nerdstone.neatformcore.viewmodel.DataViewModel +import com.nerdstone.neatformcore.viewmodel.ReadOnlyFieldsViewModel import com.nerdstone.neatformcore.views.containers.MultiChoiceCheckBox import com.nerdstone.neatformcore.views.containers.RadioGroupView import com.nerdstone.neatformcore.views.containers.VerticalRootView @@ -69,6 +72,8 @@ object JsonFormConstants { * main thread using [DispatcherProvider.main] scope. */ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { + private var formDataJson: String? = null + private lateinit var readOnlyFields: MutableSet private var mainLayout: ViewGroup? = null private val viewDispatcher: ViewDispatcher = ViewDispatcher.INSTANCE private val rulesFactory: RulesFactory = RulesFactory.INSTANCE @@ -80,10 +85,10 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { override var formString: String? = null override lateinit var neatStepperLayout: NeatStepperLayout override lateinit var context: Context - override lateinit var viewModel: DataViewModel + override lateinit var dataViewModel: DataViewModel override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var registeredViews = hashMapOf>() - private var readOnlyFields = mutableSetOf() + override lateinit var readOnlyFieldsViewModel: ReadOnlyFieldsViewModel constructor(context: Context, fileSource: String, mainLayout: ViewGroup?) : this() { @@ -91,7 +96,10 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { this.fileSource = fileSource this.mainLayout = mainLayout this.neatStepperLayout = NeatStepperLayout(context) - this.viewModel = ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] + this.dataViewModel = + ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] + this.readOnlyFieldsViewModel = + ViewModelProvider(context)[ReadOnlyFieldsViewModel::class.java] } constructor(jsonString: String, context: Context, mainLayout: ViewGroup?) @@ -100,7 +108,10 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { this.context = context this.mainLayout = mainLayout this.neatStepperLayout = NeatStepperLayout(context) - this.viewModel = ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] + this.dataViewModel = + ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] + this.readOnlyFieldsViewModel = + ViewModelProvider(context)[ReadOnlyFieldsViewModel::class.java] } init { @@ -141,13 +152,14 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { Timber.e(throwable) } } + if (jsonFormStepBuilderModel.isNull()) preFillForm() return this } private fun parseJsonForm(): NForm? { return when { - formString != null -> parseJson(formString) - fileSource != null -> parseJson( + formString.isNotNull() -> parseJson(formString) + fileSource.isNotNull() -> parseJson( AssetFile.readAssetFileAsString(context, fileSource!!) ) else -> null @@ -155,44 +167,41 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { } /*** - * @param context android context + * Create views within the provided [context]. If [jsonFormStepBuilderModel] is also specified use + * it to configure the form steps. + * + * If the [views] is also not null, the form will be */ override fun createFormViews( context: Context, views: List?, jsonFormStepBuilderModel: JsonFormStepBuilderModel? ) { - if (form != null) { + if (form.isNotNull()) { when { - jsonFormStepBuilderModel != null && (mainLayout == null || mainLayout != null) -> { - neatStepperLayout.stepperModel = jsonFormStepBuilderModel.stepperModel - - if (jsonFormStepBuilderModel.stepperActions != null) + jsonFormStepBuilderModel.isNotNull() && (mainLayout == null || mainLayout.isNotNull()) -> { + neatStepperLayout.stepperModel = jsonFormStepBuilderModel!!.stepperModel + if (jsonFormStepBuilderModel.stepperActions.isNotNull()) neatStepperLayout.stepperActions = jsonFormStepBuilderModel.stepperActions - - val fragmentsList: MutableList = mutableListOf() - + val stepFragmentsList = mutableListOf() form!!.steps.withIndex().forEach { (index, formContent) -> val rootView = VerticalRootView(context) rootView.formBuilder = this addViewsToVerticalRootView(views, index, formContent, rootView) - val stepFragment = StepFragment.newInstance( - index, - StepModel.Builder() - .title(form!!.formName) - .subTitle(formContent.stepName as CharSequence) - .build(), - rootView + stepFragmentsList.add( + StepFragment.newInstance( + index, StepModel.Builder().title(form!!.formName) + .subTitle(formContent.stepName as CharSequence) + .build(), rootView, formDataJson + ) ) - fragmentsList.add(stepFragment) } neatStepperLayout.setUpViewWithAdapter( StepperPagerAdapter( - (context as FragmentActivity).supportFragmentManager, - fragmentsList + (context as FragmentActivity).supportFragmentManager, stepFragmentsList ) ) neatStepperLayout.showLoadingIndicators(false) } - mainLayout != null && jsonFormStepBuilderModel == null -> { + mainLayout.isNotNull() && jsonFormStepBuilderModel == null -> { val formViews = ScrollView(context) form!!.steps.withIndex().forEach { (index, formContent) -> val rootView = VerticalRootView(context) @@ -200,23 +209,24 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { addViewsToVerticalRootView(views, index, formContent, rootView) } mainLayout?.addView(formViews) + readOnlyFieldsViewModel.readOnlyFields.value?.let { this.readOnlyFields = it } + dataViewModel.details.observe(context as LifecycleOwner, Observer { + ViewUtils.updateFieldValues(it, context, readOnlyFields) + }) } - else -> Toast.makeText( - context, R.string.form_builder_error, Toast.LENGTH_LONG - ).show() + else -> Toast.makeText(context, R.string.form_builder_error, Toast.LENGTH_LONG) + .show() } } - observeViewModel() } private fun addViewsToVerticalRootView( - customViews: List?, stepIndex: Int, - formContent: NFormContent, verticalRootView: VerticalRootView + customViews: List?, stepIndex: Int, formContent: NFormContent, + verticalRootView: VerticalRootView ) { - val view = customViews?.getOrNull(stepIndex) when { - view != null -> { + view.isNotNull() -> { verticalRootView.addView(view) verticalRootView.addChildren(formContent.fields, viewDispatcher, true) } @@ -230,33 +240,34 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { override fun getFormDataAsJson(): String { if (formValidator.invalidFields.isEmpty() && formValidator.requiredFields.isEmpty()) { - val formDetails = hashMapOf().also { it[JsonFormConstants.FORM_NAME] = form?.formName it[JsonFormConstants.FORM_METADATA] = form?.formMetadata it[JsonFormConstants.FORM_VERSION] = form?.formVersion - it[JsonFormConstants.FORM_DATA] = viewModel.details.value + it[JsonFormConstants.FORM_DATA] = dataViewModel.details.value } - return Utils.getJsonFromModel(formDetails) } FormErrorDialog(context).show() return "" } - override fun updateFormData(formDataJson: String, readOnlyFields: MutableSet) { - if (this::viewModel.isInitialized) { - this.readOnlyFields.addAll(readOnlyFields) - val viewData = Gson() - .parseJson>(formDataJson) - .filter { it.value.value != null } - viewModel.updateDetails(viewData as HashMap) + override fun withFormData(formDataJson: String, readOnlyFields: MutableSet): + FormBuilder { + this.formDataJson = formDataJson + this.readOnlyFields = readOnlyFields + return this + } + + override fun preFillForm() { + if (this::dataViewModel.isInitialized && formDataJson.isNotNull()) { + this.readOnlyFieldsViewModel.readOnlyFields.value?.addAll(readOnlyFields) + dataViewModel.updateDetails(FormUtils.parseFormDataJson(formDataJson!!)) } } - override fun registerFormRulesFromFile( - context: Context, rulesFileType: RulesFileType - ): Boolean { + override fun registerFormRulesFromFile(context: Context, rulesFileType: RulesFileType) + : Boolean { form?.rulesFile?.also { rulesFactory.readRulesFromFile(context, it, rulesFileType) } @@ -265,7 +276,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { override fun getFormData(): HashMap { if (formValidator.invalidFields.isEmpty() && formValidator.requiredFields.isEmpty()) { - return viewModel.details.value!! + return dataViewModel.details.value!! } FormErrorDialog(context).show() return hashMapOf() @@ -282,82 +293,89 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { registeredViews[ViewType.RADIO_GROUP] = RadioGroupView::class registeredViews[ViewType.TOAST_NOTIFICATION] = NotificationNFormView::class } - - /** - * This method watches for changes in the view model by observing the mutable live data. - */ - private fun observeViewModel() { - this.viewModel = ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] - viewModel.details.observe(context as FragmentActivity, Observer { - it.filterNot { entry -> entry.key.endsWith(Constants.RuleActions.CALCULATION) } - .forEach { entry -> - val view: View? = ViewUtils.findViewWithKey(entry.key, context) - entry.value.value?.let { realValue -> - if (view != null) (view as NFormView).setValue( - realValue, !readOnlyFields.contains(entry.key) - ) - } - Timber.d("Updated field %s : %s", entry.key, entry.value.value) - } - }) - } } const val FRAGMENT_VIEW = "fragment_view" const val FRAGMENT_INDEX = "index" +const val FORM_DATA_JSON = "form_fields_data" -class StepFragment : Step { +class StepFragment : Step, FormDataListener { + + private lateinit var dataViewModel: DataViewModel private var index: Int? = null private var formView: View? = null + private var formDataJson: String? = null constructor() constructor(stepModel: StepModel) : super(stepModel) companion object { + @JvmStatic fun newInstance( - index: Int, stepModel: StepModel, verticalRootView: VerticalRootView - ): StepFragment { - - val args = Bundle().apply { - putInt(FRAGMENT_INDEX, index) - putSerializable(FRAGMENT_VIEW, verticalRootView) + index: Int, stepModel: StepModel, verticalRootView: VerticalRootView, + formDataJson: String? + ) = + StepFragment(stepModel).apply { + arguments = Bundle().apply { + putInt(FRAGMENT_INDEX, index) + putSerializable(FRAGMENT_VIEW, verticalRootView) + putString(FORM_DATA_JSON, formDataJson) + } } - return StepFragment(stepModel).apply { arguments = args } - } } + override fun onAttach(context: Context) { + super.onAttach(context) + if (context !is StepperActions) throw ClassCastException("$context MUST implement FormActions") + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.also { index = it.getInt(FRAGMENT_INDEX) formView = it.getSerializable(FRAGMENT_VIEW) as VerticalRootView? + formDataJson = it.getString(FORM_DATA_JSON) } } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - if (formView != null && formView?.parent != null) { - return formView?.parent as View + if (formView.isNotNull() && formView?.parent.isNotNull()) return formView?.parent as View + return ScrollView(activity).apply { addView(formView) } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + with(ViewModelProvider(this)[DataViewModel::class.java]) { + dataViewModel = this + details.observe(viewLifecycleOwner, Observer { + ViewUtils.updateFieldValues(it, activity as Context, mutableSetOf()) + }) + formDataJson?.let { + FormUtils.parseFormDataJson(it).also { data -> dataViewModel.updateDetails(data) } + } } - val scroller = ScrollView(activity) - scroller.addView(formView) - return scroller + } + + override fun onDestroyView() { + super.onDestroyView() + formView = null } override fun onSaveInstanceState(outState: Bundle) { //No call for super(). Bug on API Level > 11. } - override fun verifyStep(): StepVerificationState { - return StepVerificationState(true, null) - } + override fun verifyStep() = StepVerificationState(true, null) override fun onSelected() = Unit override fun onError(stepVerificationState: StepVerificationState) = Unit + override fun onReceiveDataEvent(formFieldsData: FormFieldsData) { + dataViewModel.updateDetails(formFieldsData.data) + } + } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt index 183e73f9..a3572ad4 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt @@ -6,6 +6,7 @@ import com.nerdstone.neatformcore.domain.listeners.CalculationChangeListener import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.RulesHandler import com.nerdstone.neatformcore.utils.Constants +import com.nerdstone.neatformcore.utils.DisposableList import com.nerdstone.neatformcore.utils.ViewUtils import org.jeasy.rules.api.Facts import org.jeasy.rules.api.Rule @@ -16,7 +17,7 @@ class NFormRulesHandler private constructor() : RulesHandler { override lateinit var formBuilder: FormBuilder override lateinit var executableRulesList: HashSet - var calculationListeners: MutableList = mutableListOf() + var calculationListeners: DisposableList = DisposableList() companion object { @@ -37,9 +38,7 @@ class NFormRulesHandler private constructor() : RulesHandler { if (rule != null && facts != null && !evaluationResult) { if (facts.asMap().containsKey(rule.name) && rule.name.toLowerCase(Locale.getDefault()) .endsWith(Constants.RuleActions.VISIBILITY) - ) { - facts.put(rule.name, false) - } + ) facts.put(rule.name, false) } } @@ -68,13 +67,13 @@ class NFormRulesHandler private constructor() : RulesHandler { filterCurrentRules(Constants.RuleActions.CALCULATION) .forEach { key -> val value = facts?.asMap()?.get(key) - formBuilder.viewModel.saveFieldValue(key, NFormViewData("Calculation", value, null)) + formBuilder.dataViewModel.saveFieldValue(key, NFormViewData("Calculation", value, null)) updateCalculationListeners(Pair(key, value)) } } private fun updateCalculationListeners(calculation: Pair) = - calculationListeners.forEach { it.onCalculationChanged(calculation) } + calculationListeners.get().forEach { it.onCalculationChanged(calculation) } fun hideOrShowField(key: String, isVisible: Boolean?) { val view = ViewUtils.findViewWithKey(key, formBuilder.context) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Extensions.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Extensions.kt index 51942dcc..794d4bf5 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Extensions.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Extensions.kt @@ -3,7 +3,6 @@ package com.nerdstone.neatformcore.utils import android.view.View import android.view.ViewGroup - /*** * Ported from this Gist on Github https://gist.github.com/orip/5566666 */ @@ -23,4 +22,11 @@ fun View.getViewsByTagValue(tag: Int, value: Any): List { fun String.removeAsterisk(): String { return this.trim().removeSuffix("*").trim() -} \ No newline at end of file +} + +fun Any?.isNotNull(): Boolean { + return this != null +} +fun Any?.isNull(): Boolean { + return this == null +} diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Helpers.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Helpers.kt index 20bd77ea..b54b3a2c 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Helpers.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Helpers.kt @@ -4,6 +4,8 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import java.lang.ref.WeakReference +import java.util.* /** * A helper class to execute tasks sequentially in coroutines. @@ -65,4 +67,53 @@ interface DispatcherProvider { fun unconfined(): CoroutineDispatcher = Dispatchers.Unconfined } -class DefaultDispatcherProvider : DispatcherProvider \ No newline at end of file +class DefaultDispatcherProvider : DispatcherProvider + +/** + * This is a weak collection list that internally uses [LinkedList]. We use this to hold weak references + * of items in order to avoid memory leaks as in the case oh caching listeners in a singleton class + */ +class DisposableList { + + private val linkedList: LinkedList> = LinkedList() + + fun add(item: T): Boolean { + val currentList = get() + for (oldItem in currentList) { + if (item === oldItem) { + return false + } + } + return linkedList.add(WeakReference(item)) + } + + fun get(): MutableList { + val finalList = arrayListOf() + val itemsToRemove = LinkedList>() + linkedList.forEach { weakReference -> + when (val item: T? = weakReference.get()) { + null -> itemsToRemove.add(weakReference) + else -> finalList.add(item) + } + } + itemsToRemove.forEach { weakReference -> + linkedList.remove(weakReference) + } + return finalList + } + + fun remove(item: T): Boolean { + var weakReferenceToRemove: WeakReference? = null + for (weakReference in linkedList) { + val currentItem: T? = weakReference.get() + if (currentItem === currentItem) { + weakReferenceToRemove = weakReference + break + } + } + return if (weakReferenceToRemove != null) { + linkedList.remove(weakReferenceToRemove) + true + } else false + } +} \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Utils.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Utils.kt index 66a8790b..1d244380 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Utils.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Utils.kt @@ -6,8 +6,11 @@ import android.os.Build import android.util.TypedValue import android.view.View import android.view.inputmethod.InputMethodManager +import com.google.gson.Gson import com.google.gson.GsonBuilder +import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.NFormView +import com.nerdstone.neatformcore.form.json.JsonParser.parseJson import java.util.* object ThemeColor { @@ -74,7 +77,7 @@ object Utils { .create().toJson(model) fun getOptionMetadata(nFormView: NFormView, optionName: String): Map? { - return nFormView.viewProperties.options?.first { option -> + return nFormView.viewProperties.options?.first { option -> option.name == optionName }?.viewMetadata } @@ -89,4 +92,17 @@ object DialogUtil { create() } } +} + +/** + * Utility methods for forms + */ +object FormUtils { + /** + * Parse [formDataJson] into a [HashMap] of field key against its [NFormViewData] + */ + fun parseFormDataJson(formDataJson: String): HashMap { + return Gson().parseJson>(formDataJson) + .filter { it.value.value.isNotNull() } as HashMap + } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt index 92d433c5..7e001a4a 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt @@ -18,6 +18,7 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModelProvider import com.nerdstone.neatformcore.BuildConfig import com.nerdstone.neatformcore.R +import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.NFormView @@ -60,12 +61,10 @@ object ViewUtils { try { getView(view as NFormView, viewProperty, viewDispatcher) } catch (e: Exception) { - Timber.e(e) - if (BuildConfig.DEBUG) - Toast.makeText( - context, "ERROR: The view with name ${viewProperty.name} " + - "defined in json form is missing in custom layout", LENGTH_SHORT - ).show() + val message = + "ERROR: The view with name ${viewProperty.name} defined in json form is missing in custom layout" + Timber.e(e, message) + if (BuildConfig.DEBUG) Toast.makeText(context, message, LENGTH_SHORT).show() } } else { val constructor = kClass.constructors.minBy { it.parameters.size } @@ -237,7 +236,8 @@ object ViewUtils { @Throws(Throwable::class) fun findViewWithKey(key: String, context: Context): View? { - val activityRootView = (context as Activity).findViewById(android.R.id.content).rootView + val activityRootView = + (context as Activity).findViewById(android.R.id.content).rootView return activityRootView.findViewWithTag(key) } @@ -245,4 +245,24 @@ object ViewUtils { isEnabled = enabled isFocusable = enabled } + + /** + * This method updates the values of the fields with the provided [fieldValues]. Fields that should be disabled + * are listed in the [readOnlyFields] + */ + fun updateFieldValues( + fieldValues: HashMap, context: Context, + readOnlyFields: MutableSet + ) { + fieldValues.filterNot { entry -> entry.key.endsWith(Constants.RuleActions.CALCULATION) } + .forEach { entry -> + val view: View? = findViewWithKey(entry.key, context) + entry.value.value?.let { realValue -> + if (view != null) (view as NFormView).setValue( + realValue, !readOnlyFields.contains(entry.key) + ) + } + Timber.d("Updated field %s : %s", entry.key, entry.value.value) + } + } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt index 1fdb0060..a2aa467b 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt @@ -1,5 +1,6 @@ package com.nerdstone.neatformcore.viewmodel +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.nerdstone.neatformcore.domain.model.NFormViewData @@ -12,7 +13,7 @@ import com.nerdstone.neatformcore.domain.model.NFormViewData class DataViewModel : ViewModel() { - var details: MutableLiveData> = MutableLiveData(HashMap()) + var details: LiveData> = MutableLiveData(HashMap()) fun saveFieldValue(fieldName: String, fieldValue: NFormViewData) { details.value?.set(fieldName, fieldValue) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/ReadOnlyFieldsViewModel.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/ReadOnlyFieldsViewModel.kt new file mode 100644 index 00000000..4c416076 --- /dev/null +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/ReadOnlyFieldsViewModel.kt @@ -0,0 +1,15 @@ +package com.nerdstone.neatformcore.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +/** + * @author Elly Nerdstone + *This view model is used to hold state of read only fields. which is a [MutableSet] of field names + */ +class ReadOnlyFieldsViewModel : ViewModel() { + private val _readOnlyFields: LiveData> = + MutableLiveData(mutableSetOf()) + val readOnlyFields: LiveData> = _readOnlyFields +} \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/MultiChoiceCheckBoxViewBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/MultiChoiceCheckBoxViewBuilder.kt index 426ba448..c08e4778 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/MultiChoiceCheckBoxViewBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/MultiChoiceCheckBoxViewBuilder.kt @@ -10,6 +10,7 @@ import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.utils.getViewsByTagValue import com.nerdstone.neatformcore.views.containers.MultiChoiceCheckBox import java.util.* @@ -21,7 +22,7 @@ open class MultiChoiceCheckBoxViewBuilder(final override val nFormView: NFormVie private var valuesMap: HashMap? = null enum class MultiChoiceCheckBoxProperties { - TEXT, OPTIONS_TEXT_SIZE,LABEL_TEXT_SIZE + TEXT, OPTIONS_TEXT_SIZE, LABEL_TEXT_SIZE } override val acceptedAttributes get() = Utils.convertEnumToSet(MultiChoiceCheckBoxProperties::class.java) @@ -30,7 +31,6 @@ open class MultiChoiceCheckBoxViewBuilder(final override val nFormView: NFormVie override fun buildView() { ViewUtils.applyViewAttributes( nFormView = multiChoiceCheckBox, - acceptedAttributes = acceptedAttributes, task = this::setViewProperties ) @@ -72,24 +72,19 @@ open class MultiChoiceCheckBoxViewBuilder(final override val nFormView: NFormVie setTag(R.id.is_checkbox_option, true) ViewUtils.applyCheckBoxStyle(multiChoiceCheckBox.context, checkBox) setOnCheckedChangeListener { compoundButton, isChecked -> - if (valuesMap == null) { - valuesMap = hashMapOf() - } + if (valuesMap == null) valuesMap = hashMapOf() + val fieldName = compoundButton.getTag(R.id.field_name) if (isChecked) { valuesMap?.put( - compoundButton.getTag(R.id.field_name) as String, + fieldName as String, NFormViewData( - type = null, - value = compoundButton.text.toString(), - metadata = Utils.getOptionMetadata( - multiChoiceCheckBox, - compoundButton.getTag(R.id.field_name) as String - ) + null, compoundButton.text.toString(), + Utils.getOptionMetadata(multiChoiceCheckBox, fieldName) ) ) handleExclusiveChecks(this) } else { - valuesMap?.remove(compoundButton.getTag(R.id.field_name) as String) + valuesMap?.remove(fieldName as String) } multiChoiceCheckBox.viewDetails.value = valuesMap multiChoiceCheckBox.dataActionListener?.onPassData(multiChoiceCheckBox.viewDetails) @@ -111,19 +106,16 @@ open class MultiChoiceCheckBoxViewBuilder(final override val nFormView: NFormVie private fun handleExclusiveChecks(checkBox: CheckBox) { val isExclusive = checkBox.getTag(R.id.is_exclusive_checkbox) as Boolean? val checkBoxes = (checkBox.parent as View).getViewsByTagValue(R.id.is_checkbox_option, true) - when (isExclusive) { null, false -> checkBoxes.forEach { view -> - if (view is CheckBox && view.getTag(R.id.is_exclusive_checkbox) != null && - view.getTag(R.id.is_exclusive_checkbox) == true - ) { + val exclusiveTag = view.getTag(R.id.is_exclusive_checkbox) + if (view is CheckBox && exclusiveTag != null && exclusiveTag == true) view.isChecked = false - } } else -> checkBoxes.forEach { view -> - if (view is CheckBox && view.getTag(R.id.field_name) != checkBox.getTag(R.id.field_name) && isExclusive == true) { + val fieldTag = view.getTag(R.id.field_name) + if (view is CheckBox && fieldTag != checkBox.getTag(R.id.field_name) && isExclusive == true) view.isChecked = false - } } } } @@ -131,15 +123,20 @@ open class MultiChoiceCheckBoxViewBuilder(final override val nFormView: NFormVie fun resetCheckBoxValues() { (multiChoiceCheckBox as View).getViewsByTagValue(R.id.is_checkbox_option, true) .map { it as CheckBox } - .forEach { view -> - if (view.isChecked) { - view.isChecked = false - } - } + .forEach { view -> if (view.isChecked) view.isChecked = false } valuesMap = null multiChoiceCheckBox.viewDetails.value = valuesMap multiChoiceCheckBox.dataActionListener?.onPassData(multiChoiceCheckBox.viewDetails) } + + fun setValue(selectedOptions: Set, enabled: Boolean) { + (multiChoiceCheckBox as View).getViewsByTagValue(R.id.is_checkbox_option, true) + .map { it as CheckBox } + .forEach { view -> + if (selectedOptions.contains(view.getTag(R.id.field_name))) view.isChecked = true + view.setReadOnlyState(enabled) + } + } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/RadioGroupViewBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/RadioGroupViewBuilder.kt index 4a67fddf..98e51ace 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/RadioGroupViewBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/builders/RadioGroupViewBuilder.kt @@ -10,6 +10,7 @@ import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.utils.getViewsByTagValue import com.nerdstone.neatformcore.views.containers.RadioGroupView import java.util.* @@ -24,7 +25,6 @@ open class RadioGroupViewBuilder(final override val nFormView: NFormView) : View override val acceptedAttributes get() = Utils.convertEnumToSet(RadioGroupViewProperties::class.java) - override fun buildView() { ViewUtils.applyViewAttributes( nFormView = radioGroupView, @@ -63,9 +63,8 @@ open class RadioGroupViewBuilder(final override val nFormView: NFormView) : View mutableMapOf( radioButton.getTag(R.id.field_name) to NFormViewData( - type = null, - value = radioButton.text.toString(), - metadata = Utils.getOptionMetadata( + null, radioButton.text.toString(), + Utils.getOptionMetadata( radioGroupView, radioButton.getTag(R.id.field_name) as String ) @@ -75,9 +74,8 @@ open class RadioGroupViewBuilder(final override val nFormView: NFormView) : View radioGroupView.dataActionListener?.onPassData(radioGroupView.viewDetails) } } + radioGroupView.addView(this) } - - radioGroupView.addView(radioButton) } /** @@ -87,12 +85,10 @@ open class RadioGroupViewBuilder(final override val nFormView: NFormView) : View (radioButton.parent as View).getViewsByTagValue(R.id.is_radio_group_option, true) .map { it as RadioButton } .forEach { view -> - if (view.getTag(R.id.field_name) as String != selectedOption && view.getTag(R.id.is_radio_group_option) != null && view.getTag( - R.id.is_radio_group_option - ) == true - ) { - view.isChecked = false - } + val optionTag = view.getTag(R.id.is_radio_group_option) + if (view.getTag(R.id.field_name) as String != selectedOption + && optionTag != null && optionTag == true + ) view.isChecked = false } } @@ -100,13 +96,22 @@ open class RadioGroupViewBuilder(final override val nFormView: NFormView) : View (radioGroupView as View).getViewsByTagValue(R.id.is_radio_group_option, true) .map { it as RadioButton } .forEach { view -> - if (view.isChecked) { - view.isChecked = false - } + if (view.isChecked) view.isChecked = false } radioGroupView.viewDetails.value = null radioGroupView.dataActionListener?.onPassData(radioGroupView.viewDetails) } + + fun setValue(selectedOption: String, enabled: Boolean) { + for (view in radioGroupView.getViewsByTagValue(R.id.is_radio_group_option, true) + .map { it as RadioButton }) { + val optionTag = view.getTag(R.id.is_radio_group_option) + if (view.getTag(R.id.field_name) as String == selectedOption + && optionTag != null && optionTag == true + ) view.isChecked = true + view.setReadOnlyState(enabled) + } + } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt index dc1514d0..46f5d5e5 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt @@ -6,6 +6,7 @@ import android.widget.LinearLayout import com.nerdstone.neatformcore.R import com.nerdstone.neatformcore.domain.listeners.DataActionListener import com.nerdstone.neatformcore.domain.listeners.VisibilityChangeListener +import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator @@ -17,6 +18,7 @@ import com.nerdstone.neatformcore.views.builders.MultiChoiceCheckBoxViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class MultiChoiceCheckBox : LinearLayout, NFormView { + override lateinit var viewProperties: NFormViewProperty override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = @@ -47,7 +49,14 @@ class MultiChoiceCheckBox : LinearLayout, NFormView { } override fun setValue(value: Any, enabled: Boolean) { - TODO("Not yet implemented") + initialValue = value + when (value) { + is Map<*, *> -> { + viewBuilder.setValue(value.keys, enabled) + } + is NFormViewData -> { + } + } } override fun validateValue(): Boolean = @@ -82,23 +91,4 @@ class MultiChoiceCheckBox : LinearLayout, NFormView { } } } - - /** - * adding passed xml attributes to MultiChoice Checkbox viewAttributes - */ - private fun setPassedAttributes(viewProperty: NFormViewProperty) { - if (checkBoxOptionsTextSize != 0f) { - viewProperty.viewAttributes?.put( - MultiChoiceCheckBoxViewBuilder.MultiChoiceCheckBoxProperties.OPTIONS_TEXT_SIZE.name, - checkBoxOptionsTextSize - ) - } - - if (labelTextSize != 0f) { - viewProperty.viewAttributes?.put( - MultiChoiceCheckBoxViewBuilder.MultiChoiceCheckBoxProperties.LABEL_TEXT_SIZE.name, - labelTextSize - ) - } - } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt index 936414a0..bafe8209 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt @@ -43,11 +43,16 @@ class RadioGroupView : LinearLayout, NFormView { formValidator.validateLabeledField(this) override fun setValue(value: Any, enabled: Boolean) { - TODO("Not yet implemented") + initialValue = value + when (value) { + is Map<*, *> -> { + viewBuilder.setValue(value.keys.first() as String, enabled) + } + } } override fun setVisibility(visibility: Int) { - super.setVisibility( visibility) + super.setVisibility(visibility) visibilityChangeListener?.onVisibilityChanged(this, visibility) } } \ No newline at end of file diff --git a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt index 6fc1189e..df3e4b90 100644 --- a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt +++ b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt @@ -79,7 +79,7 @@ class FormActivity : AppCompatActivity(), StepperActions { "gender": { "type": "SpinnerNFormView", "value": { - "value": "Male" + "value": "Female" } }, "country": { @@ -92,6 +92,40 @@ class FormActivity : AppCompatActivity(), StepperActions { "visible": true }, "visible": true + }, + "choose_language": { + "type": "MultiChoiceCheckBox", + "value": { + "kisw": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "A123123123123", + "openmrs_entity_parent": "" + }, + "value": "Kiswahili", + "visible": true + }, + "french": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "A123123123123", + "openmrs_entity_parent": "" + }, + "value": "French", + "visible": true + } + }, + "visible": true + }, + "wiki_contribution": { + "type": "RadioGroupView", + "value": { + "yes": { + "value": "Yes", + "visible": true + } + }, + "visible": true } } """.trimIndent() @@ -141,10 +175,13 @@ class FormActivity : AppCompatActivity(), StepperActions { formBuilder = JsonFormBuilder(this, formData.filePath, formLayout) formBuilder?.also { it.registeredViews["custom_image"] = CustomImageView::class - it.buildForm() - updateForm() + it.withFormData( + previousFormData, mutableSetOf( + "dob", "time", "email_subscription", "country", + "no_prev_pregnancies", "choose_language", "wiki_contribution" + ) + ).buildForm() } - } FormType.embeddableCustomized -> { formBuilder = JsonFormBuilder(this, formData.filePath, formLayout) @@ -158,14 +195,10 @@ class FormActivity : AppCompatActivity(), StepperActions { formBuilder = JsonFormBuilder(this, formData.filePath, null) formBuilder?.also { it.registeredViews["custom_image"] = CustomImageView::class - it.buildForm( - JsonFormStepBuilderModel.Builder( - this, - stepperModel - ).build() + it.withFormData(previousFormData, mutableSetOf()).buildForm( + JsonFormStepBuilderModel.Builder(this, stepperModel).build() ) } - updateForm() replaceView(mainLayout, (formBuilder as JsonFormBuilder).neatStepperLayout) } FormType.stepperCustomized -> { @@ -175,7 +208,6 @@ class FormActivity : AppCompatActivity(), StepperActions { JsonFormStepBuilderModel.Builder(this, stepperModel).build(), views ) - updateForm() replaceView(mainLayout, (formBuilder as JsonFormBuilder).neatStepperLayout) } else -> Toast.makeText( @@ -186,16 +218,6 @@ class FormActivity : AppCompatActivity(), StepperActions { } } - private fun updateForm() { - formBuilder?.apply { - if (viewModel.details.value?.isEmpty()!!) { - updateFormData( - previousFormData, mutableSetOf("dob", "time", "email_subscription", "gender", "country", "no_prev_pregnancies") - ) - } - } - } - override fun onStepError(stepVerificationState: StepVerificationState) = Unit override fun onButtonNextClick(step: Step) = Unit From c527829bb9fe7e0a35e03164838a673ce0156808 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Sun, 24 May 2020 01:00:59 +0300 Subject: [PATCH 07/15] Delete FormDataListener Signed-off-by: Elly Kitoto --- .../domain/listeners/FormDataListener.kt | 21 ------------------- .../neatformcore/form/json/JsonFormBuilder.kt | 9 +------- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/FormDataListener.kt diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/FormDataListener.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/FormDataListener.kt deleted file mode 100644 index ae320678..00000000 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/listeners/FormDataListener.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.nerdstone.neatformcore.domain.listeners - -import com.nerdstone.neatformcore.domain.model.NFormViewData - -/** - * @author Elly Nerdstone - * - * This interface is used by the form builder to communicate with the steps. - * [onReceiveDataEvent] method is called when the values of the fields in the form are updated. - * - * This updated record is passed to the view model that the Steps(Fragments) are watching on. - */ - -interface FormDataListener { - /** - * When new form [formFieldsData] has been received by the form builder, it is passed to this method. - */ - fun onReceiveDataEvent(formFieldsData: FormFieldsData) -} - -class FormFieldsData(val data: HashMap) \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index 84831f6a..1bb1ed49 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -20,8 +20,6 @@ import com.nerdstone.neatandroidstepper.core.widget.NeatStepperLayout import com.nerdstone.neatformcore.R import com.nerdstone.neatformcore.datasource.AssetFile import com.nerdstone.neatformcore.domain.builders.FormBuilder -import com.nerdstone.neatformcore.domain.listeners.FormDataListener -import com.nerdstone.neatformcore.domain.listeners.FormFieldsData import com.nerdstone.neatformcore.domain.model.JsonFormStepBuilderModel import com.nerdstone.neatformcore.domain.model.NForm import com.nerdstone.neatformcore.domain.model.NFormContent @@ -299,7 +297,7 @@ const val FRAGMENT_VIEW = "fragment_view" const val FRAGMENT_INDEX = "index" const val FORM_DATA_JSON = "form_fields_data" -class StepFragment : Step, FormDataListener { +class StepFragment : Step { private lateinit var dataViewModel: DataViewModel private var index: Int? = null @@ -373,9 +371,4 @@ class StepFragment : Step, FormDataListener { override fun onSelected() = Unit override fun onError(stepVerificationState: StepVerificationState) = Unit - - override fun onReceiveDataEvent(formFieldsData: FormFieldsData) { - dataViewModel.updateDetails(formFieldsData.data) - } - } From 58d0fb47b714d6d22120d73f42f5b36f099029b9 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Sun, 24 May 2020 21:31:43 +0300 Subject: [PATCH 08/15] Test set value on views Signed-off-by: Elly Kitoto --- .../neatformcore/form/json/JsonFormBuilder.kt | 2 +- .../nerdstone/neatformcore/utils/Helpers.kt | 18 ++++++- .../views/containers/MultiChoiceCheckBox.kt | 9 +--- .../views/containers/RadioGroupView.kt | 6 +-- .../views/widgets/DateTimePickerNFormView.kt | 1 + .../views/widgets/EditTextNFormView.kt | 1 + .../junit/utils/DisposableListTest.kt | 36 +++++++++++++ .../builders/CheckBoxViewBuilderTest.kt | 21 ++++++-- .../builders/DateTimePickerViewBuilderTest.kt | 19 ++++++- .../builders/EditTextViewBuilderTest.kt | 9 ++++ .../builders/JsonFormBuilderTest.kt | 53 ++++++++++++++++++- .../MultiChoiceCheckBoxViewBuilderTest.kt | 30 +++++++++-- .../builders/NumberSelectorViewBuilderTest.kt | 11 ++++ .../builders/RadioGroupViewBuilderTest.kt | 14 +++++ .../builders/SpinnerViewBuilderTest.kt | 15 ++++++ .../builders/TextInputEditTextBuilderTest.kt | 10 ++++ 16 files changed, 230 insertions(+), 25 deletions(-) create mode 100644 neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/DisposableListTest.kt diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index 1bb1ed49..8e0cf1ed 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -70,7 +70,6 @@ object JsonFormConstants { * main thread using [DispatcherProvider.main] scope. */ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { - private var formDataJson: String? = null private lateinit var readOnlyFields: MutableSet private var mainLayout: ViewGroup? = null private val viewDispatcher: ViewDispatcher = ViewDispatcher.INSTANCE @@ -80,6 +79,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { var defaultContextProvider: DispatcherProvider var form: NForm? = null var fileSource: String? = null + var formDataJson: String? = null override var formString: String? = null override lateinit var neatStepperLayout: NeatStepperLayout override lateinit var context: Context diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Helpers.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Helpers.kt index b54b3a2c..cf7197e6 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Helpers.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/Helpers.kt @@ -70,6 +70,8 @@ interface DispatcherProvider { class DefaultDispatcherProvider : DispatcherProvider /** + * @author Elly Nerdstone + * * This is a weak collection list that internally uses [LinkedList]. We use this to hold weak references * of items in order to avoid memory leaks as in the case oh caching listeners in a singleton class */ @@ -77,6 +79,10 @@ class DisposableList { private val linkedList: LinkedList> = LinkedList() + /** + * Add a new [item] to the linked list. We first check if the item already exists in the list + * if not we add it otherwise we return false. + */ fun add(item: T): Boolean { val currentList = get() for (oldItem in currentList) { @@ -87,6 +93,13 @@ class DisposableList { return linkedList.add(WeakReference(item)) } + /** + * This method is used to retrieve a list of elements of type [T]. It also check if list has null + * values on the [WeakReference]. If so the items will be removed from the list. This is useful when + * trying to avoid memory leaks by the list maintaining references to items that had been garbage + * collected + * f + */ fun get(): MutableList { val finalList = arrayListOf() val itemsToRemove = LinkedList>() @@ -102,11 +115,14 @@ class DisposableList { return finalList } + /** + * This method is used to remove the provided [item] from the [linkedList] + */ fun remove(item: T): Boolean { var weakReferenceToRemove: WeakReference? = null for (weakReference in linkedList) { val currentItem: T? = weakReference.get() - if (currentItem === currentItem) { + if (currentItem === item) { weakReferenceToRemove = weakReference break } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt index 46f5d5e5..e30228c9 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt @@ -6,7 +6,6 @@ import android.widget.LinearLayout import com.nerdstone.neatformcore.R import com.nerdstone.neatformcore.domain.listeners.DataActionListener import com.nerdstone.neatformcore.domain.listeners.VisibilityChangeListener -import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator @@ -50,12 +49,8 @@ class MultiChoiceCheckBox : LinearLayout, NFormView { override fun setValue(value: Any, enabled: Boolean) { initialValue = value - when (value) { - is Map<*, *> -> { - viewBuilder.setValue(value.keys, enabled) - } - is NFormViewData -> { - } + if (value is Map<*, *>) { + viewBuilder.setValue(value.keys, enabled) } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt index bafe8209..3bf49e7d 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt @@ -44,10 +44,8 @@ class RadioGroupView : LinearLayout, NFormView { override fun setValue(value: Any, enabled: Boolean) { initialValue = value - when (value) { - is Map<*, *> -> { - viewBuilder.setValue(value.keys.first() as String, enabled) - } + if (value is Map<*, *>) { + viewBuilder.setValue(value.keys.first() as String, enabled) } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt index d0bda3c8..9e63faaf 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt @@ -43,6 +43,7 @@ class DateTimePickerNFormView : TextInputLayout, NFormView { } override fun setValue(value: Any, enabled: Boolean) { + initialValue = value when (value) { is Double -> viewBuilder.selectedDate.timeInMillis = value.toLong() is Long -> viewBuilder.selectedDate.timeInMillis = value diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt index 79979758..5f1e5bfd 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt @@ -61,6 +61,7 @@ class EditTextNFormView : AppCompatEditText, NFormView { } override fun setValue(value: Any, enabled: Boolean) { + initialValue = value setText(value as String) setReadOnlyState(enabled) } diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/DisposableListTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/DisposableListTest.kt new file mode 100644 index 00000000..ffbcc37f --- /dev/null +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/junit/utils/DisposableListTest.kt @@ -0,0 +1,36 @@ +package com.nerdstone.neatformcore.junit.utils + +import com.nerdstone.neatformcore.utils.DisposableList +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class DisposableListTest { + + private val disposableList = DisposableList() + + @Before + fun `Before everything`() { + disposableList.add("item1") + } + + @Test + fun `Should add a new item to the list`() { + Assert.assertTrue(disposableList.add("item2")) + Assert.assertFalse(disposableList.add("item1")) + Assert.assertEquals(disposableList.get().size, 2) + } + + @Test + fun `Should return list items`() { + Assert.assertEquals(disposableList.get().size, 1) + } + + @Test + fun `Should remove an item from the list`() { + disposableList.add("item2") + Assert.assertTrue(disposableList.remove("item2")) + Assert.assertFalse(disposableList.remove("item3")) + Assert.assertEquals(disposableList.get().size, 1) + } +} \ No newline at end of file diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt index a2e80667..5b0ff0cf 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt @@ -2,6 +2,7 @@ package com.nerdstone.neatformcore.robolectric.builders import android.view.View import com.nerdstone.neatformcore.TestNeatFormApp +import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.CheckBoxViewBuilder @@ -18,7 +19,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class CheckBoxViewBuilderTest : BaseJsonViewBuilderTest(){ +class CheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) private val checkBoxNFormView = CheckBoxNFormView(activity.get()) @@ -38,7 +39,7 @@ class CheckBoxViewBuilderTest : BaseJsonViewBuilderTest(){ fun `Should set text and textSize on checkbox`() { val text = "Am a checkbox" val checkBoxTextSize = 14 - viewProperty.viewAttributes = mutableMapOf("text" to text,"text_size" to checkBoxTextSize) + viewProperty.viewAttributes = mutableMapOf("text" to text, "text_size" to checkBoxTextSize) checkBoxViewBuilder.buildView() Assert.assertTrue(checkBoxNFormView.text.isNotEmpty() && checkBoxNFormView.text.toString() == text) Assert.assertTrue(checkBoxNFormView.textSize.toInt() == checkBoxTextSize) @@ -51,9 +52,10 @@ class CheckBoxViewBuilderTest : BaseJsonViewBuilderTest(){ viewProperty.requiredStatus = "yes:Am required" checkBoxViewBuilder.buildView() Assert.assertTrue( - checkBoxNFormView.text.toString().isNotEmpty() && checkBoxNFormView.text.toString().endsWith( - "*" - ) + checkBoxNFormView.text.toString().isNotEmpty() && checkBoxNFormView.text.toString() + .endsWith( + "*" + ) ) } @@ -85,6 +87,15 @@ class CheckBoxViewBuilderTest : BaseJsonViewBuilderTest(){ Assert.assertNull(checkBoxNFormView.viewDetails.value) } + @Test + fun `Should set value when provided`() { + checkBoxViewBuilder.buildView() + val valueHashMap = mapOf("name" to NFormViewData().apply { value = "Am a checkbox" }) + checkBoxNFormView.setValue(valueHashMap) + Assert.assertEquals(checkBoxNFormView.initialValue, valueHashMap) + Assert.assertTrue(checkBoxNFormView.isChecked) + } + @After fun `After everything else`() { unmockkAll() diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt index 4218583e..caa8cd45 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt @@ -7,6 +7,7 @@ import com.nerdstone.neatformcore.views.builders.DateTimePickerViewBuilder import com.nerdstone.neatformcore.views.widgets.DateTimePickerNFormView import io.mockk.spyk import io.mockk.unmockkAll +import io.mockk.verify import org.junit.After import org.junit.Assert import org.junit.Before @@ -17,7 +18,7 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) -class DateTimePickerViewBuilderTest : BaseJsonViewBuilderTest(){ +class DateTimePickerViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) private val dateTimePickerNFormView = DateTimePickerNFormView(activity.get()) @@ -99,7 +100,7 @@ class DateTimePickerViewBuilderTest : BaseJsonViewBuilderTest(){ hashMapOf("hint" to hint, "display_format" to "dd/MM/yyyy", "type" to type) dateTimePickerViewBuilder.buildView() dateTimePickerViewBuilder.textInputEditText.performClick() - dateTimePickerViewBuilder.onDateSet(spyk(), 2019, 0,1) + dateTimePickerViewBuilder.onDateSet(spyk(), 2019, 0, 1) Assert.assertTrue(dateTimePickerViewBuilder.textInputEditText.text.toString() == "01/01/2019") } @@ -115,6 +116,20 @@ class DateTimePickerViewBuilderTest : BaseJsonViewBuilderTest(){ Assert.assertTrue(dateTimePickerViewBuilder.textInputEditText.text.toString() == "11:30 AM") } + @Test + fun `Should set value to the date picker when provided`() { + dateTimePickerViewBuilder.buildView() + val timestamp = 1589555422331 + dateTimePickerNFormView.setValue(timestamp) + Assert.assertEquals(dateTimePickerNFormView.initialValue, timestamp) + Assert.assertEquals( + dateTimePickerNFormView.viewBuilder.selectedDate.timeInMillis, 1589555422331 + ) + Assert.assertEquals( + dateTimePickerNFormView.viewBuilder.textInputEditText.text.toString(), "2020-05-15" + ) + } + @After fun `After everything else`() { unmockkAll() diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt index 7786577f..a3f699ff 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt @@ -98,6 +98,15 @@ class EditTextViewBuilderTest : BaseJsonViewBuilderTest() { } + @Test + fun `Should set value to the edittext when provided`() { + editTextViewBuilder.buildView() + val textValue = "0723721920" + editTextNFormView.setValue(textValue) + Assert.assertEquals(editTextNFormView.initialValue, textValue) + Assert.assertEquals(editTextNFormView.text.toString(), "0723721920") + } + @After fun `After everything else`() { unmockkAll() diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt index 10db571e..4176bd7e 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt @@ -9,6 +9,7 @@ import android.widget.ScrollView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.Observer import androidx.viewpager.widget.ViewPager import com.nerdstone.neatandroidstepper.core.model.StepperModel import com.nerdstone.neatandroidstepper.core.stepper.StepperPagerAdapter @@ -17,6 +18,7 @@ import com.nerdstone.neatformcore.R import com.nerdstone.neatformcore.TestConstants import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.JsonFormStepBuilderModel +import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.form.json.FRAGMENT_VIEW import com.nerdstone.neatformcore.form.json.JsonFormBuilder import com.nerdstone.neatformcore.form.json.StepFragment @@ -24,11 +26,15 @@ import com.nerdstone.neatformcore.views.containers.MultiChoiceCheckBox import com.nerdstone.neatformcore.views.containers.RadioGroupView import com.nerdstone.neatformcore.views.containers.VerticalRootView import com.nerdstone.neatformcore.views.widgets.* +import io.mockk.impl.annotations.MockK +import io.mockk.mockk import io.mockk.spyk +import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel import kotlinx.coroutines.test.runBlockingTest -import org.junit.* +import org.junit.Assert +import org.junit.Rule +import org.junit.Test import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner @@ -42,6 +48,29 @@ class JsonFormBuilderTest { private val activity = Robolectric.buildActivity(AppCompatActivity::class.java).setup() private val mainLayout: LinearLayout = LinearLayout(activity.get()) private lateinit var jsonFormBuilder: JsonFormBuilder + var observer: Observer> = spyk() + private val previousFormData = """ + { + "age": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + }, + "type": "TextInputEditTextNFormView", + "value": "54" + }, + "child": { + "type": "TextInputEditTextNFormView", + "value": "yes" + }, + "adult": { + "type": "TextInputEditTextNFormView", + "value": "0723721920" + } + } + """.trimIndent() + @get:Rule var coroutinesTestRule = CoroutineTestRule() @@ -254,4 +283,24 @@ class JsonFormBuilderTest { } } + @Test + fun `Should build a pre-filled form`() { + coroutinesTestRule.runBlockingTest { + jsonFormBuilder = spyk( + JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE, mainLayout) + ) + jsonFormBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider + jsonFormBuilder + .withFormData(previousFormData, mutableSetOf("age", "child", "adult")) + .buildForm(null, null) + Assert.assertNotNull(jsonFormBuilder.form) + Assert.assertNotNull(jsonFormBuilder.formDataJson) + val details = jsonFormBuilder.dataViewModel.details + details.observeForever(observer) + verify { observer.onChanged(any()) } + Assert.assertEquals((details.value?.get("age") as NFormViewData).value, "54") + Assert.assertEquals((details.value?.get("child") as NFormViewData).value, "yes") + Assert.assertEquals((details.value?.get("adult") as NFormViewData).value, "0723721920") + } + } } \ No newline at end of file diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt index 7285924f..45798cce 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt @@ -64,13 +64,19 @@ class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { fun `Should set label for multi choice checkbox`() { val text = "Pick the programming languages" val labelTextSize = 20 - viewProperty.viewAttributes = mutableMapOf("text" to text,"label_text_size" to labelTextSize) + viewProperty.viewAttributes = + mutableMapOf("text" to text, "label_text_size" to labelTextSize) multiChoiceCheckBoxViewBuilder.buildView() val view = multiChoiceCheckBox.getChildAt(0) val textView = view.findViewById(R.id.labelTextView) Assert.assertTrue(textView.text.toString() == text) - Assert.assertTrue(textView.textSize == Utils.pixelsToSp(textView.context,labelTextSize.toFloat())) + Assert.assertTrue( + textView.textSize == Utils.pixelsToSp( + textView.context, + labelTextSize.toFloat() + ) + ) Assert.assertTrue(multiChoiceCheckBox.findViewById(R.id.errorMessageTextView).visibility == View.GONE) } @@ -91,7 +97,8 @@ class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { fun `Should create multiple checkboxes when options are defined`() { val text = "Pick your programming languages" val checkBoxOptionsTextSize = 22 - viewProperty.viewAttributes = mutableMapOf("text" to text,"options_text_size" to checkBoxOptionsTextSize) + viewProperty.viewAttributes = + mutableMapOf("text" to text, "options_text_size" to checkBoxOptionsTextSize) viewProperty.options = listOf(checkBoxOption1, checkBoxOption2, checkBoxOption3, checkBoxOption4) multiChoiceCheckBoxViewBuilder.buildView() @@ -193,7 +200,24 @@ class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { (1 until multiChoiceCheckBox.childCount).forEach { i -> Assert.assertTrue(!(multiChoiceCheckBox.getChildAt(i) as CheckBox).isChecked) } + } + @Test + fun `Should set value on multi choice checkbox when provided`() { + viewProperty.viewAttributes = hashMapOf("text" to "Pick your languages") + viewProperty.options = + listOf(checkBoxOption1, checkBoxOption2, checkBoxOption3, checkBoxOption4) + ViewUtils.setupView(multiChoiceCheckBox, viewProperty, spyk()) + val valueHashMap = mapOf( + "kotlin" to NFormViewData(value = "Kotlin"), + "java" to NFormViewData(value = "Java") + ) + multiChoiceCheckBox.setValue(valueHashMap, false) + Assert.assertEquals(multiChoiceCheckBox.initialValue, valueHashMap) + Assert.assertTrue(multiChoiceCheckBox.viewDetails.value is HashMap<*, *>) + val hashMap = multiChoiceCheckBox.viewDetails.value as HashMap<*, *> + Assert.assertTrue(hashMap.containsKey("kotlin")) + Assert.assertTrue(hashMap.containsKey("java")) } @After diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt index 4fc97186..79b2805b 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt @@ -1,6 +1,7 @@ package com.nerdstone.neatformcore.robolectric.builders import android.view.View +import android.view.ViewGroup import android.widget.LinearLayout import android.widget.TextView import com.nerdstone.neatformcore.R @@ -114,6 +115,16 @@ class NumberSelectorViewBuilderTest : BaseJsonViewBuilderTest() { Assert.assertNull(numberSelector.viewDetails.value) } + @Test + fun `Should set value to the number selector when provided`() { + ViewUtils.setupView(numberSelector, viewProperty, spyk()) + val textValue = 3 + numberSelector.setValue(textValue, false) + Assert.assertEquals(numberSelector.initialValue, textValue) + Assert.assertEquals(numberSelector.viewDetails.value, 3) + Assert.assertFalse((numberSelector.getChildAt(1) as ViewGroup).getChildAt(0).isEnabled) + } + @After fun `After everything else`() { unmockkAll() diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt index b700df46..531eb653 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt @@ -141,6 +141,20 @@ class RadioGroupViewBuilderTest : BaseJsonViewBuilderTest() { } } + @Test + fun `Should set value on radio group when provided`() { + viewProperty.viewAttributes = hashMapOf("text" to "Pick your preferred language") + viewProperty.options = + listOf(radioOption1, radioOption2, radioOption3) + ViewUtils.setupView(radioGroupView, viewProperty, spyk()) + val valueHashMap = mapOf("kotlin" to NFormViewData(value = "Kotlin")) + radioGroupView.setValue(valueHashMap) + Assert.assertEquals(radioGroupView.initialValue, valueHashMap) + Assert.assertTrue(radioGroupView.viewDetails.value is HashMap<*, *>) + val hashMap = radioGroupView.viewDetails.value as HashMap<*, *> + Assert.assertTrue(hashMap.containsKey("kotlin")) + } + @After fun `After everything else`() { unmockkAll() diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt index ded77e12..eca516ff 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt @@ -4,6 +4,7 @@ import android.view.View import com.chivorn.smartmaterialspinner.SmartMaterialSpinner import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormSubViewProperty +import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.SpinnerViewBuilder @@ -115,6 +116,20 @@ class SpinnerViewBuilderTest : BaseJsonViewBuilderTest(){ Assert.assertTrue(materialSpinner.item[materialSpinner.selectedItemPosition] == "Female") } + @Test + fun `Should set value on spinner when provided`() { + viewProperty.viewAttributes = hashMapOf("text" to "Pick your gender") + viewProperty.options = + listOf(spinnerOption1, spinnerOption2, spinnerOption3) + ViewUtils.setupView(spinnerNFormView, viewProperty, spyk()) + val valueHashMap = mapOf("value" to "Female") + spinnerNFormView.setValue(valueHashMap) + Assert.assertEquals(spinnerNFormView.initialValue, valueHashMap) + Assert.assertTrue(spinnerNFormView.viewDetails.value is NFormViewData) + val viewData = spinnerNFormView.viewDetails.value as NFormViewData + Assert.assertEquals(viewData.value ,"Female") + } + @After fun `After everything else`() { unmockkAll() diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt index 24571fd2..31a4c637 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt @@ -88,4 +88,14 @@ class TextInputEditTextBuilderTest : BaseJsonViewBuilderTest(){ Assert.assertFalse(textInputEditTextNFormView.validateValue()) Assert.assertTrue(textInputEditTextNFormView.error == "Please enter a valid email address") } + + + @Test + fun `Should set value to the edittext when provided`() { + testInputLayoutBuilder.buildView() + val textValue = "0723721920" + textInputEditTextNFormView.setValue(textValue) + Assert.assertEquals(textInputEditTextNFormView.initialValue, textValue) + Assert.assertEquals(textInputEditTextNFormView.editText?.text.toString(), "0723721920") + } } \ No newline at end of file From 357062760a5f163d107605e35bb7c809befaa4fa Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Mon, 25 May 2020 21:41:04 +0300 Subject: [PATCH 09/15] Refactor form builder - Split JSON form creation into 2 (JsonFormEmbedded & JsonFormStepper) JsonEmbbededForm - creates a form that is embededded to the provided layout JsonStepperForm - creates a form with a stepper Signed-off-by: Elly Kitoto --- README.md | 4 +- neat-form-core/build.gradle | 2 +- .../domain/builders/FormBuilder.kt | 36 +-- .../domain/model/JsonFormStepBuilderModel.kt | 22 -- .../neatformcore/form/common/FormActions.kt | 8 + .../neatformcore/form/json/JsonFormBuilder.kt | 236 ++---------------- .../form/json/JsonFormEmbedded.kt | 76 ++++++ .../neatformcore/form/json/JsonFormStepper.kt | 167 +++++++++++++ .../nerdstone/neatformcore/utils/ViewUtils.kt | 6 +- .../neatformcore/viewmodel/DataViewModel.kt | 4 +- ...nlyFieldsViewModel.kt => FormViewModel.kt} | 11 +- .../builders/BaseJsonViewBuilderTest.kt | 2 +- .../builders/JsonFormBuilderTest.kt | 52 ++-- .../rules/NeatFormValidatorTest.kt | 17 +- .../robolectric/rules/RulesFactoryTest.kt | 4 +- sample/src/main/AndroidManifest.xml | 39 +-- .../main/assets/sample/sample_two_form.json | 4 +- .../com/nerdstone/neatform/StepperActivity.kt | 89 +++++++ .../nerdstone/neatform/form/FormActivity.kt | 187 ++------------ .../com/nerdstone/neatform/utils/Constants.kt | 105 ++++++++ .../src/main/res/layout/activity_stepper.xml | 11 + .../layout/sample_one_form_custom_layout.xml | 2 +- sample/src/main/res/values/colors.xml | 1 + sample/src/main/res/values/strings.xml | 2 +- 24 files changed, 578 insertions(+), 509 deletions(-) delete mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/JsonFormStepBuilderModel.kt create mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/form/common/FormActions.kt create mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormEmbedded.kt create mode 100644 neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormStepper.kt rename neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/{ReadOnlyFieldsViewModel.kt => FormViewModel.kt} (50%) create mode 100644 sample/src/main/java/com/nerdstone/neatform/StepperActivity.kt create mode 100644 sample/src/main/java/com/nerdstone/neatform/utils/Constants.kt create mode 100644 sample/src/main/res/layout/activity_stepper.xml diff --git a/README.md b/README.md index 9d0edbab..a3d2d711 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Add the library as a dependency to your app's `build.gradle` file ```groovy dependencies { //.... - implementation "com.nerdstone:neat-form-core:1.0.13" + implementation "com.nerdstone:neat-form-core:1.1.0" //.... } @@ -82,7 +82,7 @@ Add the library in the dependency section of your application's `build.gradle` f ```groovy dependencies { //consume library - use the latest version available on github packages - implementation "com.nerdstone:neat-form-core:1.0.13" + implementation "com.nerdstone:neat-form-core:1.1.0" //.... } diff --git a/neat-form-core/build.gradle b/neat-form-core/build.gradle index ad66f194..60674195 100644 --- a/neat-form-core/build.gradle +++ b/neat-form-core/build.gradle @@ -11,7 +11,7 @@ jacoco { def properties = new Properties() properties.load(new FileInputStream(rootProject.file("local.properties"))) -version = "1.0.13" +version = "1.1.0" android { compileSdkVersion 28 diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt index ad912cd8..860e7a89 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt @@ -1,14 +1,11 @@ package com.nerdstone.neatformcore.domain.builders import android.content.Context -import android.view.View -import com.nerdstone.neatandroidstepper.core.widget.NeatStepperLayout -import com.nerdstone.neatformcore.domain.model.JsonFormStepBuilderModel import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.viewmodel.DataViewModel -import com.nerdstone.neatformcore.viewmodel.ReadOnlyFieldsViewModel +import com.nerdstone.neatformcore.viewmodel.FormViewModel import kotlin.reflect.KClass /** @@ -17,10 +14,6 @@ import kotlin.reflect.KClass * [formString] is the string representation of the form depending on the schema which can be JSON * YAML or even XML * - * The form builder requires a FragmentActivity [context] which it will use to create its views. If the form is - * generated with a stepper. That is when the generated form is not embedded into an existing view. The - * [neatStepperLayout] is the view holding the form steps. - * * The form builder also uses the [formValidator] to validate fields. * * Before building views, views are first registered withing the [registeredViews] map. This is a map @@ -38,38 +31,13 @@ interface FormBuilder { val context: Context - var neatStepperLayout: NeatStepperLayout - var dataViewModel: DataViewModel var formValidator: FormValidator var registeredViews: HashMap> - var readOnlyFieldsViewModel: ReadOnlyFieldsViewModel - - /** - * THis is the method that hooks up everything on the form builder. It parses the file say JSON file that it has - * been provided with, reads the rules from Assets directory and generate the actual views. The functionality - * for generating views is delegated to [createFormViews] - */ - fun buildForm( - jsonFormStepBuilderModel: JsonFormStepBuilderModel? = null, viewList: List? = null - ): FormBuilder - - /** - * This method creates within [context]. It also has an optional [jsonFormStepBuilderModel] parameter - * that can be used to customize the stepper that will be shown on the [neatStepperLayout] - * - * When using custom layout to build the form you need to provide the layouts to the [views] list. - * The form builder will generate the views for steps in the order that the layouts are added to the list - * i.e. for step one views will be on the layout in position 0 of the [views] - * - */ - fun createFormViews( - context: Context, views: List? = null, - jsonFormStepBuilderModel: JsonFormStepBuilderModel? = null - ) + var formViewModel: FormViewModel /** * This method reads the rules file available withing the given [context] and returns true when done diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/JsonFormStepBuilderModel.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/JsonFormStepBuilderModel.kt deleted file mode 100644 index 94057939..00000000 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/model/JsonFormStepBuilderModel.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.nerdstone.neatformcore.domain.model - -import com.nerdstone.neatandroidstepper.core.domain.StepperActions -import com.nerdstone.neatandroidstepper.core.model.StepperModel - -class JsonFormStepBuilderModel private constructor() { - var stepperModel = StepperModel.Builder().build() - var stepperActions: StepperActions? = null - - data class Builder( - var stepperActions: StepperActions? = null, - var stepperModel: StepperModel = StepperModel.Builder().build() - ) { - - fun build(): JsonFormStepBuilderModel { - return JsonFormStepBuilderModel().also { - it.stepperModel = stepperModel - it.stepperActions = stepperActions - } - } - } -} \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/common/FormActions.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/common/FormActions.kt new file mode 100644 index 00000000..0be3604b --- /dev/null +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/common/FormActions.kt @@ -0,0 +1,8 @@ +package com.nerdstone.neatformcore.form.common + +import com.nerdstone.neatandroidstepper.core.domain.StepperActions +import com.nerdstone.neatformcore.domain.builders.FormBuilder + +interface FormActions: StepperActions { + var formBuilder: FormBuilder +} \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index 8e0cf1ed..63c46611 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -1,26 +1,11 @@ package com.nerdstone.neatformcore.form.json import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import android.widget.ScrollView -import android.widget.Toast import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider -import com.nerdstone.neatandroidstepper.core.domain.StepperActions -import com.nerdstone.neatandroidstepper.core.model.StepModel -import com.nerdstone.neatandroidstepper.core.stepper.Step -import com.nerdstone.neatandroidstepper.core.stepper.StepVerificationState -import com.nerdstone.neatandroidstepper.core.stepper.StepperPagerAdapter -import com.nerdstone.neatandroidstepper.core.widget.NeatStepperLayout -import com.nerdstone.neatformcore.R import com.nerdstone.neatformcore.datasource.AssetFile import com.nerdstone.neatformcore.domain.builders.FormBuilder -import com.nerdstone.neatformcore.domain.model.JsonFormStepBuilderModel import com.nerdstone.neatformcore.domain.model.NForm import com.nerdstone.neatformcore.domain.model.NFormContent import com.nerdstone.neatformcore.domain.model.NFormViewData @@ -33,16 +18,13 @@ import com.nerdstone.neatformcore.rules.RulesFactory.RulesFileType import com.nerdstone.neatformcore.utils.* import com.nerdstone.neatformcore.utils.Constants.ViewType import com.nerdstone.neatformcore.viewmodel.DataViewModel -import com.nerdstone.neatformcore.viewmodel.ReadOnlyFieldsViewModel +import com.nerdstone.neatformcore.viewmodel.FormViewModel import com.nerdstone.neatformcore.views.containers.MultiChoiceCheckBox import com.nerdstone.neatformcore.views.containers.RadioGroupView import com.nerdstone.neatformcore.views.containers.VerticalRootView import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import com.nerdstone.neatformcore.views.widgets.* import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import timber.log.Timber import kotlin.reflect.KClass @@ -69,47 +51,36 @@ object JsonFormConstants { * Reading of rules file however is run by [DispatcherProvider.io] whereas creation of views is done on the * main thread using [DispatcherProvider.main] scope. */ -class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { - private lateinit var readOnlyFields: MutableSet - private var mainLayout: ViewGroup? = null +class JsonFormBuilder() : FormBuilder { + internal val readOnlyFields = mutableSetOf() private val viewDispatcher: ViewDispatcher = ViewDispatcher.INSTANCE private val rulesFactory: RulesFactory = RulesFactory.INSTANCE private val rulesHandler = rulesFactory.rulesHandler - private val singleRunner = SingleRunner() var defaultContextProvider: DispatcherProvider var form: NForm? = null var fileSource: String? = null var formDataJson: String? = null override var formString: String? = null - override lateinit var neatStepperLayout: NeatStepperLayout override lateinit var context: Context override lateinit var dataViewModel: DataViewModel override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var registeredViews = hashMapOf>() - override lateinit var readOnlyFieldsViewModel: ReadOnlyFieldsViewModel + override lateinit var formViewModel: FormViewModel - constructor(context: Context, fileSource: String, mainLayout: ViewGroup?) - : this() { + constructor(context: Context, fileSource: String) : this() { this.context = context this.fileSource = fileSource - this.mainLayout = mainLayout - this.neatStepperLayout = NeatStepperLayout(context) this.dataViewModel = ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] - this.readOnlyFieldsViewModel = - ViewModelProvider(context)[ReadOnlyFieldsViewModel::class.java] + this.formViewModel = ViewModelProvider(context)[FormViewModel::class.java] } - constructor(jsonString: String, context: Context, mainLayout: ViewGroup?) - : this() { + constructor(jsonString: String, context: Context) : this() { this.formString = jsonString this.context = context - this.mainLayout = mainLayout - this.neatStepperLayout = NeatStepperLayout(context) this.dataViewModel = ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] - this.readOnlyFieldsViewModel = - ViewModelProvider(context)[ReadOnlyFieldsViewModel::class.java] + this.formViewModel = ViewModelProvider(context)[FormViewModel::class.java] } init { @@ -118,43 +89,8 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { defaultContextProvider = DefaultDispatcherProvider() } - override fun buildForm( - jsonFormStepBuilderModel: JsonFormStepBuilderModel?, viewList: List? - ): FormBuilder { - registerViews() - launch(defaultContextProvider.main()) { - try { - if (form == null) { - form = withContext(defaultContextProvider.default()) { - singleRunner.afterPrevious { - parseJsonForm() - } - } - } - val rulesAsync = withContext(defaultContextProvider.io()) { - singleRunner.afterPrevious { - registerFormRulesFromFile(context, RulesFileType.YAML) - } - } - if (rulesAsync) { - withContext(defaultContextProvider.main()) { - if (viewList == null) - createFormViews(context, arrayListOf(), jsonFormStepBuilderModel) - else - createFormViews(context, viewList, jsonFormStepBuilderModel) - } - } - - } catch (throwable: Throwable) { - Timber.e(throwable) - } - } - if (jsonFormStepBuilderModel.isNull()) preFillForm() - return this - } - - private fun parseJsonForm(): NForm? { + internal fun parseJsonForm(): NForm? { return when { formString.isNotNull() -> parseJson(formString) fileSource.isNotNull() -> parseJson( @@ -164,61 +100,7 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { } } - /*** - * Create views within the provided [context]. If [jsonFormStepBuilderModel] is also specified use - * it to configure the form steps. - * - * If the [views] is also not null, the form will be - */ - override fun createFormViews( - context: Context, views: List?, jsonFormStepBuilderModel: JsonFormStepBuilderModel? - ) { - if (form.isNotNull()) { - when { - jsonFormStepBuilderModel.isNotNull() && (mainLayout == null || mainLayout.isNotNull()) -> { - neatStepperLayout.stepperModel = jsonFormStepBuilderModel!!.stepperModel - if (jsonFormStepBuilderModel.stepperActions.isNotNull()) - neatStepperLayout.stepperActions = jsonFormStepBuilderModel.stepperActions - val stepFragmentsList = mutableListOf() - form!!.steps.withIndex().forEach { (index, formContent) -> - val rootView = VerticalRootView(context) - rootView.formBuilder = this - addViewsToVerticalRootView(views, index, formContent, rootView) - stepFragmentsList.add( - StepFragment.newInstance( - index, StepModel.Builder().title(form!!.formName) - .subTitle(formContent.stepName as CharSequence) - .build(), rootView, formDataJson - ) - ) - } - neatStepperLayout.setUpViewWithAdapter( - StepperPagerAdapter( - (context as FragmentActivity).supportFragmentManager, stepFragmentsList - ) - ) - neatStepperLayout.showLoadingIndicators(false) - } - mainLayout.isNotNull() && jsonFormStepBuilderModel == null -> { - val formViews = ScrollView(context) - form!!.steps.withIndex().forEach { (index, formContent) -> - val rootView = VerticalRootView(context) - formViews.addView(rootView.initRootView(this) as View) - addViewsToVerticalRootView(views, index, formContent, rootView) - } - mainLayout?.addView(formViews) - readOnlyFieldsViewModel.readOnlyFields.value?.let { this.readOnlyFields = it } - dataViewModel.details.observe(context as LifecycleOwner, Observer { - ViewUtils.updateFieldValues(it, context, readOnlyFields) - }) - } - else -> Toast.makeText(context, R.string.form_builder_error, Toast.LENGTH_LONG) - .show() - } - } - } - - private fun addViewsToVerticalRootView( + internal fun addViewsToVerticalRootView( customViews: List?, stepIndex: Int, formContent: NFormContent, verticalRootView: VerticalRootView ) { @@ -247,19 +129,23 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { return Utils.getJsonFromModel(formDetails) } FormErrorDialog(context).show() + logInvalidFields() return "" } override fun withFormData(formDataJson: String, readOnlyFields: MutableSet): FormBuilder { this.formDataJson = formDataJson - this.readOnlyFields = readOnlyFields + this.readOnlyFields.addAll(readOnlyFields) return this } override fun preFillForm() { - if (this::dataViewModel.isInitialized && formDataJson.isNotNull()) { - this.readOnlyFieldsViewModel.readOnlyFields.value?.addAll(readOnlyFields) + if ( + this::dataViewModel.isInitialized && dataViewModel.details.value.isNullOrEmpty() + && formDataJson.isNotNull() + ) { + this.formViewModel.readOnlyFields.value?.addAll(readOnlyFields) dataViewModel.updateDetails(FormUtils.parseFormDataJson(formDataJson!!)) } } @@ -277,9 +163,15 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { return dataViewModel.details.value!! } FormErrorDialog(context).show() + logInvalidFields() return hashMapOf() } + private fun logInvalidFields() { + Timber.d("Invalid fields (${formValidator.invalidFields.size}) -> ${formValidator.invalidFields}") + Timber.d("Required fields (${formValidator.requiredFields.size}) -> ${formValidator.requiredFields}") + } + override fun registerViews() { registeredViews[ViewType.EDIT_TEXT] = EditTextNFormView::class registeredViews[ViewType.TEXT_INPUT_EDIT_TEXT] = TextInputEditTextNFormView::class @@ -291,84 +183,4 @@ class JsonFormBuilder() : FormBuilder, CoroutineScope by MainScope() { registeredViews[ViewType.RADIO_GROUP] = RadioGroupView::class registeredViews[ViewType.TOAST_NOTIFICATION] = NotificationNFormView::class } -} - -const val FRAGMENT_VIEW = "fragment_view" -const val FRAGMENT_INDEX = "index" -const val FORM_DATA_JSON = "form_fields_data" - -class StepFragment : Step { - - private lateinit var dataViewModel: DataViewModel - private var index: Int? = null - private var formView: View? = null - private var formDataJson: String? = null - - constructor() - - constructor(stepModel: StepModel) : super(stepModel) - - companion object { - @JvmStatic - fun newInstance( - index: Int, stepModel: StepModel, verticalRootView: VerticalRootView, - formDataJson: String? - ) = - StepFragment(stepModel).apply { - arguments = Bundle().apply { - putInt(FRAGMENT_INDEX, index) - putSerializable(FRAGMENT_VIEW, verticalRootView) - putString(FORM_DATA_JSON, formDataJson) - } - } - } - - override fun onAttach(context: Context) { - super.onAttach(context) - if (context !is StepperActions) throw ClassCastException("$context MUST implement FormActions") - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.also { - index = it.getInt(FRAGMENT_INDEX) - formView = it.getSerializable(FRAGMENT_VIEW) as VerticalRootView? - formDataJson = it.getString(FORM_DATA_JSON) - } - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - if (formView.isNotNull() && formView?.parent.isNotNull()) return formView?.parent as View - return ScrollView(activity).apply { addView(formView) } - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - with(ViewModelProvider(this)[DataViewModel::class.java]) { - dataViewModel = this - details.observe(viewLifecycleOwner, Observer { - ViewUtils.updateFieldValues(it, activity as Context, mutableSetOf()) - }) - formDataJson?.let { - FormUtils.parseFormDataJson(it).also { data -> dataViewModel.updateDetails(data) } - } - } - } - - override fun onDestroyView() { - super.onDestroyView() - formView = null - } - - override fun onSaveInstanceState(outState: Bundle) { - //No call for super(). Bug on API Level > 11. - } - - override fun verifyStep() = StepVerificationState(true, null) - - override fun onSelected() = Unit - - override fun onError(stepVerificationState: StepVerificationState) = Unit -} +} \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormEmbedded.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormEmbedded.kt new file mode 100644 index 00000000..98507d15 --- /dev/null +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormEmbedded.kt @@ -0,0 +1,76 @@ +package com.nerdstone.neatformcore.form.json + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.ScrollView +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.Observer +import com.nerdstone.neatformcore.rules.RulesFactory +import com.nerdstone.neatformcore.utils.SingleRunner +import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.isNotNull +import com.nerdstone.neatformcore.views.containers.VerticalRootView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber + +class JsonFormEmbedded( + private val jsonFormBuilder: JsonFormBuilder, private val mainLayout: ViewGroup +) : CoroutineScope by MainScope() { + + private val singleRunner = SingleRunner() + + fun buildForm(viewList: List? = null): JsonFormEmbedded { + jsonFormBuilder.registerViews() + val context = jsonFormBuilder.context + launch(jsonFormBuilder.defaultContextProvider.main()) { + try { + if (jsonFormBuilder.form == null) { + jsonFormBuilder.form = + withContext(jsonFormBuilder.defaultContextProvider.default()) { + singleRunner.afterPrevious { + jsonFormBuilder.parseJsonForm() + } + } + } + + val rulesAsync = withContext(jsonFormBuilder.defaultContextProvider.io()) { + singleRunner.afterPrevious { + jsonFormBuilder.registerFormRulesFromFile( + context, + RulesFactory.RulesFileType.YAML + ) + } + } + if (rulesAsync) { + withContext(jsonFormBuilder.defaultContextProvider.main()) { + createFormViews(context, viewList ?: arrayListOf()) + } + } + } catch (throwable: Throwable) { + Timber.e(throwable) + } + } + jsonFormBuilder.preFillForm() + return this + } + + private fun createFormViews(context: Context, views: List?) { + if (jsonFormBuilder.form.isNotNull() && mainLayout.isNotNull()) { + val formViews = ScrollView(context) + jsonFormBuilder.form!!.steps.withIndex().forEach { (index, formContent) -> + val rootView = + VerticalRootView(context).apply { formBuilder = jsonFormBuilder } + formViews.addView(rootView.initRootView(jsonFormBuilder) as View) + jsonFormBuilder.addViewsToVerticalRootView(views, index, formContent, rootView) + } + mainLayout.addView(formViews) + jsonFormBuilder.dataViewModel.details.observe(context as LifecycleOwner, Observer { + ViewUtils.updateFieldValues(it, context, jsonFormBuilder.readOnlyFields) + }) + } + } +} diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormStepper.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormStepper.kt new file mode 100644 index 00000000..7795759d --- /dev/null +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormStepper.kt @@ -0,0 +1,167 @@ +package com.nerdstone.neatformcore.form.json + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ScrollView +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Observer +import com.nerdstone.neatandroidstepper.core.model.StepModel +import com.nerdstone.neatandroidstepper.core.stepper.Step +import com.nerdstone.neatandroidstepper.core.stepper.StepVerificationState +import com.nerdstone.neatandroidstepper.core.stepper.StepperPagerAdapter +import com.nerdstone.neatandroidstepper.core.widget.NeatStepperLayout +import com.nerdstone.neatformcore.form.common.FormActions +import com.nerdstone.neatformcore.rules.RulesFactory +import com.nerdstone.neatformcore.utils.SingleRunner +import com.nerdstone.neatformcore.utils.ViewUtils +import com.nerdstone.neatformcore.utils.isNotNull +import com.nerdstone.neatformcore.viewmodel.DataViewModel +import com.nerdstone.neatformcore.views.containers.VerticalRootView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber + +class JsonFormStepper( + private val jsonFormBuilder: JsonFormBuilder, private val neatStepperLayout: NeatStepperLayout +) : CoroutineScope by MainScope() { + + private val singleRunner = SingleRunner() + + fun buildForm(viewList: List? = null): JsonFormStepper { + jsonFormBuilder.registerViews() + val defaultContextProvider = jsonFormBuilder.defaultContextProvider + launch(defaultContextProvider.main()) { + try { + if (jsonFormBuilder.form == null) { + jsonFormBuilder.form = withContext(defaultContextProvider.default()) { + singleRunner.afterPrevious { + jsonFormBuilder.parseJsonForm() + } + } + } + + val rulesAsync = withContext(defaultContextProvider.io()) { + singleRunner.afterPrevious { + jsonFormBuilder.registerFormRulesFromFile( + jsonFormBuilder.context, + RulesFactory.RulesFileType.YAML + ) + } + } + if (rulesAsync) { + withContext(defaultContextProvider.main()) { + createFormViews(jsonFormBuilder.context, viewList ?: arrayListOf()) + } + } + + } catch (throwable: Throwable) { + Timber.e(throwable) + } + } + return this + } + + private fun createFormViews(context: Context, views: List?) { + if (jsonFormBuilder.form.isNotNull()) { + val stepFragmentsList = mutableListOf() + jsonFormBuilder.form!!.steps.withIndex().forEach { (index, formContent) -> + val rootView = VerticalRootView(context) + rootView.formBuilder = jsonFormBuilder + jsonFormBuilder.addViewsToVerticalRootView( + views, index, formContent, rootView + ) + stepFragmentsList.add( + StepFragment.newInstance( + index, StepModel.Builder().title(jsonFormBuilder.form!!.formName) + .subTitle(formContent.stepName as CharSequence) + .build(), rootView + ) + ) + } + neatStepperLayout.apply { + showLoadingIndicators(false) + setUpViewWithAdapter( + StepperPagerAdapter( + (context as FragmentActivity).supportFragmentManager, stepFragmentsList + ) + ) + } + } + } +} + + +const val FRAGMENT_VIEW = "fragment_view" +const val FRAGMENT_INDEX = "index" + +class StepFragment : Step { + + private lateinit var dataViewModel: DataViewModel + private var index: Int? = null + private var formView: View? = null + + constructor() + + constructor(stepModel: StepModel) : super(stepModel) + + companion object { + @JvmStatic + fun newInstance( + index: Int, stepModel: StepModel, verticalRootView: VerticalRootView + ) = + StepFragment(stepModel).apply { + arguments = Bundle().apply { + putInt(FRAGMENT_INDEX, index) + putSerializable(FRAGMENT_VIEW, verticalRootView) + } + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + if (context !is FormActions) throw ClassCastException("$context MUST implement StepperActions") + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) +// dataViewModel = ViewModelProvider(this)[DataViewModel::class.java] + arguments?.also { + index = it.getInt(FRAGMENT_INDEX) + formView = it.getSerializable(FRAGMENT_VIEW) as VerticalRootView? + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + if (formView.isNotNull() && formView?.parent.isNotNull()) { + return formView?.parent as View + } + return ScrollView(activity).apply { addView(formView) } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + dataViewModel = (activity as FormActions).formBuilder.dataViewModel + with(dataViewModel) { + details.observe(viewLifecycleOwner, Observer { + ViewUtils.updateFieldValues(it, activity as Context, mutableSetOf()) + }) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + //No call for super(). Bug on API Level > 11. + } + + override fun verifyStep() = StepVerificationState(true, null) + + override fun onSelected() = Unit + + override fun onError(stepVerificationState: StepVerificationState) = Unit +} diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt index 7e001a4a..5b0d19b6 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt @@ -194,11 +194,15 @@ object ViewUtils { * data will not be valid and an empty map will be returned if you try to access the form data. */ fun handleRequiredStatus(nFormView: NFormView) { + val viewContext = getViewContext(nFormView.viewDetails) + val dataViewModel = + ViewModelProvider(viewContext as FragmentActivity)[DataViewModel::class.java] (nFormView as View).tag?.also { val formValidator = nFormView.formValidator if (Utils.isFieldRequired(nFormView) && nFormView.viewDetails.value == null && - nFormView.viewDetails.view.visibility == View.VISIBLE + nFormView.viewDetails.view.visibility == View.VISIBLE && + !dataViewModel.details.value?.containsKey(nFormView.viewDetails.name)!! ) { formValidator.requiredFields.add(nFormView.viewDetails.name) } else { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt index a2aa467b..718477f1 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/DataViewModel.kt @@ -1,6 +1,5 @@ package com.nerdstone.neatformcore.viewmodel -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.nerdstone.neatformcore.domain.model.NFormViewData @@ -13,7 +12,8 @@ import com.nerdstone.neatformcore.domain.model.NFormViewData class DataViewModel : ViewModel() { - var details: LiveData> = MutableLiveData(HashMap()) + private val _details = MutableLiveData>(HashMap()) + val details get() = _details fun saveFieldValue(fieldName: String, fieldValue: NFormViewData) { details.value?.set(fieldName, fieldValue) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/ReadOnlyFieldsViewModel.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/FormViewModel.kt similarity index 50% rename from neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/ReadOnlyFieldsViewModel.kt rename to neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/FormViewModel.kt index 4c416076..e182fa3b 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/ReadOnlyFieldsViewModel.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/viewmodel/FormViewModel.kt @@ -1,6 +1,5 @@ package com.nerdstone.neatformcore.viewmodel -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -8,8 +7,10 @@ import androidx.lifecycle.ViewModel * @author Elly Nerdstone *This view model is used to hold state of read only fields. which is a [MutableSet] of field names */ -class ReadOnlyFieldsViewModel : ViewModel() { - private val _readOnlyFields: LiveData> = - MutableLiveData(mutableSetOf()) - val readOnlyFields: LiveData> = _readOnlyFields +class FormViewModel : ViewModel() { + + private val _readOnlyFields = MutableLiveData>(mutableSetOf()) + + val readOnlyFields get() = _readOnlyFields + } \ No newline at end of file diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt index 8622ab13..b5b2be27 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt @@ -13,7 +13,7 @@ open class BaseJsonViewBuilderTest { protected val activity: ActivityController = Robolectric.buildActivity(AppCompatActivity::class.java).setup() val mainLayout: ViewGroup = LinearLayout(activity.get()) - var formBuilder = JsonFormBuilder(TestConstants.SAMPLE_JSON, activity.get(), mainLayout) + var formBuilder = JsonFormBuilder(TestConstants.SAMPLE_JSON, activity.get()) val formValidator = NeatFormValidator.INSTANCE init { diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt index 4176bd7e..23cee474 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt @@ -13,21 +13,17 @@ import androidx.lifecycle.Observer import androidx.viewpager.widget.ViewPager import com.nerdstone.neatandroidstepper.core.model.StepperModel import com.nerdstone.neatandroidstepper.core.stepper.StepperPagerAdapter +import com.nerdstone.neatandroidstepper.core.widget.NeatStepperLayout import com.nerdstone.neatformcore.CoroutineTestRule import com.nerdstone.neatformcore.R import com.nerdstone.neatformcore.TestConstants import com.nerdstone.neatformcore.TestNeatFormApp -import com.nerdstone.neatformcore.domain.model.JsonFormStepBuilderModel import com.nerdstone.neatformcore.domain.model.NFormViewData -import com.nerdstone.neatformcore.form.json.FRAGMENT_VIEW -import com.nerdstone.neatformcore.form.json.JsonFormBuilder -import com.nerdstone.neatformcore.form.json.StepFragment +import com.nerdstone.neatformcore.form.json.* import com.nerdstone.neatformcore.views.containers.MultiChoiceCheckBox import com.nerdstone.neatformcore.views.containers.RadioGroupView import com.nerdstone.neatformcore.views.containers.VerticalRootView import com.nerdstone.neatformcore.views.widgets.* -import io.mockk.impl.annotations.MockK -import io.mockk.mockk import io.mockk.spyk import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -47,7 +43,10 @@ class JsonFormBuilderTest { private val activity = Robolectric.buildActivity(AppCompatActivity::class.java).setup() private val mainLayout: LinearLayout = LinearLayout(activity.get()) + private val neatStepperLayout = NeatStepperLayout(activity.get()) private lateinit var jsonFormBuilder: JsonFormBuilder + private lateinit var jsonFormStepper: JsonFormStepper + private lateinit var jsonFormEmbedded: JsonFormEmbedded var observer: Observer> = spyk() private val previousFormData = """ { @@ -78,10 +77,10 @@ class JsonFormBuilderTest { fun `Should parse json from file source, create views and register form rules`() = coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( - JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE, mainLayout) + JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE) ) jsonFormBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider - jsonFormBuilder.buildForm() + jsonFormEmbedded = JsonFormEmbedded(jsonFormBuilder, mainLayout).buildForm() Assert.assertNotNull(jsonFormBuilder.form) Assert.assertTrue(jsonFormBuilder.form?.steps?.size == 1) Assert.assertTrue(jsonFormBuilder.form?.steps?.get(0)?.stepName == "Test and counselling") @@ -115,10 +114,10 @@ class JsonFormBuilderTest { fun `Should parse json from json string, create views and register form rules`() = coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( - JsonFormBuilder(TestConstants.SAMPLE_JSON.trimIndent(), activity.get(), mainLayout) + JsonFormBuilder(TestConstants.SAMPLE_JSON.trimIndent(), activity.get()) ) jsonFormBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider - jsonFormBuilder.buildForm() + jsonFormEmbedded = JsonFormEmbedded(jsonFormBuilder, mainLayout).buildForm() Assert.assertNotNull(jsonFormBuilder.form) Assert.assertTrue(jsonFormBuilder.form?.steps?.size == 1) Assert.assertTrue(jsonFormBuilder.form?.steps?.get(0)?.stepName == "Demographics") @@ -150,7 +149,7 @@ class JsonFormBuilderTest { fun `Should parse json from file source, update views from provided layout view with form rules`() = coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( - JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE, mainLayout) + JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE) ) jsonFormBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider val inflater = @@ -158,8 +157,7 @@ class JsonFormBuilderTest { val view = inflater.inflate(R.layout.sample_custom_form_layout, null) val viewsList = listOf(view) - - jsonFormBuilder.buildForm(null, viewsList) + jsonFormEmbedded = JsonFormEmbedded(jsonFormBuilder, mainLayout).buildForm(viewsList) Assert.assertTrue(mainLayout.getChildAt(0) is ScrollView) val scrollView = mainLayout.getChildAt(0) as ScrollView val verticalRootView = scrollView.getChildAt(0) as VerticalRootView @@ -173,24 +171,21 @@ class JsonFormBuilderTest { } @Test - fun `Should build default form (in vertical layout) with using stepper library`() = + fun `Should build default form (in vertical layout) using stepper library`() = coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( objToCopy = - JsonFormBuilder(activity.get(), TestConstants.SAMPLE_TWO_FORM_FILE, mainLayout), + JsonFormBuilder(activity.get(), TestConstants.SAMPLE_TWO_FORM_FILE), recordPrivateCalls = true ) jsonFormBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider val stepperModel = StepperModel.Builder() .toolbarColorResource(R.color.colorBlack) .build() - jsonFormBuilder.buildForm( - JsonFormStepBuilderModel.Builder(stepperModel = stepperModel) - .build() - ) + neatStepperLayout.stepperModel = stepperModel + jsonFormStepper = JsonFormStepper(jsonFormBuilder, neatStepperLayout).buildForm() Assert.assertNotNull(jsonFormBuilder.form) - Assert.assertNotNull(jsonFormBuilder.neatStepperLayout.stepperModel.toolbarColorResId == R.color.colorBlack) - val neatStepperLayout = jsonFormBuilder.neatStepperLayout + Assert.assertNotNull(neatStepperLayout.stepperModel.toolbarColorResId == R.color.colorBlack) Assert.assertTrue(neatStepperLayout.findViewById(R.id.titleTextView).text.toString() == "Profile") val innerStepperLayout = neatStepperLayout.getChildAt(0) as LinearLayout //Stepper has toolbar, frameLayout (fragment content) and bottom navigation @@ -231,7 +226,7 @@ class JsonFormBuilderTest { jsonFormBuilder = spyk( objToCopy = - JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE, null), + JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE), recordPrivateCalls = true ) jsonFormBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider @@ -242,14 +237,11 @@ class JsonFormBuilderTest { val stepperModel = StepperModel.Builder() .toolbarColorResource(R.color.colorBlack) .build() - jsonFormBuilder.buildForm( - JsonFormStepBuilderModel.Builder(stepperModel = stepperModel) - .build(), viewsList - ) + neatStepperLayout.stepperModel = stepperModel + jsonFormStepper = JsonFormStepper(jsonFormBuilder, neatStepperLayout).buildForm(viewsList) Assert.assertNotNull(jsonFormBuilder.form) - Assert.assertNotNull(jsonFormBuilder.neatStepperLayout.stepperModel.toolbarColorResId == R.color.colorBlack) - val neatStepperLayout = jsonFormBuilder.neatStepperLayout + Assert.assertNotNull(neatStepperLayout.stepperModel.toolbarColorResId == R.color.colorBlack) Assert.assertTrue(neatStepperLayout.findViewById(R.id.titleTextView).text.toString() == "Profile") val innerStepperLayout = neatStepperLayout.getChildAt(0) as LinearLayout //Stepper has toolbar, frameLayout (fragment content) and bottom navigation @@ -287,12 +279,12 @@ class JsonFormBuilderTest { fun `Should build a pre-filled form`() { coroutinesTestRule.runBlockingTest { jsonFormBuilder = spyk( - JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE, mainLayout) + JsonFormBuilder(activity.get(), TestConstants.SAMPLE_ONE_FORM_FILE) ) jsonFormBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider jsonFormBuilder .withFormData(previousFormData, mutableSetOf("age", "child", "adult")) - .buildForm(null, null) + jsonFormEmbedded = JsonFormEmbedded(jsonFormBuilder, mainLayout).buildForm() Assert.assertNotNull(jsonFormBuilder.form) Assert.assertNotNull(jsonFormBuilder.formDataJson) val details = jsonFormBuilder.dataViewModel.details diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt index 7a608439..ca2fe041 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt @@ -2,7 +2,6 @@ package com.nerdstone.neatformcore.robolectric.rules import android.view.View import android.widget.* -import androidx.appcompat.app.AppCompatActivity import com.chivorn.smartmaterialspinner.SmartMaterialSpinner import com.google.android.material.textfield.TextInputLayout import com.nerdstone.neatformcore.CoroutineTestRule @@ -11,6 +10,7 @@ import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.form.json.JsonFormBuilder import com.nerdstone.neatformcore.form.json.JsonFormConstants +import com.nerdstone.neatformcore.form.json.JsonFormEmbedded import com.nerdstone.neatformcore.robolectric.builders.BaseJsonViewBuilderTest import com.nerdstone.neatformcore.views.containers.MultiChoiceCheckBox import com.nerdstone.neatformcore.views.containers.RadioGroupView @@ -27,7 +27,6 @@ import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -189,15 +188,17 @@ class NeatFormValidatorTest: BaseJsonViewBuilderTest() { @get:Rule var coroutinesTestRule = CoroutineTestRule() + private lateinit var jsonFormEmbedded: JsonFormEmbedded + @Test fun `Should display error message and return empty map when required fields are missing`() = coroutinesTestRule.runBlockingTest { formBuilder = spyk( - JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get(), mainLayout) + JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get()) ) formBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider - formBuilder.buildForm() + jsonFormEmbedded = JsonFormEmbedded(formBuilder, mainLayout).buildForm() Assert.assertTrue(formBuilder.getFormData().isEmpty()) Assert.assertTrue(formBuilder.getFormDataAsJson() == "") @@ -208,11 +209,11 @@ class NeatFormValidatorTest: BaseJsonViewBuilderTest() { fun `Should display error message when there is invalid input`() = coroutinesTestRule.runBlockingTest { formBuilder = spyk( - JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get(), mainLayout) + JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get()) ) formBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider - formBuilder.buildForm() + jsonFormEmbedded = JsonFormEmbedded(formBuilder, mainLayout).buildForm() val scrollView = mainLayout.getChildAt(0) as ScrollView val verticalRootView = scrollView.getChildAt(0) as VerticalRootView @@ -291,11 +292,11 @@ class NeatFormValidatorTest: BaseJsonViewBuilderTest() { private fun updateViewValues() { coroutinesTestRule.runBlockingTest { formBuilder = spyk( - JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get(), mainLayout) + JsonFormBuilder(VALIDATION_FORM.trimIndent(), activity.get()) ) formBuilder.defaultContextProvider = coroutinesTestRule.testDispatcherProvider - formBuilder.buildForm() + jsonFormEmbedded = JsonFormEmbedded(formBuilder, mainLayout).buildForm() val scrollView = mainLayout.getChildAt(0) as ScrollView val verticalRootView = scrollView.getChildAt(0) as VerticalRootView diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt index 86fc1dab..1e315aca 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt @@ -66,8 +66,8 @@ class RulesFactoryTest { //Setup rules handler with form builder and views map rulesHandler.formBuilder = JsonFormBuilder( - activity.get(), - TestConstants.SAMPLE_ONE_FORM_FILE, mainLayout + activity.get(), + TestConstants.SAMPLE_ONE_FORM_FILE ) every { rulesFactory.rulesHandler } returns rulesHandler } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 0f820e88..e606763f 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,25 +1,26 @@ + xmlns:tools="http://schemas.android.com/tools" + package="com.nerdstone.neatform"> - - - - + + + + + - - - - - + + + + + \ No newline at end of file diff --git a/sample/src/main/assets/sample/sample_two_form.json b/sample/src/main/assets/sample/sample_two_form.json index 9639745b..c7a0da34 100644 --- a/sample/src/main/assets/sample/sample_two_form.json +++ b/sample/src/main/assets/sample/sample_two_form.json @@ -11,7 +11,6 @@ "properties": { "hint": "This is an adult", "type": "name", - "text": "", "padding": "8" }, "meta_data": { @@ -278,8 +277,7 @@ "type": "text_input_edit_text", "properties": { "hint": "Add any extra information", - "type": "name", - "padding": "8" + "type": "name" }, "meta_data": { "openmrs_entity": "", diff --git a/sample/src/main/java/com/nerdstone/neatform/StepperActivity.kt b/sample/src/main/java/com/nerdstone/neatform/StepperActivity.kt new file mode 100644 index 00000000..72ae8f84 --- /dev/null +++ b/sample/src/main/java/com/nerdstone/neatform/StepperActivity.kt @@ -0,0 +1,89 @@ +package com.nerdstone.neatform + +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.nerdstone.neatandroidstepper.core.model.StepperModel +import com.nerdstone.neatandroidstepper.core.stepper.Step +import com.nerdstone.neatandroidstepper.core.stepper.StepVerificationState +import com.nerdstone.neatform.custom.views.CustomImageView +import com.nerdstone.neatform.form.FILE_PATH +import com.nerdstone.neatform.form.PRE_FILLED +import com.nerdstone.neatform.utils.Constants +import com.nerdstone.neatformcore.domain.builders.FormBuilder +import com.nerdstone.neatformcore.form.common.FormActions +import com.nerdstone.neatformcore.form.json.JsonFormBuilder +import com.nerdstone.neatformcore.form.json.JsonFormStepper +import com.nerdstone.neatformcore.utils.DialogUtil +import com.nerdstone.neatformcore.utils.FormUtils +import kotlinx.android.synthetic.main.activity_stepper.* +import timber.log.Timber + +class StepperActivity : AppCompatActivity(), FormActions { + + override lateinit var formBuilder: FormBuilder + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_stepper) + val filePath = intent?.extras?.getString(FILE_PATH) + val preFilled = intent?.extras?.getBoolean(PRE_FILLED, false) + + if (filePath == null) throw NullPointerException("Filepath cannot be null") + + neatStepperLayout.apply { + stepperActions = this@StepperActivity + stepperModel = StepperModel.Builder() + .exitButtonDrawableResource(R.drawable.ic_clear) + .indicatorType(StepperModel.IndicatorType.DOT_INDICATOR) + .toolbarColorResource(R.color.colorPrimaryDark) + .build() + } + + formBuilder = JsonFormBuilder(this, filePath) + formBuilder.also { + it.registeredViews["custom_image"] = CustomImageView::class + if (preFilled!!) it.withFormData(Constants.PREVIOUS_DATA, mutableSetOf()) + } + JsonFormStepper(formBuilder as JsonFormBuilder, neatStepperLayout).buildForm() + formBuilder.dataViewModel.also { + if (it.details.value.isNullOrEmpty()) + it.updateDetails(FormUtils.parseFormDataJson(Constants.PREVIOUS_DATA)) + } + } + + override fun onStepError(stepVerificationState: StepVerificationState) = Unit + + override fun onButtonNextClick(step: Step) = Unit + + override fun onButtonPreviousClick(step: Step) = Unit + + override fun onStepComplete(step: Step) { + if (formBuilder.getFormDataAsJson() != "") { + Toast.makeText(formBuilder.context, "Completed entire step", Toast.LENGTH_LONG) + .show() + Timber.d("Saved Data = %s", formBuilder.getFormDataAsJson()) + finish() + } + } + + override fun onExitStepper() { + DialogUtil.createAlertDialog( + context = formBuilder.context, title = "Confirm close", + message = "All the unsaved data will get lost if you quit" + ).apply { + setPositiveButton("Exit") { _, _ -> finish() } + setNegativeButton("Cancel") { _, _ -> return@setNegativeButton } + create() + }.show() + } + + override fun onCompleteStepper() { + if (formBuilder.getFormDataAsJson() != "") { + Toast.makeText(formBuilder.context, "Completed entire step", Toast.LENGTH_LONG) + .show() + Timber.d("Saved Data = %s", formBuilder.getFormDataAsJson()) + finish() + } + } +} diff --git a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt index df3e4b90..f207115a 100644 --- a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt +++ b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt @@ -1,5 +1,6 @@ package com.nerdstone.neatform.form +import android.content.Intent import android.os.Bundle import android.view.View import android.widget.ImageView @@ -8,21 +9,20 @@ import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar -import com.nerdstone.neatandroidstepper.core.domain.StepperActions -import com.nerdstone.neatandroidstepper.core.model.StepperModel -import com.nerdstone.neatandroidstepper.core.stepper.Step -import com.nerdstone.neatandroidstepper.core.stepper.StepVerificationState import com.nerdstone.neatform.FormType import com.nerdstone.neatform.R +import com.nerdstone.neatform.StepperActivity import com.nerdstone.neatform.custom.views.CustomImageView -import com.nerdstone.neatform.utils.replaceView +import com.nerdstone.neatform.utils.Constants import com.nerdstone.neatformcore.domain.builders.FormBuilder -import com.nerdstone.neatformcore.domain.model.JsonFormStepBuilderModel import com.nerdstone.neatformcore.form.json.JsonFormBuilder -import com.nerdstone.neatformcore.utils.DialogUtil +import com.nerdstone.neatformcore.form.json.JsonFormEmbedded import timber.log.Timber -class FormActivity : AppCompatActivity(), StepperActions { +const val FILE_PATH = "FILE_PATH" +const val PRE_FILLED = "PRE_FILLED" + +class FormActivity : AppCompatActivity() { private lateinit var formLayout: LinearLayout private lateinit var mainLayout: LinearLayout @@ -32,104 +32,6 @@ class FormActivity : AppCompatActivity(), StepperActions { private lateinit var completeButton: ImageView private var formBuilder: FormBuilder? = null - private val previousFormData = """ - { - "age": { - "meta_data": { - "openmrs_entity": "", - "openmrs_entity_id": "", - "openmrs_entity_parent": "" - }, - "type": "TextInputEditTextNFormView", - "value": "54" - }, - "child": { - "meta_data": { - "openmrs_entity": "", - "openmrs_entity_id": "", - "openmrs_entity_parent": "" - }, - "type": "TextInputEditTextNFormView", - "value": "child" - }, - "dob": { - "type": "DateTimePickerNFormView", - "value": 1589555422331 - }, - "time": { - "type": "DateTimePickerNFormView", - "value": 1589555422335 - }, - "adult": { - "type": "TextInputEditTextNFormView", - "value": "0723721920" - }, - "email_subscription": { - "type": "CheckBoxNFormView", - "value": { - "email_subscription": "Subscribe to email notifications" - }, - "visible": true - }, - "no_prev_pregnancies": { - "type": "NumberSelectorNFormView", - "value": 1, - "visible": true - }, - "gender": { - "type": "SpinnerNFormView", - "value": { - "value": "Female" - } - }, - "country": { - "type": "SpinnerNFormView", - "value": { - "meta_data": { - "country_code": "+61" - }, - "value": "Australia", - "visible": true - }, - "visible": true - }, - "choose_language": { - "type": "MultiChoiceCheckBox", - "value": { - "kisw": { - "meta_data": { - "openmrs_entity": "", - "openmrs_entity_id": "A123123123123", - "openmrs_entity_parent": "" - }, - "value": "Kiswahili", - "visible": true - }, - "french": { - "meta_data": { - "openmrs_entity": "", - "openmrs_entity_id": "A123123123123", - "openmrs_entity_parent": "" - }, - "value": "French", - "visible": true - } - }, - "visible": true - }, - "wiki_contribution": { - "type": "RadioGroupView", - "value": { - "yes": { - "value": "Yes", - "visible": true - } - }, - "visible": true - } - } - """.trimIndent() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.form_activity) @@ -141,13 +43,6 @@ class FormActivity : AppCompatActivity(), StepperActions { exitFormImageView = findViewById(R.id.exitFormImageView) completeButton = findViewById(R.id.completeButton) - val stepperModel = StepperModel.Builder() - .exitButtonDrawableResource(R.drawable.ic_clear) - .indicatorType(StepperModel.IndicatorType.DOT_INDICATOR) - .toolbarColorResource(R.color.colorPrimaryDark) - .build() - - if (intent.extras != null) { val formData = intent?.extras?.getSerializable("formData") as FormData pageTitleTextView.text = formData.formTitle @@ -172,43 +67,30 @@ class FormActivity : AppCompatActivity(), StepperActions { ) when (formData.formCategory) { FormType.embeddableDefault -> { - formBuilder = JsonFormBuilder(this, formData.filePath, formLayout) + formBuilder = JsonFormBuilder(this, formData.filePath) formBuilder?.also { it.registeredViews["custom_image"] = CustomImageView::class it.withFormData( - previousFormData, mutableSetOf( + Constants.PREVIOUS_DATA, mutableSetOf( "dob", "time", "email_subscription", "country", "no_prev_pregnancies", "choose_language", "wiki_contribution" ) - ).buildForm() + ) + JsonFormEmbedded(formBuilder as JsonFormBuilder, formLayout).buildForm() } } FormType.embeddableCustomized -> { - formBuilder = JsonFormBuilder(this, formData.filePath, formLayout) + formBuilder = JsonFormBuilder(this, formData.filePath) formBuilder?.also { it.registeredViews["custom_image"] = CustomImageView::class - it.buildForm(viewList = views) } + JsonFormEmbedded(formBuilder as JsonFormBuilder, formLayout).buildForm(views) } FormType.stepperDefault -> { - sampleToolBar.visibility = View.GONE - formBuilder = JsonFormBuilder(this, formData.filePath, null) - formBuilder?.also { - it.registeredViews["custom_image"] = CustomImageView::class - it.withFormData(previousFormData, mutableSetOf()).buildForm( - JsonFormStepBuilderModel.Builder(this, stepperModel).build() - ) - } - replaceView(mainLayout, (formBuilder as JsonFormBuilder).neatStepperLayout) + startStepperActivity(formData.filePath, true) } FormType.stepperCustomized -> { - sampleToolBar.visibility = View.GONE - formBuilder = JsonFormBuilder(this, formData.filePath, formLayout) - .buildForm( - JsonFormStepBuilderModel.Builder(this, stepperModel).build(), - views - ) - replaceView(mainLayout, (formBuilder as JsonFormBuilder).neatStepperLayout) + startStepperActivity(formData.filePath) } else -> Toast.makeText( this, "Please provide the right form type", @@ -218,36 +100,11 @@ class FormActivity : AppCompatActivity(), StepperActions { } } - override fun onStepError(stepVerificationState: StepVerificationState) = Unit - - override fun onButtonNextClick(step: Step) = Unit - - override fun onButtonPreviousClick(step: Step) = Unit - - override fun onStepComplete(step: Step) { - if (formBuilder?.getFormDataAsJson() != "") { - Toast.makeText(this, "Completed entire step", Toast.LENGTH_LONG).show() - Timber.d("Saved Data = %s", formBuilder?.getFormDataAsJson()) - finish() - } - } - - override fun onExitStepper() { - DialogUtil.createAlertDialog( - context = this, title = "Confirm close", - message = "All the unsaved data will get lost if you quit" - ).apply { - setPositiveButton("Exit") { _, _ -> finish() } - setNegativeButton("Cancel") { _, _ -> return@setNegativeButton } - create() - }.show() - } - - override fun onCompleteStepper() { - if (formBuilder?.getFormDataAsJson() != "") { - Toast.makeText(this, "Completed entire step", Toast.LENGTH_LONG).show() - Timber.d("Saved Data = %s", formBuilder?.getFormDataAsJson()) - finish() - } + private fun startStepperActivity(filePath: String, preFilled: Boolean = false) { + finish() + startActivity(Intent(this, StepperActivity::class.java).apply { + putExtra(FILE_PATH, filePath) + putExtra(PRE_FILLED, preFilled) + }) } } \ No newline at end of file diff --git a/sample/src/main/java/com/nerdstone/neatform/utils/Constants.kt b/sample/src/main/java/com/nerdstone/neatform/utils/Constants.kt new file mode 100644 index 00000000..72bd56a3 --- /dev/null +++ b/sample/src/main/java/com/nerdstone/neatform/utils/Constants.kt @@ -0,0 +1,105 @@ +package com.nerdstone.neatform.utils + +object Constants { + val PREVIOUS_DATA = """ + { + "age": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + }, + "type": "TextInputEditTextNFormView", + "value": "54" + }, + "condition": { + "type": "TextInputEditTextNFormView", + "value": "Get's tired easily" + }, + "child": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + }, + "type": "TextInputEditTextNFormView", + "value": "child" + }, + "dob": { + "type": "DateTimePickerNFormView", + "value": 1589555422331 + }, + "time": { + "type": "DateTimePickerNFormView", + "value": 1589555422335 + }, + "adult": { + "type": "TextInputEditTextNFormView", + "value": "0723721920" + }, + "email_subscription": { + "type": "CheckBoxNFormView", + "value": { + "email_subscription": "Subscribe to email notifications" + }, + "visible": true + }, + "no_prev_pregnancies": { + "type": "NumberSelectorNFormView", + "value": 1, + "visible": true + }, + "gender": { + "type": "SpinnerNFormView", + "value": { + "value": "Female" + } + }, + "country": { + "type": "SpinnerNFormView", + "value": { + "meta_data": { + "country_code": "+61" + }, + "value": "Australia", + "visible": true + }, + "visible": true + }, + "choose_language": { + "type": "MultiChoiceCheckBox", + "value": { + "kisw": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "A123123123123", + "openmrs_entity_parent": "" + }, + "value": "Kiswahili", + "visible": true + }, + "french": { + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "A123123123123", + "openmrs_entity_parent": "" + }, + "value": "French", + "visible": true + } + }, + "visible": true + }, + "wiki_contribution": { + "type": "RadioGroupView", + "value": { + "no": { + "value": "No", + "visible": true + } + }, + "visible": true + } + } + """.trimIndent() +} \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_stepper.xml b/sample/src/main/res/layout/activity_stepper.xml new file mode 100644 index 00000000..1afd0851 --- /dev/null +++ b/sample/src/main/res/layout/activity_stepper.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/sample/src/main/res/layout/sample_one_form_custom_layout.xml b/sample/src/main/res/layout/sample_one_form_custom_layout.xml index 537137bf..262030be 100644 --- a/sample/src/main/res/layout/sample_one_form_custom_layout.xml +++ b/sample/src/main/res/layout/sample_one_form_custom_layout.xml @@ -48,7 +48,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_weight="1" - android:visibility="visible"> + android:visibility="gone"> #cfcfcf #ffffff #2E88B4 + #FFEB3B diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index b2b5ce79..6c64fc71 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - Neat Form + Neat Form From 50e6d8ecd09c0e2f5027f16fd421359ceafa6c1f Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Tue, 26 May 2020 00:05:16 +0300 Subject: [PATCH 10/15] Clean up code Signed-off-by: Elly Kitoto --- neat-form-core/build.gradle | 8 +++++++- .../nerdstone/neatformcore/form/json/JsonFormStepper.kt | 3 +-- .../java/com/nerdstone/neatformcore/CoroutineTestRules.kt | 2 +- .../test/java/com/nerdstone/neatformcore/TestConstants.kt | 1 - .../robolectric/{builders => json}/JsonFormBuilderTest.kt | 2 +- .../main/java/com/nerdstone/neatform/StepperActivity.kt | 8 ++------ 6 files changed, 12 insertions(+), 12 deletions(-) rename neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/{builders => json}/JsonFormBuilderTest.kt (99%) diff --git a/neat-form-core/build.gradle b/neat-form-core/build.gradle index 60674195..0e7845be 100644 --- a/neat-form-core/build.gradle +++ b/neat-form-core/build.gradle @@ -31,6 +31,10 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } lintOptions { abortOnError false @@ -89,9 +93,11 @@ dependencies { api 'com.google.code.gson:gson:2.8.6' api 'com.github.chivorns:smartmaterialspinner:1.2.1' - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13' testImplementation 'org.robolectric:robolectric:4.3.1' testImplementation "org.robolectric:shadows-multidex:4.3.1" + debugImplementation 'androidx.fragment:fragment-testing:1.2.4' + testImplementation 'androidx.test:core:1.2.0' testImplementation "io.mockk:mockk:$mockKVersion" testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2' diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormStepper.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormStepper.kt index 7795759d..2ceac83b 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormStepper.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormStepper.kt @@ -63,6 +63,7 @@ class JsonFormStepper( Timber.e(throwable) } } + jsonFormBuilder.preFillForm() return this } @@ -95,7 +96,6 @@ class JsonFormStepper( } } - const val FRAGMENT_VIEW = "fragment_view" const val FRAGMENT_INDEX = "index" @@ -129,7 +129,6 @@ class StepFragment : Step { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) -// dataViewModel = ViewModelProvider(this)[DataViewModel::class.java] arguments?.also { index = it.getInt(FRAGMENT_INDEX) formView = it.getSerializable(FRAGMENT_VIEW) as VerticalRootView? diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/CoroutineTestRules.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/CoroutineTestRules.kt index 86d26ea0..fb22f092 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/CoroutineTestRules.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/CoroutineTestRules.kt @@ -28,8 +28,8 @@ class CoroutineTestRule(val testDispatcher: TestCoroutineDispatcher = TestCorout override fun evaluate() { Dispatchers.setMain(testDispatcher) base?.evaluate() - cleanupTestCoroutines() Dispatchers.resetMain() + testDispatcher.cleanupTestCoroutines() } } } \ No newline at end of file diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/TestConstants.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/TestConstants.kt index 48e036b7..2ab223e0 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/TestConstants.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/TestConstants.kt @@ -4,7 +4,6 @@ object TestConstants { private const val SAMPLE_DIR = "sample/" const val SAMPLE_ONE_FORM_FILE = SAMPLE_DIR + "sample_one_form.json" const val SAMPLE_TWO_FORM_FILE = SAMPLE_DIR + "sample_two_form.json" - const val VALIDATION_TEST = SAMPLE_DIR + "validation_test.json" const val SAMPLE_JSON = """{ "form": "Profile", diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/json/JsonFormBuilderTest.kt similarity index 99% rename from neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt rename to neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/json/JsonFormBuilderTest.kt index 23cee474..0493d9a7 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/JsonFormBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/json/JsonFormBuilderTest.kt @@ -1,4 +1,4 @@ -package com.nerdstone.neatformcore.robolectric.builders +package com.nerdstone.neatformcore.robolectric.json import android.content.Context import android.view.LayoutInflater diff --git a/sample/src/main/java/com/nerdstone/neatform/StepperActivity.kt b/sample/src/main/java/com/nerdstone/neatform/StepperActivity.kt index 72ae8f84..0bf342c0 100644 --- a/sample/src/main/java/com/nerdstone/neatform/StepperActivity.kt +++ b/sample/src/main/java/com/nerdstone/neatform/StepperActivity.kt @@ -15,7 +15,6 @@ import com.nerdstone.neatformcore.form.common.FormActions import com.nerdstone.neatformcore.form.json.JsonFormBuilder import com.nerdstone.neatformcore.form.json.JsonFormStepper import com.nerdstone.neatformcore.utils.DialogUtil -import com.nerdstone.neatformcore.utils.FormUtils import kotlinx.android.synthetic.main.activity_stepper.* import timber.log.Timber @@ -40,16 +39,13 @@ class StepperActivity : AppCompatActivity(), FormActions { .build() } - formBuilder = JsonFormBuilder(this, filePath) + formBuilder = + JsonFormBuilder(this, filePath) formBuilder.also { it.registeredViews["custom_image"] = CustomImageView::class if (preFilled!!) it.withFormData(Constants.PREVIOUS_DATA, mutableSetOf()) } JsonFormStepper(formBuilder as JsonFormBuilder, neatStepperLayout).buildForm() - formBuilder.dataViewModel.also { - if (it.details.value.isNullOrEmpty()) - it.updateDetails(FormUtils.parseFormDataJson(Constants.PREVIOUS_DATA)) - } } override fun onStepError(stepVerificationState: StepVerificationState) = Unit From 966d464a5320e431140b85c132a6eb84a0b7323f Mon Sep 17 00:00:00 2001 From: cozej4 Date: Tue, 26 May 2020 10:33:59 +0300 Subject: [PATCH 11/15] Updated forms to recreate the rule engine bug --- .../main/assets/sample/tb_followup_visit.json | 466 ++++++++++++++++++ .../main/assets/sample/tb_registration.json | 334 +++++++++++++ 2 files changed, 800 insertions(+) create mode 100644 sample/src/main/assets/sample/tb_followup_visit.json create mode 100644 sample/src/main/assets/sample/tb_registration.json diff --git a/sample/src/main/assets/sample/tb_followup_visit.json b/sample/src/main/assets/sample/tb_followup_visit.json new file mode 100644 index 00000000..0708e328 --- /dev/null +++ b/sample/src/main/assets/sample/tb_followup_visit.json @@ -0,0 +1,466 @@ +{ + "form": "TB Followup form", + "count": "1", + "encounter_type": "TB Followup", + "entity_id": "", + "relational_id": "", + "rules_file": "rule/tb_followup_form_rules.yml", + "metadata": { + "start": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "start", + "openmrs_entity_id": "163137AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "end": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "end", + "openmrs_entity_id": "163138AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "today": { + "openmrs_entity_parent": "", + "openmrs_entity": "encounter", + "openmrs_entity_id": "encounter_date" + }, + "deviceid": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "deviceid", + "openmrs_entity_id": "163149AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "subscriberid": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "subscriberid", + "openmrs_entity_id": "163150AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "simserial": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "simserial", + "openmrs_entity_id": "163151AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "phonenumber": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "phonenumber", + "openmrs_entity_id": "163152AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "encounter_location": "", + "look_up": { + "entity_id": "", + "value": "" + } + }, + "steps": [ + { + "title": "TB Followup form", + "fields": [ + { + "name": "registration_or_followup_status", + "type": "spinner", + "properties": { + "text": "Registration/Followup status" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "registration_or_followup_status", + "openmrs_entity_parent": "" + }, + "options": [ + { + "name": "new_client", + "text": "New Client", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "new_client", + "openmrs_entity_parent": "" + } + }, + { + "name": "continuing_with_services", + "text": "Continuing with services", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "continuing_with_services", + "openmrs_entity_parent": "" + } + }, + { + "name": "deceased", + "text": "Deceased", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "deceased", + "openmrs_entity_parent": "" + } + }, + { + "name": "client_not_found", + "text": "Client not found", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_not_found", + "openmrs_entity_parent": "" + } + }, + { + "name": "client_relocated_to_another_location", + "text": "Client has relocated to another location", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_relocated_to_another_location", + "openmrs_entity_parent": "" + } + }, + { + "name": "client_has_moved", + "text": "Client has moved ", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_has_moved", + "openmrs_entity_parent": "" + } + }, + { + "name": "client_has_absconded", + "text": "Client has absconded", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_has_absconded", + "openmrs_entity_parent": "" + } + }, + { + "name": "client_continues_with_clinic_from_elsewhere", + "text": "Clients continues with clinic from elsewhere", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_continues_with_clinic_from_elsewhere", + "openmrs_entity_parent": "" + } + }, + { + "name": "completed_and_qualified_from_the_services ", + "text": "Client has completed and qualified from the services ", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "completed_and_qualified_from_the_services", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please select the registration/followup status" + }, + { + "name": "client_condition", + "type": "spinner", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_condition", + "openmrs_entity_parent": "" + }, + "properties": { + "text": "Condition of the client" + }, + "options": [ + { + "name": "client_does_her_daily_activities", + "text": "Client does her daily activities", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_does_her_daily_activities", + "openmrs_entity_parent": "" + } + }, + { + "name": "client_takes_care_of_himself", + "text": "Client takes care of him/herself (bathing, Eating)", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_takes_care_of_himself", + "openmrs_entity_parent": "" + } + }, + { + "name": "client_is_unable_to_take_care_of_himself", + "text": "Client is unable to take care of him/herself", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_is_unable_to_take_care_of_himself", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please specify client's condition", + "subjects": "registration_or_followup_status:text" + }, + { + "name": "problem", + "type": "multi_choice_checkbox", + "properties": { + "text": "Pick problem/Social challenges faced by the Client." + }, + "meta_data": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_entity_id": "problem" + }, + "options": [ + { + "name": "other_problems", + "text": "Other Problems", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "other_problems", + "openmrs_entity_parent": "" + } + }, + { + "name": "none", + "text": "None", + "is_exclusive": true, + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please specify client's problems", + "subjects": "registration_or_followup_status:text" + }, + { + "name": "problem_other", + "type": "text_input_edit_text", + "properties": { + "hint": "Other symptoms", + "type": "name" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "problem_other", + "openmrs_entity_parent": "problem" + }, + "required_status": "true:Please specify other symptoms", + "subjects": "problem:map" + }, + { + "name": "client_behaviour_and_environmental_risk", + "type": "spinner", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_behaviour_and_environmental_risk", + "openmrs_entity_parent": "" + }, + "properties": { + "text": "Client's behaviour & environmental risks" + }, + "options": [ + { + "name": "none", + "text": "None", + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + } + }, + { + "name": "alcoholism", + "text": "Alcoholism", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "alcoholism", + "openmrs_entity_parent": "" + } + }, + { + "name": "vulnerable_environment", + "text": "Clients lives in a vulnerable environment", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "vulnerable_environment", + "openmrs_entity_parent": "" + } + }, + { + "name": "hiv_positive", + "text": "Client is HIV Positive", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "hiv_positive", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please specify client's behaviour", + "subjects": "registration_or_followup_status:text" + }, + { + "name": "supplies_provided", + "type": "multi_choice_checkbox", + "properties": { + "text": "Supplies/medicines provided" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "supplies_provided", + "openmrs_entity_parent": "" + }, + "options": [ + { + "name": "other_medicine", + "text": "Other treatment", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "other_medicine", + "openmrs_entity_parent": "" + } + }, + { + "name": "none", + "text": "None", + "is_exclusive": true, + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please choose supplies/medicine given", + "subjects": "registration_or_followup_status:text" + }, + { + "name": "supplies_provided_other", + "type": "text_input_edit_text", + "properties": { + "hint": "Other Supplies/Medicine", + "type": "name" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "supplies_provided_other", + "openmrs_entity_parent": "supplies_provided" + }, + "required_status": "true:Please specify other supplies/Medicine given", + "subjects": "supplies_provided:map" + }, + { + "name": "tb_services_provided", + "type": "multi_choice_checkbox", + "properties": { + "text": "TB services provided by CHW at the community" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "tb_services_provided", + "openmrs_entity_parent": "" + }, + "options": [ + { + "name": "other_services", + "text": "Other TB services", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "other_tb_services", + "openmrs_entity_parent": "" + } + }, + { + "name": "none", + "text": "None", + "is_exclusive": true, + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please TB services provided", + "subjects": "registration_or_followup_status:text" + }, + { + "name": "tb_services_provided_other", + "type": "text_input_edit_text", + "properties": { + "hint": "Other TB Services Provided", + "type": "name" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "tb_services_provided_other", + "openmrs_entity_parent": "tb_services_provided" + }, + "required_status": "true:Please specify other TB services provided", + "subjects": "tb_services_provided:map" + }, + { + "name": "state_of_therapy", + "type": "spinner", + "properties": { + "text": "State of therapy and (TB) care" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "state_of_therapy", + "openmrs_entity_parent": "" + }, + "options": [ + { + "name": "registered_but_not_began_medication", + "text": "Registered in TB clinic but hasn't begun TB/DR-TB medication", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "registered_but_not_began_medication", + "openmrs_entity_parent": "" + } + }, + { + "name": "registered_and_uses_medication", + "text": "Registered in TB clinic and uses TB/ DR TB medication", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "registered_and_uses_medication", + "openmrs_entity_parent": "" + } + }, + { + "name": "not_registered_in_tb_clinic", + "text": "Client is not registed in TB clinic", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "not_registered_in_tb_clinic", + "openmrs_entity_parent": "" + } + }, + { + "name": "none", + "text": "None", + "meta_data": { + "openmrs_entity": "", + "openmrs_entity_id": "", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please specify the state of TB therapy", + "subjects": "registration_or_followup_status:text" + }, + { + "name": "tb_followup_visit_date", + "type": "hidden", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "tb_followup_visit_date", + "openmrs_entity_parent": "" + }, + "required_status": "false" + } + ] + } + ] +} \ No newline at end of file diff --git a/sample/src/main/assets/sample/tb_registration.json b/sample/src/main/assets/sample/tb_registration.json new file mode 100644 index 00000000..7d04b27d --- /dev/null +++ b/sample/src/main/assets/sample/tb_registration.json @@ -0,0 +1,334 @@ +{ + "form": "TB Registration form", + "count": "1", + "encounter_type": "TB Registration", + "entity_id": "", + "relational_id": "", + "rules_file": "rule/tb_registration_form_rules.yml", + "metadata": { + "start": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "start", + "openmrs_entity_id": "163137AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "end": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "end", + "openmrs_entity_id": "163138AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "today": { + "openmrs_entity_parent": "", + "openmrs_entity": "encounter", + "openmrs_entity_id": "encounter_date" + }, + "deviceid": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "deviceid", + "openmrs_entity_id": "163149AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "subscriberid": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "subscriberid", + "openmrs_entity_id": "163150AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "simserial": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "simserial", + "openmrs_entity_id": "163151AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "phonenumber": { + "openmrs_entity_parent": "", + "openmrs_entity": "concept", + "openmrs_data_type": "phonenumber", + "openmrs_entity_id": "163152AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "encounter_location": "", + "look_up": { + "entity_id": "", + "value": "" + } + }, + "steps": [ + { + "title": "TB Clients Registration form", + "fields": [ + { + "name": "community_client_tb_registration_number", + "type": "text_input_edit_text", + "properties": { + "hint": "Community Based Health Services Registration Number (CBHS Number)", + "type": "name", + "padding": "8" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "community_client_tb_registration_number", + "openmrs_entity_parent": "" + }, + "required_status": "yes:Please add cbhs number" + }, + { + "name": "client_tb_status_during_registration", + "type": "spinner", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_tb_status_during_registration", + "openmrs_entity_parent": "" + }, + "properties": { + "text": "Client's TB status during registration" + }, + "options": [ + { + "name": "unknown", + "text": "Unknown", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "unknown", + "openmrs_entity_parent": "" + } + }, + { + "name": "positive", + "text": "Positive", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "positive", + "openmrs_entity_parent": "" + } + }, + { + "name": "negative", + "text": "Negative", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "negative", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please specify your gender" + }, + { + "name": "place_of_domicile", + "type": "spinner", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "place_of_domicile", + "openmrs_entity_parent": "" + }, + "properties": { + "text": "Place of Domicile" + }, + "options": [ + { + "name": "a_family_with_tb_patient", + "text": "A family with TB patient", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "a_family_with_tb_patient", + "openmrs_entity_parent": "" + } + }, + { + "name": "a_family_without_tb_patient", + "text": "A family without TB patient", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "a_family_without_tb_patient", + "openmrs_entity_parent": "" + } + }, + { + "name": "community_gathering", + "text": "Community Gathering", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "community_gathering", + "openmrs_entity_parent": "" + } + }, + { + "name": "pharmacy", + "text": "Pharmacy", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "pharmacy", + "openmrs_entity_parent": "" + } + }, + { + "name": "traditional_doctors", + "text": "Traditional doctors", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "traditional_doctors", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please specify place of domicile" + }, + { + "name": "community_gathering", + "type": "spinner", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "community_gathering", + "openmrs_entity_parent": "" + }, + "properties": { + "text": "Type of community gathering" + }, + "options": [ + { + "name": "school", + "text": "School", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "school", + "openmrs_entity_parent": "" + } + }, + { + "name": "worship_houses", + "text": "Worship houses", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "worship_houses", + "openmrs_entity_parent": "" + } + }, + { + "name": "mining", + "text": "Mining", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "mining", + "openmrs_entity_parent": "" + } + }, + { + "name": "public_meetings", + "text": "Public meetings", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "public_meetings", + "openmrs_entity_parent": "" + } + }, + { + "name": "prison", + "text": "Prison", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "prison", + "openmrs_entity_parent": "" + } + }, + { + "name": "others", + "text": "Others", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "others", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please specify the type of community gathering", + "subjects": "place_of_domicile:text" + }, + { + "name": "other_community_gathering", + "type": "text_input_edit_text", + "properties": { + "hint": "Specify other community gathering" + }, + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "other_community_gathering", + "openmrs_entity_parent": "" + }, + "required_status": "yes:Please specify other community gathering", + "subjects": "community_gathering:text" + }, + { + "name": "client_screening_results", + "type": "spinner", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "client_screening_results", + "openmrs_entity_parent": "" + }, + "properties": { + "text": "Client's screening results" + }, + "options": [ + { + "name": "coughing", + "text": "Coughing", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "coughing", + "openmrs_entity_parent": "" + } + }, + { + "name": "hemoptysis", + "text": "Sputum with blood (hemoptysis) ", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "hemoptysis", + "openmrs_entity_parent": "" + } + }, + { + "name": "fever", + "text": "Fever", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "fever", + "openmrs_entity_parent": "" + } + }, + { + "name": "weight_lose", + "text": "Weight lose", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "weight_lose", + "openmrs_entity_parent": "" + } + }, + { + "name": "history_of_night_sweats", + "text": "History of night sweats", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "history_of_night_sweats", + "openmrs_entity_parent": "" + } + } + ], + "required_status": "yes:Please specify your gender" + }, + { + "name": "tb_registration_date", + "type": "hidden", + "meta_data": { + "openmrs_entity": "concept", + "openmrs_entity_id": "tb_registration_date", + "openmrs_entity_parent": "" + }, + "required_status": "false" + } + ] + } + ] +} \ No newline at end of file From f11a1b342cc4f5340fb11149d70bfee0851bccd9 Mon Sep 17 00:00:00 2001 From: cozej4 Date: Tue, 26 May 2020 11:20:51 +0300 Subject: [PATCH 12/15] Added missing rule files --- .../rules/yml/tb_followup_form_rules.yml | 64 +++++++++++++++++++ .../rules/yml/tb_registration_form_rules.yml | 14 ++++ .../main/assets/sample/tb_followup_visit.json | 12 +--- .../main/assets/sample/tb_registration.json | 12 +--- .../com/nerdstone/neatform/MainActivity.kt | 10 +-- 5 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 sample/src/main/assets/rules/yml/tb_followup_form_rules.yml create mode 100644 sample/src/main/assets/rules/yml/tb_registration_form_rules.yml diff --git a/sample/src/main/assets/rules/yml/tb_followup_form_rules.yml b/sample/src/main/assets/rules/yml/tb_followup_form_rules.yml new file mode 100644 index 00000000..81856270 --- /dev/null +++ b/sample/src/main/assets/rules/yml/tb_followup_form_rules.yml @@ -0,0 +1,64 @@ +--- +name: "client_condition_visibility" +description: "client_condition visibility" +priority: 1 +condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +actions: + - "client_condition_visibility = true" +--- +name: "problem_visibility" +description: "problem visibility" +priority: 1 +condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +actions: + - "problem_visibility = true" +--- +name: "problem_other_visibility" +description: "problem visibility" +priority: 1 +condition: "problem['other_problems'] != null" +actions: + - "problem_other_visibility = true" +--- +name: "client_behaviour_and_environmental_risk_visibility" +description: "client_behaviour_and_environmental_risk visibility" +priority: 1 +condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +actions: + - "client_behaviour_and_environmental_risk_visibility = true" +--- +name: "supplies_provided_visibility" +description: "supplies_provided visibility" +priority: 1 +condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +actions: + - "supplies_provided_visibility = true" +--- +name: "supplies_provided_other_visibility" +description: "supplies_provided_other visibility" +priority: 1 +condition: "supplies_provided['other_medicine'] != null" +actions: + - "supplies_provided_other_visibility = true" +--- +name: "tb_services_provided_visibility" +description: "tb_services_provided visibility" +priority: 1 +condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +actions: + - "tb_services_provided_visibility = true" +--- +name: "tb_services_provided_other_visibility" +description: "tb_services_provided_other visibility" +priority: 1 +condition: "tb_services_provided['other_services'] != null" +actions: + - "tb_services_provided_other_visibility = true" +--- +name: "state_of_therapy_visibility" +description: "state_of_therapy visibility" +priority: 1 +condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +actions: + - "state_of_therapy_visibility = true" + diff --git a/sample/src/main/assets/rules/yml/tb_registration_form_rules.yml b/sample/src/main/assets/rules/yml/tb_registration_form_rules.yml new file mode 100644 index 00000000..a3b2cdeb --- /dev/null +++ b/sample/src/main/assets/rules/yml/tb_registration_form_rules.yml @@ -0,0 +1,14 @@ +--- +name: "community_gathering_visibility" +description: "Display community gathering types if community gathering is chosen" +priority: 1 +condition: "place_of_domicile.value=='Community Gathering'" +actions: + - "community_gathering_visibility = true" +--- +name: "other_community_gathering_visibility" +description: "Display other community gathering types if others is chosen" +priority: 1 +condition: "community_gathering.value == 'Others'" +actions: + - "other_community_gathering_visibility = true" \ No newline at end of file diff --git a/sample/src/main/assets/sample/tb_followup_visit.json b/sample/src/main/assets/sample/tb_followup_visit.json index 0708e328..4611b27a 100644 --- a/sample/src/main/assets/sample/tb_followup_visit.json +++ b/sample/src/main/assets/sample/tb_followup_visit.json @@ -4,7 +4,7 @@ "encounter_type": "TB Followup", "entity_id": "", "relational_id": "", - "rules_file": "rule/tb_followup_form_rules.yml", + "rules_file": "rules/yml/tb_followup_form_rules.yml", "metadata": { "start": { "openmrs_entity_parent": "", @@ -449,16 +449,6 @@ ], "required_status": "yes:Please specify the state of TB therapy", "subjects": "registration_or_followup_status:text" - }, - { - "name": "tb_followup_visit_date", - "type": "hidden", - "meta_data": { - "openmrs_entity": "concept", - "openmrs_entity_id": "tb_followup_visit_date", - "openmrs_entity_parent": "" - }, - "required_status": "false" } ] } diff --git a/sample/src/main/assets/sample/tb_registration.json b/sample/src/main/assets/sample/tb_registration.json index 7d04b27d..f8f4e3fd 100644 --- a/sample/src/main/assets/sample/tb_registration.json +++ b/sample/src/main/assets/sample/tb_registration.json @@ -4,7 +4,7 @@ "encounter_type": "TB Registration", "entity_id": "", "relational_id": "", - "rules_file": "rule/tb_registration_form_rules.yml", + "rules_file": "rules/yml/tb_registration_form_rules.yml", "metadata": { "start": { "openmrs_entity_parent": "", @@ -317,16 +317,6 @@ } ], "required_status": "yes:Please specify your gender" - }, - { - "name": "tb_registration_date", - "type": "hidden", - "meta_data": { - "openmrs_entity": "concept", - "openmrs_entity_id": "tb_registration_date", - "openmrs_entity_parent": "" - }, - "required_status": "false" } ] } diff --git a/sample/src/main/java/com/nerdstone/neatform/MainActivity.kt b/sample/src/main/java/com/nerdstone/neatform/MainActivity.kt index 19049713..d638a28b 100644 --- a/sample/src/main/java/com/nerdstone/neatform/MainActivity.kt +++ b/sample/src/main/java/com/nerdstone/neatform/MainActivity.kt @@ -45,14 +45,14 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { formRecyclerAdapter.formList = mutableListOf( FormData( - formTitle = "Profile - Sample", + formTitle = "TB Registration", formCategory = FormType.embeddableDefault, - filePath = "sample/sample_one_form.json" + filePath = "sample/tb_registration.json" ), FormData( - formTitle = "Profile - Customized Sample", - formCategory = FormType.embeddableCustomized, - filePath = "sample/sample_one_form.json" + formTitle = "TB Followup", + formCategory = FormType.embeddableDefault, + filePath = "sample/tb_followup_visit.json" ), FormData( formTitle = "Profile - Sample 2", From 9cf85860527a2a82580480bc97d7dc520a05a23a Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Wed, 27 May 2020 02:51:01 +0300 Subject: [PATCH 13/15] Fix bug on rules engine - Remove singleton classes that are interfering with generation of views Signed-off-by: Elly Kitoto --- .../main/assets/sample/sample_one_form.json | 2 +- .../main/assets/sample/sample_two_form.json | 19 ++++---- .../domain/builders/FormBuilder.kt | 6 ++- .../neatformcore/domain/view/FormValidator.kt | 4 -- .../neatformcore/domain/view/RootView.kt | 9 ++-- .../neatformcore/form/json/JsonFormBuilder.kt | 20 ++++---- .../neatformcore/rules/NeatFormValidator.kt | 37 +++++---------- .../neatformcore/rules/RulesFactory.kt | 14 +----- .../nerdstone/neatformcore/utils/ViewUtils.kt | 47 ++++++++++--------- .../views/containers/MultiChoiceCheckBox.kt | 6 +-- .../views/containers/RadioGroupView.kt | 3 +- .../views/containers/VerticalRootView.kt | 5 +- .../views/handlers/ViewDispatcher.kt | 16 +------ .../views/widgets/CheckBoxNFormView.kt | 3 +- .../views/widgets/DateTimePickerNFormView.kt | 3 +- .../views/widgets/EditTextNFormView.kt | 3 +- .../views/widgets/NotificationNFormView.kt | 3 +- .../views/widgets/NumberSelectorNFormView.kt | 3 +- .../views/widgets/SpinnerNFormView.kt | 3 +- .../widgets/TextInputEditTextNFormView.kt | 3 +- .../builders/BaseJsonViewBuilderTest.kt | 6 +-- .../builders/EditTextViewBuilderTest.kt | 4 +- .../builders/NotificationViewBuilderTest.kt | 11 ++--- .../builders/TextInputEditTextBuilderTest.kt | 4 +- .../handlers/ViewDispatcherTest.kt | 11 ++--- .../rules/yml/tb_followup_form_rules.yml | 17 ++++--- .../rules/yml/tb_registration_form_rules.yml | 2 +- .../main/assets/sample/sample_two_form.json | 27 +++++------ .../main/assets/sample/tb_registration.json | 4 +- .../com/nerdstone/neatform/MainActivity.kt | 29 +++++++----- .../neatform/custom/views/CustomImageView.kt | 2 +- .../nerdstone/neatform/form/FormActivity.kt | 8 ++-- 32 files changed, 137 insertions(+), 197 deletions(-) diff --git a/neat-form-core/src/main/assets/sample/sample_one_form.json b/neat-form-core/src/main/assets/sample/sample_one_form.json index 4e666be1..d65ac1a1 100644 --- a/neat-form-core/src/main/assets/sample/sample_one_form.json +++ b/neat-form-core/src/main/assets/sample/sample_one_form.json @@ -30,7 +30,7 @@ "error_message": "Not a valid phone number" } ], - "subjects": "age:number, child:text, dob:number", + "subjects": "age:number, child:text", "required_status": "True:please add phone number" }, { diff --git a/neat-form-core/src/main/assets/sample/sample_two_form.json b/neat-form-core/src/main/assets/sample/sample_two_form.json index 1e0cc9f5..61211095 100644 --- a/neat-form-core/src/main/assets/sample/sample_two_form.json +++ b/neat-form-core/src/main/assets/sample/sample_two_form.json @@ -9,9 +9,7 @@ "name": "adult", "type": "text_input_edit_text", "properties": { - "hint": "This is an adult", - "type": "name", - "padding": "8" + "hint": "This is an adult" }, "meta_data": { "openmrs_entity": "", @@ -21,15 +19,16 @@ "validation": [ { "validation_name": "length", - "condition": "value.length() < 8", - "error_message": "value should be less than eight digits" - },{ - "validation_name": "special characters", - "condition": "!value.contains('@')", - "error_message": "value should not contain special character" + "condition": "value.length() < 11", + "error_message": "value should be less than ten digits" + }, + { + "validation_name": "phone number", + "condition": "value.matches(\"\\\\d{10}\")", + "error_message": "Not a valid phone number" } ], - "subjects": "age:number, child:text, dob:number", + "subjects": "age:number", "required_status": "True:please add phone number" }, { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt index 860e7a89..27f5c619 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/builders/FormBuilder.kt @@ -6,6 +6,7 @@ import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.viewmodel.DataViewModel import com.nerdstone.neatformcore.viewmodel.FormViewModel +import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import kotlin.reflect.KClass /** @@ -33,12 +34,15 @@ interface FormBuilder { var dataViewModel: DataViewModel - var formValidator: FormValidator + val formValidator: FormValidator var registeredViews: HashMap> var formViewModel: FormViewModel + val rulesFactory: RulesFactory + val viewDispatcher: ViewDispatcher + /** * This method reads the rules file available withing the given [context] and returns true when done * false otherwise. You also setup the [rulesFileType] in order for this to work. The form builder diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/FormValidator.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/FormValidator.kt index 5eedc3e2..72cf244b 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/FormValidator.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/FormValidator.kt @@ -1,11 +1,7 @@ package com.nerdstone.neatformcore.domain.view -import com.nerdstone.neatformcore.domain.builders.FormBuilder - interface FormValidator { - var formBuilder: FormBuilder - val invalidFields: HashSet val requiredFields: HashSet diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/RootView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/RootView.kt index b30aeafc..c7f08e52 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/RootView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/domain/view/RootView.kt @@ -2,10 +2,9 @@ package com.nerdstone.neatformcore.domain.view import com.nerdstone.neatformcore.domain.builders.FormBuilder import com.nerdstone.neatformcore.domain.model.NFormViewProperty -import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import java.io.Serializable -interface RootView: Serializable{ +interface RootView : Serializable { var formBuilder: FormBuilder @@ -13,6 +12,8 @@ interface RootView: Serializable{ fun addChild(nFormView: NFormView) - fun addChildren(viewProperties: List, viewDispatcher: ViewDispatcher,buildFromLayout: Boolean=false) - + fun addChildren( + viewProperties: List, formBuilder: FormBuilder, + buildFromLayout: Boolean = false + ) } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt index 63c46611..94e300d6 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/form/json/JsonFormBuilder.kt @@ -53,19 +53,19 @@ object JsonFormConstants { */ class JsonFormBuilder() : FormBuilder { internal val readOnlyFields = mutableSetOf() - private val viewDispatcher: ViewDispatcher = ViewDispatcher.INSTANCE - private val rulesFactory: RulesFactory = RulesFactory.INSTANCE + override lateinit var context: Context + override lateinit var dataViewModel: DataViewModel + override lateinit var formViewModel: FormViewModel + override val rulesFactory: RulesFactory = RulesFactory() + override val viewDispatcher: ViewDispatcher = ViewDispatcher(rulesFactory) + override val formValidator: FormValidator = NeatFormValidator() + override var registeredViews = hashMapOf>() private val rulesHandler = rulesFactory.rulesHandler var defaultContextProvider: DispatcherProvider var form: NForm? = null var fileSource: String? = null var formDataJson: String? = null override var formString: String? = null - override lateinit var context: Context - override lateinit var dataViewModel: DataViewModel - override var formValidator: FormValidator = NeatFormValidator.INSTANCE - override var registeredViews = hashMapOf>() - override lateinit var formViewModel: FormViewModel constructor(context: Context, fileSource: String) : this() { this.context = context @@ -73,6 +73,7 @@ class JsonFormBuilder() : FormBuilder { this.dataViewModel = ViewModelProvider(context as FragmentActivity)[DataViewModel::class.java] this.formViewModel = ViewModelProvider(context)[FormViewModel::class.java] + } constructor(jsonString: String, context: Context) : this() { @@ -85,7 +86,6 @@ class JsonFormBuilder() : FormBuilder { init { rulesHandler.formBuilder = this - formValidator.formBuilder = this defaultContextProvider = DefaultDispatcherProvider() } @@ -108,9 +108,9 @@ class JsonFormBuilder() : FormBuilder { when { view.isNotNull() -> { verticalRootView.addView(view) - verticalRootView.addChildren(formContent.fields, viewDispatcher, true) + verticalRootView.addChildren(formContent.fields, this, true) } - else -> verticalRootView.addChildren(formContent.fields, viewDispatcher) + else -> verticalRootView.addChildren(formContent.fields, this) } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NeatFormValidator.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NeatFormValidator.kt index a3bb863f..c019c190 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NeatFormValidator.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NeatFormValidator.kt @@ -4,16 +4,15 @@ import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import android.widget.TextView -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.ViewModelProvider import com.nerdstone.neatformcore.R -import com.nerdstone.neatformcore.domain.builders.FormBuilder import com.nerdstone.neatformcore.domain.model.NFormFieldValidation +import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.utils.VALIDATION_RESULT import com.nerdstone.neatformcore.utils.VALUE +import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.viewmodel.DataViewModel import org.jeasy.rules.api.Facts import org.jeasy.rules.api.Rule @@ -25,11 +24,9 @@ import java.util.* /*** * @author Elly Nerdstone * This class is used to handle form validations - * @property formBuilder Form builder that validation is performed on */ -class NeatFormValidator private constructor() : FormValidator { +class NeatFormValidator : FormValidator { - override lateinit var formBuilder: FormBuilder private val rulesEngine = DefaultRulesEngine() private val facts = Facts() override val invalidFields = hashSetOf() @@ -58,7 +55,7 @@ class NeatFormValidator private constructor() : FormValidator { } if (nFormView.viewProperties.validations != null) { nFormView.viewProperties.validations?.forEach { validation -> - if (!performValidation(validation, viewDetails.value)) { + if (!performValidation(validation, viewDetails)) { invalidFields.add(viewDetails.name) return Pair(false, validation.message) } @@ -100,13 +97,14 @@ class NeatFormValidator private constructor() : FormValidator { /** * @param validation Validation to run - * @param value value used to run the validation against + * @param viewDetails details about the current view * @return true if validation passes false otherwise */ - private fun performValidation(validation: NFormFieldValidation, value: Any?): Boolean { + private fun performValidation(validation: NFormFieldValidation, viewDetails: NFormViewDetails): Boolean { facts.put(VALIDATION_RESULT, false) - facts.asMap().putAll(getFormData()) - facts.put(VALUE, value) + val dataViewModel:DataViewModel = ViewUtils.getDataViewModel(viewDetails) + facts.asMap().putAll(getFormData(dataViewModel)) + facts.put(VALUE, viewDetails.value) val customRule: Rule = MVELRule() .name(UUID.randomUUID().toString()) @@ -120,21 +118,8 @@ class NeatFormValidator private constructor() : FormValidator { return facts.get(VALIDATION_RESULT) } - private fun getFormData(): MutableMap { - return ViewModelProvider(formBuilder.context as FragmentActivity)[DataViewModel::class.java] - .details.value?.mapValuesTo(mutableMapOf(), { entry -> entry.value })!! - } - - companion object { - @Volatile - private var instance: NeatFormValidator? = null - - val INSTANCE: NeatFormValidator - get() = instance ?: synchronized(this) { - NeatFormValidator().also { - instance = it - } - } + private fun getFormData(dataViewModel: DataViewModel): MutableMap { + return dataViewModel.details.value?.mapValuesTo(mutableMapOf(), { entry -> entry.value })!! } } \ No newline at end of file diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/RulesFactory.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/RulesFactory.kt index b0dbb43c..78a681da 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/RulesFactory.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/RulesFactory.kt @@ -23,7 +23,7 @@ import java.io.BufferedReader import java.io.InputStreamReader import java.util.* -class RulesFactory private constructor() : RuleListener { +class RulesFactory : RuleListener { private var facts: Facts = Facts() private var rulesEngine: DefaultRulesEngine = DefaultRulesEngine() @@ -176,16 +176,4 @@ class RulesFactory private constructor() : RuleListener { enum class RulesFileType { JSON, YAML } - - companion object { - @Volatile - private var instance: RulesFactory? = null - - val INSTANCE: RulesFactory - get() = instance ?: synchronized(this) { - RulesFactory().also { - instance = it - } - } - } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt index 6d441c76..b674f6b2 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt @@ -19,13 +19,13 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModelProvider import com.nerdstone.neatformcore.BuildConfig import com.nerdstone.neatformcore.R +import com.nerdstone.neatformcore.domain.builders.FormBuilder import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.domain.view.RootView import com.nerdstone.neatformcore.viewmodel.DataViewModel -import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import timber.log.Timber import java.util.* import kotlin.reflect.KClass @@ -38,20 +38,20 @@ object ViewUtils { fun createViews( rootView: RootView, viewProperties: List, - viewDispatcher: ViewDispatcher, buildFromLayout: Boolean = false + formBuilder: FormBuilder, buildFromLayout: Boolean = false ) { - val registeredViews = rootView.formBuilder.registeredViews + val registeredViews = formBuilder.registeredViews for (viewProperty in viewProperties) { - buildView( - buildFromLayout, rootView, viewProperty, viewDispatcher, - registeredViews[viewProperty.type] as KClass<*> - ) + val kClass = registeredViews[viewProperty.type] as KClass<*> + if (kClass.isNotNull()) { + buildView(buildFromLayout, rootView, viewProperty, formBuilder, kClass) + } } } private fun buildView( buildFromLayout: Boolean, rootView: RootView, - viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher, kClass: KClass<*> + viewProperty: NFormViewProperty, formBuilder: FormBuilder, kClass: KClass<*> ) { val androidView = rootView as View val context = rootView.context @@ -60,7 +60,7 @@ object ViewUtils { context.resources.getIdentifier(viewProperty.name, ID, context.packageName) ) try { - getView(view as NFormView, viewProperty, viewDispatcher) + getView(view as NFormView, viewProperty, formBuilder) } catch (e: Exception) { val message = "ERROR: The view with name ${viewProperty.name} defined in json form is missing in custom layout" @@ -70,25 +70,27 @@ object ViewUtils { } else { val constructor = kClass.constructors.minBy { it.parameters.size } rootView.addChild( - getView(constructor!!.call(context) as NFormView, viewProperty, viewDispatcher) + getView(constructor!!.call(context) as NFormView, viewProperty, formBuilder) ) } } private fun getView( - nFormView: NFormView, viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher + nFormView: NFormView, viewProperty: NFormViewProperty, formBuilder: FormBuilder ): NFormView { if (viewProperty.subjects != null) { - viewDispatcher.rulesFactory - .registerSubjects(splitText(viewProperty.subjects, ","), viewProperty) - val hasVisibilityRule = viewDispatcher.rulesFactory.viewHasVisibilityRule(viewProperty) - if (hasVisibilityRule) { - viewDispatcher.rulesFactory.rulesHandler.changeVisibility( - false, nFormView.viewDetails.view - ) + val viewDispatcher = formBuilder.viewDispatcher + viewDispatcher.run { + rulesFactory.registerSubjects(splitText(viewProperty.subjects, ","), viewProperty) + val hasVisibilityRule = rulesFactory.viewHasVisibilityRule(viewProperty) + if (hasVisibilityRule) { + rulesFactory.rulesHandler.changeVisibility( + false, nFormView.viewDetails.view + ) + } } } - return setupView(nFormView, viewProperty, viewDispatcher) + return setupView(nFormView, viewProperty, formBuilder) } fun splitText(text: String?, delimiter: String): List { @@ -118,13 +120,14 @@ object ViewUtils { } fun setupView( - nFormView: NFormView, viewProperty: NFormViewProperty, viewDispatcher: ViewDispatcher + nFormView: NFormView, viewProperty: NFormViewProperty, formBuilder: FormBuilder ): NFormView { with(nFormView) { - //Set view properties and view dispatcher + //Set late init properties viewProperties = viewProperty - dataActionListener = viewDispatcher + dataActionListener = formBuilder.viewDispatcher + formValidator = formBuilder.formValidator with(viewDetails) { name = viewProperty.name diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt index e30228c9..00e9cfb9 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/MultiChoiceCheckBox.kt @@ -10,7 +10,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.MultiChoiceCheckBoxViewBuilder @@ -19,12 +18,12 @@ import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class MultiChoiceCheckBox : LinearLayout, NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override val viewBuilder = MultiChoiceCheckBoxViewBuilder(this) override val viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var initialValue: Any? = null private var checkBoxOptionsTextSize: Float = 0f @@ -54,8 +53,7 @@ class MultiChoiceCheckBox : LinearLayout, NFormView { } } - override fun validateValue(): Boolean = - formValidator.validateLabeledField(this) + override fun validateValue() = formValidator.validateLabeledField(this) override fun setVisibility(visibility: Int) { super.setVisibility(visibility) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt index 3bf49e7d..d04fecc8 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/RadioGroupView.kt @@ -9,7 +9,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.RadioGroupViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler @@ -17,12 +16,12 @@ import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class RadioGroupView : LinearLayout, NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override val viewBuilder = RadioGroupViewBuilder(this) override val viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var initialValue: Any? = null init { diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/VerticalRootView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/VerticalRootView.kt index e011b238..479e3305 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/VerticalRootView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/containers/VerticalRootView.kt @@ -9,7 +9,6 @@ import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.domain.view.RootView import com.nerdstone.neatformcore.utils.Utils import com.nerdstone.neatformcore.utils.ViewUtils -import com.nerdstone.neatformcore.views.handlers.ViewDispatcher class VerticalRootView : LinearLayout, RootView { @@ -40,6 +39,6 @@ class VerticalRootView : LinearLayout, RootView { } override fun addChildren( - viewProperties: List, viewDispatcher: ViewDispatcher, buildFromLayout: Boolean - ) = ViewUtils.createViews(this, viewProperties, viewDispatcher,buildFromLayout) + viewProperties: List, formBuilder: FormBuilder ,buildFromLayout: Boolean + ) = ViewUtils.createViews(this, viewProperties, formBuilder, buildFromLayout) } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt index 41363bf4..5e00f20e 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/handlers/ViewDispatcher.kt @@ -19,9 +19,7 @@ import com.nerdstone.neatformcore.utils.isNotNull * * The rules are handled by the [RulesFactory] class and the validation */ -class ViewDispatcher private constructor() : DataActionListener { - - val rulesFactory: RulesFactory = RulesFactory.INSTANCE +class ViewDispatcher(val rulesFactory: RulesFactory ) : DataActionListener { /** * Dispatches an action when view value changes. If value is the same as what had already been @@ -52,16 +50,4 @@ class ViewDispatcher private constructor() : DataActionListener { } } } - - companion object { - @Volatile - private var instance: ViewDispatcher? = null - - val INSTANCE: ViewDispatcher - get() = instance ?: synchronized(this) { - ViewDispatcher().also { - instance = it - } - } - } } diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt index 14cfdd62..8895c138 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/CheckBoxNFormView.kt @@ -9,7 +9,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.utils.removeAsterisk @@ -19,12 +18,12 @@ import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class CheckBoxNFormView : AppCompatCheckBox, NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override val viewBuilder = CheckBoxViewBuilder(this) override val viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var initialValue: Any? = null constructor(context: Context) : super(context) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt index 9e63faaf..4889e888 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/DateTimePickerNFormView.kt @@ -9,7 +9,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.views.builders.DateTimePickerViewBuilder @@ -18,12 +17,12 @@ import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class DateTimePickerNFormView : TextInputLayout, NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override val viewBuilder = DateTimePickerViewBuilder(this) override val viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var initialValue: Any? = null constructor(context: Context) : super(context) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt index a3636856..131db9d1 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/EditTextNFormView.kt @@ -9,7 +9,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.utils.removeAsterisk @@ -19,12 +18,12 @@ import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class EditTextNFormView : AppCompatEditText, NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override val viewBuilder = EditTextViewBuilder(this) override var viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var initialValue: Any? = null constructor(context: Context) : super(context) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt index d844da8f..74dc1d69 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NotificationNFormView.kt @@ -11,7 +11,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView import com.nerdstone.neatformcore.rules.NFormRulesHandler -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.views.builders.NotificationViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler import timber.log.Timber @@ -19,12 +18,12 @@ import timber.log.Timber class NotificationNFormView : FrameLayout, NFormView, CalculationChangeListener { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override val viewBuilder = NotificationViewBuilder(this) override val viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var initialValue: Any? = null private val rulesHandler = NFormRulesHandler.INSTANCE diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt index 50e73f37..9f1278e5 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/NumberSelectorNFormView.kt @@ -9,7 +9,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.NumberSelectorViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler @@ -17,10 +16,10 @@ import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class NumberSelectorNFormView : LinearLayout, NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override val viewBuilder = NumberSelectorViewBuilder(this) override var viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override var initialValue: Any? = null diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt index b30642f5..d21e1bfc 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/SpinnerNFormView.kt @@ -10,7 +10,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.VALUE import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState @@ -20,10 +19,10 @@ import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class SpinnerNFormView : LinearLayout, NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override val viewBuilder = SpinnerViewBuilder(this) override var viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override var initialValue: Any? = null diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt index 1f0b3944..059d4dba 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/views/widgets/TextInputEditTextNFormView.kt @@ -9,7 +9,6 @@ import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.domain.view.FormValidator import com.nerdstone.neatformcore.domain.view.NFormView -import com.nerdstone.neatformcore.rules.NeatFormValidator import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.utils.ViewUtils.setReadOnlyState import com.nerdstone.neatformcore.views.builders.TextInputEditTextBuilder @@ -18,12 +17,12 @@ import com.nerdstone.neatformcore.views.handlers.ViewVisibilityChangeHandler class TextInputEditTextNFormView : TextInputLayout, NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override val viewBuilder = TextInputEditTextBuilder(this) override var viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var initialValue: Any? = null constructor(context: Context) : super(context) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt index b5b2be27..31314023 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt @@ -14,9 +14,5 @@ open class BaseJsonViewBuilderTest { Robolectric.buildActivity(AppCompatActivity::class.java).setup() val mainLayout: ViewGroup = LinearLayout(activity.get()) var formBuilder = JsonFormBuilder(TestConstants.SAMPLE_JSON, activity.get()) - val formValidator = NeatFormValidator.INSTANCE - - init { - formValidator.formBuilder = formBuilder - } + val formValidator = NeatFormValidator() } \ No newline at end of file diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt index 06794970..1e66a1c5 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt @@ -5,6 +5,7 @@ import android.view.View import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormFieldValidation import com.nerdstone.neatformcore.domain.model.NFormViewProperty +import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.EditTextViewBuilder import com.nerdstone.neatformcore.views.handlers.ViewDispatcher @@ -26,7 +27,6 @@ class EditTextViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) private val editTextNFormView = EditTextNFormView(activity.get()) private val editTextViewBuilder = spyk(EditTextViewBuilder(editTextNFormView)) - private val dataActionListener = spyk(objToCopy = ViewDispatcher.INSTANCE) @Before fun `Before doing anything else`() { @@ -35,7 +35,7 @@ class EditTextViewBuilderTest : BaseJsonViewBuilderTest() { //Set EditText properties and assign EditText view builder editTextNFormView.formValidator = this.formValidator editTextNFormView.viewProperties = viewProperty - ViewUtils.setupView(editTextNFormView, viewProperty, dataActionListener) + ViewUtils.setupView(editTextNFormView, viewProperty, formBuilder) } @Test diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt index 48619607..5ec1246f 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt @@ -30,7 +30,6 @@ class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { private val notificationNFormView = NotificationNFormView(activity.get()) private val viewProperty = spyk(NFormViewProperty()) - private val dataActionListener = spyk(objToCopy = ViewDispatcher.INSTANCE) @Before fun `Before doing anything else`() { @@ -46,7 +45,7 @@ class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { @Test fun `Should have title and text`() { - ViewUtils.setupView(notificationNFormView, viewProperty, dataActionListener) + ViewUtils.setupView(notificationNFormView, viewProperty, formBuilder) val constraintLayout = notificationNFormView.getChildAt(0) as ViewGroup val titleTextView = constraintLayout.findViewById(R.id.notificationTitleTextView) val notificationTextTextView = @@ -65,7 +64,7 @@ class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { fun `Should change text and background color of toast notification`() { notificationNFormView.viewProperties.viewAttributes?.set("text_color", "#cccccc") notificationNFormView.viewProperties.viewAttributes?.set("background_color", "#fc3c5c") - ViewUtils.setupView(notificationNFormView, viewProperty, dataActionListener) + ViewUtils.setupView(notificationNFormView, viewProperty, formBuilder) val constraintLayout = notificationNFormView.getChildAt(0) as ViewGroup val titleTextView = constraintLayout.findViewById(R.id.notificationTitleTextView) Assert.assertNotEquals(titleTextView.currentTextColor , -1) @@ -75,7 +74,7 @@ class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { @Test fun `Should dismiss the notification when cancel is clicked`() { notificationNFormView.viewProperties.viewAttributes?.set("dismissible", true) - ViewUtils.setupView(notificationNFormView, viewProperty, dataActionListener) + ViewUtils.setupView(notificationNFormView, viewProperty, formBuilder) val constraintLayout = notificationNFormView.getChildAt(0) as ViewGroup val cancelIcon = constraintLayout.findViewById(R.id.notificationCancelIcon) Assert.assertEquals(cancelIcon.visibility, View.VISIBLE) @@ -93,7 +92,7 @@ class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { "notification_dialog_text", "I am dialog text" ) - ViewUtils.setupView(notificationNFormView, viewProperty, dataActionListener) + ViewUtils.setupView(notificationNFormView, viewProperty, formBuilder) val constraintLayout = notificationNFormView.getChildAt(0) as ViewGroup val infoIcon = constraintLayout.findViewById(R.id.notificationInfoIcon) Assert.assertEquals(infoIcon.visibility, View.VISIBLE) @@ -109,7 +108,7 @@ class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { "title", "Title: {title_calculation}" ) notificationNFormView.viewProperties.viewAttributes?.set("text", "Text: {text_calculation}") - ViewUtils.setupView(notificationNFormView, viewProperty, dataActionListener) + ViewUtils.setupView(notificationNFormView, viewProperty, formBuilder) val constraintLayout = notificationNFormView.getChildAt(0) as ViewGroup //Title should be updated when calculation changes diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt index bbb47c9b..0d8b665e 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt @@ -33,8 +33,6 @@ class TextInputEditTextBuilderTest : BaseJsonViewBuilderTest(){ objToCopy = TextInputEditTextBuilder(textInputEditTextNFormView), recordPrivateCalls = true ) - private val dataActionListener = spyk(objToCopy = ViewDispatcher.INSTANCE) - @Before fun `Before doing anything else`() { @@ -42,7 +40,7 @@ class TextInputEditTextBuilderTest : BaseJsonViewBuilderTest(){ viewProperty.name = "first_name" viewProperty.type = "text_input_layout" textInputEditTextNFormView.viewProperties = viewProperty - ViewUtils.setupView(textInputEditTextNFormView, viewProperty, dataActionListener) + ViewUtils.setupView(textInputEditTextNFormView, viewProperty, formBuilder) } @Test diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt index 7604bbdb..4899e937 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt @@ -20,8 +20,7 @@ import org.robolectric.annotation.Config class ViewDispatcherTest : BaseJsonViewBuilderTest(){ private val viewProperty = NFormViewProperty() - private val viewDispatcher = spyk() - private val rulesFactory = RulesFactory.INSTANCE + private val rulesFactory = RulesFactory() private val editTextNFormView = EditTextNFormView(activity.get()) @Before @@ -31,17 +30,17 @@ class ViewDispatcherTest : BaseJsonViewBuilderTest(){ viewProperty.viewAttributes = hashMapOf("hint" to "Am a sample hint on field") editTextNFormView.viewProperties = viewProperty editTextNFormView.formValidator = this.formValidator - every { viewDispatcher.rulesFactory } returns rulesFactory + every { formBuilder.viewDispatcher.rulesFactory } returns rulesFactory } @Test fun `Verify that field value is passed to the dispatcher`() { - ViewUtils.setupView(editTextNFormView, viewProperty, viewDispatcher) + ViewUtils.setupView(editTextNFormView, viewProperty, formBuilder) editTextNFormView.setText("Sample text") verifyOrder { - viewDispatcher.onPassData(editTextNFormView.viewDetails) + formBuilder. viewDispatcher.onPassData(editTextNFormView.viewDetails) } - confirmVerified(viewDispatcher) + confirmVerified(formBuilder) } @After diff --git a/sample/src/main/assets/rules/yml/tb_followup_form_rules.yml b/sample/src/main/assets/rules/yml/tb_followup_form_rules.yml index 81856270..c4500339 100644 --- a/sample/src/main/assets/rules/yml/tb_followup_form_rules.yml +++ b/sample/src/main/assets/rules/yml/tb_followup_form_rules.yml @@ -2,19 +2,19 @@ name: "client_condition_visibility" description: "client_condition visibility" priority: 1 -condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +condition: "registration_or_followup_status.value == 'New Client' || registration_or_followup_status.value == 'Continuing with services'" actions: - "client_condition_visibility = true" --- name: "problem_visibility" description: "problem visibility" priority: 1 -condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +condition: "registration_or_followup_status.value == 'New Client' || registration_or_followup_status.value == 'Continuing with services'" actions: - "problem_visibility = true" --- name: "problem_other_visibility" -description: "problem visibility" +description: "problem other visibility" priority: 1 condition: "problem['other_problems'] != null" actions: @@ -23,14 +23,14 @@ actions: name: "client_behaviour_and_environmental_risk_visibility" description: "client_behaviour_and_environmental_risk visibility" priority: 1 -condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +condition: "registration_or_followup_status.value == 'New Client' || registration_or_followup_status.value == 'Continuing with services'" actions: - "client_behaviour_and_environmental_risk_visibility = true" --- name: "supplies_provided_visibility" description: "supplies_provided visibility" priority: 1 -condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +condition: "registration_or_followup_status.value == 'New Client' || registration_or_followup_status.value == 'Continuing with services'" actions: - "supplies_provided_visibility = true" --- @@ -44,7 +44,7 @@ actions: name: "tb_services_provided_visibility" description: "tb_services_provided visibility" priority: 1 -condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +condition: "registration_or_followup_status.value == 'New Client' || registration_or_followup_status.value == 'Continuing with services'" actions: - "tb_services_provided_visibility = true" --- @@ -58,7 +58,6 @@ actions: name: "state_of_therapy_visibility" description: "state_of_therapy visibility" priority: 1 -condition: "registration_or_followup_status.value=='New Client' || registration_or_followup_status.value=='Continuing with services'" +condition: "registration_or_followup_status.value == 'New Client' || registration_or_followup_status.value == 'Continuing with services'" actions: - - "state_of_therapy_visibility = true" - + - "state_of_therapy_visibility = true" \ No newline at end of file diff --git a/sample/src/main/assets/rules/yml/tb_registration_form_rules.yml b/sample/src/main/assets/rules/yml/tb_registration_form_rules.yml index a3b2cdeb..8fa03bbc 100644 --- a/sample/src/main/assets/rules/yml/tb_registration_form_rules.yml +++ b/sample/src/main/assets/rules/yml/tb_registration_form_rules.yml @@ -2,7 +2,7 @@ name: "community_gathering_visibility" description: "Display community gathering types if community gathering is chosen" priority: 1 -condition: "place_of_domicile.value=='Community Gathering'" +condition: "place_of_domicile.value == 'Community Gathering'" actions: - "community_gathering_visibility = true" --- diff --git a/sample/src/main/assets/sample/sample_two_form.json b/sample/src/main/assets/sample/sample_two_form.json index c7a0da34..28e7c5f1 100644 --- a/sample/src/main/assets/sample/sample_two_form.json +++ b/sample/src/main/assets/sample/sample_two_form.json @@ -9,9 +9,7 @@ "name": "adult", "type": "text_input_edit_text", "properties": { - "hint": "This is an adult", - "type": "name", - "padding": "8" + "hint": "This is an adult" }, "meta_data": { "openmrs_entity": "", @@ -21,24 +19,23 @@ "validation": [ { "validation_name": "length", - "condition": "value.length() < 8", - "error_message": "value should be less than eight digits" - },{ - "validation_name": "special characters", - "condition": "!value.contains('@')", - "error_message": "value should not contain special character" + "condition": "value.length() < 11", + "error_message": "value should be less than ten digits" + }, + { + "validation_name": "phone number", + "condition": "value.matches(\"\\\\d{10}\")", + "error_message": "Not a valid phone number" } ], - "subjects": "age:number, child:text, dob:number", + "subjects": "age:number, child:text", "required_status": "True:please add username" }, { "name": "age", "type": "text_input_edit_text", "properties": { - "hint": "Enter your age", - "type": "name", - "padding": "8" + "hint": "Enter your age" }, "meta_data": { "openmrs_entity": "", @@ -51,9 +48,7 @@ "name": "child", "type": "text_input_edit_text", "properties": { - "hint": "I am a child", - "type": "name", - "padding": "8" + "hint": "I am a child" }, "meta_data": { "openmrs_entity": "", diff --git a/sample/src/main/assets/sample/tb_registration.json b/sample/src/main/assets/sample/tb_registration.json index f8f4e3fd..9fc5915c 100644 --- a/sample/src/main/assets/sample/tb_registration.json +++ b/sample/src/main/assets/sample/tb_registration.json @@ -61,9 +61,7 @@ "name": "community_client_tb_registration_number", "type": "text_input_edit_text", "properties": { - "hint": "Community Based Health Services Registration Number (CBHS Number)", - "type": "name", - "padding": "8" + "hint": "Community Based Health Services Registration Number (CBHS Number)" }, "meta_data": { "openmrs_entity": "concept", diff --git a/sample/src/main/java/com/nerdstone/neatform/MainActivity.kt b/sample/src/main/java/com/nerdstone/neatform/MainActivity.kt index d638a28b..698636c5 100644 --- a/sample/src/main/java/com/nerdstone/neatform/MainActivity.kt +++ b/sample/src/main/java/com/nerdstone/neatform/MainActivity.kt @@ -15,10 +15,10 @@ import com.nerdstone.neatform.form.FormRecyclerAdapter import kotlinx.android.synthetic.main.activity_main.* object FormType { - const val embeddableDefault = "embeddable - default" - const val embeddableCustomized = "embeddable - customised" - const val stepperDefault = "stepper - default" - const val stepperCustomized = "stepper - customised" + const val jsonFromEmbeddedDefault = "JsonFormEmbedded - default" + const val jsonFormEmbeddedCustomized = "JsonFormEmbedded - customised" + const val jsonFormStepperDefault = "JsonFormStepper - default" + const val jsonFormStepperCustomized = "JsonFormStepper - customised" } class MainActivity : AppCompatActivity(), View.OnClickListener { @@ -46,27 +46,32 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { mutableListOf( FormData( formTitle = "TB Registration", - formCategory = FormType.embeddableDefault, + formCategory = FormType.jsonFromEmbeddedDefault, filePath = "sample/tb_registration.json" ), FormData( formTitle = "TB Followup", - formCategory = FormType.embeddableDefault, + formCategory = FormType.jsonFromEmbeddedDefault, filePath = "sample/tb_followup_visit.json" ), FormData( - formTitle = "Profile - Sample 2", - formCategory = FormType.stepperDefault, + formTitle = "Customized Embedded Form", + formCategory = FormType.jsonFormEmbeddedCustomized, + filePath = "sample/sample_one_form.json" + ), + FormData( + formTitle = "Default Stepper Form", + formCategory = FormType.jsonFormStepperDefault, filePath = "sample/sample_two_form.json" ), FormData( - formTitle = "Profile - Customized Sample 2", - formCategory = FormType.stepperCustomized, + formTitle = "Customized Stepper Form", + formCategory = FormType.jsonFormStepperCustomized, filePath = "sample/sample_two_form.json" ), FormData( - formTitle = "Profile - Single Step", - formCategory = FormType.stepperDefault, + formTitle = "Single Stepper Form", + formCategory = FormType.jsonFormStepperDefault, filePath = "sample/sample_one_form.json" ) ) diff --git a/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt b/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt index 1547568c..90d2c63c 100644 --- a/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt +++ b/sample/src/main/java/com/nerdstone/neatform/custom/views/CustomImageView.kt @@ -16,12 +16,12 @@ import de.hdodenhof.circleimageview.CircleImageView class CustomImageView(context: Context) : CircleImageView(context), NFormView { override lateinit var viewProperties: NFormViewProperty + override lateinit var formValidator: FormValidator override var dataActionListener: DataActionListener? = null override var visibilityChangeListener: VisibilityChangeListener? = ViewVisibilityChangeHandler.INSTANCE override val viewBuilder = CustomImageViewBuilder(this) override var viewDetails = NFormViewDetails(this) - override var formValidator: FormValidator = NeatFormValidator.INSTANCE override var initialValue: Any? = null override fun resetValueWhenHidden() = Unit diff --git a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt index f207115a..694aee4b 100644 --- a/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt +++ b/sample/src/main/java/com/nerdstone/neatform/form/FormActivity.kt @@ -66,7 +66,7 @@ class FormActivity : AppCompatActivity() { layoutInflater.inflate(R.layout.sample_one_form_custom_layout, null) ) when (formData.formCategory) { - FormType.embeddableDefault -> { + FormType.jsonFromEmbeddedDefault -> { formBuilder = JsonFormBuilder(this, formData.filePath) formBuilder?.also { it.registeredViews["custom_image"] = CustomImageView::class @@ -79,17 +79,17 @@ class FormActivity : AppCompatActivity() { JsonFormEmbedded(formBuilder as JsonFormBuilder, formLayout).buildForm() } } - FormType.embeddableCustomized -> { + FormType.jsonFormEmbeddedCustomized -> { formBuilder = JsonFormBuilder(this, formData.filePath) formBuilder?.also { it.registeredViews["custom_image"] = CustomImageView::class } JsonFormEmbedded(formBuilder as JsonFormBuilder, formLayout).buildForm(views) } - FormType.stepperDefault -> { + FormType.jsonFormStepperDefault -> { startStepperActivity(formData.filePath, true) } - FormType.stepperCustomized -> { + FormType.jsonFormStepperCustomized -> { startStepperActivity(formData.filePath) } else -> Toast.makeText( From 041c6cc6c194e7171e46d37d8453b6933a0f90d3 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Wed, 27 May 2020 03:48:26 +0300 Subject: [PATCH 14/15] Fix failing tests Signed-off-by: Elly Kitoto --- .../nerdstone/neatformcore/utils/ViewUtils.kt | 1 - .../builders/BaseJsonViewBuilderTest.kt | 19 +++++++++++--- .../builders/CheckBoxViewBuilderTest.kt | 9 +------ .../builders/DateTimePickerViewBuilderTest.kt | 8 ------ .../builders/EditTextViewBuilderTest.kt | 9 ------- .../MultiChoiceCheckBoxViewBuilderTest.kt | 17 ++++-------- .../builders/NotificationViewBuilderTest.kt | 9 ------- .../builders/NumberSelectorViewBuilderTest.kt | 13 +++------- .../builders/RadioGroupViewBuilderTest.kt | 13 +++------- .../builders/SpinnerViewBuilderTest.kt | 13 +++------- .../builders/TextInputEditTextBuilderTest.kt | 8 ------ .../handlers/ViewDispatcherTest.kt | 26 +++++-------------- .../rules/NeatFormValidatorTest.kt | 3 +-- .../robolectric/rules/RulesFactoryTest.kt | 20 ++++---------- 14 files changed, 44 insertions(+), 124 deletions(-) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt index b674f6b2..c7c97e1a 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/utils/ViewUtils.kt @@ -312,5 +312,4 @@ object ViewUtils { "textWebPassword" to (InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD), "time" to (InputType.TYPE_CLASS_DATETIME or InputType.TYPE_DATETIME_VARIATION_TIME) ) - } \ No newline at end of file diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt index 31314023..28f3751e 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/BaseJsonViewBuilderTest.kt @@ -3,16 +3,29 @@ package com.nerdstone.neatformcore.robolectric.builders import android.view.ViewGroup import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.viewModelScope import com.nerdstone.neatformcore.TestConstants +import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.form.json.JsonFormBuilder -import com.nerdstone.neatformcore.rules.NeatFormValidator +import kotlinx.coroutines.cancelChildren +import org.junit.After +import org.junit.runner.RunWith import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner import org.robolectric.android.controller.ActivityController +import org.robolectric.annotation.Config -open class BaseJsonViewBuilderTest { +@RunWith(RobolectricTestRunner::class) +@Config(application = TestNeatFormApp::class) +abstract class BaseJsonViewBuilderTest { protected val activity: ActivityController = Robolectric.buildActivity(AppCompatActivity::class.java).setup() val mainLayout: ViewGroup = LinearLayout(activity.get()) var formBuilder = JsonFormBuilder(TestConstants.SAMPLE_JSON, activity.get()) - val formValidator = NeatFormValidator() + + @After + fun `Clean resources`() { + formBuilder.dataViewModel.viewModelScope.coroutineContext.cancelChildren() + formBuilder.formViewModel.viewModelScope.coroutineContext.cancelChildren() + } } \ No newline at end of file diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt index 5b0ff0cf..b80bcba2 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/CheckBoxViewBuilderTest.kt @@ -1,7 +1,6 @@ package com.nerdstone.neatformcore.robolectric.builders import android.view.View -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.utils.ViewUtils @@ -13,12 +12,7 @@ import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) class CheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) @@ -30,9 +24,8 @@ class CheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { viewProperty.name = "name" viewProperty.type = "checkbox" //Set EditText properties and assign EditText view builder - checkBoxNFormView.formValidator = this.formValidator checkBoxNFormView.viewProperties = viewProperty - ViewUtils.setupView(checkBoxNFormView, viewProperty, spyk()) + ViewUtils.setupView(checkBoxNFormView, viewProperty, formBuilder) } @Test diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt index caa8cd45..7b62230f 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/DateTimePickerViewBuilderTest.kt @@ -1,23 +1,16 @@ package com.nerdstone.neatformcore.robolectric.builders import android.view.View -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.views.builders.DateTimePickerViewBuilder import com.nerdstone.neatformcore.views.widgets.DateTimePickerNFormView import io.mockk.spyk import io.mockk.unmockkAll -import io.mockk.verify import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) class DateTimePickerViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) @@ -32,7 +25,6 @@ class DateTimePickerViewBuilderTest : BaseJsonViewBuilderTest() { viewProperty.name = "dob" viewProperty.type = "datetime_picker" //Set EditText properties and assign EditText view builder - dateTimePickerNFormView.formValidator = this.formValidator dateTimePickerNFormView.viewProperties = viewProperty } diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt index 1e66a1c5..ba675947 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/EditTextViewBuilderTest.kt @@ -2,13 +2,10 @@ package com.nerdstone.neatformcore.robolectric.builders import android.text.InputType import android.view.View -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormFieldValidation import com.nerdstone.neatformcore.domain.model.NFormViewProperty -import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.EditTextViewBuilder -import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import com.nerdstone.neatformcore.views.widgets.EditTextNFormView import io.mockk.spyk import io.mockk.unmockkAll @@ -16,12 +13,7 @@ import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) class EditTextViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) @@ -33,7 +25,6 @@ class EditTextViewBuilderTest : BaseJsonViewBuilderTest() { viewProperty.name = "name" viewProperty.type = "edit_text" //Set EditText properties and assign EditText view builder - editTextNFormView.formValidator = this.formValidator editTextNFormView.viewProperties = viewProperty ViewUtils.setupView(editTextNFormView, viewProperty, formBuilder) } diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt index 45798cce..ddd26267 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/MultiChoiceCheckBoxViewBuilderTest.kt @@ -4,7 +4,6 @@ import android.view.View import android.widget.CheckBox import android.widget.TextView import com.nerdstone.neatformcore.R -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormSubViewProperty import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewProperty @@ -18,12 +17,7 @@ import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) @@ -55,9 +49,8 @@ class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { checkBoxOption4.isExclusive = true checkBoxOption4.text = "None" checkBoxOption4.viewAttributes = hashMapOf() - multiChoiceCheckBox.formValidator = this.formValidator multiChoiceCheckBox.viewProperties = viewProperty - ViewUtils.setupView(multiChoiceCheckBox, viewProperty, spyk()) + ViewUtils.setupView(multiChoiceCheckBox, viewProperty, formBuilder) } @Test @@ -133,7 +126,7 @@ class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { viewProperty.viewAttributes = hashMapOf("text" to text) viewProperty.options = listOf(checkBoxOption1, checkBoxOption2, checkBoxOption3, checkBoxOption4) - ViewUtils.setupView(multiChoiceCheckBox, viewProperty, spyk()) + ViewUtils.setupView(multiChoiceCheckBox, viewProperty, formBuilder) //Select 2 checkboxes and ensure value map contains the 2 checkboxes val checkBox1 = multiChoiceCheckBox.getChildAt(1) as CheckBox val checkBox2 = multiChoiceCheckBox.getChildAt(2) as CheckBox @@ -162,7 +155,7 @@ class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { viewProperty.viewAttributes = hashMapOf("text" to text) viewProperty.options = listOf(checkBoxOption1, checkBoxOption2, checkBoxOption3, checkBoxOption4) - ViewUtils.setupView(multiChoiceCheckBox, viewProperty, spyk()) + ViewUtils.setupView(multiChoiceCheckBox, viewProperty, formBuilder) //An exclusive option can only be selected independently val checkBox1 = multiChoiceCheckBox.getChildAt(1) as CheckBox //kotlin val checkBox3 = multiChoiceCheckBox.getChildAt(3) as CheckBox //don't know @@ -190,7 +183,7 @@ class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { viewProperty.viewAttributes = hashMapOf("text" to text) viewProperty.options = listOf(checkBoxOption1, checkBoxOption2, checkBoxOption3, checkBoxOption4) - ViewUtils.setupView(multiChoiceCheckBox, viewProperty, spyk()) + ViewUtils.setupView(multiChoiceCheckBox, viewProperty, formBuilder) val checkBox1 = multiChoiceCheckBox.getChildAt(1) as CheckBox val checkBox2 = multiChoiceCheckBox.getChildAt(2) as CheckBox checkBox1.isChecked = true @@ -207,7 +200,7 @@ class MultiChoiceCheckBoxViewBuilderTest : BaseJsonViewBuilderTest() { viewProperty.viewAttributes = hashMapOf("text" to "Pick your languages") viewProperty.options = listOf(checkBoxOption1, checkBoxOption2, checkBoxOption3, checkBoxOption4) - ViewUtils.setupView(multiChoiceCheckBox, viewProperty, spyk()) + ViewUtils.setupView(multiChoiceCheckBox, viewProperty, formBuilder) val valueHashMap = mapOf( "kotlin" to NFormViewData(value = "Kotlin"), "java" to NFormViewData(value = "Java") diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt index 5ec1246f..13dd2f29 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NotificationViewBuilderTest.kt @@ -7,10 +7,8 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import com.nerdstone.neatformcore.R -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.utils.ViewUtils -import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import com.nerdstone.neatformcore.views.widgets.NotificationNFormView import io.mockk.spyk import io.mockk.unmockkAll @@ -18,14 +16,8 @@ import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config import org.robolectric.shadows.ShadowAlertDialog -import org.robolectric.shadows.ShadowTextView -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class, shadows = [ShadowTextView::class]) class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { private val notificationNFormView = NotificationNFormView(activity.get()) @@ -33,7 +25,6 @@ class NotificationViewBuilderTest : BaseJsonViewBuilderTest() { @Before fun `Before doing anything else`() { - notificationNFormView.formValidator = this.formValidator viewProperty.name = "notification_view" viewProperty.type = "toast_notification" notificationNFormView.viewProperties = viewProperty diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt index 79b2805b..d1d26fc1 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/NumberSelectorViewBuilderTest.kt @@ -5,7 +5,6 @@ import android.view.ViewGroup import android.widget.LinearLayout import android.widget.TextView import com.nerdstone.neatformcore.R -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.NumberSelectorViewBuilder @@ -16,12 +15,7 @@ import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) class NumberSelectorViewBuilderTest : BaseJsonViewBuilderTest() { private val numberSelector = NumberSelectorNFormView(activity.get()) private val numberSelectorViewBuilder = spyk(NumberSelectorViewBuilder(numberSelector)) @@ -29,7 +23,6 @@ class NumberSelectorViewBuilderTest : BaseJsonViewBuilderTest() { @Before fun `Before doing anything else`() { - numberSelector.formValidator = this.formValidator viewProperty.name = "number_selector_test" viewProperty.type = "number_selector" numberSelector.viewProperties = viewProperty @@ -92,7 +85,7 @@ class NumberSelectorViewBuilderTest : BaseJsonViewBuilderTest() { @Test fun `Should set pass value as integer when number is selected`() { - ViewUtils.setupView(numberSelector, viewProperty, spyk()) + ViewUtils.setupView(numberSelector, viewProperty, formBuilder) //Value is null first but will always contain a value when selection is done Assert.assertNull(numberSelector.viewDetails.value) @@ -107,7 +100,7 @@ class NumberSelectorViewBuilderTest : BaseJsonViewBuilderTest() { @Test fun `Should deselect number when the number selector view is gone`() { - ViewUtils.setupView(numberSelector, viewProperty, spyk()) + ViewUtils.setupView(numberSelector, viewProperty, formBuilder) val linearLayout = numberSelector.getChildAt(1) as LinearLayout val textView = linearLayout.getChildAt(3) as TextView textView.performClick() @@ -117,7 +110,7 @@ class NumberSelectorViewBuilderTest : BaseJsonViewBuilderTest() { @Test fun `Should set value to the number selector when provided`() { - ViewUtils.setupView(numberSelector, viewProperty, spyk()) + ViewUtils.setupView(numberSelector, viewProperty, formBuilder) val textValue = 3 numberSelector.setValue(textValue, false) Assert.assertEquals(numberSelector.initialValue, textValue) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt index 531eb653..fea42f7b 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/RadioGroupViewBuilderTest.kt @@ -4,7 +4,6 @@ import android.view.View import android.widget.RadioButton import android.widget.TextView import com.nerdstone.neatformcore.R -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormSubViewProperty import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewProperty @@ -17,12 +16,7 @@ import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) class RadioGroupViewBuilderTest : BaseJsonViewBuilderTest() { private val viewProperty = spyk(NFormViewProperty()) @@ -34,7 +28,6 @@ class RadioGroupViewBuilderTest : BaseJsonViewBuilderTest() { @Before fun `Before doing anything else`() { - radioGroupView.formValidator = this.formValidator viewProperty.name = "choose_language" viewProperty.type = "radio_group" @@ -113,7 +106,7 @@ class RadioGroupViewBuilderTest : BaseJsonViewBuilderTest() { val text = "Pick your preferred Android programming language" viewProperty.viewAttributes = hashMapOf("text" to text) viewProperty.options = listOf(radioOption1, radioOption2, radioOption3) - ViewUtils.setupView(radioGroupView, viewProperty, spyk()) + ViewUtils.setupView(radioGroupView, viewProperty, formBuilder) //Value is null first but will always contain a value when selection is done Assert.assertNull(radioGroupView.viewDetails.value) @@ -131,7 +124,7 @@ class RadioGroupViewBuilderTest : BaseJsonViewBuilderTest() { val text = "Pick your preferred Android programming language" viewProperty.viewAttributes = hashMapOf("text" to text) viewProperty.options = listOf(radioOption1, radioOption2, radioOption3) - ViewUtils.setupView(radioGroupView, viewProperty, spyk()) + ViewUtils.setupView(radioGroupView, viewProperty, formBuilder) val radioButton1 = radioGroupView.getChildAt(1) as RadioButton radioButton1.isChecked = true radioGroupView.visibility = View.GONE @@ -146,7 +139,7 @@ class RadioGroupViewBuilderTest : BaseJsonViewBuilderTest() { viewProperty.viewAttributes = hashMapOf("text" to "Pick your preferred language") viewProperty.options = listOf(radioOption1, radioOption2, radioOption3) - ViewUtils.setupView(radioGroupView, viewProperty, spyk()) + ViewUtils.setupView(radioGroupView, viewProperty, formBuilder) val valueHashMap = mapOf("kotlin" to NFormViewData(value = "Kotlin")) radioGroupView.setValue(valueHashMap) Assert.assertEquals(radioGroupView.initialValue, valueHashMap) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt index eca516ff..d8d89315 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/SpinnerViewBuilderTest.kt @@ -2,7 +2,6 @@ package com.nerdstone.neatformcore.robolectric.builders import android.view.View import com.chivorn.smartmaterialspinner.SmartMaterialSpinner -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormSubViewProperty import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.model.NFormViewProperty @@ -15,12 +14,7 @@ import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) class SpinnerViewBuilderTest : BaseJsonViewBuilderTest(){ private val viewProperty = spyk(NFormViewProperty()) @@ -33,7 +27,6 @@ class SpinnerViewBuilderTest : BaseJsonViewBuilderTest(){ @Before fun `Before doing anything else`() { - spinnerNFormView.formValidator = this.formValidator viewProperty.name = "gender" viewProperty.type = "spinner" spinnerNFormView.viewProperties = viewProperty @@ -98,7 +91,7 @@ class SpinnerViewBuilderTest : BaseJsonViewBuilderTest(){ viewProperty.viewAttributes = hashMapOf("text" to text) viewProperty.options = listOf(spinnerOption1, spinnerOption2, spinnerOption3) - ViewUtils.setupView(spinnerNFormView, viewProperty, spyk()) + ViewUtils.setupView(spinnerNFormView, viewProperty, formBuilder) spinnerNFormView.visibility = View.GONE Assert.assertNull(spinnerNFormView.viewDetails.value) } @@ -109,7 +102,7 @@ class SpinnerViewBuilderTest : BaseJsonViewBuilderTest(){ viewProperty.viewAttributes = hashMapOf("text" to text) viewProperty.options = listOf(spinnerOption1, spinnerOption2, spinnerOption3) - ViewUtils.setupView(spinnerNFormView, viewProperty, spyk()) + ViewUtils.setupView(spinnerNFormView, viewProperty, formBuilder) val materialSpinner = spinnerNFormView.getChildAt(0) as SmartMaterialSpinner<*> materialSpinner.setSelection(1) materialSpinner.isSelected = true @@ -121,7 +114,7 @@ class SpinnerViewBuilderTest : BaseJsonViewBuilderTest(){ viewProperty.viewAttributes = hashMapOf("text" to "Pick your gender") viewProperty.options = listOf(spinnerOption1, spinnerOption2, spinnerOption3) - ViewUtils.setupView(spinnerNFormView, viewProperty, spyk()) + ViewUtils.setupView(spinnerNFormView, viewProperty, formBuilder) val valueHashMap = mapOf("value" to "Female") spinnerNFormView.setValue(valueHashMap) Assert.assertEquals(spinnerNFormView.initialValue, valueHashMap) diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt index 0d8b665e..a9d15893 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/builders/TextInputEditTextBuilderTest.kt @@ -2,20 +2,15 @@ package com.nerdstone.neatformcore.robolectric.builders import android.text.InputType import android.view.View -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormFieldValidation import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.utils.ViewUtils import com.nerdstone.neatformcore.views.builders.TextInputEditTextBuilder -import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import com.nerdstone.neatformcore.views.widgets.TextInputEditTextNFormView import io.mockk.spyk import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config /** * Created by cozej4 on 2019-11-20. @@ -23,8 +18,6 @@ import org.robolectric.annotation.Config * @cozej4 https://github.com/cozej4 */ -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) class TextInputEditTextBuilderTest : BaseJsonViewBuilderTest(){ private val viewProperty = spyk(NFormViewProperty()) @@ -36,7 +29,6 @@ class TextInputEditTextBuilderTest : BaseJsonViewBuilderTest(){ @Before fun `Before doing anything else`() { - textInputEditTextNFormView.formValidator = this.formValidator viewProperty.name = "first_name" viewProperty.type = "text_input_layout" textInputEditTextNFormView.viewProperties = viewProperty diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt index 4899e937..ceb3d52b 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/handlers/ViewDispatcherTest.kt @@ -1,26 +1,18 @@ package com.nerdstone.neatformcore.robolectric.handlers -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.robolectric.builders.BaseJsonViewBuilderTest -import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.utils.ViewUtils -import com.nerdstone.neatformcore.views.handlers.ViewDispatcher import com.nerdstone.neatformcore.views.widgets.EditTextNFormView -import io.mockk.* +import io.mockk.unmockkAll import org.junit.After +import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) -class ViewDispatcherTest : BaseJsonViewBuilderTest(){ +class ViewDispatcherTest : BaseJsonViewBuilderTest() { private val viewProperty = NFormViewProperty() - private val rulesFactory = RulesFactory() private val editTextNFormView = EditTextNFormView(activity.get()) @Before @@ -29,18 +21,14 @@ class ViewDispatcherTest : BaseJsonViewBuilderTest(){ viewProperty.type = "edit_text" viewProperty.viewAttributes = hashMapOf("hint" to "Am a sample hint on field") editTextNFormView.viewProperties = viewProperty - editTextNFormView.formValidator = this.formValidator - every { formBuilder.viewDispatcher.rulesFactory } returns rulesFactory + ViewUtils.setupView(editTextNFormView, viewProperty, formBuilder) } @Test fun `Verify that field value is passed to the dispatcher`() { - ViewUtils.setupView(editTextNFormView, viewProperty, formBuilder) - editTextNFormView.setText("Sample text") - verifyOrder { - formBuilder. viewDispatcher.onPassData(editTextNFormView.viewDetails) - } - confirmVerified(formBuilder) + val sample = "Sample text" + editTextNFormView.setText(sample) + Assert.assertEquals(editTextNFormView.viewDetails.value, sample) } @After diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt index ca2fe041..99d6e040 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/NeatFormValidatorTest.kt @@ -23,7 +23,6 @@ import io.mockk.spyk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runBlockingTest import org.junit.Assert -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -182,7 +181,7 @@ const val VALIDATION_FORM = """ @RunWith(RobolectricTestRunner::class) @Config(application = TestNeatFormApp::class) @ExperimentalCoroutinesApi -@Ignore("I still don't understand how this test passes individually but fails when run with others") +//@Ignore("I still don't understand how this test passes individually but fails when run with others") class NeatFormValidatorTest: BaseJsonViewBuilderTest() { @get:Rule diff --git a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt index 1e315aca..e0fc8fce 100644 --- a/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt +++ b/neat-form-core/src/test/java/com/nerdstone/neatformcore/robolectric/rules/RulesFactoryTest.kt @@ -1,15 +1,12 @@ package com.nerdstone.neatformcore.robolectric.rules import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import androidx.appcompat.app.AppCompatActivity import com.nerdstone.neatformcore.TestConstants -import com.nerdstone.neatformcore.TestNeatFormApp import com.nerdstone.neatformcore.domain.model.NFormRule import com.nerdstone.neatformcore.domain.model.NFormViewDetails import com.nerdstone.neatformcore.domain.model.NFormViewProperty import com.nerdstone.neatformcore.form.json.JsonFormBuilder +import com.nerdstone.neatformcore.robolectric.builders.BaseJsonViewBuilderTest import com.nerdstone.neatformcore.rules.NFormRulesHandler import com.nerdstone.neatformcore.rules.RulesFactory import com.nerdstone.neatformcore.utils.ViewUtils @@ -25,17 +22,9 @@ import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.Robolectric -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config - -@RunWith(RobolectricTestRunner::class) -@Config(application = TestNeatFormApp::class) -class RulesFactoryTest { - private val activity = Robolectric.buildActivity(AppCompatActivity::class.java).setup() + +class RulesFactoryTest: BaseJsonViewBuilderTest() { private val view = EditTextNFormView(activity.get()) - private val mainLayout: ViewGroup = LinearLayout(activity.get()) private val viewDetails = NFormViewDetails(view) private val rulesFactory = spyk(recordPrivateCalls = true) private val rulesHandler = NFormRulesHandler.INSTANCE @@ -55,7 +44,8 @@ class RulesFactoryTest { viewMetadata = hashMapOf() calculations = listOf("decade") } - ViewUtils.setupView(view, viewProperties, spyk()) + val formBuilder = spyk() + ViewUtils.setupView(view, viewProperties, formBuilder) view.id = 1 view.visibility = View.GONE From 222824b514efa91f86829c9f3fea4b73b8fb3574 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Wed, 27 May 2020 14:15:38 +0300 Subject: [PATCH 15/15] Remove _calculation suffix when saving calculations Signed-off-by: Elly Kitoto --- .../com/nerdstone/neatformcore/rules/NFormRulesHandler.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt index a3572ad4..f1eba02e 100644 --- a/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt +++ b/neat-form-core/src/main/java/com/nerdstone/neatformcore/rules/NFormRulesHandler.kt @@ -6,6 +6,7 @@ import com.nerdstone.neatformcore.domain.listeners.CalculationChangeListener import com.nerdstone.neatformcore.domain.model.NFormViewData import com.nerdstone.neatformcore.domain.view.RulesHandler import com.nerdstone.neatformcore.utils.Constants +import com.nerdstone.neatformcore.utils.Constants.RuleActions.CALCULATION import com.nerdstone.neatformcore.utils.DisposableList import com.nerdstone.neatformcore.utils.ViewUtils import org.jeasy.rules.api.Facts @@ -64,10 +65,12 @@ class NFormRulesHandler private constructor() : RulesHandler { } override fun handleCalculations(facts: Facts?) { - filterCurrentRules(Constants.RuleActions.CALCULATION) + filterCurrentRules(CALCULATION) .forEach { key -> val value = facts?.asMap()?.get(key) - formBuilder.dataViewModel.saveFieldValue(key, NFormViewData("Calculation", value, null)) + formBuilder.dataViewModel.saveFieldValue( + key.replace(CALCULATION, ""), NFormViewData("Calculation", value, null) + ) updateCalculationListeners(Pair(key, value)) } }