commit 7a2a621574bb6d8fa96248553296d8480589f2c9 Author: Rajinikanth Date: Fri Apr 7 16:30:15 2023 +0530 TP-TP-24174 | Module separation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/UiTron/.gitignore b/UiTron/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/UiTron/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/UiTron/build.gradle b/UiTron/build.gradle new file mode 100644 index 0000000..8bd97d7 --- /dev/null +++ b/UiTron/build.gradle @@ -0,0 +1,58 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'kotlin-parcelize' +} + +android { + namespace 'com.navi.uitron' + compileSdk 32 + + defaultConfig { + minSdk 21 + targetSdk 31 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.2.0-beta03' + } +} + +dependencies { + implementation "androidx.compose.ui:ui:1.2.0-beta03" + implementation "androidx.compose.material:material:1.2.0-beta03" + implementation "androidx.compose.ui:ui-tooling-preview:1.2.0-beta03" + implementation "com.google.accompanist:accompanist-pager:0.24.10-beta" + implementation "com.google.accompanist:accompanist-pager-indicators:0.24.10-beta" + implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha02" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0-rc02' + implementation 'androidx.activity:activity-compose:1.4.0' + implementation 'androidx.core:core-ktx:1.6.0' + implementation 'com.google.code.gson:gson:2.8.9' + implementation 'androidx.hilt:hilt-navigation-compose:1.0.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.6.1' + implementation "com.airbnb.android:lottie-compose:5.2.0" + implementation "io.coil-kt:coil-compose:2.2.2" + testImplementation "junit:junit:4.13.2" + androidTestImplementation "androidx.test.ext:junit:1.1.4" +} diff --git a/UiTron/consumer-rules.pro b/UiTron/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/UiTron/proguard-rules.pro b/UiTron/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/UiTron/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/UiTron/src/androidTest/java/com/navi/uitron/ExampleInstrumentedTest.kt b/UiTron/src/androidTest/java/com/navi/uitron/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..1462311 --- /dev/null +++ b/UiTron/src/androidTest/java/com/navi/uitron/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.navi.uitron + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.navi.uitron.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/UiTron/src/main/AndroidManifest.xml b/UiTron/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/UiTron/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/IUiTronDependencyProvider.kt b/UiTron/src/main/java/com/navi/uitron/IUiTronDependencyProvider.kt new file mode 100644 index 0000000..e31344a --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/IUiTronDependencyProvider.kt @@ -0,0 +1,183 @@ +package com.navi.uitron + +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import com.navi.uitron.utils.EMPTY +import com.navi.uitron.utils.SPACE + +interface IUiTronDependencyProvider { + + fun getFontFamily(fontFamily: String?): FontFamily { + return when (fontFamily) { + "ttComposeFontFamily" -> FontFamily( + Font(getFontStyle(FontWeightEnum.TT_MEDIUM), FontWeight.Medium), + Font(getFontStyle(FontWeightEnum.TT_BOLD), FontWeight.Bold) + ) + + else -> FontFamily( + Font(getFontStyle(FontWeightEnum.ROBOTO_REGULAR), FontWeight.Normal), + Font(getFontStyle(FontWeightEnum.ROBOTO_MEDIUM), FontWeight.Medium), + Font(getFontStyle(FontWeightEnum.ROBOTO_BOLD), FontWeight.Bold), + ) + } + } + + fun getFontStyle(fontWeight: FontWeightEnum?): Int = getFontStyle(fontWeight?.name) + + fun getFontStyle(fontWeightName: String?) = when (fontWeightName) { + FontWeightEnum.TT_REGULAR.name, + FontWeightEnum.ROBOTO_REGULAR.name, + + FontWeightEnum.ROBOTO_MEDIUM.name, + + FontWeightEnum.ROBOTO_BOLD.name, + + FontWeightEnum.TT_MEDIUM.name -> R.font.tt_medium + + FontWeightEnum.TT_BOLD.name -> R.font.tt_bold + + FontWeightEnum.TT_SEMI_BOLD.name -> R.font.tt_semi_bold + + else -> R.font.tt_regular + } + + fun getFontWeight(fontWeight: String?): FontWeight { + return when (fontWeight) { + FontWeightEnum.ROBOTO_REGULAR.name, + FontWeightEnum.ROBOTO_MEDIUM.name, + FontWeightEnum.TT_MEDIUM.name -> FontWeight.Medium + + FontWeightEnum.ROBOTO_BOLD.name, + FontWeightEnum.TT_BOLD.name -> FontWeight.Bold + + else -> FontWeight.Normal + } + } + + enum class FontWeightEnum { + ROBOTO_REGULAR, + ROBOTO_MEDIUM, + ROBOTO_BOLD, + TT_MEDIUM, + TT_BOLD, + TT_SEMI_BOLD, + TT_REGULAR + } + + fun numberToWords(amount: String): String { + try { + val number = amount.toDouble().toInt().toString() + if (number == "0") return "Zero rupees" + + if (number.length <= 9) { + var numberInStringFormat = number.reversed() + var numberInWords = "" + while (numberInStringFormat.length < 9) { + numberInStringFormat = numberInStringFormat.plus("0") + } + numberInStringFormat = numberInStringFormat.reversed() + while (numberInStringFormat.isNotEmpty()) { + if (numberInStringFormat.length > 7) { + val leftMostTwoDigits = numberInStringFormat.take(2).toInt() + if (leftMostTwoDigits != 0) { + numberInWords = + numberInWords + .plus(twoDigitNumberToWords(leftMostTwoDigits)) + .plus(" crore ") + } + } else if (numberInStringFormat.length > 5) { + val leftMostTwoDigits = numberInStringFormat.take(2).toInt() + if (leftMostTwoDigits != 0) { + numberInWords = + numberInWords + .plus(twoDigitNumberToWords(leftMostTwoDigits)) + .plus(" lakh ") + } + } else if (numberInStringFormat.length > 3) { + val leftMostTwoDigits = numberInStringFormat.take(2).toInt() + if (leftMostTwoDigits != 0) { + numberInWords = + numberInWords + .plus(twoDigitNumberToWords(leftMostTwoDigits)) + .plus(" thousand ") + } + } else if (numberInStringFormat.length > 2) { + val leftMostDigit = numberInStringFormat.take(1).toInt() + if (leftMostDigit != 0) { + numberInWords = + numberInWords + .plus(twoDigitNumberToWords(leftMostDigit)) + .plus(" hundred ") + } + } else { + val lastDigits = numberInStringFormat.toInt() + if (lastDigits != 0) { + numberInWords = numberInWords.plus(twoDigitNumberToWords(lastDigits)) + } + } + if (numberInStringFormat.length > 3) { + numberInStringFormat = numberInStringFormat.drop(2) + } else if (numberInStringFormat.length > 2) { + numberInStringFormat = numberInStringFormat.drop(1) + } else { + numberInStringFormat = "" + } + } + numberInWords = numberInWords.trim().replaceFirstChar { it.uppercase() } + if (numberInWords == "One") { + numberInWords = numberInWords.plus(" rupee") + } else { + numberInWords = numberInWords.plus(" rupees") + } + return numberInWords + } else { + return EMPTY + } + } catch (_: Exception) {} + return EMPTY + } + + private fun twoDigitNumberToWords(num: Int): String { + when (num) { + 0 -> return "zero" + 1 -> return "one" + 2 -> return "two" + 3 -> return "three" + 4 -> return "four" + 5 -> return "five" + 6 -> return "six" + 7 -> return "seven" + 8 -> return "eight" + 9 -> return "nine" + 10 -> return "ten" + 11 -> return "eleven" + 12 -> return "twelve" + 13 -> return "thirteen" + 14 -> return "fourteen" + 15 -> return "fifteen" + 16 -> return "sixteen" + 17 -> return "seventeen" + 18 -> return "eighteen" + 19 -> return "nineteen" + 20 -> return "twenty" + 30 -> return "thirty" + 40 -> return "forty" + 50 -> return "fifty" + 60 -> return "sixty" + 70 -> return "seventy" + 80 -> return "eighty" + 90 -> return "ninety" + } + + if (num in 21..99) { + val tens = twoDigitNumberToWords((num / 10) * 10) + val ones = twoDigitNumberToWords(num % 10) + return tens.plus(SPACE).plus(ones) + } + + return "" + } + + +} \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/UiTronSdk.kt b/UiTron/src/main/java/com/navi/uitron/UiTronSdk.kt new file mode 100644 index 0000000..004f35b --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/UiTronSdk.kt @@ -0,0 +1,15 @@ +package com.navi.uitron + +object UiTronSdk { + + private var uiTronDependencyProvider: IUiTronDependencyProvider = object : IUiTronDependencyProvider {} + + fun init(uiTronDependencyProvider: IUiTronDependencyProvider) { + this.uiTronDependencyProvider = uiTronDependencyProvider + } + + fun getDependencyProvider(): IUiTronDependencyProvider { + return uiTronDependencyProvider + } + +} \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/deserializer/ComposePropertyDeserializer.kt b/UiTron/src/main/java/com/navi/uitron/deserializer/ComposePropertyDeserializer.kt new file mode 100644 index 0000000..2d594a1 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/deserializer/ComposePropertyDeserializer.kt @@ -0,0 +1,117 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.deserializer + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.navi.uitron.model.ui.* +import java.lang.reflect.Type + +/** + * Copyright © 2022 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +class ComposePropertyDeserializer : JsonDeserializer { + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): BaseProperty? { + json?.let { + val jsonObject = it.asJsonObject + if (jsonObject["viewType"] == null) + return null + return when (jsonObject["viewType"].asString) { + ComposeViewType.Image.name -> { + context?.deserialize(jsonObject, ImageProperty::class.java) + } + ComposeViewType.ConstraintLayout.name -> { + context?.deserialize(jsonObject, ConstraintProperty::class.java) + } + ComposeViewType.Text.name -> { + context?.deserialize(jsonObject, TextProperty::class.java) + } + ComposeViewType.Divider.name -> { + context?.deserialize(jsonObject, DividerProperty::class.java) + } + ComposeViewType.LinearProgressIndicator.name -> { + context?.deserialize(jsonObject, LinearProgressIndicatorProperty::class.java) + } + ComposeViewType.Column.name -> { + context?.deserialize(jsonObject, ColumnProperty::class.java) + } + ComposeViewType.OutlinedTextField.name -> { + context?.deserialize(jsonObject, OutlinedTextFieldProperty::class.java) + } + ComposeViewType.Button.name -> { + context?.deserialize(jsonObject, ButtonProperty::class.java) + } + ComposeViewType.Card.name -> { + context?.deserialize(jsonObject, CardProperty::class.java) + } + ComposeViewType.Spacer.name -> { + context?.deserialize(jsonObject, SpacerProperty::class.java) + } + ComposeViewType.Row.name -> { + context?.deserialize(jsonObject, RowProperty::class.java) + } + ComposeViewType.Lottie.name -> { + context?.deserialize(jsonObject, LottieProperty::class.java) + } + ComposeViewType.Slider.name -> { + context?.deserialize(jsonObject, SliderProperty::class.java) + } + ComposeViewType.Checkbox.name -> { + context?.deserialize(jsonObject, CheckBoxProperty::class.java) + } + ComposeViewType.RadioButton.name -> { + context?.deserialize(jsonObject, RadioButtonProperty::class.java) + } + ComposeViewType.Switch.name -> { + context?.deserialize(jsonObject, SwitchProperty::class.java) + } + ComposeViewType.Dialog.name -> { + context?.deserialize(jsonObject, DialogProperty::class.java) + } + ComposeViewType.Dropdown.name -> { + context?.deserialize(json, DropdownProperty::class.java) + } + ComposeViewType.CountDownTimer.name -> { + context?.deserialize(jsonObject, CountDownTimerProperty::class.java) + } + ComposeViewType.Box.name -> { + context?.deserialize(jsonObject, BoxProperty::class.java) + } + ComposeViewType.Toast.name -> { + context?.deserialize(jsonObject, ToastProperty::class.java) + } + ComposeViewType.JackpotText.name -> { + context?.deserialize(jsonObject, TextProperty::class.java) + } + ComposeViewType.Pager.name -> { + context?.deserialize(jsonObject , PagerProperty::class.java) + } + ComposeViewType.PagerIndicator.name -> { + context?.deserialize(jsonObject , PagerIndicatorProperty::class.java) + } + ComposeViewType.LazyColumn.name -> { + context?.deserialize(jsonObject, LazyColumnProperty::class.java) + } + ComposeViewType.LazyRow.name -> { + context?.deserialize(jsonObject, LazyRowProperty::class.java) + } + ComposeViewType.Grid.name -> { + context?.deserialize(jsonObject, GridProperty::class.java) + } + else -> null + } + } + return null + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronApiDeserializer.kt b/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronApiDeserializer.kt new file mode 100644 index 0000000..b79d31e --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronApiDeserializer.kt @@ -0,0 +1,47 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.deserializer + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.navi.uitron.model.action.* +import java.lang.reflect.Type + +class UiTronApiDeserializer : JsonDeserializer { + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): TriggerApiAction? { + json?.let { + val jsonObject = it.asJsonObject + val type = jsonObject["apiTag"] ?: return null + return when (type.asString) { + ApiType.GetApplicationId.name -> context?.deserialize( + jsonObject, + GetApplicationIdApiAction::class.java + ) + ApiType.GetScreenDefinition.name -> context?.deserialize( + jsonObject, + GetScreenDefinitionApiAction::class.java + ) + ApiType.SubmitInputData.name -> context?.deserialize( + jsonObject, + SubmitInputDataApiAction::class.java + ) + ApiType.GetNextScreenData.name -> context?.deserialize( + jsonObject, + GetNextScreenApiAction::class.java + ) + else -> null + } + } + return null + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronDataDeserializer.kt b/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronDataDeserializer.kt new file mode 100644 index 0000000..c598f3f --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronDataDeserializer.kt @@ -0,0 +1,132 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.deserializer + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.navi.uitron.model.data.* +import com.navi.uitron.model.ui.ComposeViewType +import java.lang.reflect.Type + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +class UiTronDataDeserializer : JsonDeserializer { + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): UiTronData? { + json?.let { + val jsonObject = it.asJsonObject + if (jsonObject["viewType"] == null) + return null + return when (jsonObject["viewType"].asString) { + ComposeViewType.Image.name -> { + context?.deserialize(jsonObject, ImageData::class.java) + } + ComposeViewType.Text.name -> { + context?.deserialize(jsonObject, TextData::class.java) + } + ComposeViewType.OutlinedTextField.name -> { + context?.deserialize(jsonObject, OutlinedTextFieldData::class.java) + } + ComposeViewType.ConstraintLayout.name -> { + context?.deserialize(jsonObject, ConstraintData::class.java) + } + ComposeViewType.LinearProgressIndicator.name -> { + context?.deserialize(jsonObject, LinearProgressIndicatorData::class.java) + } + ComposeViewType.Button.name -> { + context?.deserialize(jsonObject, ButtonData::class.java) + } + ComposeViewType.Column.name -> { + val columnChildrenDataList = mutableListOf() + jsonObject["childrenDataList"]?.asJsonArray?.forEach { jsonElement -> + columnChildrenDataList.add( + context?.deserialize( + jsonElement, + UiTronData::class.java + ) + ) + } + val uiTronColumnData = context?.deserialize( + jsonObject, + ColumnData::class.java + ) + uiTronColumnData?.childrenDataList = columnChildrenDataList.toList() + uiTronColumnData + } + ComposeViewType.Row.name -> { + val rowChildrenDataList = mutableListOf() + jsonObject["childrenDataList"]?.asJsonArray?.forEach { jsonElement -> + rowChildrenDataList.add( + context?.deserialize( + jsonElement, + UiTronData::class.java + ) + ) + } + val uiTronRowData = + context?.deserialize(jsonObject, RowData::class.java) + uiTronRowData?.childrenDataList = rowChildrenDataList.toList() + uiTronRowData + } + ComposeViewType.Lottie.name -> { + context?.deserialize(jsonObject, LottieData::class.java) + } + ComposeViewType.Card.name -> { + context?.deserialize(jsonObject, CardData::class.java) + } + ComposeViewType.Slider.name -> { + context?.deserialize(jsonObject, UiTronSliderData::class.java) + } + ComposeViewType.Checkbox.name -> { + context?.deserialize(jsonObject, CheckBoxData::class.java) + } + ComposeViewType.RadioButton.name -> { + context?.deserialize(jsonObject, RadioButtonData::class.java) + } + ComposeViewType.Switch.name -> { + context?.deserialize(jsonObject, SwitchData::class.java) + } + ComposeViewType.Dialog.name -> { + context?.deserialize(jsonObject, DialogData::class.java) + } + ComposeViewType.Box.name -> { + context?.deserialize(jsonObject, BoxData::class.java) + } + ComposeViewType.Dropdown.name -> { + context?.deserialize(jsonObject, DropdownData::class.java) + } + ComposeViewType.CountDownTimer.name -> { + context?.deserialize(jsonObject, CountDownTimerData::class.java) + } + ComposeViewType.Toast.name -> { + context?.deserialize(jsonObject, ToastData::class.java) + } + ComposeViewType.LazyColumn.name -> { + context?.deserialize(jsonObject, LazyColumnData::class.java) + } + ComposeViewType.LazyRow.name -> { + context?.deserialize(jsonObject, LazyRowData::class.java) + } + ComposeViewType.Pager.name -> { + context?.deserialize(jsonObject , PagerData::class.java) + } + ComposeViewType.Grid.name -> { + context?.deserialize(jsonObject , GridData::class.java) + } + else -> null + } + } + return null + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronValidationDeserializer.kt b/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronValidationDeserializer.kt new file mode 100644 index 0000000..140afa4 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/deserializer/UiTronValidationDeserializer.kt @@ -0,0 +1,50 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.deserializer + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.navi.uitron.validation.* +import java.lang.reflect.Type + +class UiTronValidationDeserializer : JsonDeserializer { + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): UiTronBaseValidation? { + json?.let { + val jsonObject = json.asJsonObject + if (jsonObject["validationType"] == null) + return null + return when (jsonObject["validationType"].asString) { + NotBlankValidation.VALIDATION_NAME -> { + context?.deserialize(jsonObject, NotBlankValidation::class.java) + } + RegexValidation.VALIDATION_NAME -> { + context?.deserialize(jsonObject, RegexValidation::class.java) + } + ValueRangeValidation.VALIDATION_NAME -> { + context?.deserialize(jsonObject, ValueRangeValidation::class.java) + } + LengthRangeValidation.VALIDATION_NAME -> { + context?.deserialize(jsonObject, LengthRangeValidation::class.java) + } + DateFormatValidation.VALIDATION_NAME -> { + context?.deserialize(jsonObject, DateFormatValidation::class.java) + } + DateRangeValidation.VALIDATION_NAME -> { + context?.deserialize(jsonObject, DateRangeValidation::class.java) + } + else -> null + } + } + return null + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/helpers/ScrollEventHandler.kt b/UiTron/src/main/java/com/navi/uitron/helpers/ScrollEventHandler.kt new file mode 100644 index 0000000..a0c767a --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/helpers/ScrollEventHandler.kt @@ -0,0 +1,78 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.helpers + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import com.navi.uitron.model.data.ScrollAction +import com.navi.uitron.viewmodel.UiTronViewModel +import dpToPx + +class ScrollEventHandler(private val viewModel: UiTronViewModel) { + + private var prevFirstItemVisibleIndex = 0 + private var prevFirstVisibleItemOffset = 0 + + @Composable + fun HandleListScroll(listState: LazyListState, scrollData: Map) { + val latestFirstVisibleItemScrollOffset = + remember { derivedStateOf { listState.firstVisibleItemScrollOffset } }.value + val latestFirstVisibleItemIndex = + remember { derivedStateOf { listState.firstVisibleItemIndex } }.value + + scrollData.apply { + keys.forEach { targetPosition -> + get(targetPosition)?.offsetInDp?.let { + runCatching { + checkAndFireActions( + prevFirstItemVisibleIndex, + prevFirstVisibleItemOffset, + targetPosition.toInt(), + dpToPx(it).toInt(), + latestFirstVisibleItemIndex, + latestFirstVisibleItemScrollOffset, + get(targetPosition) + ) + } + } + } + } + + prevFirstItemVisibleIndex = latestFirstVisibleItemIndex + prevFirstVisibleItemOffset = latestFirstVisibleItemScrollOffset + } + + //LLD Link: https://navihq.atlassian.net/wiki/spaces/ABP/pages/632193236/LazyList+scroll+actions+LLD + private fun checkAndFireActions( + prevFirstItemVisibleIndex: Int, + prevFirstVisibleItemOffset: Int, + targetItemIndex: Int, + targetItemPosition: Int, + latestFirstItemVisibleIndex: Int, + latestFirstVisibleItemOffset: Int, + scrollAction: ScrollAction? + ) { + val isScrollingUp = + (prevFirstItemVisibleIndex < targetItemIndex && latestFirstItemVisibleIndex == targetItemIndex && latestFirstVisibleItemOffset >= targetItemPosition) + || (targetItemIndex in (prevFirstItemVisibleIndex + 1) until latestFirstItemVisibleIndex) + || (prevFirstItemVisibleIndex == targetItemIndex && prevFirstVisibleItemOffset <= targetItemPosition && latestFirstItemVisibleIndex > targetItemIndex) + || (prevFirstItemVisibleIndex == targetItemIndex && prevFirstVisibleItemOffset <= targetItemPosition && latestFirstItemVisibleIndex == targetItemIndex && latestFirstVisibleItemOffset > targetItemPosition) + val isScrollingDown = + (prevFirstItemVisibleIndex == targetItemIndex && prevFirstVisibleItemOffset >= targetItemPosition && latestFirstItemVisibleIndex < targetItemIndex) + || (prevFirstItemVisibleIndex == targetItemIndex && prevFirstVisibleItemOffset >= targetItemPosition && latestFirstItemVisibleIndex == targetItemIndex && latestFirstVisibleItemOffset < targetItemPosition) + || (prevFirstItemVisibleIndex > targetItemIndex && latestFirstItemVisibleIndex == targetItemIndex && latestFirstVisibleItemOffset <= targetItemPosition) + || (targetItemIndex in (latestFirstItemVisibleIndex + 1) until prevFirstItemVisibleIndex) + if (isScrollingUp) { + viewModel.handleActions(scrollAction?.up) + } else if (isScrollingDown) { + viewModel.handleActions(scrollAction?.down) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/UiTronResponse.kt b/UiTron/src/main/java/com/navi/uitron/model/UiTronResponse.kt new file mode 100644 index 0000000..c80195a --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/UiTronResponse.kt @@ -0,0 +1,19 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model + +import android.os.Parcelable +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.UiTronView +import kotlinx.parcelize.Parcelize + +@Parcelize +data class UiTronResponse( + val parentComposeView: List? = null, + val data: MutableMap? = null +) : Parcelable diff --git a/UiTron/src/main/java/com/navi/uitron/model/action/AnalyticsAction.kt b/UiTron/src/main/java/com/navi/uitron/model/action/AnalyticsAction.kt new file mode 100644 index 0000000..cc63e8b --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/action/AnalyticsAction.kt @@ -0,0 +1,29 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.action + +import androidx.lifecycle.SavedStateHandle +import com.navi.uitron.model.data.UiTronAction +import kotlinx.coroutines.flow.MutableSharedFlow + +data class AnalyticsAction( + val eventType: String? = null, + val eventName: String? = null, + val eventProperties: Map? = null, + val isNeededForAppsflyer: Boolean = true, + val isNeededForFirebase: Boolean = false +) : UiTronAction() { + override suspend fun manageAction( + handle: SavedStateHandle, + uiTronAction: UiTronAction, + actionCallbackFlow: MutableSharedFlow? + ) { + val action = uiTronAction as AnalyticsAction + actionCallbackFlow?.emit(action) + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/action/BottomSheetAction.kt b/UiTron/src/main/java/com/navi/uitron/model/action/BottomSheetAction.kt new file mode 100644 index 0000000..ecc77a5 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/action/BottomSheetAction.kt @@ -0,0 +1,28 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.action + +import androidx.lifecycle.SavedStateHandle +import com.navi.uitron.model.data.UiTronAction +import kotlinx.coroutines.flow.MutableSharedFlow + +data class BottomSheetAction( + val bottomSheetId: String? = null, + val action: String? = null +) : UiTronAction() { + override suspend fun manageAction( + handle: SavedStateHandle, + uiTronAction: UiTronAction, + actionCallbackFlow: MutableSharedFlow? + ) { + val action = uiTronAction as BottomSheetAction + action.bottomSheetId?.let { + handle[it] = uiTronAction.action + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/action/MakeApiAction.kt b/UiTron/src/main/java/com/navi/uitron/model/action/MakeApiAction.kt new file mode 100644 index 0000000..07129ee --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/action/MakeApiAction.kt @@ -0,0 +1,9 @@ +package com.navi.uitron.model.action + +import com.google.gson.annotations.SerializedName +import com.navi.uitron.model.data.UiTronAction + +open class MakeApiAction( + @SerializedName("apiTag") + val apiTag: String? = null +): UiTronAction() diff --git a/UiTron/src/main/java/com/navi/uitron/model/action/TriggerApiAction.kt b/UiTron/src/main/java/com/navi/uitron/model/action/TriggerApiAction.kt new file mode 100644 index 0000000..7cc0ea2 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/action/TriggerApiAction.kt @@ -0,0 +1,46 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.action + +import androidx.lifecycle.SavedStateHandle +import com.google.gson.annotations.SerializedName +import com.navi.uitron.model.data.UiTronAction +import kotlinx.coroutines.flow.MutableSharedFlow + +open class TriggerApiAction( + @SerializedName("apiType") + val apiType: String? = null +) : UiTronAction() { + override suspend fun manageAction( + handle: SavedStateHandle, + uiTronAction: UiTronAction, + actionCallbackFlow: MutableSharedFlow? + ) { + val action = uiTronAction as TriggerApiAction + actionCallbackFlow?.emit(action) + } +} + +class GetApplicationIdApiAction() : TriggerApiAction() + +class GetScreenDefinitionApiAction() : TriggerApiAction() + +class SubmitInputDataApiAction(@SerializedName("submitInputData") val submitInputData: SubmitInputData) : + TriggerApiAction() + +class GetNextScreenApiAction() : TriggerApiAction() + + +data class SubmitInputData( + @SerializedName("payloadIds") + val payloadIds: Map? = null +) + +enum class ApiType { + GetApplicationId, GetScreenDefinition, SubmitInputData, GetNextScreenData +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/action/UpdateStateHandleAction.kt b/UiTron/src/main/java/com/navi/uitron/model/action/UpdateStateHandleAction.kt new file mode 100644 index 0000000..10175fe --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/action/UpdateStateHandleAction.kt @@ -0,0 +1,27 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.action + +import androidx.lifecycle.SavedStateHandle +import com.navi.uitron.model.data.UiTronAction +import kotlinx.coroutines.flow.MutableSharedFlow + +data class UpdateStateHandleAction( + val statesMap: Map? = null +) : UiTronAction(){ + override suspend fun manageAction( + handle: SavedStateHandle, + uiTronAction: UiTronAction, + actionCallbackFlow: MutableSharedFlow? + ) { + val action = uiTronAction as UpdateStateHandleAction + action.statesMap?.entries?.forEach { + handle[it.key] = it.value + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/action/UpdateViewStateAction.kt b/UiTron/src/main/java/com/navi/uitron/model/action/UpdateViewStateAction.kt new file mode 100644 index 0000000..b80ba8f --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/action/UpdateViewStateAction.kt @@ -0,0 +1,30 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.action + +import androidx.lifecycle.SavedStateHandle +import com.navi.uitron.model.data.UiTronAction +import com.navi.uitron.model.ui.BaseProperty +import kotlinx.coroutines.flow.MutableSharedFlow + +data class UpdateViewStateAction( +// layoutId -> view state + val viewStates: Map? = null +) : UiTronAction() { + override suspend fun manageAction( + handle: SavedStateHandle, + uiTronAction: UiTronAction, + actionCallbackFlow: MutableSharedFlow? + ) { + val action = uiTronAction as UpdateViewStateAction + action.viewStates?.keys?.forEach { key -> + handle[key + BaseProperty.PROPERTY_SUFFIX] = + uiTronAction.viewStates?.get(key) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/action/ValidateInputTextAction.kt b/UiTron/src/main/java/com/navi/uitron/model/action/ValidateInputTextAction.kt new file mode 100644 index 0000000..cd7bd6b --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/action/ValidateInputTextAction.kt @@ -0,0 +1,54 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.action + +import androidx.lifecycle.SavedStateHandle +import com.navi.uitron.model.data.TextFieldInputWrapper +import com.navi.uitron.model.data.UiTronAction +import com.navi.uitron.model.ui.BaseProperty +import com.navi.uitron.validation.UiTronBaseValidation +import kotlinx.coroutines.flow.MutableSharedFlow + +data class ValidateInputTextAction( + val validations: Map?>? = null +) : UiTronAction(){ + override suspend fun manageAction( + handle: SavedStateHandle, + uiTronAction: UiTronAction, + actionCallbackFlow: MutableSharedFlow? + ) { + val action = uiTronAction as ValidateInputTextAction + action.validations?.entries?.forEach { entry -> + val dataLayoutId = entry.key + BaseProperty.DATA_SUFFIX + val inputWrapper = handle.get(dataLayoutId) + inputWrapper?.let { + entry.value?.forEach { + if (!it.performValidation(inputWrapper.inputText)) { + handle[dataLayoutId] = + handle.getStateFlow( + dataLayoutId, TextFieldInputWrapper() + ).value.copy( + inputText = inputWrapper.inputText, + hasError = true, + errorMessage = it.errorMessage + ) + return@let + } else { + handle[dataLayoutId] = + handle.getStateFlow( + dataLayoutId, TextFieldInputWrapper() + ).value.copy( + inputText = inputWrapper.inputText, + hasError = false + ) + } + } + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/BoxData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/BoxData.kt new file mode 100644 index 0000000..5ca5ec0 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/BoxData.kt @@ -0,0 +1,10 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +class BoxData: UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/ButtonData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/ButtonData.kt new file mode 100644 index 0000000..aaf79d2 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/ButtonData.kt @@ -0,0 +1,20 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +data class ButtonData( + val textData: ButtonTextData? = null +) : UiTronData() + +data class ButtonTextData( + val text: String? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/CardData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/CardData.kt new file mode 100644 index 0000000..20b022c --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/CardData.kt @@ -0,0 +1,10 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +class CardData : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/CheckBoxData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/CheckBoxData.kt new file mode 100644 index 0000000..10d11ed --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/CheckBoxData.kt @@ -0,0 +1,3 @@ +package com.navi.uitron.model.data + +class CheckBoxData : UiTronData() \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/ColumnData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/ColumnData.kt new file mode 100644 index 0000000..a98717d --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/ColumnData.kt @@ -0,0 +1,12 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +class ColumnData : UiTronData() { + var childrenDataList: List? = null +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/ConstraintData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/ConstraintData.kt new file mode 100644 index 0000000..9577763 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/ConstraintData.kt @@ -0,0 +1,14 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +/** + * Copyright © 2022 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +class ConstraintData : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/CountDownTimerData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/CountDownTimerData.kt new file mode 100644 index 0000000..9d551c5 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/CountDownTimerData.kt @@ -0,0 +1,22 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +class CountDownTimerData( + val remainingTimeInMilliseconds: Long? = null, + val countDownIntervalInMilliseconds: Long? = null, + val timeUnitValuesLayoutIds: TimeUnitValuesLayoutIds? = null +) : UiTronData() + +data class TimeUnitValuesLayoutIds( + val milliSecondsFieldLayoutId: String? = null, + val secondsFieldLayoutId: String? = null, + val minutesFieldLayoutId: String? = null, + val hoursFieldLayoutId: String? = null, + val daysFieldLayoutId: String? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/DialogData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/DialogData.kt new file mode 100644 index 0000000..c66b574 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/DialogData.kt @@ -0,0 +1,12 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +data class DialogData( + val onDismiss: UiTronActionData? = null +) : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/DropdownData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/DropdownData.kt new file mode 100644 index 0000000..07e8c1b --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/DropdownData.kt @@ -0,0 +1,10 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +class DropdownData : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/GridData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/GridData.kt new file mode 100644 index 0000000..bbdd186 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/GridData.kt @@ -0,0 +1,6 @@ +package com.navi.uitron.model.data + +data class GridData( +// (layoutId, span) + val childSpanCountMap: Map? = null +): UiTronData() \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/ImageData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/ImageData.kt new file mode 100644 index 0000000..099f0fb --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/ImageData.kt @@ -0,0 +1,18 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +data class ImageData( + val iconUrl: String? = null, + val contentDescription: String? = null, + val contentScale: String? = null +) : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/LazyColumnData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/LazyColumnData.kt new file mode 100644 index 0000000..2f25675 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/LazyColumnData.kt @@ -0,0 +1,26 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +import com.google.gson.annotations.SerializedName + +class LazyColumnData( + @SerializedName("scrollData") + val scrollData: Map? = null, + @SerializedName("childrenDataList") + var childrenDataList: List? = null +) : UiTronData() + +data class ScrollAction( + @SerializedName("offsetInDp") + val offsetInDp: Int? = null, + @SerializedName("up") + val up: UiTronActionData? = null, + @SerializedName("down") + val down: UiTronActionData? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/LazyRowData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/LazyRowData.kt new file mode 100644 index 0000000..0f88d35 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/LazyRowData.kt @@ -0,0 +1,17 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +import com.google.gson.annotations.SerializedName + +class LazyRowData( + @SerializedName("scrollData") + val scrollData: Map? = null, + @SerializedName("childrenDataList") + var childrenDataList: List? = null +) : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/LinearProgressIndicatorData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/LinearProgressIndicatorData.kt new file mode 100644 index 0000000..b7dd968 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/LinearProgressIndicatorData.kt @@ -0,0 +1,17 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +data class LinearProgressIndicatorData( + // 0 to 1 + val progress: Float? = null +) : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/LottieData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/LottieData.kt new file mode 100644 index 0000000..16fe6f5 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/LottieData.kt @@ -0,0 +1,14 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +class LottieData( + val lottieUrl: String? = null, + val iterations: Int = 1, + val onAnimationEnd : UiTronActionData? = null +) : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/OutlinedTextFieldData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/OutlinedTextFieldData.kt new file mode 100644 index 0000000..abf8d14 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/OutlinedTextFieldData.kt @@ -0,0 +1,30 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +import android.os.Parcelable +import com.navi.uitron.validation.UiTronBaseValidation +import kotlinx.parcelize.Parcelize + + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +data class OutlinedTextFieldData( + val value: String? = null, + val hint: String? = null, + val onValueChangeAction: UiTronActionData? = null +) : UiTronData() + +@Parcelize +data class TextFieldInputWrapper( + val inputText: String? = null, + val hasError: Boolean? = null, + val errorMessage: String? = null +) : Parcelable diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/PagerData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/PagerData.kt new file mode 100644 index 0000000..2fce437 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/PagerData.kt @@ -0,0 +1,5 @@ +package com.navi.uitron.model.data + +data class PagerData( + val pageChangeActions: Map? = null +) : UiTronData() \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/RadioButtonData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/RadioButtonData.kt new file mode 100644 index 0000000..2981d82 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/RadioButtonData.kt @@ -0,0 +1,3 @@ +package com.navi.uitron.model.data + +class RadioButtonData : UiTronData() \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/RowData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/RowData.kt new file mode 100644 index 0000000..bc232c1 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/RowData.kt @@ -0,0 +1,12 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +class RowData : UiTronData() { + var childrenDataList: List? = null +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/SliderData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/SliderData.kt new file mode 100644 index 0000000..b1f8cbf --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/SliderData.kt @@ -0,0 +1,33 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +data class UiTronSliderData( + val sliderValue: Float? = null, + val stepValues: List? = null, + val selectedRawValue: String? = null, + var initialAnimationEnabled: Boolean? = null, + var animationDelay: Long? = null, + var animationStepSize: Long? = null, + var animationStepCount: Long? = null, + var animationTargetValue: Long? = null, +) : UiTronData() + +data class StepValue( + val formattedValue: String? = null, + val rawValue: String? = null +) + +@Parcelize +data class UiTronSliderWrapper( + val sliderValue: Float? = null +) : Parcelable \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/SwitchData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/SwitchData.kt new file mode 100644 index 0000000..739ba9b --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/SwitchData.kt @@ -0,0 +1,13 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +data class SwitchData( + val checkedStateClickData: UiTronActionData? = null, + val unCheckedStateClickData: UiTronActionData? = null +) : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/TextData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/TextData.kt new file mode 100644 index 0000000..197ac58 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/TextData.kt @@ -0,0 +1,22 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +/** + * Copyright © 2022 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +data class TextData( + val text: String? = null, + val textFormatter: TextFormatter? = null +) : UiTronData() { + data class TextFormatter( + val textFormat: String? = null, + val placeholder: String? = null + ) +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/ToastData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/ToastData.kt new file mode 100644 index 0000000..5b38070 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/ToastData.kt @@ -0,0 +1,12 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +data class ToastData( + val onDismiss: UiTronActionData? = null +) : UiTronData() diff --git a/UiTron/src/main/java/com/navi/uitron/model/data/UiTronData.kt b/UiTron/src/main/java/com/navi/uitron/model/data/UiTronData.kt new file mode 100644 index 0000000..4b3fdb5 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/data/UiTronData.kt @@ -0,0 +1,53 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.data + +import android.os.Parcelable +import androidx.lifecycle.SavedStateHandle +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.parcelize.Parcelize + +@Parcelize +open class UiTronData : Parcelable { + val viewType: String? = null + val onClick: UiTronActionData? = null +} + +@Parcelize +data class UiTronActionData( + val type: String? = ExecutionType.SEQUENTIAL.name, + val actions: List? = null, +) : Parcelable + +@Parcelize +open class UiTronAction( + val type: String? = null +) : Parcelable { + open suspend fun manageAction( + handle: SavedStateHandle, + uiTronAction: UiTronAction, + actionCallbackFlow: MutableSharedFlow? = null + ) { + } +} + +enum class ExecutionType { + SEQUENTIAL, PARALLEL +} + +enum class UiTronActionType { + UpdateViewState, ApiCall, CtaAction, BottomSheetAction, AnalyticsAction, ValidateInputText, + UpdateStateHandle +} + +enum class ApiTagType { +} + +enum class AnalyticsEventType { + PRODUCT, DEBUG +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/BoxProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/BoxProperty.kt new file mode 100644 index 0000000..9acc183 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/BoxProperty.kt @@ -0,0 +1,24 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +data class BoxProperty( + var contentAlignment: String? = null, + var propagateMinConstraints: Boolean? = null, + var verticalScroll: ScrollData? = null, + var horizontalScroll: ScrollData? = null, +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val boxProperty = property as? BoxProperty? + boxProperty?.contentAlignment?.let { contentAlignment = it } + boxProperty?.propagateMinConstraints?.let { propagateMinConstraints = it } + boxProperty?.verticalScroll?.let { verticalScroll = it } + boxProperty?.horizontalScroll?.let { horizontalScroll = it } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/ButtonProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/ButtonProperty.kt new file mode 100644 index 0000000..a8d415b --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/ButtonProperty.kt @@ -0,0 +1,40 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + + +data class ButtonProperty( + var colors: UiTronButtonColors? = null, + var enabled: Boolean? = null, + var elevation: UiTronButtonElevation? = null, + var borderStroke: BorderStrokeData? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val buttonProperty = property as? ButtonProperty + buttonProperty?.colors?.let { colors = it } + buttonProperty?.enabled?.let { enabled = it } + buttonProperty?.elevation?.let { elevation = it } + buttonProperty?.borderStroke?.let { borderStroke = it } + } +} + +data class UiTronButtonColors( + val backgroundColor: String? = null, + val contentColor: String? = null, + val disabledBackgroundColor: String? = null, + val disabledContentColor: String? = null +) + +data class UiTronButtonElevation( + val defaultElevation: Int? = null, + val pressedElevation: Int? = null, + val disabledElevation: Int? = null, + val hoveredElevation: Int? = null, + val focusedElevation: Int? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/CardProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/CardProperty.kt new file mode 100644 index 0000000..4286d54 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/CardProperty.kt @@ -0,0 +1,29 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +data class CardProperty( + var elevation: Int? = null, + var borderStrokeData: BorderStrokeData? = null, + var ambientColor: String? = null, + var spotColor: String? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val cardProperty = property as? CardProperty? + cardProperty?.elevation?.let { elevation = it } + cardProperty?.borderStrokeData?.let { borderStrokeData = it } + cardProperty?.ambientColor?.let { ambientColor = it } + cardProperty?.spotColor?.let { spotColor = it } + } +} + +data class BorderStrokeData( + val width: Int? = null, + val color: String? = null, +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/CheckBoxProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/CheckBoxProperty.kt new file mode 100644 index 0000000..f74f25e --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/CheckBoxProperty.kt @@ -0,0 +1,26 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +class CheckBoxProperty( + var isChecked: Boolean? = null, + var enabled: Boolean? = null, + var checkedColor: String? = null, + var uncheckedColor: String? = null, + var checkmarkColor: String? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val checkBoxProperty = property as? CheckBoxProperty + checkBoxProperty?.isChecked?.let { isChecked = it } + checkBoxProperty?.enabled?.let { enabled = it } + checkBoxProperty?.checkedColor?.let { checkedColor = it } + checkBoxProperty?.uncheckedColor?.let { uncheckedColor = it } + checkBoxProperty?.checkmarkColor?.let { checkmarkColor = it } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/ColumnProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/ColumnProperty.kt new file mode 100644 index 0000000..6941164 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/ColumnProperty.kt @@ -0,0 +1,34 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +data class ColumnProperty( + var arrangementData: ArrangementData? = null, + var horizontalAlignment: String? = null, + var verticalScroll: ScrollData? = null, + var horizontalScroll: ScrollData? = null, + var repeat: Int? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val columnProperty = property as? ColumnProperty + columnProperty?.arrangementData?.let { arrangementData = it } + columnProperty?.horizontalAlignment?.let { horizontalAlignment = it } + columnProperty?.verticalScroll?.let { verticalScroll = it } + columnProperty?.horizontalScroll?.let { horizontalScroll = it } + columnProperty?.repeat?.let { repeat = it } + } +} + +data class ScrollData( + val enabled: Boolean? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/ConstraintProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/ConstraintProperty.kt new file mode 100644 index 0000000..15d58bf --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/ConstraintProperty.kt @@ -0,0 +1,46 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + + +class ConstraintProperty( + var verticalScroll: ScrollData? = null, + var horizontalScroll: ScrollData? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val constraintProperty = property as? ConstraintProperty + constraintProperty?.verticalScroll?.let { verticalScroll = it } + constraintProperty?.horizontalScroll?.let { horizontalScroll = it } + } +} + +@Parcelize +data class ConstraintLink( + val start: Link? = null, + val top: Link? = null, + val end: Link? = null, + val bottom: Link? = null +) : Parcelable + +@Parcelize +data class Link( + val viewId: String? = null, + val constraint: String? = null, + val margin: Int? = null +) : Parcelable + +enum class ConstraintOption { + START, + TOP, + END, + BOTTOM +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/CountDownTimerProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/CountDownTimerProperty.kt new file mode 100644 index 0000000..c85bc6c --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/CountDownTimerProperty.kt @@ -0,0 +1,10 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +class CountDownTimerProperty : BaseProperty() diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/DialogProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/DialogProperty.kt new file mode 100644 index 0000000..2908818 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/DialogProperty.kt @@ -0,0 +1,20 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +data class DialogProperty( + var dismissableOnBackPress: Boolean? = null, + var dismissableOnClickOutside: Boolean? = null, +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val constraintProperty = property as? DialogProperty + constraintProperty?.dismissableOnBackPress?.let { dismissableOnBackPress = it } + constraintProperty?.dismissableOnClickOutside?.let { dismissableOnClickOutside = it } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/DividerProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/DividerProperty.kt new file mode 100644 index 0000000..0aaadce --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/DividerProperty.kt @@ -0,0 +1,24 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +data class DividerProperty( + var color: String? = null, + var thickness: Int? = null, + var separation: Int? = null, + var dashedAtomicWidth: Int? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val dividerProperty = property as? DividerProperty + dividerProperty?.color?.let { color = it } + dividerProperty?.thickness?.let { thickness = it } + dividerProperty?.separation?.let { separation = it } + dividerProperty?.dashedAtomicWidth?.let { dashedAtomicWidth = it } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/DropdownProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/DropdownProperty.kt new file mode 100644 index 0000000..9bb5939 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/DropdownProperty.kt @@ -0,0 +1,33 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +data class DropdownProperty( + var isExpanded: Boolean? = null, + var offset: Offset? = null, + var popupProperties: PopupProperties +) : BaseProperty() { + + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val dropdownProperty = property as? DropdownProperty + dropdownProperty?.isExpanded?.let { isExpanded = it } + dropdownProperty?.offset?.let { offset = it } + dropdownProperty?.popupProperties?.let { popupProperties = it } + } + + data class Offset(val x: Int? = null, val y: Int? = null) + + data class PopupProperties constructor( + val focusable: Boolean? = null, + val dismissOnBackPress: Boolean? = null, + val dismissOnClickOutside: Boolean? = null, + val excludeFromSystemGesture: Boolean? = null, + val clippingEnabled: Boolean? = null, + ) +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/GridProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/GridProperty.kt new file mode 100644 index 0000000..f1401eb --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/GridProperty.kt @@ -0,0 +1,39 @@ +package com.navi.uitron.model.ui + +data class GridProperty( + var gridStateKey: String? = null, + var contentPadding: ComposePadding? = null, + var gridCell: GridCell? = null, + var orientation: String? = ORIENTATION_VERTICAL, + var reverseLayout: Boolean? = null, + var userScrollEnabled: Boolean? = null, + var horizontalArrangementData: ArrangementData? = null, + var verticalArrangementData: ArrangementData? = null, +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val gridProperty = property as? GridProperty? + gridProperty?.gridStateKey?.let { gridStateKey = it } + gridProperty?.contentPadding?.let { contentPadding = it } + gridProperty?.gridCell?.let { gridCell = it } + gridProperty?.orientation?.let { orientation = it } + gridProperty?.reverseLayout?.let { reverseLayout = it } + gridProperty?.userScrollEnabled?.let { userScrollEnabled = it } + gridProperty?.horizontalArrangementData?.let { horizontalArrangementData = it } + gridProperty?.verticalArrangementData?.let { verticalArrangementData = it } + } + + data class GridCell( + var count: Int? = 3, + var minSize: Int? = 100, + var gridType: String? = GRID_TYPE_FIXED + ) + + companion object { + const val ORIENTATION_HORIZONTAL = "horizontal" + const val ORIENTATION_VERTICAL = "vertical" + const val GRID_TYPE_FIXED = "fixed" + const val GRID_TYPE_ADAPTIVE = "adaptive" + } + +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/ImageProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/ImageProperty.kt new file mode 100644 index 0000000..5440dcc --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/ImageProperty.kt @@ -0,0 +1,10 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +class ImageProperty: BaseProperty() diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/LazyColumnProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/LazyColumnProperty.kt new file mode 100644 index 0000000..e993414 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/LazyColumnProperty.kt @@ -0,0 +1,28 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +class LazyColumnProperty( + var arrangementData: ArrangementData? = null, + var horizontalAlignment: String? = null, + var repeat: Int? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val lazyColumnProperty = property as? LazyColumnProperty + lazyColumnProperty?.arrangementData?.let { + arrangementData = it + } + lazyColumnProperty?.horizontalAlignment?.let { + horizontalAlignment = it + } + lazyColumnProperty?.repeat?.let { + repeat = it + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/LazyRowProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/LazyRowProperty.kt new file mode 100644 index 0000000..2c9a6bc --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/LazyRowProperty.kt @@ -0,0 +1,28 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +class LazyRowProperty ( + var arrangementData: ArrangementData? = null, + var verticalAlignment: String? = null, + var repeat: Int? = null, +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val rowProperty = property as? RowProperty + rowProperty?.arrangementData?.let { + arrangementData = it + } + rowProperty?.verticalAlignment?.let { + verticalAlignment = it + } + rowProperty?.repeat?.let { + repeat = it + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/LinearProgressIndicatorProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/LinearProgressIndicatorProperty.kt new file mode 100644 index 0000000..5a072a4 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/LinearProgressIndicatorProperty.kt @@ -0,0 +1,22 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +data class LinearProgressIndicatorProperty( + var color: String? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val linearProgressIndicatorProperty = property as? LinearProgressIndicatorProperty + linearProgressIndicatorProperty?.color?.let { color = it } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/LottieProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/LottieProperty.kt new file mode 100644 index 0000000..2df4ef9 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/LottieProperty.kt @@ -0,0 +1,10 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +class LottieProperty : BaseProperty() diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/OutlinedTextFieldProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/OutlinedTextFieldProperty.kt new file mode 100644 index 0000000..bd99fe6 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/OutlinedTextFieldProperty.kt @@ -0,0 +1,75 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +import com.navi.uitron.model.UiTronResponse + +data class OutlinedTextFieldProperty( + var textStyle: OutlinedTextFieldTextStyle, + var singleLine: Boolean? = null, + var maxChar: Int? = null, + var colors: OutlinedTextFieldColors? = null, + var keyboardOptions: OutlinedTextFieldKeyBoardOptions? = null, + var visualTransformation: OutlinedTextFieldVisualTransformation? = null, + var successTextTransformation: OutlinedTextFieldValueTransformation? = null, + var hintStyle: TextProperty? = null, + var errorView: UiTronResponse? = null, + var successView: UiTronResponse? = null, + var errorMessageLayoutId: String? = null, + var successMessageLayoutId: String? = null, + var applyValidationOnValueChange: Boolean? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val outlinedTextFieldProperty = property as? OutlinedTextFieldProperty + outlinedTextFieldProperty?.textStyle?.let { textStyle = it } + outlinedTextFieldProperty?.singleLine?.let { singleLine = it } + outlinedTextFieldProperty?.maxChar?.let { maxChar = it } + outlinedTextFieldProperty?.colors?.let { colors = it } + outlinedTextFieldProperty?.keyboardOptions?.let { keyboardOptions = it } + outlinedTextFieldProperty?.visualTransformation?.let { visualTransformation = it } + outlinedTextFieldProperty?.successTextTransformation?.let { successTextTransformation = it } + outlinedTextFieldProperty?.hintStyle?.let { hintStyle = it } + outlinedTextFieldProperty?.errorView?.let { errorView = it } + outlinedTextFieldProperty?.successView?.let { successView = it } + outlinedTextFieldProperty?.errorMessageLayoutId?.let { errorMessageLayoutId = it } + outlinedTextFieldProperty?.successMessageLayoutId?.let { successMessageLayoutId = it } + outlinedTextFieldProperty?.applyValidationOnValueChange?.let { applyValidationOnValueChange = it } + } +} + +data class OutlinedTextFieldTextStyle( + val fontSize: Int? = null, + val color: String? = null, + val fontFamily: String? = null, + val fontWeight: String? = null +) + +data class OutlinedTextFieldColors( + val focusedBorderColor: String? = null, + val unfocusedBorderColor: String? = null, + val errorBorderColor: String? = null +) + +data class OutlinedTextFieldKeyBoardOptions( + val capitalization: String? = null, + val keyboardType: String? = null, + val imeAction: String? = null +) + +data class OutlinedTextFieldVisualTransformation( + val type: String? = null, + val dateSeparator: String? = null, + val dateSeparatorStyle: TextProperty? = null, + val mask: Char? = null +) + +data class OutlinedTextFieldValueTransformation( + val type: String? = null, + val formatPattern: String? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/PagerIndicatorProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/PagerIndicatorProperty.kt new file mode 100644 index 0000000..411f2d9 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/PagerIndicatorProperty.kt @@ -0,0 +1,32 @@ +package com.navi.uitron.model.ui + +data class PagerIndicatorProperty( + var orientation: String? = ORIENTATION_HORIZONTAL, + var pagerStateKey: String? = null, + var activeColor: String? = null, + var inactiveColor: String? = null, + var indicatorWidth: Int? = null, + var indicatorHeight: Int? = null, + var spacing: Int? = null, + var indicatorShape: UiTronShape? = null, +): BaseProperty() { + + companion object { + const val ORIENTATION_HORIZONTAL = "horizontal" + const val ORIENTATION_VERTICAL = "vertical" + } + + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val pagerProperty = property as? PagerIndicatorProperty? + pagerProperty?.orientation?.let { orientation = it } + pagerProperty?.pagerStateKey?.let { pagerStateKey = it } + pagerProperty?.activeColor?.let { activeColor = it } + pagerProperty?.inactiveColor?.let { inactiveColor = it } + pagerProperty?.indicatorWidth?.let { indicatorWidth = it } + pagerProperty?.indicatorHeight?.let { indicatorHeight = it } + pagerProperty?.spacing?.let { spacing = it } + pagerProperty?.indicatorShape?.let { indicatorShape = it } + } + +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/PagerProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/PagerProperty.kt new file mode 100644 index 0000000..a306871 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/PagerProperty.kt @@ -0,0 +1,32 @@ +package com.navi.uitron.model.ui + +data class PagerProperty( + var scrollDelay: Long? = null, + var orientation: String? = ORIENTATION_HORIZONTAL, + var reverseLayout: Boolean? = null, + var itemSpacing: Int? = null, + var verticalAlignment: String? = null, + var horizontalAlignment: String? = null, + var contentPadding: ComposePadding? = null, + var pagerStateKey: String? = null +) : BaseProperty() { + + companion object { + const val ORIENTATION_HORIZONTAL = "horizontal" + const val ORIENTATION_VERTICAL = "vertical" + } + + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val pagerProperty = property as? PagerProperty? + pagerProperty?.scrollDelay?.let { scrollDelay = it } + pagerProperty?.orientation?.let { orientation = it } + pagerProperty?.reverseLayout?.let { reverseLayout = it } + pagerProperty?.itemSpacing?.let { itemSpacing = it } + pagerProperty?.verticalAlignment?.let { verticalAlignment = it } + pagerProperty?.horizontalAlignment?.let { horizontalAlignment = it } + pagerProperty?.contentPadding?.let { contentPadding = it } + pagerProperty?.pagerStateKey?.let { pagerStateKey = it } + } + +} \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/RadioButtonProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/RadioButtonProperty.kt new file mode 100644 index 0000000..2379280 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/RadioButtonProperty.kt @@ -0,0 +1,25 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + + +class RadioButtonProperty( + var isSelected: Boolean? = null, + var enabled: Boolean? = null, + var selectedColor: String? = null, + var unselectedColor: String? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val radioButtonProperty = property as? RadioButtonProperty + radioButtonProperty?.isSelected?.let { isSelected = it } + radioButtonProperty?.enabled?.let { enabled = it } + radioButtonProperty?.selectedColor?.let { selectedColor = it } + radioButtonProperty?.unselectedColor?.let { unselectedColor = it } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/RowProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/RowProperty.kt new file mode 100644 index 0000000..a986bfb --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/RowProperty.kt @@ -0,0 +1,32 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + + +data class RowProperty( + var arrangementData: ArrangementData? = null, + var verticalAlignment: String? = null, + var verticalScroll: ScrollData? = null, + var horizontalScroll: ScrollData? = null, + var repeat: Int? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val rowProperty = property as? RowProperty + rowProperty?.arrangementData?.let { arrangementData = it } + rowProperty?.verticalAlignment?.let { verticalAlignment = it } + rowProperty?.verticalScroll?.let { verticalScroll = it } + rowProperty?.horizontalScroll?.let { horizontalScroll = it } + rowProperty?.repeat?.let { repeat = it } + } +} + +data class ArrangementData( + val arrangementType: String? = null, + val spacingValue: Int? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/SliderProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/SliderProperty.kt new file mode 100644 index 0000000..bc3ab07 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/SliderProperty.kt @@ -0,0 +1,56 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +import com.google.gson.annotations.SerializedName + +data class SliderProperty( + @SerializedName("sliderColors") + var sliderColors: SliderColors, + @SerializedName("enabled") + var enabled: Boolean? = null, + @SerializedName("rotation") + var rotation: Float? = null, + @SerializedName("uiTronIds", alternate = ["uiTronFormattedIds"]) + var uiTronIds: List? = null, + @SerializedName("uiTronRawValueIds") + var uiTronRawValueIds: List? = null, + @SerializedName("sliderValueRange") + var sliderValueRange: SliderValueRange? = null, + @SerializedName("steps") + var steps: Int? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val sliderProperty = property as? SliderProperty + sliderProperty?.sliderColors?.let { sliderColors = it } + sliderProperty?.enabled?.let { enabled = it } + sliderProperty?.rotation?.let { rotation = it } + sliderProperty?.uiTronIds?.let { uiTronIds = it } + sliderProperty?.sliderValueRange?.let { sliderValueRange = it } + sliderProperty?.steps?.let { steps = it } + } +} + +data class SliderColors( + val thumbColor: String? = null, + val activeTrackColor: String? = null, + val activeTickColor: String? = null, + val inactiveTickColor: String? = null, + val disabledActiveTickColor: String? = null, + val inactiveTrackColor: String? = null, + val disabledActiveTrackColor: String? = null, + val disabledInactiveTickColor: String? = null, + val disabledInactiveTrackColor: String? = null, + val disabledThumbColor: String? = null +) + +data class SliderValueRange( + val start: Float? = null, + val end: Float? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/SpacerProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/SpacerProperty.kt new file mode 100644 index 0000000..2540cbc --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/SpacerProperty.kt @@ -0,0 +1,14 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +class SpacerProperty : BaseProperty() diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/SpeedoMeterTextViewProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/SpeedoMeterTextViewProperty.kt new file mode 100644 index 0000000..e90bda2 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/SpeedoMeterTextViewProperty.kt @@ -0,0 +1,4 @@ +package com.navi.uitron.model.ui + +class SpeedoMeterTextViewProperty { +} \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/SwitchProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/SwitchProperty.kt new file mode 100644 index 0000000..bb2efe9 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/SwitchProperty.kt @@ -0,0 +1,36 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + + +data class SwitchProperty( + var isChecked: Boolean? = null, + var enabled: Boolean? = null, + var switchColors: SwitchColors, +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val switchProperty = property as? SwitchProperty + switchProperty?.isChecked?.let { isChecked = it } + switchProperty?.enabled?.let { enabled = it } + switchProperty?.switchColors?.let { switchColors = it } + } +} + +data class SwitchColors( + val checkedThumbColor: String? = null, + val checkedTrackColor: String? = null, + val checkedTrackAlpha: Float? = null, + val uncheckedThumbColor: String? = null, + val uncheckedTrackColor: String? = null, + val uncheckedTrackAlpha: Float? = null, + val disabledCheckedThumbColor: String? = null, + val disabledCheckedTrackColor: String? = null, + val disabledUncheckedThumbColor: String? = null, + val disabledUncheckedTrackColor: String? = null +) diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/TextProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/TextProperty.kt new file mode 100644 index 0000000..d8d9dae --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/TextProperty.kt @@ -0,0 +1,28 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +data class TextProperty( + var fontFamily: String? = null, + var fontWeight: String? = null, + var fontSize: Int? = null, + var textColor: String? = null, + var textDecoration: String? = null, + var letterSpacing: Float? = null +) : BaseProperty() { + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val textProperty = property as? TextProperty? + textProperty?.fontFamily?.let { fontFamily = it } + textProperty?.fontWeight?.let { fontWeight = it } + textProperty?.fontSize?.let { fontSize = it } + textProperty?.textColor?.let { textColor = it } + textProperty?.textDecoration?.let { textDecoration = it } + textProperty?.letterSpacing?.let { letterSpacing = it } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/ToastProperty.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/ToastProperty.kt new file mode 100644 index 0000000..d7a44da --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/ToastProperty.kt @@ -0,0 +1,21 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +class ToastProperty( + var message: String? = null, + var duration: String? = null +) : BaseProperty() { + + override fun copyNonNullFrom(property: BaseProperty?) { + super.copyNonNullFrom(property) + val toastProperty = property as? ToastProperty? + toastProperty?.message?.let { message = it } + toastProperty?.duration?.let { duration = it } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/model/ui/UiTronView.kt b/UiTron/src/main/java/com/navi/uitron/model/ui/UiTronView.kt new file mode 100644 index 0000000..af934dd --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/model/ui/UiTronView.kt @@ -0,0 +1,223 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.model.ui + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + + +@Parcelize +data class UiTronView( + val property: BaseProperty? = null, + val childrenViews: List? = null +) : Parcelable + +@Parcelize +open class BaseProperty( + var viewType: String? = null, + var layoutId: String? = null, + var width: String? = null, + var height: String? = null, + var padding: ComposePadding? = null, + var constraintLinks: ConstraintLink? = null, + var clipData: UiTronShape? = null, + val isStateFul: Boolean? = false, + val interaction: UiTronInteraction? = null, + var backGroundBrushData: BrushData? = null, + var scaleData: ScaleData? = null, + var backgroundColor: String? = null, + var shape: UiTronShape? = null, + var statesMap: Map? = null, + var visible: Boolean? = null +) : Parcelable { + open fun copyNonNullFrom(property: BaseProperty?) { + property?.width?.let { width = it } + property?.height?.let { height = it } + property?.padding?.let { padding = it } + property?.constraintLinks?.let { constraintLinks = it } + property?.clipData?.let { clipData = it } + property?.backGroundBrushData?.let { backGroundBrushData = it } + property?.scaleData?.let { scaleData = it } + property?.backgroundColor?.let { backgroundColor = it } + property?.shape?.let { shape = it } + property?.statesMap?.let { statesMap = it } + property?.visible?.let { visible = it } + } + + fun getPropertyId() = layoutId + PROPERTY_SUFFIX + + companion object { + const val PROPERTY_SUFFIX = "_property" + const val DATA_SUFFIX = "_wrapper" + } + +} + +@Parcelize +data class ComposePadding( + var start: Int? = null, + var end: Int? = null, + var top: Int? = null, + var bottom: Int? = null +) : Parcelable + +@Parcelize +data class UiTronShape( + val shapeType: String? = null, + val size: Int? = null +) : Parcelable + +@Parcelize +data class UiTronInteraction( + val interactionType: InteractionType = InteractionType.NONE, + val color: String? = null +) : Parcelable + +@Parcelize +data class BrushData( + val brushType: String? = null, + val colorStops: List>? = null, + val tileMode: String? = null, + val offSetData: OffSetData? = null +) : Parcelable + +@Parcelize +data class OffSetData( + val x: Float? = null, + val y: Float? = null, +) : Parcelable + +@Parcelize +data class ScaleData( + val x: Float? = null, + val y: Float? = null, +) : Parcelable + +enum class ComposeSize { + WRAP_CONTENT, + MATCH_PARENT, + FILL_TO_CONSTRAINTS, + INTRINSIC_MIN, + INTRINSIC_MAX +} + +enum class ComposeViewType { + ConstraintLayout, + Image, + Text, + Divider, + Column, + LinearProgressIndicator, + OutlinedTextField, + Button, + Card, + Spacer, + Row, + Lottie, + Slider, + Checkbox, + RadioButton, + Switch, + Dialog, + Dropdown, + CountDownTimer, + Box, + Toast, + JackpotText, + Pager, + PagerIndicator, + LazyColumn, + LazyRow, + Grid +} + +enum class HorizontalArrangementType { + SpaceEvenly, + SpaceBetween, + SpaceAround, + SpacedBy, + Center, + Start, + End +} + +enum class VerticalArrangementType { + SpaceEvenly, + SpaceBetween, + SpaceAround, + SpacedBy, + Center, + Top, + Bottom +} + +enum class VerticalAlignmentType { + Top, + Bottom, + CenterVertically +} + +enum class HorizontalAlignmentType { + Start, + End, + CenterHorizontally +} + +enum class ContentAlignmentType { + TopStart, + TopCenter, + TopEnd, + CenterStart, + Center, + CenterEnd, + BottomStart, + BottomCenter, + BottomEnd, +} + +enum class ContentScaleType { + Crop, + FillBounds, + FillHeight, + Fit, + Inside, + FillWidth, + None +} + +enum class UiTronTextDecoration { + Underline, + LineThrough, + None +} + + +enum class InteractionType { + NONE, + RIPPLE +} + +enum class BrushType { + LINEAR, + RADIAL, + SWEEP, + VERTICAL, + HORIZONTAL +} + +enum class VisualTransformationType { + DOB, + MONEY, + PASSWORD, + ALL_CAPS +} + +enum class InputTransformationType { + NUMBER_TO_WORDS, + DOB_TO_READABLE_STRING +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/BoxRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/BoxRenderer.kt new file mode 100644 index 0000000..fbffb26 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/BoxRenderer.kt @@ -0,0 +1,75 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.BoxProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getContentAlignment +import orFalse +import orTrue +import setBackground +import setHeight +import setHorizontalScroll +import setVerticalScroll +import setWidth + +class BoxRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + @Composable + override fun Render(property: BoxProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + Box( + contentAlignment = getContentAlignment(contentAlignment = property.contentAlignment), + propagateMinConstraints = property.propagateMinConstraints.orFalse(), + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setVerticalScroll(property.verticalScroll) + .setHorizontalScroll(property.horizontalScroll) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable({ + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions) + ) { + uiTronRenderer.Render(composeViews = childrenComposeViews) + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/ButtonRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/ButtonRenderer.kt new file mode 100644 index 0000000..1dc6915 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/ButtonRenderer.kt @@ -0,0 +1,90 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.ContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.ButtonData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.ButtonProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.utils.ShapeUtil +import com.navi.uitron.viewmodel.UiTronViewModel +import hexToComposeColor +import orFalse +import orTrue +import setButtonElevation +import setHeight +import setWidth + +class ButtonRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + @Composable + override fun Render(property: ButtonProperty, uiTronData: UiTronData?) { + val uiTronButtonData = uiTronData as? ButtonData + if (property.isStateFul.orFalse()) { + uiTronViewModel.addKeyToSavedStateHandle(property.layoutId.orEmpty()) + val viewState = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(viewState.value)) + } + if (property.visible.orTrue()) { + Button( + shape = ShapeUtil.getShape(shape = property.shape), + colors = ButtonDefaults.buttonColors( + backgroundColor = property.colors?.backgroundColor?.hexToComposeColor + ?: Color.Red, + contentColor = property.colors?.contentColor?.hexToComposeColor ?: Color.White, + disabledBackgroundColor = property.colors?.disabledBackgroundColor?.hexToComposeColor + ?: MaterialTheme.colors.onSurface.copy(alpha = 0.12f) + .compositeOver(MaterialTheme.colors.surface), + disabledContentColor = property.colors?.disabledContentColor?.hexToComposeColor + ?: MaterialTheme.colors.onSurface + .copy(alpha = ContentAlpha.disabled) + ), + enabled = property.enabled.orTrue(), + border = BorderStroke( + property.borderStroke?.width?.dp ?: 0.dp, + color = property.borderStroke?.color?.hexToComposeColor ?: Color.Transparent + ), + elevation = setButtonElevation(property), + modifier = Modifier + .setHeight(property.height) + .setWidth(property.width) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .layoutId(property.layoutId.orEmpty()), + onClick = { + uiTronViewModel.handleActions(uiTronData?.onClick) + }) { + uiTronRenderer.Render(composeViews = childrenComposeViews) + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/CardRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/CardRenderer.kt new file mode 100644 index 0000000..991aaa6 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/CardRenderer.kt @@ -0,0 +1,83 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Card +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.DefaultShadowColor +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.CardProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.utils.ShapeUtil +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import hexToComposeColor +import orFalse +import orTrue +import setHeight +import setWidth + +class CardRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + + @Composable + override fun Render(property: CardProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + Card( + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setWidth(property.width) + .setHeight(property.height) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .shadow( + elevation = property.elevation?.dp ?: 8.dp, + ambientColor = property.ambientColor?.hexToComposeColor + ?: DefaultShadowColor, + spotColor = property.spotColor?.hexToComposeColor ?: DefaultShadowColor + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ), + shape = ShapeUtil.getShape(shape = property.shape), + backgroundColor = property.backgroundColor?.hexToComposeColor ?: Color.White, + border = BorderStroke( + property.borderStrokeData?.width?.dp ?: 0.dp, + property.borderStrokeData?.color?.hexToComposeColor ?: Color.Transparent + ) + ) { + uiTronRenderer.Render(composeViews = childrenComposeViews) + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/CheckBoxRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/CheckBoxRenderer.kt new file mode 100644 index 0000000..0d0bbef --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/CheckBoxRenderer.kt @@ -0,0 +1,84 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Checkbox +import androidx.compose.material.CheckboxDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.CheckBoxData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.CheckBoxProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import hexToComposeColor +import orFalse +import orTrue +import setBackground +import setHeight +import setWidth + +class CheckBoxRenderer(private val uiTronViewModel: UiTronViewModel) : + Renderer { + + @Composable + override fun Render(property: CheckBoxProperty, uiTronData: UiTronData?) { + val checkboxData = uiTronData as? CheckBoxData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + val isChecked = remember { + mutableStateOf(property.isChecked ?: false) + } + if (property.visible.orTrue()) { + Checkbox( + checked = isChecked.value, + onCheckedChange = { + isChecked.value = isChecked.value.not() + uiTronViewModel.handleActions(uiTronData?.onClick) + }, + enabled = property.enabled ?: true, + colors = CheckboxDefaults.colors( + property.checkedColor?.hexToComposeColor ?: Color.Black, + property.uncheckedColor?.hexToComposeColor ?: Color.Black, + property.checkmarkColor?.hexToComposeColor ?: Color.Black + ), + modifier = Modifier + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ) + .setBackground( + property.backgroundColor, property.shape, property.backGroundBrushData + ) + .setWidth(property.width) + .setHeight(property.height) + .layoutId(property.layoutId.orEmpty()) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/ColumnRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/ColumnRenderer.kt new file mode 100644 index 0000000..39c6806 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/ColumnRenderer.kt @@ -0,0 +1,99 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.ColumnData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.ColumnProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getHorizontalAlignment +import orFalse +import orTrue +import setBackground +import setHeight +import setHorizontalScroll +import setVerticalArrangement +import setVerticalScroll +import setWidth + +class ColumnRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val dataMap: MutableMap?, + private val uiTronViewModel: UiTronViewModel +) : + Renderer { + @Composable + override fun Render(property: ColumnProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + Column( + verticalArrangement = Arrangement.setVerticalArrangement(arrangementData = property.arrangementData), + horizontalAlignment = getHorizontalAlignment(horizontalAlignment = property.horizontalAlignment), + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setVerticalScroll(property.verticalScroll) + .setHorizontalScroll(property.horizontalScroll) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) { + if (property.repeat == null || (property.repeat ?: 0) <= 1) { + uiTronRenderer.Render(composeViews = childrenComposeViews) + } else { + val uiTronColumnData = uiTronData as? ColumnData + uiTronColumnData?.childrenDataList?.forEachIndexed { index, uiTronData -> + dataMap?.put(property.layoutId.plus(index), uiTronData) + } + repeat(property.repeat ?: 0) { index -> + val uiTronComposeViewProperty = childrenComposeViews.getOrNull(0)?.property + uiTronComposeViewProperty?.layoutId = + property.layoutId?.plus(index) + val childComposeView = UiTronView( + uiTronComposeViewProperty, + childrenComposeViews.getOrNull(0)?.childrenViews + ) + uiTronRenderer.Render(listOf(childComposeView)) + } + } + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/ConstraintLayoutRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/ConstraintLayoutRenderer.kt new file mode 100644 index 0000000..5d88fc9 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/ConstraintLayoutRenderer.kt @@ -0,0 +1,77 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import RenderUtility +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.layoutId +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.ConstraintProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import orFalse +import orTrue +import setBackground +import setHeight +import setHorizontalScroll +import setVerticalScroll +import setWidth + +class ConstraintLayoutRenderer( + private val childrenViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : + Renderer { + @Composable + override fun Render(property: ConstraintProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + ConstraintLayout( + constraintSet = RenderUtility.decoupledConstraints(childrenViews), + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setHeight(property.height) + .setWidth(property.width) + .setVerticalScroll(property.verticalScroll) + .setHorizontalScroll(property.horizontalScroll) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) { + uiTronRenderer.Render(childrenViews) + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/CountDownTimerRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/CountDownTimerRenderer.kt new file mode 100644 index 0000000..2f55dbf --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/CountDownTimerRenderer.kt @@ -0,0 +1,93 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import android.os.CountDownTimer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.collectAsState +import com.navi.uitron.model.data.CountDownTimerData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.CountDownTimerProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import formatDuration +import orFalse +import orTrue +import orZero + +class CountDownTimerRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + + @Composable + override fun Render( + property: CountDownTimerProperty, + uiTronData: UiTronData? + ) { + val countDownTimerData = uiTronData as? CountDownTimerData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + + if (property.visible.orTrue()) { + DisposableEffect(Unit) { + val countDownTimer = getCountDownTimer(countDownTimerData, uiTronViewModel) + countDownTimer.start() + onDispose { + countDownTimer.cancel() + } + } + uiTronRenderer.Render(composeViews = childrenComposeViews) + } + } + + private fun getCountDownTimer( + countDownTimerData: CountDownTimerData?, + uiTronViewModel: UiTronViewModel + ): CountDownTimer { + val countDownTimer = object : CountDownTimer( + countDownTimerData?.remainingTimeInMilliseconds.orZero(), + countDownTimerData?.countDownIntervalInMilliseconds.orZero() + ) { + override fun onTick(millisUntilFinished: Long) { + val (days, hours, minutes, seconds, milliSeconds) = formatDuration( + millisUntilFinished + ) + countDownTimerData?.timeUnitValuesLayoutIds?.apply { + daysFieldLayoutId?.let { uiTronId -> + uiTronViewModel.onTextFieldValueChanged(uiTronId, days) + } + hoursFieldLayoutId?.let { uiTronId -> + uiTronViewModel.onTextFieldValueChanged(uiTronId, hours) + } + minutesFieldLayoutId?.let { uiTronId -> + uiTronViewModel.onTextFieldValueChanged(uiTronId, minutes) + } + secondsFieldLayoutId?.let { uiTronId -> + uiTronViewModel.onTextFieldValueChanged(uiTronId, seconds) + } + milliSecondsFieldLayoutId?.let { uiTronId -> + uiTronViewModel.onTextFieldValueChanged(uiTronId, milliSeconds) + } + } + } + + override fun onFinish() { + uiTronViewModel.handleActions(countDownTimerData?.onClick) + } + } + return countDownTimer + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/DialogRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/DialogRenderer.kt new file mode 100644 index 0000000..1283e56 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/DialogRenderer.kt @@ -0,0 +1,53 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.navi.uitron.model.data.DialogData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.DialogProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import orFalse +import orTrue + + +class DialogRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + + @Composable + override fun Render(property: DialogProperty, uiTronData: UiTronData?) { + val dialogData = uiTronData as? DialogData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible == true) { + Dialog( + properties = DialogProperties( + dismissOnBackPress = property.dismissableOnBackPress.orTrue(), + dismissOnClickOutside = property.dismissableOnClickOutside.orTrue() + ), + onDismissRequest = { + uiTronViewModel.handleActions(dialogData?.onDismiss) // When user click outside of dialog + }, + ) { + uiTronRenderer.Render(composeViews = childrenComposeViews) + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/DividerRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/DividerRenderer.kt new file mode 100644 index 0000000..0cc98c1 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/DividerRenderer.kt @@ -0,0 +1,89 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.DividerProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import hexToComposeColor +import orFalse +import orTrue +import setHeight +import setWidth + +class DividerRenderer(private val uiTronViewModel: UiTronViewModel) : Renderer { + @Composable + override fun Render(property: DividerProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + if (property.separation == null || property.separation == 0) { + Divider( + modifier = Modifier + .setHeight(property.height) + .setWidth(property.width) + .layoutId(property.layoutId.orEmpty()) + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ), + color = property.color?.hexToComposeColor ?: Color.Black, + thickness = property.thickness?.dp ?: 1.dp + ) + } else { + val pathEffect = PathEffect.dashPathEffect( + floatArrayOf( + property.dashedAtomicWidth?.toFloat() ?: property.separation?.times(2f) + ?: 0f, + property.separation?.toFloat() ?: 0f + ), 0f + ) + Canvas( + Modifier + .setHeight(property.height) + .setWidth(property.width) + .layoutId(property.layoutId.orEmpty()) + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ) + ) { + + drawLine( + color = property.color?.hexToComposeColor ?: Color.Black, + start = Offset(0f, 0f), + end = Offset(size.width, size.height), + pathEffect = pathEffect, + strokeWidth = property.thickness?.dp?.toPx() ?: 1.dp.toPx() + ) + } + } + } + + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/DropdownRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/DropdownRenderer.kt new file mode 100644 index 0000000..2c9dda5 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/DropdownRenderer.kt @@ -0,0 +1,101 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.PopupProperties +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.DropdownProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import orFalse +import orTrue +import setBackground +import setHeight +import setWidth + +class DropdownRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + @Composable + override fun Render(property: DropdownProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + val isExpanded = remember { + mutableStateOf(property.isExpanded ?: false) + } + Box( + modifier = Modifier + .clickable { + isExpanded.value = !isExpanded.value + } + ) { + DropdownMenu( + expanded = isExpanded.value, + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setWidth(property.width) + .setHeight(property.height) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .setBackground( + property.backgroundColor, property.shape, property.backGroundBrushData + ), + onDismissRequest = { + isExpanded.value = false + }, + offset = DpOffset( + property.offset?.x?.dp ?: 0.dp, + property.offset?.y?.dp ?: 0.dp + ), + properties = PopupProperties( + focusable = property.popupProperties.focusable ?: false, + dismissOnBackPress = property.popupProperties.dismissOnBackPress ?: true, + dismissOnClickOutside = property.popupProperties.dismissOnClickOutside + ?: true, + excludeFromSystemGesture = property.popupProperties.excludeFromSystemGesture + ?: true, + clippingEnabled = property.popupProperties.clippingEnabled ?: true, + ) + ) { + childrenComposeViews.forEach { + DropdownMenuItem(onClick = { + isExpanded.value = false + }) { + uiTronRenderer.Render(composeViews = listOf(it)) + } + } + } + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/GridRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/GridRenderer.kt new file mode 100644 index 0000000..e37462c --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/GridRenderer.kt @@ -0,0 +1,134 @@ +package com.navi.uitron.render + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.layoutId +import com.navi.uitron.model.data.GridData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.GridProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getContentPaddingValues +import orFalse +import orTrue +import orVal +import setBackground +import setHeight +import setHorizontalArrangement +import setVerticalArrangement +import setWidth + +class GridRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + + @Composable + override fun Render(property: GridProperty, uiTronData: UiTronData?) { + val gridData = uiTronData as? GridData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + val gridState = uiTronViewModel.stateHolder.getOrUpdateGridState(key = property.gridStateKey) + if (property.visible.orTrue()) { + val modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, property.shape, property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, actions = uiTronData?.onClick?.actions + ) + if (property.orientation == GridProperty.ORIENTATION_HORIZONTAL) { + LazyHorizontalGrid( + modifier = modifier, + state = gridState ?: rememberLazyGridState(), + contentPadding = getContentPaddingValues(property.contentPadding), + reverseLayout = property.reverseLayout.orFalse(), + verticalArrangement = Arrangement.setVerticalArrangement(arrangementData = property.verticalArrangementData), + horizontalArrangement = Arrangement.setHorizontalArrangement(arrangementData = property.horizontalArrangementData), + rows = if (property.gridCell?.gridType == GridProperty.GRID_TYPE_ADAPTIVE) { + GridCells.Adaptive( + (if ((property.gridCell?.minSize ?: 100) > 0) { + property.gridCell?.minSize ?: 100 + } else { + 100 + }).dp + ) + } else { + GridCells.Fixed(property.gridCell?.count ?: 3) + }, + userScrollEnabled = property.userScrollEnabled ?: true, + content = { + getContent(gridData) + } + ) + } else { + LazyVerticalGrid( + modifier = modifier, + state = gridState ?: rememberLazyGridState(), + contentPadding = getContentPaddingValues(property.contentPadding), + reverseLayout = property.reverseLayout.orFalse(), + verticalArrangement = Arrangement.setVerticalArrangement(arrangementData = property.verticalArrangementData), + horizontalArrangement = Arrangement.setHorizontalArrangement(arrangementData = property.horizontalArrangementData), + columns = if (property.gridCell?.gridType == GridProperty.GRID_TYPE_ADAPTIVE) { + GridCells.Adaptive( + (if ((property.gridCell?.minSize ?: 100) > 0) { + property.gridCell?.minSize ?: 100 + } else { + 100 + }).dp + ) + } else { + GridCells.Fixed(property.gridCell?.count ?: 3) + }, + userScrollEnabled = property.userScrollEnabled ?: true, + content = { + getContent(gridData) + } + ) + } + } + } + + private fun LazyGridScope.getContent(gridData: GridData?) { + childrenComposeViews.forEach { uiTronView -> + item( + span = { + GridItemSpan( + currentLineSpan = gridData?.childSpanCountMap?.get(uiTronView.property?.layoutId).orVal(1) + ) + } + ) { + uiTronRenderer.Render(listOf(uiTronView)) + } + } + } + +} \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/render/ImageRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/ImageRenderer.kt new file mode 100644 index 0000000..0db2cb3 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/ImageRenderer.kt @@ -0,0 +1,108 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import coil.size.Size +import com.navi.uitron.model.data.ImageData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.ImageProperty +import com.navi.uitron.utils.IconUtils +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getContentScale +import orFalse +import orTrue +import setBackground +import setHeight +import setWidth + + +class ImageRenderer(private val uiTronViewModel: UiTronViewModel) : Renderer { + @Composable + override fun Render(property: ImageProperty, uiTronData: UiTronData?) { + val uiTronImageData = uiTronData as? ImageData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + IconUtils.getImageFromLocal(uiTronImageData?.iconUrl)?.let { iconCode -> + Image( + painter = painterResource(id = iconCode), + contentDescription = uiTronImageData?.contentDescription, + contentScale = getContentScale(contentScale = uiTronImageData?.contentScale), + modifier = Modifier + .setWidth(property.width) + .setHeight(property.height) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .layoutId(property.layoutId.orEmpty()) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) + } ?: kotlin.run { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(uiTronImageData?.iconUrl) + .size(Size.ORIGINAL) + .build(), + contentDescription = "", + contentScale = getContentScale(contentScale = uiTronImageData?.contentScale), + modifier = Modifier + .setWidth(property.width) + .setHeight(property.height) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .layoutId(property.layoutId.orEmpty()) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/JackpotTextRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/JackpotTextRenderer.kt new file mode 100644 index 0000000..ea85793 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/JackpotTextRenderer.kt @@ -0,0 +1,135 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.animation.* +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.navi.uitron.IUiTronDependencyProvider +import com.navi.uitron.UiTronSdk +import com.navi.uitron.model.data.TextData +import com.navi.uitron.model.data.TextFieldInputWrapper +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.TextProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getText +import getTextDecoration +import hexToComposeColor +import orFalse +import setBackground + +class JackpotTextRenderer( + private val uiTronViewModel: UiTronViewModel +) : Renderer { + private val defaultFontSize = 14.sp + private var oldString: String = "1000" + + @OptIn(ExperimentalAnimationApi::class) + @Composable + override fun Render( + property: TextProperty, + uiTronData: UiTronData? + ) { + val uiTronTextData = uiTronData as? TextData + var uiTronTextDataState: State? = null + if (property.isStateFul.orFalse()) { + uiTronViewModel.addKeyToSavedStateHandle(property.layoutId.orEmpty()) + uiTronTextDataState = uiTronViewModel.handle.getStateFlow( + property.layoutId.orEmpty(), + TextFieldInputWrapper(inputText = uiTronTextData?.text) + ).collectAsState() + } + SideEffect { + oldString = + uiTronTextDataState?.value?.inputText?.getText(uiTronTextData?.textFormatter) + ?: uiTronTextData?.text.orEmpty() + } + Row( + Modifier + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + ) { + val countString = + uiTronTextDataState?.value?.inputText?.getText(uiTronTextData?.textFormatter) + ?: uiTronTextData?.text.orEmpty() + val oldCountString = oldString + for (i in countString.indices) { + val oldChar = oldCountString.getOrNull(i) + val newChar = countString[i] + val char = if (oldChar == newChar) { + oldCountString[i] + } else { + countString[i] + } + AnimatedContent( + targetState = char, + transitionSpec = if (char > (oldChar ?: '0')) { + bottomUpTransformation() + } else { + topDownTransformation() + } + ) { char -> + Text( + text = char.toString(), + fontFamily = UiTronSdk.getDependencyProvider().getFontFamily(property.fontFamily), + fontWeight = UiTronSdk.getDependencyProvider().getFontWeight(property.fontWeight), + fontSize = property.fontSize?.sp ?: defaultFontSize, + color = property.textColor?.hexToComposeColor ?: Color.Black, + textDecoration = getTextDecoration(property.textDecoration), + letterSpacing = property.letterSpacing?.sp ?: 0.sp, + modifier = Modifier + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .layoutId(property.layoutId.orEmpty()) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, + interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) + } + } + } + } + + @OptIn(ExperimentalAnimationApi::class) + fun bottomUpTransformation(): AnimatedContentScope.() -> ContentTransform { + return { + slideInVertically { it } with slideOutVertically { -it } + } + } + + @OptIn(ExperimentalAnimationApi::class) + fun topDownTransformation(): AnimatedContentScope.() -> ContentTransform { + return { + slideInVertically { -it } with slideOutVertically { it } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/LazyColumnRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/LazyColumnRenderer.kt new file mode 100644 index 0000000..88c3d2d --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/LazyColumnRenderer.kt @@ -0,0 +1,94 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.helpers.ScrollEventHandler +import com.navi.uitron.model.data.LazyColumnData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.LazyColumnProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getHorizontalAlignment +import orFalse +import orTrue +import setBackground +import setHeight +import setVerticalArrangement +import setWidth + +class LazyColumnRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + + private val scrollEventHandler = ScrollEventHandler(uiTronViewModel) + + @Composable + override fun Render(property: LazyColumnProperty, uiTronData: UiTronData?) { + val lazyListState = rememberLazyListState() + val lazyColumnData = uiTronData as? LazyColumnData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + LazyColumn( + state = lazyListState, + verticalArrangement = Arrangement.setVerticalArrangement(arrangementData = property.arrangementData), + horizontalAlignment = getHorizontalAlignment(horizontalAlignment = property.horizontalAlignment), + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ), + ) { + items(childrenComposeViews){ + uiTronRenderer.Render(listOf(it)) + } + } + lazyColumnData?.scrollData?.let { + scrollEventHandler.HandleListScroll( + listState = lazyListState, + scrollData = it + ) + } + } + } + +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/LazyRowRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/LazyRowRenderer.kt new file mode 100644 index 0000000..0cf8090 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/LazyRowRenderer.kt @@ -0,0 +1,96 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.helpers.ScrollEventHandler +import com.navi.uitron.model.data.LazyRowData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.LazyRowProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getVerticalAlignment +import orFalse +import orTrue +import setBackground +import setHeight +import setHorizontalArrangement +import setWidth + + +class LazyRowRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + + private val scrollEventHandler = ScrollEventHandler(uiTronViewModel) + + @Composable + override fun Render(property: LazyRowProperty, uiTronData: UiTronData?) { + val lazyRowData = uiTronData as? LazyRowData + val lazyListState = rememberLazyListState() + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + LazyRow( + state = lazyListState, + horizontalArrangement = Arrangement.setHorizontalArrangement(arrangementData = property.arrangementData), + verticalAlignment = getVerticalAlignment(verticalAlignment = property.verticalAlignment), + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, + interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) { + items(childrenComposeViews) { + uiTronRenderer.Render(listOf(it)) + } + } + lazyRowData?.scrollData?.let { + scrollEventHandler.HandleListScroll( + listState = lazyListState, + scrollData = it + ) + } + } + } + +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/LinearProgressIndicatorRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/LinearProgressIndicatorRenderer.kt new file mode 100644 index 0000000..0ec08cf --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/LinearProgressIndicatorRenderer.kt @@ -0,0 +1,51 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layoutId +import clipContent +import com.navi.uitron.model.data.LinearProgressIndicatorData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.LinearProgressIndicatorProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import hexToComposeColor +import orFalse +import orTrue +import setHeight +import setWidth + +class LinearProgressIndicatorRenderer(private val uiTronViewModel: UiTronViewModel) : + Renderer { + @Composable + override fun Render(property: LinearProgressIndicatorProperty, uiTronData: UiTronData?) { + val linearProgressIndicatorData = uiTronData as? LinearProgressIndicatorData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + LinearProgressIndicator( + progress = linearProgressIndicatorData?.progress ?: 0f, + modifier = Modifier + .setWidth(property.width) + .setHeight(property.height) + .clipContent(property.clipData) + .layoutId(property.layoutId.orEmpty()), + color = property.color?.hexToComposeColor ?: Color.Green + ) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/LottieRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/LottieRenderer.kt new file mode 100644 index 0000000..0ca0776 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/LottieRenderer.kt @@ -0,0 +1,78 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.compose.rememberLottieComposition +import com.navi.uitron.model.data.LottieData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.LottieProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import orFalse +import orTrue +import setHeight +import setWidth + +class LottieRenderer(private val uiTronViewModel: UiTronViewModel) : Renderer { + + @Composable + override fun Render(property: LottieProperty, uiTronData: UiTronData?) { + val uiTronLottieData = uiTronData as? LottieData + + val spec = LottieCompositionSpec.Url(url = uiTronLottieData?.lottieUrl.orEmpty()) + val composition by rememberLottieComposition(spec) + val progress by animateLottieCompositionAsState(composition) + if (progress == 1.0f) { + LaunchedEffect(Unit) { + uiTronLottieData?.onAnimationEnd?.let { + uiTronViewModel.handleActions(it) + } + } + } + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + LottieAnimation( + composition = composition, + iterations = uiTronLottieData?.iterations ?: 1, + modifier = Modifier + .setWidth(property.width) + .setHeight(property.height) + .layoutId(property.layoutId.orEmpty()) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/OutlinedTextFieldRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/OutlinedTextFieldRenderer.kt new file mode 100644 index 0000000..dae921c --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/OutlinedTextFieldRenderer.kt @@ -0,0 +1,180 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import KeyboardUtil +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.navi.uitron.UiTronSdk +import com.navi.uitron.model.data.OutlinedTextFieldData +import com.navi.uitron.model.data.TextData +import com.navi.uitron.model.data.TextFieldInputWrapper +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.BaseProperty.Companion.DATA_SUFFIX +import com.navi.uitron.model.ui.OutlinedTextFieldProperty +import com.navi.uitron.utils.ShapeUtil +import com.navi.uitron.viewmodel.UiTronViewModel +import getTransformedText +import getVisualTransformation +import hexToComposeColor +import isNotNullAndNotEmpty +import orFalse +import orTrue +import setHeight +import setWidth + +class OutlinedTextFieldRenderer( + private val uiTronViewModel: UiTronViewModel, + private val inputWrapper: TextFieldInputWrapper +) : Renderer { + + @Composable + override fun Render( + property: OutlinedTextFieldProperty, + uiTronData: UiTronData? + ) { + val uiTronOutlinedTextFieldData = uiTronData as? OutlinedTextFieldData + val fieldValue = remember { + mutableStateOf( + TextFieldValue( + inputWrapper.inputText.orEmpty(), + TextRange(inputWrapper.inputText?.length ?: 0) + ) + ) + } + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + Column( + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setWidth(property.width) + .setHeight(property.height) + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + textStyle = TextStyle( + fontSize = property.textStyle.fontSize?.sp ?: 14.sp, + color = property.textStyle.color?.hexToComposeColor ?: Color.Black, + fontFamily = UiTronSdk.getDependencyProvider().getFontFamily(property.textStyle.fontFamily), + fontWeight = UiTronSdk.getDependencyProvider().getFontWeight(property.textStyle.fontWeight) + ), + keyboardOptions = KeyboardOptions( + capitalization = KeyboardUtil.getKeyboardCapitalization(property.keyboardOptions?.capitalization), + keyboardType = KeyboardUtil.getKeyboardType(property.keyboardOptions?.keyboardType), + imeAction = KeyboardUtil.getImeAction(property.keyboardOptions?.imeAction) + ), + value = fieldValue.value, + onValueChange = { + uiTronViewModel.handle[property.layoutId.orEmpty()] = it.text + property.maxChar?.let { maxChar -> + if (maxChar >= it.text.length) { + fieldValue.value = it + uiTronViewModel.onTextFieldValueChanged( + property.layoutId + DATA_SUFFIX, + it.text + ) + if (property.applyValidationOnValueChange.orFalse() || inputWrapper.hasError == true) { + uiTronViewModel.handleActions(uiTronOutlinedTextFieldData?.onValueChangeAction) + } + } + } ?: run { + fieldValue.value = it + uiTronViewModel.onTextFieldValueChanged( + property.layoutId + DATA_SUFFIX, + it.text + ) + if (property.applyValidationOnValueChange.orFalse() || inputWrapper.hasError == true) { + uiTronViewModel.handleActions(uiTronOutlinedTextFieldData?.onValueChangeAction) + } + } + }, + singleLine = property.singleLine ?: false, + colors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = property.colors?.focusedBorderColor?.hexToComposeColor + ?: Color.Gray, + unfocusedBorderColor = property.colors?.unfocusedBorderColor?.hexToComposeColor + ?: Color.Gray, + errorBorderColor = property.colors?.errorBorderColor?.hexToComposeColor + ?: Color.Red + ), + shape = ShapeUtil.getShape(property.shape), + placeholder = { + property.hintStyle?.let { + TextRenderer(uiTronViewModel).Render( + property = it, + TextData(text = uiTronOutlinedTextFieldData?.hint) + ) + } + }, isError = inputWrapper.hasError ?: false, + visualTransformation = getVisualTransformation(visualTransformation = property.visualTransformation) + ) + if (inputWrapper.hasError == true && inputWrapper.errorMessage.isNullOrBlank() + .not() + ) { + property.errorView?.let { + UiTronRenderer( + it.data, + uiTronViewModel + ).Render(composeViews = it.parentComposeView.orEmpty()) + } + uiTronViewModel.onTextFieldValueChanged( + property.errorMessageLayoutId.orEmpty(), + inputWrapper.errorMessage.orEmpty() + ) + } else if (inputWrapper.inputText.isNotNullAndNotEmpty()) { + val successText = getTransformedText( + property.successTextTransformation, + inputWrapper.inputText.orEmpty() + ) + if (successText.isNotNullAndNotEmpty()) { + property.successView?.let { + UiTronRenderer( + it.data, + uiTronViewModel + ).Render(composeViews = it.parentComposeView.orEmpty()) + } + uiTronViewModel.onTextFieldValueChanged( + property.successMessageLayoutId.orEmpty(), + successText.orEmpty() + ) + } + } + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/PagerIndicatorRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/PagerIndicatorRenderer.kt new file mode 100644 index 0000000..1b8ffda --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/PagerIndicatorRenderer.kt @@ -0,0 +1,134 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ContentAlpha +import androidx.compose.material.LocalContentAlpha +import androidx.compose.material.LocalContentColor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.layoutId +import com.google.accompanist.pager.ExperimentalPagerApi +import com.google.accompanist.pager.HorizontalPagerIndicator +import com.google.accompanist.pager.VerticalPagerIndicator +import com.google.accompanist.pager.rememberPagerState +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.PagerIndicatorProperty +import com.navi.uitron.model.ui.UiTronShape +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.utils.ShapeType +import com.navi.uitron.utils.ShapeUtil +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import hexToComposeColor +import orFalse +import orTrue +import setBackground +import setHeight +import setWidth + +@OptIn(ExperimentalPagerApi::class) +class PagerIndicatorRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel, +) : Renderer { + + @Composable + override fun Render( + property: PagerIndicatorProperty, + uiTronData: UiTronData? + ) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + + val pagerState = uiTronViewModel.stateHolder.getOrUpdatePagerState(property.pagerStateKey) + + if (property.visible.orTrue()) { + if (property.orientation == PagerIndicatorProperty.ORIENTATION_VERTICAL) { + VerticalPagerIndicator( + pagerState = pagerState ?: rememberPagerState(), + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, + interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ), + activeColor = property.activeColor?.hexToComposeColor + ?: LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + inactiveColor = property.inactiveColor?.hexToComposeColor ?: (property.activeColor?.hexToComposeColor + ?: Color.Transparent).copy(ContentAlpha.disabled), + indicatorWidth = (property.indicatorWidth ?: 0).dp, + indicatorHeight = (property.indicatorHeight ?: 0).dp, + spacing = (property.spacing ?: 0).dp, + indicatorShape = ShapeUtil.getShape(property.indicatorShape ?: UiTronShape(ShapeType.CircleShape.name)) + ) + } else { + HorizontalPagerIndicator( + pagerState = pagerState ?: rememberPagerState(), + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, + interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ), + activeColor = property.activeColor?.hexToComposeColor + ?: LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + inactiveColor = property.inactiveColor?.hexToComposeColor ?: (property.activeColor?.hexToComposeColor + ?: Color.Transparent).copy(ContentAlpha.disabled), + indicatorWidth = (property.indicatorWidth ?: 0).dp, + indicatorHeight = (property.indicatorHeight ?: 0).dp, + spacing = (property.spacing ?: 0).dp, + indicatorShape = ShapeUtil.getShape(property.indicatorShape ?: UiTronShape(ShapeType.CircleShape.name)) + ) + } + } + } + +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/PagerRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/PagerRenderer.kt new file mode 100644 index 0000000..b336e6d --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/PagerRenderer.kt @@ -0,0 +1,152 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.layoutId +import com.google.accompanist.pager.ExperimentalPagerApi +import com.google.accompanist.pager.HorizontalPager +import com.google.accompanist.pager.VerticalPager +import com.google.accompanist.pager.rememberPagerState +import com.navi.uitron.model.data.PagerData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.PagerProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getContentPaddingValues +import getHorizontalAlignment +import getVerticalAlignment +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import orFalse +import orTrue +import orZero +import setBackground +import setHeight +import setWidth + +@OptIn(ExperimentalPagerApi::class) +class PagerRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + + @Composable + override fun Render( + property: PagerProperty, + uiTronData: UiTronData? + ) { + val pagerData = uiTronData as? PagerData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + val pagerState = uiTronViewModel.stateHolder.getOrUpdatePagerState(property.pagerStateKey) + ?: rememberPagerState() + if (property.orientation == PagerProperty.ORIENTATION_VERTICAL) { + VerticalPager( + count = childrenComposeViews.size, + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, + interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ), + state = pagerState, + reverseLayout = property.reverseLayout.orFalse(), + itemSpacing = (property.itemSpacing ?: 0).dp, + horizontalAlignment = getHorizontalAlignment(property.horizontalAlignment), + contentPadding = getContentPaddingValues(property.contentPadding) + ) { page -> + uiTronRenderer.Render(listOf(childrenComposeViews[page])) + } + } else { + HorizontalPager( + count = childrenComposeViews.size, + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, + interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ), + state = pagerState, + reverseLayout = property.reverseLayout.orFalse(), + itemSpacing = (property.itemSpacing ?: 0).dp, + verticalAlignment = getVerticalAlignment(verticalAlignment = property.verticalAlignment), + contentPadding = getContentPaddingValues(property.contentPadding) + ) { page -> + uiTronRenderer.Render(listOf(childrenComposeViews[page])) + } + } + if (property.scrollDelay.orZero() > 0) { + LaunchedEffect(key1 = pagerState.currentPage) { + launch { + snapshotFlow { pagerState.currentPage }.collect { page -> + uiTronViewModel.handleActions(pagerData?.pageChangeActions?.get(page)) + } + } + launch { + delay(property.scrollDelay.orZero()) + with(pagerState) { + val target = if (currentPage < pageCount - 1) currentPage + 1 else 0 + animateScrollToPage( + page = target, + pageOffset = 0.0f + ) + } + } + } + } + } + } + +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/RadioButtonRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/RadioButtonRenderer.kt new file mode 100644 index 0000000..86a7cd7 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/RadioButtonRenderer.kt @@ -0,0 +1,73 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.RadioButton +import androidx.compose.material.RadioButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.RadioButtonProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import hexToComposeColor +import orFalse +import orTrue +import setBackground +import setHeight +import setWidth + +class RadioButtonRenderer(private val uiTronViewModel: UiTronViewModel) : + Renderer { + + @Composable + override fun Render(property: RadioButtonProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + RadioButton( + selected = property.isSelected ?: false, + enabled = property.enabled ?: true, + colors = RadioButtonDefaults.colors( + property.selectedColor?.hexToComposeColor ?: Color.Black, + property.unselectedColor?.hexToComposeColor ?: Color.Black + ), + modifier = Modifier + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ) + .setBackground( + property.backgroundColor, property.shape, property.backGroundBrushData + ) + .setWidth(property.width) + .setHeight(property.height) + .layoutId(property.layoutId.orEmpty()) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ), + onClick = {} + ) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/Renderer.kt b/UiTron/src/main/java/com/navi/uitron/render/Renderer.kt new file mode 100644 index 0000000..2abed99 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/Renderer.kt @@ -0,0 +1,21 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.runtime.Composable +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.BaseProperty + +/** + * Copyright © 2022 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +interface Renderer { + @Composable + fun Render(property: T, uiTronData: UiTronData?) +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/RowRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/RowRenderer.kt new file mode 100644 index 0000000..9835d4c --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/RowRenderer.kt @@ -0,0 +1,99 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.RowData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.RowProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getVerticalAlignment +import orFalse +import orTrue +import setBackground +import setHeight +import setHorizontalArrangement +import setHorizontalScroll +import setVerticalScroll +import setWidth + +class RowRenderer( + private val childrenComposeViews: List, + private val uiTronRenderer: UiTronRenderer, + private val dataMap: MutableMap?, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + @Composable + override fun Render(property: RowProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + Row( + horizontalArrangement = Arrangement.setHorizontalArrangement(arrangementData = property.arrangementData), + verticalAlignment = getVerticalAlignment(verticalAlignment = property.verticalAlignment), + modifier = Modifier + .layoutId(property.layoutId.orEmpty()) + .setVerticalScroll(property.verticalScroll) + .setHorizontalScroll(property.horizontalScroll) + .setHeight(property.height) + .setWidth(property.width) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .customClickable( + { + uiTronViewModel.handleActions(uiTronData?.onClick) + }, + interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) { + if (property.repeat == null || (property.repeat ?: 0) <= 1) { + uiTronRenderer.Render(composeViews = childrenComposeViews) + } else { + val uiTronRowData = uiTronData as? RowData + uiTronRowData?.childrenDataList?.forEachIndexed { index, uiTronData -> + dataMap?.put(property.layoutId.plus(index), uiTronData) + } + repeat(property.repeat ?: 0) { index -> + val uiTronComposeViewProperty = childrenComposeViews.getOrNull(0)?.property + uiTronComposeViewProperty?.layoutId = + property.layoutId?.plus(index) + val childComposeView = UiTronView( + uiTronComposeViewProperty, + childrenComposeViews.getOrNull(0)?.childrenViews + ) + uiTronRenderer.Render(listOf(childComposeView)) + } + } + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/SliderRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/SliderRenderer.kt new file mode 100644 index 0000000..25bd590 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/SliderRenderer.kt @@ -0,0 +1,303 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Slider +import androidx.compose.material.SliderDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.StepValue +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.data.UiTronSliderData +import com.navi.uitron.model.ui.SliderProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import hexToComposeColor +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import orFalse +import orTrue +import orZero +import setHeight +import setWidth +import kotlin.math.floor + +class SliderRenderer(private val uiTronViewModel: UiTronViewModel) : + Renderer { + @Composable + override fun Render(property: SliderProperty, uiTronData: UiTronData?) { + val sliderUiTronData = uiTronData as? UiTronSliderData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + /* + * The step values we get from config, has step and value mapped to each step. + * The slider will only return the slider position which will be + * percentage of the progress. + * So we create a map for progress percentage to step in the below formula + * And we will use the map to get step mapped to the slider position on + * slider value change. + * */ + + val progressPercentageToStep: HashMap = HashMap() + property.steps?.let { + val totalSteps = it + 1 + for (i in 0 until totalSteps) { + val end: Float? = property.sliderValueRange?.end?.toFloat() + val start: Float? = property.sliderValueRange?.start?.toFloat() + if (end != null && start != null) { + val total: Float = end - start + val multiplier: Float = total / it + val progressPercentage: Float = start + (i.times(multiplier)) + progressPercentageToStep[floor(progressPercentage.toDouble())] = i + } + } + } + val selectedSliderValue = + getSliderPosition(property, sliderUiTronData, progressPercentageToStep) + val sliderPosition = remember { + mutableStateOf(selectedSliderValue) + } + val coroutineScope = rememberCoroutineScope() + + if (sliderUiTronData?.initialAnimationEnabled.orTrue()) { + LaunchedEffect(key1 = Unit) { + coroutineScope.launch { + if (sliderUiTronData?.animationTargetValue != null && sliderUiTronData.stepValues != null) { + val steps = getSliderPositionSteps(sliderUiTronData) ?: 0 + for (i in 1..steps) { + sliderPosition.value = + (i * (getProgressPercentage(progressPercentageToStep, i) + ?: 1f)) + emitSliderValueChange( + uiTronViewModel, + property, + sliderUiTronData, + sliderPosition, + progressPercentageToStep + ) + delay(sliderUiTronData.animationDelay ?: 100) + } + } + if (sliderUiTronData?.animationStepCount != null) { + val animationStepCount = sliderUiTronData.animationStepCount ?: 10 + val animationStepSize = sliderUiTronData.animationStepSize ?: 5 + for (i in 1..animationStepCount) { + sliderPosition.value = (i * animationStepSize).toFloat() + emitSliderValueChange( + uiTronViewModel, + property, + sliderUiTronData, + sliderPosition, + progressPercentageToStep + ) + delay(sliderUiTronData.animationDelay ?: 100) + } + } + } + } + } + if (property.visible.orTrue()) { + Slider( + value = sliderPosition.value, + onValueChange = { + sliderPosition.value = it + emitSliderValueChange( + uiTronViewModel, + property, + sliderUiTronData, + sliderPosition, + progressPercentageToStep + ) + }, + enabled = property.enabled.orTrue(), + onValueChangeFinished = { + val stepValue = getSliderValue( + sliderPosition.value, + progressPercentageToStep, + sliderUiTronData?.stepValues + ) + val sliderRawValue: String? = if (sliderUiTronData?.stepValues.isNullOrEmpty() + .not() + ) stepValue?.rawValue else sliderPosition.value.toString() + val sliderFormattedValue: String? = + if (sliderUiTronData?.stepValues.isNullOrEmpty() + .not() + ) stepValue?.formattedValue else sliderPosition.value.toString() + uiTronViewModel.onSliderValueChangeFinished( + property.layoutId.orEmpty(), + sliderRawValue, + sliderFormattedValue,// can be replaced by value to be emitted if mapping is coming + property.uiTronIds.orEmpty() + ) + }, + valueRange = (property.sliderValueRange?.start + ?: 0f)..(property.sliderValueRange?.end + ?: 1f), + steps = if (property.steps?.dec().orZero() < 0) 0 else property.steps?.dec() + .orZero(), + modifier = Modifier + .setWidth(property.width) + .setHeight(property.height) + .layoutId(property.layoutId.orEmpty()) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .rotate(property.rotation ?: 0f), + colors = SliderDefaults.colors( + thumbColor = property.sliderColors.thumbColor?.hexToComposeColor + ?: MaterialTheme.colors.primary, + activeTrackColor = property.sliderColors.activeTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.primary, + activeTickColor = property.sliderColors.activeTickColor?.hexToComposeColor + ?: MaterialTheme.colors.primary, + inactiveTickColor = property.sliderColors.inactiveTickColor?.hexToComposeColor + ?: MaterialTheme.colors.primary, + disabledActiveTickColor = property.sliderColors.disabledActiveTickColor?.hexToComposeColor + ?: ((property.sliderColors.activeTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.primary).copy(alpha = SliderDefaults.TickAlpha)), + inactiveTrackColor = property.sliderColors.inactiveTrackColor?.hexToComposeColor + ?: (property.sliderColors.activeTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.primary).copy(alpha = SliderDefaults.InactiveTrackAlpha), + disabledActiveTrackColor = property.sliderColors.disabledActiveTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.onSurface.copy(alpha = SliderDefaults.DisabledActiveTrackAlpha), + disabledInactiveTickColor = property.sliderColors.disabledInactiveTickColor?.hexToComposeColor + ?: ((property.sliderColors.disabledActiveTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.onSurface.copy(alpha = SliderDefaults.DisabledActiveTrackAlpha)).copy( + alpha = SliderDefaults.DisabledInactiveTrackAlpha + )).copy(alpha = SliderDefaults.DisabledTickAlpha), + disabledInactiveTrackColor = property.sliderColors.disabledInactiveTrackColor?.hexToComposeColor + ?: (property.sliderColors.disabledActiveTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.onSurface.copy(alpha = SliderDefaults.DisabledActiveTrackAlpha)).copy( + alpha = SliderDefaults.DisabledInactiveTrackAlpha + ), + disabledThumbColor = property.sliderColors.disabledThumbColor?.hexToComposeColor + ?: MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) + .compositeOver(MaterialTheme.colors.surface) + ) + ) + } + } + + private fun emitSliderValueChange( + uiTronViewModel: UiTronViewModel, + property: SliderProperty, + sliderUiTronData: UiTronSliderData?, + sliderPosition: State, + progressPercentageToStep: java.util.HashMap, + ) { + val stepValue = getSliderValue( + sliderPosition.value, + progressPercentageToStep, + sliderUiTronData?.stepValues + ) + val sliderRawValue: String? = + if (sliderUiTronData?.stepValues.isNullOrEmpty() + .not() + ) stepValue?.rawValue else sliderPosition.value.toString() + val sliderFormattedValue: String? = + if (sliderUiTronData?.stepValues.isNullOrEmpty() + .not() + ) stepValue?.formattedValue else sliderPosition.value.toString() + uiTronViewModel.onSliderValueProgressChanged( + property.layoutId.orEmpty(), + sliderRawValue, + sliderFormattedValue,// can be replaced by value to be emitted if mapping is coming + property.uiTronIds.orEmpty(), + property.uiTronRawValueIds.orEmpty() + ) + } + + private fun getSliderPosition( + property: SliderProperty, + sliderUiTronData: UiTronSliderData?, + progressPercentageToStep: java.util.HashMap + ): Float { + sliderUiTronData?.selectedRawValue?.let { selectedRawValue -> + var step: Int? = null + sliderUiTronData.stepValues?.forEachIndexed { index, stepValue -> + if (stepValue.rawValue == selectedRawValue) { + step = index + } + } + step?.let { + val progressPercentage = getProgressPercentage(progressPercentageToStep, it) + progressPercentage?.let { + return progressPercentage + } + } + } + return sliderUiTronData?.sliderValue ?: property.sliderValueRange?.end ?: 1f + } + + private fun getSliderPositionSteps( + sliderUiTronData: UiTronSliderData? + ): Int? { + sliderUiTronData?.selectedRawValue?.let { selectedRawValue -> + sliderUiTronData.stepValues?.forEachIndexed { index, stepValue -> + if (stepValue.rawValue == selectedRawValue) { + return index + } + } + } + return null + } + + private fun getProgressPercentage( + progressPercentageToStep: java.util.HashMap, + step: Int + ): Float? { + for ((key, value) in progressPercentageToStep) { + if (value == step) { + return key.toFloat() + } + } + return null + } + + private fun getSliderValue( + sliderPosition: Float, + progressPercentageToStep: java.util.HashMap, + stepValues: List? + ): StepValue? { + stepValues?.forEach { + val currentStep = progressPercentageToStep[floor(sliderPosition).toDouble()] + return getCurrentStepValue(stepValues, currentStep) + } + return null + } + + private fun getCurrentStepValue(stepValues: List, currentStep: Int?): StepValue? { + stepValues.forEachIndexed { index, stepValue -> + if (index == currentStep) { + return stepValue + } + } + return null + } + +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/SpacerRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/SpacerRenderer.kt new file mode 100644 index 0000000..1fe5118 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/SpacerRenderer.kt @@ -0,0 +1,56 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.SpacerProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import orFalse +import orTrue +import setBackground +import setHeight +import setWidth + +class SpacerRenderer(private val uiTronViewModel: UiTronViewModel) : Renderer { + @Composable + override fun Render(property: SpacerProperty, uiTronData: UiTronData?) { + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state.value)) + } + if (property.visible.orTrue()) { + Spacer( + modifier = Modifier + .setHeight(property.height) + .setWidth(property.width) + .padding( + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp, + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp + ) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .layoutId(property.layoutId.orEmpty()) + ) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/SwitchRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/SwitchRenderer.kt new file mode 100644 index 0000000..db29ba5 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/SwitchRenderer.kt @@ -0,0 +1,98 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Switch +import androidx.compose.material.SwitchDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.data.SwitchData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.SwitchProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import hexToComposeColor +import orFalse +import orTrue + +class SwitchRenderer(private val uiTronViewModel: UiTronViewModel? = null) : + Renderer { + + @Composable + override fun Render(property: SwitchProperty, uiTronData: UiTronData?) { + val switchData = uiTronData as? SwitchData + if (property.isStateFul.orFalse()) { + val state = uiTronViewModel?.handle?.getStateFlow( + property.getPropertyId(), + null + )?.collectAsState() + property.copyNonNullFrom(property.statesMap?.get(state?.value)) + } + val isChecked = remember { + mutableStateOf(property.isChecked ?: false) + } + if (property.visible.orTrue()) { + Switch( + checked = isChecked.value, + onCheckedChange = { + isChecked.value = isChecked.value.not() + if (isChecked.value) { + uiTronViewModel?.handleActions(switchData?.checkedStateClickData) + } else { + uiTronViewModel?.handleActions(switchData?.unCheckedStateClickData) + } + }, + modifier = Modifier + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ) + .scale(property.scaleData?.x ?: 1.0f, property.scaleData?.y ?: 1.0f) + .layoutId(property.layoutId.orEmpty()), + enabled = property.enabled ?: true, + interactionSource = remember { MutableInteractionSource() }, + colors = SwitchDefaults.colors( + checkedThumbColor = property.switchColors.checkedThumbColor?.hexToComposeColor + ?: MaterialTheme.colors.secondaryVariant, + checkedTrackColor = property.switchColors.checkedTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.secondaryVariant, + checkedTrackAlpha = property.switchColors.checkedTrackAlpha ?: 0.54f, + uncheckedThumbColor = property.switchColors.uncheckedThumbColor?.hexToComposeColor + ?: MaterialTheme.colors.surface, + uncheckedTrackColor = property.switchColors.uncheckedTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.onSurface, + uncheckedTrackAlpha = property.switchColors.uncheckedTrackAlpha ?: 0.38f, + disabledCheckedThumbColor = property.switchColors.disabledCheckedThumbColor?.hexToComposeColor + ?: MaterialTheme.colors.secondaryVariant.copy(alpha = ContentAlpha.disabled) + .compositeOver(MaterialTheme.colors.surface), + disabledCheckedTrackColor = property.switchColors.disabledCheckedTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.secondaryVariant.copy(alpha = ContentAlpha.disabled) + .compositeOver(MaterialTheme.colors.surface), + disabledUncheckedThumbColor = property.switchColors.disabledUncheckedThumbColor?.hexToComposeColor + ?: MaterialTheme.colors.surface.copy(alpha = ContentAlpha.disabled) + .compositeOver(MaterialTheme.colors.surface), + disabledUncheckedTrackColor = property.switchColors.disabledUncheckedTrackColor?.hexToComposeColor + ?: MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) + .compositeOver(MaterialTheme.colors.surface), + ) + ) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/TextRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/TextRenderer.kt new file mode 100644 index 0000000..7698b0b --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/TextRenderer.kt @@ -0,0 +1,88 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.navi.uitron.UiTronSdk +import com.navi.uitron.model.data.TextData +import com.navi.uitron.model.data.TextFieldInputWrapper +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.TextProperty +import com.navi.uitron.viewmodel.UiTronViewModel +import customClickable +import getText +import getTextDecoration +import hexToComposeColor +import orFalse +import orTrue +import setBackground + +class TextRenderer(private val uiTronViewModel: UiTronViewModel? = null) : Renderer { + private val defaultFontSize = 14.sp + + @Composable + override fun Render( + property: TextProperty, + uiTronData: UiTronData? + ) { + val uiTronTextData = uiTronData as? TextData + var uiTronTextDataState: State? = null + if (property.isStateFul.orFalse()) { + uiTronViewModel?.addKeyToSavedStateHandle(property.layoutId.orEmpty()) + uiTronTextDataState = uiTronViewModel?.handle?.getStateFlow( + property.layoutId.orEmpty(), + TextFieldInputWrapper(inputText = uiTronTextData?.text) + )?.collectAsState() + val viewState = uiTronViewModel?.handle?.getStateFlow( + property.getPropertyId(), + null + )?.collectAsState() + property.copyNonNullFrom(property.statesMap?.get(viewState?.value)) + } + if (property.visible.orTrue()) { + Text( + text = uiTronTextDataState?.value?.inputText?.getText(uiTronTextData?.textFormatter) + ?: uiTronTextData?.text.orEmpty(), + fontFamily = UiTronSdk.getDependencyProvider().getFontFamily(property.fontFamily), + fontWeight = UiTronSdk.getDependencyProvider().getFontWeight(property.fontWeight), + fontSize = property.fontSize?.sp ?: defaultFontSize, + color = property.textColor?.hexToComposeColor ?: Color.Black, + textDecoration = getTextDecoration(property.textDecoration), + letterSpacing = property.letterSpacing?.sp ?: 0.sp, + modifier = Modifier + .padding( + top = property.padding?.top?.dp ?: 0.dp, + bottom = property.padding?.bottom?.dp ?: 0.dp, + start = property.padding?.start?.dp ?: 0.dp, + end = property.padding?.end?.dp ?: 0.dp + ) + .setBackground( + property.backgroundColor, + property.shape, + property.backGroundBrushData + ) + .layoutId(property.layoutId.orEmpty()) + .customClickable( + { + uiTronViewModel?.handleActions(uiTronData?.onClick) + }, interaction = property.interaction, + actions = uiTronData?.onClick?.actions + ) + ) + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/ToastRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/ToastRenderer.kt new file mode 100644 index 0000000..949594a --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/ToastRenderer.kt @@ -0,0 +1,81 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import android.widget.Toast +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext +import com.navi.uitron.model.data.ToastData +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.ToastProperty +import com.navi.uitron.model.ui.UiTronView +import com.navi.uitron.viewmodel.UiTronViewModel +import isNotNullAndNotEmpty +import orFalse + +class ToastRenderer( + private val childrenComposeViews: List?, + private val uiTronRenderer: UiTronRenderer, + private val uiTronViewModel: UiTronViewModel +) : Renderer { + @Composable + override fun Render(property: ToastProperty, uiTronData: UiTronData?) { + val toastData = uiTronData as? ToastData + + if (property.isStateFul.orFalse()) { + uiTronViewModel.addKeyToSavedStateHandle(property.layoutId.orEmpty()) + val viewState = uiTronViewModel.handle.getStateFlow( + property.getPropertyId(), + null + ).collectAsState() + property.copyNonNullFrom(property.statesMap?.get(viewState.value)) + } + + if (property.visible.orFalse()) { + if (property.message.isNotNullAndNotEmpty()) { + Toast.makeText( + LocalContext.current, + property.message, + getToastDuration(property.duration) + ).show() + } else { + Toast(LocalContext.current).apply { + duration = getToastDuration(property.duration) + view = ComposeView(LocalContext.current).apply { + childrenComposeViews?.let { + setContent { + uiTronRenderer.Render(composeViews = childrenComposeViews) + } + } + } + }.show() + } + + uiTronViewModel.handleActions(toastData?.onDismiss) + } + } +} + +enum class ToastDuration { + LENGTH_LONG, + LENGTH_SHORT +} + +fun getToastDuration(durationEnum: String?): Int { + return when (durationEnum) { + ToastDuration.LENGTH_LONG.name -> { + Toast.LENGTH_LONG + } + ToastDuration.LENGTH_SHORT.name -> { + Toast.LENGTH_SHORT + } + else -> Toast.LENGTH_SHORT + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/render/UiTronRenderer.kt b/UiTron/src/main/java/com/navi/uitron/render/UiTronRenderer.kt new file mode 100644 index 0000000..501e06f --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/render/UiTronRenderer.kt @@ -0,0 +1,345 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.render + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import com.navi.uitron.model.data.OutlinedTextFieldData +import com.navi.uitron.model.data.TextFieldInputWrapper +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.* +import com.navi.uitron.model.ui.BaseProperty.Companion.DATA_SUFFIX +import com.navi.uitron.viewmodel.UiTronViewModel + +class UiTronRenderer( + private val dataMap: MutableMap?, + private val uiTronViewModel: UiTronViewModel +) { + @Composable + fun Render(composeViews: List) { + composeViews.forEach { composeView -> + when (composeView.property?.viewType) { + ComposeViewType.ConstraintLayout.name -> { + (composeView.property as? ConstraintProperty)?.let { + ConstraintLayoutRenderer( + childrenViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, + uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { + null + }) + } + } + ComposeViewType.Text.name -> { + (composeView.property as? TextProperty)?.let { + TextRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, + uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { + null + }) + } + } + ComposeViewType.Image.name -> { + (composeView.property as? ImageProperty)?.let { + ImageRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Divider.name -> { + (composeView.property as? DividerProperty)?.let { + DividerRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, + uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { + null + } + ) + } + } + ComposeViewType.LinearProgressIndicator.name -> { + (composeView.property as? LinearProgressIndicatorProperty)?.let { + LinearProgressIndicatorRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Column.name -> { + (composeView.property as? ColumnProperty)?.let { + ColumnRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + dataMap = dataMap, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Row.name -> { + (composeView.property as? RowProperty)?.let { + RowRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + dataMap = dataMap, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.OutlinedTextField.name -> { + (composeView.property as? OutlinedTextFieldProperty)?.let { + val initialData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } as? OutlinedTextFieldData + val inputWrapper by uiTronViewModel.handle.getStateFlow( + it.layoutId + DATA_SUFFIX, + TextFieldInputWrapper(inputText = initialData?.value) + ).collectAsState() + val outlinedTextFieldRenderer = + OutlinedTextFieldRenderer( + uiTronViewModel = uiTronViewModel, + inputWrapper = inputWrapper + ) + outlinedTextFieldRenderer.Render( + property = it, uiTronData = initialData + ) + } + } + ComposeViewType.Button.name -> { + (composeView.property as? ButtonProperty)?.let { + ButtonRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty(), + ) { null } + ) + } + } + ComposeViewType.Card.name -> { + (composeView.property as? CardProperty)?.let { + CardRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Spacer.name -> { + (composeView.property as? SpacerProperty)?.let { + SpacerRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { + null + } + ) + } + } + ComposeViewType.Lottie.name -> { + (composeView.property as? LottieProperty)?.let { + LottieRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Slider.name -> { + (composeView.property as? SliderProperty)?.let { + SliderRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Checkbox.name -> { + (composeView.property as? CheckBoxProperty)?.let { + CheckBoxRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.RadioButton.name -> { + (composeView.property as? RadioButtonProperty)?.let { + RadioButtonRenderer( + uiTronViewModel = uiTronViewModel, + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Switch.name -> { + (composeView.property as? SwitchProperty)?.let { + SwitchRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Dialog.name -> { + (composeView.property as? DialogProperty)?.let { + DialogRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render(property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null }) + } + } + ComposeViewType.Box.name -> { + (composeView.property as? BoxProperty)?.let { + BoxRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render(property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null }) + } + } + ComposeViewType.Dropdown.name -> { + (composeView.property as? DropdownProperty)?.let { + DropdownRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.CountDownTimer.name -> { + (composeView.property as? CountDownTimerProperty)?.let { + CountDownTimerRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.Toast.name -> { + (composeView.property as? ToastProperty)?.let { + ToastRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, + uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { null }, + ) + } + } + ComposeViewType.JackpotText.name -> { + (composeView.property as? TextProperty)?.let { + JackpotTextRenderer(uiTronViewModel = uiTronViewModel).Render( + property = it, + uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { + null + } + ) + } + } + ComposeViewType.Pager.name -> { + (composeView.property as? PagerProperty)?.let { + PagerRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, + uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { null } + ) + } + } + ComposeViewType.PagerIndicator.name -> { + (composeView.property as? PagerIndicatorProperty)?.let { + PagerIndicatorRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, + uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { null } + ) + } + } + ComposeViewType.Grid.name -> { + (composeView.property as? GridProperty)?.let { + GridRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel + ).Render( + property = it, + uiTronData = dataMap?.getOrElse(it.layoutId.orEmpty()) { null } + ) + } + } + + ComposeViewType.LazyColumn.name -> { + (composeView.property as? LazyColumnProperty)?.let { + LazyColumnRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel, + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + ComposeViewType.LazyRow.name -> { + (composeView.property as? LazyRowProperty)?.let { + LazyRowRenderer( + childrenComposeViews = composeView.childrenViews.orEmpty(), + uiTronRenderer = this, + uiTronViewModel = uiTronViewModel, + ).Render( + property = it, uiTronData = dataMap?.getOrElse( + it.layoutId.orEmpty() + ) { null } + ) + } + } + } + } + } + + fun getData(layoutId: String?) = dataMap?.get(layoutId) +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/ActionHandler.kt b/UiTron/src/main/java/com/navi/uitron/utils/ActionHandler.kt new file mode 100644 index 0000000..8c383f5 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/ActionHandler.kt @@ -0,0 +1,37 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.utils + +import androidx.lifecycle.SavedStateHandle +import com.navi.uitron.model.data.* +import kotlinx.coroutines.flow.MutableSharedFlow + +class ActionHandler +constructor( + val handle: SavedStateHandle = SavedStateHandle(), +) { + + suspend fun handleActions( + onClick: UiTronActionData?, + ctaCallbackFlow: MutableSharedFlow + ) { + if (onClick?.type == ExecutionType.SEQUENTIAL.name) { + onClick.actions?.filterNotNull()?.forEach { + performAction(it, ctaCallbackFlow) + } + } + } + + suspend fun performAction( + uiTronAction: UiTronAction, + actionCallbackFlow: MutableSharedFlow + ) { + uiTronAction.manageAction(handle = handle , uiTronAction = uiTronAction , actionCallbackFlow = actionCallbackFlow) + } + +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/Constants.kt b/UiTron/src/main/java/com/navi/uitron/utils/Constants.kt new file mode 100644 index 0000000..a4aa550 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/Constants.kt @@ -0,0 +1,5 @@ +package com.navi.uitron.utils + +const val ZERO_STRING = "0" +const val EMPTY = "" +const val SPACE = " " diff --git a/UiTron/src/main/java/com/navi/uitron/utils/Ext.kt b/UiTron/src/main/java/com/navi/uitron/utils/Ext.kt new file mode 100644 index 0000000..c1ee769 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/Ext.kt @@ -0,0 +1,435 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +import android.content.res.Resources +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.ButtonElevation +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstrainedLayoutReference +import androidx.constraintlayout.compose.ConstraintLayoutBaseScope +import com.navi.uitron.UiTronSdk +import com.navi.uitron.model.data.TextData +import com.navi.uitron.model.data.UiTronAction +import com.navi.uitron.model.ui.* +import com.navi.uitron.utils.ShapeType +import com.navi.uitron.utils.ShapeUtil +import com.navi.uitron.utils.ZERO_STRING +import com.navi.uitron.utils.transformations.AllCapsTransformation +import com.navi.uitron.utils.transformations.DateTransformation +import com.navi.uitron.utils.transformations.DateTransformation.Companion.DEFAULT_DATE_SEPARATOR +import com.navi.uitron.utils.transformations.NumberCommaTransformation +import com.navi.uitron.utils.transformations.formatDateToReadableFormat +import java.util.concurrent.TimeUnit + +fun Modifier.setHeight(height: String?): Modifier = this.then( + when (height) { + ComposeSize.MATCH_PARENT.name -> fillMaxHeight() + ComposeSize.WRAP_CONTENT.name -> wrapContentHeight() + ComposeSize.FILL_TO_CONSTRAINTS.name -> this + ComposeSize.INTRINSIC_MIN.name -> height(IntrinsicSize.Min) + ComposeSize.INTRINSIC_MAX.name -> height(IntrinsicSize.Max) + else -> { + height?.toIntOrNull()?.let { + height(it.dp) + } ?: kotlin.run { + wrapContentHeight() + } + } + } +) + +fun Modifier.setWidth(width: String?): Modifier = this.then( + when (width) { + ComposeSize.MATCH_PARENT.name -> fillMaxWidth() + ComposeSize.WRAP_CONTENT.name -> wrapContentWidth() + ComposeSize.FILL_TO_CONSTRAINTS.name -> this + ComposeSize.INTRINSIC_MIN.name -> width(IntrinsicSize.Min) + ComposeSize.INTRINSIC_MAX.name -> width(IntrinsicSize.Max) + else -> { + width?.toIntOrNull()?.let { + width(it.dp) + } ?: kotlin.run { + wrapContentWidth() + } + } + } +) + +fun Modifier.clipContent(contentClipData: UiTronShape?): Modifier = this.then( + when (contentClipData?.shapeType) { + ShapeType.RoundedCornerShape.name -> { + clip(RoundedCornerShape(contentClipData.size?.dp ?: 0.dp)) + } + else -> this + } +) + +fun Modifier.setVerticalScroll(scrollData: ScrollData?): Modifier = composed { + if (scrollData?.enabled == true) { + this.then( + verticalScroll(state = rememberScrollState(), enabled = scrollData.enabled) + ) + } else this +} + + +fun Modifier.setHorizontalScroll(scrollData: ScrollData?): Modifier = composed { + if (scrollData?.enabled == true) { + this.then( + horizontalScroll(state = rememberScrollState(), enabled = scrollData.enabled) + ) + } else this +} + +fun ConstrainedLayoutReference.getHorizontalReference(constraint: String): ConstraintLayoutBaseScope.VerticalAnchor { + return when (constraint) { + ConstraintOption.END.name -> this.end + else -> this.start + } +} + +fun ConstrainedLayoutReference.getVerticalReference(constraint: String): ConstraintLayoutBaseScope.HorizontalAnchor { + return when (constraint) { + ConstraintOption.BOTTOM.name -> this.bottom + else -> this.top + } +} + +val String.hexToComposeColor + get() = try { + Color(android.graphics.Color.parseColor(this)) + } catch (e: Exception) { + Color.Unspecified + } + +fun Arrangement.setHorizontalArrangement(arrangementData: ArrangementData?): Arrangement.Horizontal { + return when (arrangementData?.arrangementType) { + HorizontalArrangementType.SpaceEvenly.name -> SpaceEvenly + HorizontalArrangementType.SpaceAround.name -> SpaceAround + HorizontalArrangementType.SpaceBetween.name -> SpaceBetween + HorizontalArrangementType.Center.name -> Center + HorizontalArrangementType.SpacedBy.name -> spacedBy( + space = arrangementData.spacingValue?.dp ?: 0.dp + ) + HorizontalArrangementType.Start.name -> Start + HorizontalArrangementType.End.name -> End + else -> Center + } +} + +fun Arrangement.setVerticalArrangement(arrangementData: ArrangementData?): Arrangement.Vertical { + return when (arrangementData?.arrangementType) { + VerticalArrangementType.SpaceEvenly.name -> SpaceEvenly + VerticalArrangementType.SpaceAround.name -> SpaceAround + VerticalArrangementType.SpaceBetween.name -> SpaceBetween + VerticalArrangementType.Center.name -> Center + VerticalArrangementType.SpacedBy.name -> spacedBy( + space = arrangementData.spacingValue?.dp ?: 0.dp + ) + VerticalArrangementType.Top.name -> Top + VerticalArrangementType.Bottom.name -> Bottom + else -> Center + } +} + +fun getVerticalAlignment(verticalAlignment: String?): Alignment.Vertical { + return when (verticalAlignment) { + VerticalAlignmentType.Top.name -> Alignment.Top + VerticalAlignmentType.CenterVertically.name -> Alignment.CenterVertically + VerticalAlignmentType.Bottom.name -> Alignment.Bottom + else -> Alignment.CenterVertically + } +} + +fun getHorizontalAlignment(horizontalAlignment: String?): Alignment.Horizontal { + return when (horizontalAlignment) { + HorizontalAlignmentType.Start.name -> Alignment.Start + HorizontalAlignmentType.CenterHorizontally.name -> Alignment.CenterHorizontally + HorizontalAlignmentType.End.name -> Alignment.End + else -> Alignment.CenterHorizontally + } +} +fun getContentAlignment(contentAlignment: String?): Alignment { + return when (contentAlignment) { + ContentAlignmentType.TopStart.name -> Alignment.TopStart + ContentAlignmentType.TopCenter.name -> Alignment.TopCenter + ContentAlignmentType.TopEnd.name -> Alignment.TopEnd + ContentAlignmentType.CenterStart.name -> Alignment.CenterStart + ContentAlignmentType.Center.name -> Alignment.Center + ContentAlignmentType.CenterEnd.name -> Alignment.CenterEnd + ContentAlignmentType.BottomStart.name -> Alignment.BottomStart + ContentAlignmentType.BottomCenter.name -> Alignment.BottomCenter + ContentAlignmentType.BottomEnd.name -> Alignment.BottomEnd + else -> Alignment.Center + } +} + +fun Modifier.customClickable( + onClick: () -> Unit, + interaction: UiTronInteraction? = null, + actions: List? = null +): Modifier = composed { + if (!actions.isNullOrEmpty()) { + clickable( + indication = getInteractionType(interaction = interaction), + interactionSource = remember { MutableInteractionSource() }) { + onClick() + } + } else return@composed this +} + +fun getContentScale(contentScale: String?): ContentScale { + return when (contentScale) { + ContentScaleType.Crop.name -> ContentScale.Crop + ContentScaleType.Fit.name -> ContentScale.Fit + ContentScaleType.FillBounds.name -> ContentScale.FillBounds + ContentScaleType.FillHeight.name -> ContentScale.FillHeight + ContentScaleType.Inside.name -> ContentScale.Inside + ContentScaleType.FillWidth.name -> ContentScale.FillWidth + ContentScaleType.None.name -> ContentScale.None + else -> ContentScale.Fit + } +} + +fun getTextDecoration(textDecoration: String?): TextDecoration { + return when (textDecoration) { + UiTronTextDecoration.Underline.name -> TextDecoration.Underline + UiTronTextDecoration.LineThrough.name -> TextDecoration.LineThrough + else -> TextDecoration.None + } +} + +@Composable +fun getInteractionType(interaction: UiTronInteraction?): Indication? { + return when (interaction?.interactionType) { + InteractionType.NONE -> null + InteractionType.RIPPLE -> rememberRipple( + color = interaction.color?.hexToComposeColor ?: Color.Unspecified + ) + else -> null + } +} + +fun getVisualTransformation(visualTransformation: OutlinedTextFieldVisualTransformation?): VisualTransformation { + visualTransformation?.type?.let { + return when (it) { + VisualTransformationType.DOB.name -> DateTransformation(visualTransformation.dateSeparator.orElse(DEFAULT_DATE_SEPARATOR), visualTransformation.dateSeparatorStyle) + VisualTransformationType.MONEY.name -> NumberCommaTransformation() + VisualTransformationType.PASSWORD.name -> PasswordVisualTransformation(visualTransformation.mask.orElse('\u2022')) + VisualTransformationType.ALL_CAPS.name -> AllCapsTransformation() + else -> VisualTransformation.None + } + } ?: return VisualTransformation.None +} + +fun getTransformedText( + valueTransformation: OutlinedTextFieldValueTransformation?, inputText: String +): String? { + valueTransformation?.type?.let { + return when (it) { + InputTransformationType.NUMBER_TO_WORDS.name -> UiTronSdk.getDependencyProvider().numberToWords(inputText) + InputTransformationType.DOB_TO_READABLE_STRING.name -> formatDateToReadableFormat( + inputText, + valueTransformation.formatPattern + ) + else -> null + } + } ?: return null +} + +fun Modifier.setBackground( + backgroundColor: String?, + uiTronShape: UiTronShape?, + brushData: BrushData? +): Modifier = composed { + this.then( + if (brushData != null) { + background( + brush = getBrush(brushData), + shape = ShapeUtil.getShape(shape = uiTronShape) + ) + } else if (backgroundColor.isNullOrEmpty().not()) { + background( + color = backgroundColor?.hexToComposeColor ?: Color.Transparent, + shape = ShapeUtil.getShape(shape = uiTronShape) + ) + } else { + this + } + ) +} + +fun getTileMode(tileMode: String?): TileMode { + return when (tileMode) { + TileMode.Mirror.toString() -> TileMode.Clamp + TileMode.Repeated.toString() -> TileMode.Clamp + TileMode.Decal.toString() -> TileMode.Clamp + else -> TileMode.Clamp + } +} + +fun getBrush(brushData: BrushData): Brush { + val brushArray: Array>? = brushData.colorStops?.map { + Pair(it.first, it.second.hexToComposeColor) + }?.toTypedArray() + + @Suppress("UNCHECKED_CAST") + val brushArrayOut = brushArray as Array> + return when (brushData.brushType) { + BrushType.HORIZONTAL.name -> Brush.horizontalGradient( + colorStops = brushArrayOut, + tileMode = getTileMode(brushData.tileMode) + ) + BrushType.LINEAR.name -> Brush.linearGradient( + colorStops = brushArrayOut, + tileMode = getTileMode(brushData.tileMode) + ) + + + BrushType.RADIAL.name -> Brush.radialGradient( + colorStops = brushArrayOut, + tileMode = getTileMode(brushData.tileMode) + ) + + + BrushType.SWEEP.name -> Brush.sweepGradient( + colorStops = brushArrayOut, + center = Offset( + brushData.offSetData?.x ?: Float.NaN, + brushData.offSetData?.y ?: Float.NaN + ) + ) + + else -> Brush.verticalGradient( + colorStops = brushArrayOut, + tileMode = getTileMode(brushData.tileMode) + ) + + } +} + +@Composable +fun setButtonElevation(property: ButtonProperty): ButtonElevation { + return if (property.elevation.isNull()) { + ButtonDefaults.elevation() + } else { + ButtonDefaults.elevation( + defaultElevation = property.elevation?.defaultElevation?.dp ?: 0.dp, + pressedElevation = property.elevation?.pressedElevation?.dp + ?: (property.elevation?.defaultElevation?.dp ?: 0.dp), + disabledElevation = property.elevation?.disabledElevation?.dp + ?: (property.elevation?.defaultElevation?.dp ?: 0.dp), + hoveredElevation = property.elevation?.hoveredElevation?.dp + ?: (property.elevation?.defaultElevation?.dp ?: 0.dp), + focusedElevation = property.elevation?.focusedElevation?.dp + ?: (property.elevation?.defaultElevation?.dp ?: 0.dp), + ) + } +} + +fun String.getText(textFormatter: TextData.TextFormatter?): String { + return if (textFormatter?.textFormat != null && textFormatter.placeholder != null) + textFormatter.textFormat.replace(textFormatter.placeholder, this) + else + this +} +fun getContentPaddingValues(contentPadding: ComposePadding?): PaddingValues { + return PaddingValues( + start = (contentPadding?.start ?: 0).dp, + top = (contentPadding?.top ?: 0).dp, + end = (contentPadding?.end ?: 0).dp, + bottom = (contentPadding?.bottom ?: 0).dp, + ) +} +fun Any?.isNull(): Boolean = this == null + +fun dpToPx(inpDp: Int): Float { + return (inpDp * Resources.getSystem().displayMetrics.density) +} + +fun Boolean?.orTrue() = this ?: true + +fun Boolean?.orFalse() = this ?: false + +fun Int?.orZero() = this ?: 0 +fun Long?.orZero() = this ?: 0L + +fun formatDuration(milliseconds: Long): List { + val days: Long = TimeUnit.MILLISECONDS.toDays(milliseconds) + val hours: Long = TimeUnit.MILLISECONDS.toHours(milliseconds) % 24 + val minutes: Long = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60 + val seconds: Long = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 + val milliSeconds: Long = TimeUnit.MILLISECONDS.toMillis(milliseconds) % 1000 + + return listOf( + addZeroPrefixForSingleDigit(days), + addZeroPrefixForSingleDigit(hours), + addZeroPrefixForSingleDigit(minutes), + addZeroPrefixForSingleDigit(seconds), + addZeroPrefixForSingleDigit(milliSeconds) + ) +} + +fun addZeroPrefixForSingleDigit(time: Long): String { + return if (time < 10) { + ZERO_STRING.plus(time.toString()) + } else { + time.toString() + } +} + +fun String?.isNotNullAndNotEmpty(): Boolean = !this.isNullOrEmpty() + +fun String?.orElse(value: String) = this ?: value + +fun Char?.orElse(value: Char) = this ?: value + +fun Int?.orVal(value: Int) = this ?: value + +fun Double?.orVal(value: Double) = this ?: value + +fun String?.parseColorSafe( + default: String = "#000000", +): Int = + try { + android.graphics.Color.parseColor( + when { + this == null -> default + this.startsWith("#") -> this + else -> "#$this" + } + ) + } catch (e: Exception) { + android.graphics.Color.parseColor(default) + } + +fun String?.toDoubleWithSafe(): Double { + return try { + this?.toDouble() ?: 0.0 + } catch (ex: Exception) { + 0.0 + } +} \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/utils/IconUtils.kt b/UiTron/src/main/java/com/navi/uitron/utils/IconUtils.kt new file mode 100644 index 0000000..fa94820 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/IconUtils.kt @@ -0,0 +1,28 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.utils + +import com.navi.uitron.R + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +object IconUtils { + private const val IC_CROSS = "IC_CROSS" + private const val IC_RED_TICK = "IC_RED_TICK" + private const val IC_BRIEFCASE = "IC_BRIEFCASE" + fun getImageFromLocal(iconCode: String?): Int? { + return when (iconCode) { +// IC_CROSS -> R.drawable.ic_cross +// IC_RED_TICK -> R.drawable.ic_red_tick_small +// IC_BRIEFCASE -> R.drawable.ic_briefcase + else -> null + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/KeyboardUtil.kt b/UiTron/src/main/java/com/navi/uitron/utils/KeyboardUtil.kt new file mode 100644 index 0000000..d768d3f --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/KeyboardUtil.kt @@ -0,0 +1,54 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ + +object KeyboardUtil { + fun getKeyboardCapitalization(capitalization: String?): KeyboardCapitalization { + return when (capitalization) { + "Characters" -> KeyboardCapitalization.Characters + "Words" -> KeyboardCapitalization.Words + "Sentences" -> KeyboardCapitalization.Sentences + else -> KeyboardCapitalization.None + } + } + + fun getKeyboardType(keyboardType: String?): KeyboardType { + return when (keyboardType) { + "Text" -> KeyboardType.Text + "Ascii" -> KeyboardType.Ascii + "Number" -> KeyboardType.Number + "Phone" -> KeyboardType.Phone + "Uri" -> KeyboardType.Uri + "Email" -> KeyboardType.Email + "Password" -> KeyboardType.Password + "NumberPassword" -> KeyboardType.NumberPassword + "Decimal" -> KeyboardType.Decimal + else -> KeyboardType.Text + } + } + + fun getImeAction(imeAction: String?): ImeAction { + return when (imeAction) { + "Default" -> ImeAction.Default + "Go" -> ImeAction.Go + "Search" -> ImeAction.Search + "Send" -> ImeAction.Send + "Previous" -> ImeAction.Previous + "Next" -> ImeAction.Next + "Done" -> ImeAction.Done + else -> ImeAction.None + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/RenderUtility.kt b/UiTron/src/main/java/com/navi/uitron/utils/RenderUtility.kt new file mode 100644 index 0000000..1940f14 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/RenderUtility.kt @@ -0,0 +1,73 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstrainedLayoutReference +import androidx.constraintlayout.compose.ConstraintSet +import androidx.constraintlayout.compose.Dimension +import com.navi.uitron.model.ui.ComposeSize +import com.navi.uitron.model.ui.UiTronView + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ + +object RenderUtility { + fun decoupledConstraints(composeViewList: List): ConstraintSet { + return ConstraintSet { + val referencesMap = hashMapOf() + composeViewList.forEach { + referencesMap[it.property?.layoutId.orEmpty()] = + createRefFor(it.property?.layoutId.orEmpty()) + } + composeViewList.forEach { + val constraintLink = it.property?.constraintLinks + val constraintId: String = it.property?.layoutId.orEmpty() + constrain(referencesMap[constraintId] ?: createRefFor(constraintId)) { + referencesMap["parent"] = parent + constraintLink?.start?.let { link -> + referencesMap[link.viewId]?.let { clRef -> + start.linkTo( + clRef.getHorizontalReference(link.constraint.orEmpty()), + link.margin?.dp ?: 0.dp + ) + } + } + constraintLink?.end?.let { link -> + referencesMap[link.viewId]?.let { clRef -> + end.linkTo( + clRef.getHorizontalReference(link.constraint.orEmpty()), + link.margin?.dp ?: 0.dp + ) + } + } + constraintLink?.top?.let { link -> + referencesMap[link.viewId]?.let { clRef -> + top.linkTo( + clRef.getVerticalReference(link.constraint.orEmpty()), + link.margin?.dp ?: 0.dp + ) + } + } + constraintLink?.bottom?.let { link -> + referencesMap[link.viewId]?.let { clRef -> + bottom.linkTo( + clRef.getVerticalReference(link.constraint.orEmpty()), + link.margin?.dp ?: 0.dp + ) + } + } + if (it.property?.width == ComposeSize.FILL_TO_CONSTRAINTS.name) width = + Dimension.fillToConstraints + if (it.property?.height == ComposeSize.FILL_TO_CONSTRAINTS.name) height = + Dimension.fillToConstraints + } + } + } + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/ShapeUtil.kt b/UiTron/src/main/java/com/navi/uitron/utils/ShapeUtil.kt new file mode 100644 index 0000000..cdcc018 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/ShapeUtil.kt @@ -0,0 +1,50 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.utils + +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp +import com.navi.uitron.model.ui.UiTronShape + +/** + * Copyright © 2021 by Navi Technologies Private Limited + * All rights reserved. Strictly confidential + */ +object ShapeUtil { + @Composable + fun getShape(shape: UiTronShape?): Shape { + return when (shape?.shapeType) { + ShapeType.RoundedCornerShape.name -> { + RoundedCornerShape(shape.size?.dp ?: 0.dp) + } + ShapeType.TopRoundedCornerShape.name -> { + val radius = (shape.size ?: 0).dp + RoundedCornerShape(radius, radius) + } + ShapeType.BottomRoundedCornerShape.name -> { + val radius = (shape.size ?: 0).dp + RoundedCornerShape(0.dp, 0.dp, radius, radius) + } + ShapeType.CircleShape.name -> { + CircleShape + } + else -> RectangleShape + } + } +} + +enum class ShapeType { + RoundedCornerShape, + TopRoundedCornerShape, + BottomRoundedCornerShape, + CircleShape +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/transformations/AllCapsTransformation.kt b/UiTron/src/main/java/com/navi/uitron/utils/transformations/AllCapsTransformation.kt new file mode 100644 index 0000000..2d6e9c6 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/transformations/AllCapsTransformation.kt @@ -0,0 +1,22 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.utils.transformations + +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.toUpperCase + +class AllCapsTransformation : VisualTransformation { + override fun filter(text: AnnotatedString): TransformedText { + return TransformedText( + text = text.toUpperCase(), offsetMapping = OffsetMapping.Identity + ) + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/transformations/DateToReadableStringTransfomation.kt b/UiTron/src/main/java/com/navi/uitron/utils/transformations/DateToReadableStringTransfomation.kt new file mode 100644 index 0000000..6caf484 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/transformations/DateToReadableStringTransfomation.kt @@ -0,0 +1,111 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.utils.transformations + +import com.navi.uitron.utils.EMPTY +import orVal +import java.util.Calendar + +fun formatDateToReadableFormat( + inputText: String, + formatPattern: String? = "ddMMyyyy" +): String? { + val inputLength = inputText.length + val calendar = Calendar.getInstance() + calendar.isLenient = false + + val day = try { + inputText.slice(getDateValueIndices(formatPattern, "dd", inputLength)).toInt() + } catch (e: Exception) { + return null + } + if (day == 0) { + return null + } + calendar.set(Calendar.DATE, day) + + val month = try { + inputText.slice(getDateValueIndices(formatPattern, "MM", inputLength)).toInt() + } catch (e: Exception) { + return null + } + if (month == 0) { + return null + } + calendar.set(Calendar.MONTH, month - 1) + + val daySuffix = getDaySuffix(day) + val formattedMonth = getFormattedMonth(month) + + val year = try { + inputText.slice(getDateValueIndices(formatPattern, "yyyy", inputLength)).toInt() + } catch (e: Exception) { + return if (isValidDayAndMonth(day, month)) "${day}${daySuffix} $formattedMonth" + else null + } + calendar.set(Calendar.YEAR, year) + + return if (isValidDayAndMonth(day, month, year)) "${day}${daySuffix} $formattedMonth $year" + else null +} + +private fun isValidDayAndMonth(day: Int, month: Int, year: Int? = null): Boolean { + if (day > 31 || day < 1 || month > 12 || month < 1) return false + if (day == 31 && month in intArrayOf(2, 4, 6, 9, 11)) return false + if (day == 30 && month == 2) return false + if (day == 29 && month == 2 && !isLeapYear(year.orVal(-1))) return false + return true +} + +private fun isLeapYear(year: Int): Boolean { + return (year > 0) && (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0)) +} + +private fun getDaySuffix(dayOfMonth: Int): String { + if (dayOfMonth in 11..13) return "th" + return when (dayOfMonth % 10) { + 1 -> "st" + 2 -> "nd" + 3 -> "rd" + else -> "th" + } +} + +private fun getFormattedMonth(monthOfYear: Int): String { + return when (monthOfYear) { + 1 -> "Jan" + 2 -> "Feb" + 3 -> "Mar" + 4 -> "Apr" + 5 -> "May" + 6 -> "Jun" + 7 -> "Jul" + 8 -> "Aug" + 9 -> "Sep" + 10 -> "Oct" + 11 -> "Nov" + 12 -> "Dec" + else -> EMPTY + } +} + +private fun getDateValueIndices( + formatPattern: String?, + dateType: String, + inputLength: Int +): IntRange { + return when (formatPattern) { + "ddMMyyyy" -> when (dateType) { + "dd" -> IntRange(0, 1) + "MM" -> IntRange(2, 3) + "yyyy" -> IntRange(4, inputLength - 1) + else -> IntRange(-1, -1) + } + else -> IntRange(0, 0) + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/transformations/DateTransformation.kt b/UiTron/src/main/java/com/navi/uitron/utils/transformations/DateTransformation.kt new file mode 100644 index 0000000..d64f9af --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/transformations/DateTransformation.kt @@ -0,0 +1,85 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.utils.transformations + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.sp +import com.navi.uitron.UiTronSdk +import com.navi.uitron.model.ui.TextProperty +import getTextDecoration +import parseColorSafe + +class DateTransformation( + private val dateSeparator: String = DEFAULT_DATE_SEPARATOR, + private val dateSeparatorStyle: TextProperty? +) : VisualTransformation { + companion object { + const val DEFAULT_DATE_SEPARATOR = " - " + } + override fun filter(text: AnnotatedString): TransformedText { + return dateFilter( + string = text, + dateSeparator = dateSeparator, + dateSeparatorStyle = dateSeparatorStyle + ) + } +} + +private fun dateFilter( + string: AnnotatedString, + dateSeparator: String, + dateSeparatorStyle: TextProperty? +): TransformedText { + + val trimmed = if (string.text.length >= 8) string.text.substring(0..7) else string.text + val builder = AnnotatedString.Builder() + for (i in trimmed.indices) { + builder.append(trimmed[i]) + if (i % 2 == 1 && i < 4) { + dateSeparatorStyle?.let { + builder.withStyle( + style = SpanStyle( + color = Color(it.textColor.parseColorSafe()), + fontFamily = UiTronSdk.getDependencyProvider().getFontFamily(it.fontFamily), + fontWeight = UiTronSdk.getDependencyProvider().getFontWeight(it.fontWeight), + fontSize = it.fontSize?.sp ?: 14.sp, + textDecoration = getTextDecoration(it.textDecoration) + ) + ) { + append(dateSeparator) + } + } ?: run { + builder.append(dateSeparator) + } + } + } + + val numberOffsetTranslator = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if (offset <= 1) return offset + if (offset <= 3) return offset + dateSeparator.length + if (offset <= 8) return offset + 2 * dateSeparator.length + return 8 + 2 * dateSeparator.length + } + + override fun transformedToOriginal(offset: Int): Int { + if (offset <= 2) return offset + if (offset <= 4 + dateSeparator.length) return offset - dateSeparator.length + if (offset <= 8 + 2 * dateSeparator.length) return offset - 2 * dateSeparator.length + return 8 + } + } + + return TransformedText(builder.toAnnotatedString(), numberOffsetTranslator) +} diff --git a/UiTron/src/main/java/com/navi/uitron/utils/transformations/NumberCommaTransformation.kt b/UiTron/src/main/java/com/navi/uitron/utils/transformations/NumberCommaTransformation.kt new file mode 100644 index 0000000..9c56b40 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/utils/transformations/NumberCommaTransformation.kt @@ -0,0 +1,47 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.utils.transformations + +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import toDoubleWithSafe + +class NumberCommaTransformation : VisualTransformation { + override fun filter(text: AnnotatedString): TransformedText { + return TransformedText(text = AnnotatedString(moneyFormat(text.text)), + offsetMapping = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + return moneyFormat(text.text).length + } + + override fun transformedToOriginal(offset: Int): Int { + return text.length + } + }) + } +} + +private fun moneyFormat(value: String): String { + if (value.isEmpty()) return value + var tempValue = value.toDoubleWithSafe().toLong().toString() + var result = "" + tempValue = tempValue.replace(",", "") + val lastDigit = tempValue[tempValue.length - 1] + val len = tempValue.length - 1 + var nDigits = 0 + for (i in len - 1 downTo 0) { + result = tempValue[i].toString() + result + nDigits++ + if (nDigits % 2 == 0 && i > 0) { + result = ",$result" + } + } + return result + lastDigit +} diff --git a/UiTron/src/main/java/com/navi/uitron/validation/DateFormatValidation.kt b/UiTron/src/main/java/com/navi/uitron/validation/DateFormatValidation.kt new file mode 100644 index 0000000..f22a217 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/validation/DateFormatValidation.kt @@ -0,0 +1,32 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.validation + +import java.text.SimpleDateFormat +import java.util.* + + +class DateFormatValidation(private val formatPattern: String? = null) : UiTronBaseValidation() { + override fun performValidation(inputText: String?): Boolean { + if (inputText.isNullOrEmpty()) return false + + return try { + val formatter = SimpleDateFormat(formatPattern ?: FORMAT_PATTERN, Locale.ENGLISH) + formatter.isLenient = false + formatter.parse(inputText) + true + } catch (ex: Exception) { + false + } + } + + companion object { + const val VALIDATION_NAME = "DATE_FORMAT" + const val FORMAT_PATTERN = "ddMMyyyy" + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/validation/DateRangeValidation.kt b/UiTron/src/main/java/com/navi/uitron/validation/DateRangeValidation.kt new file mode 100644 index 0000000..8091038 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/validation/DateRangeValidation.kt @@ -0,0 +1,58 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.validation + +import java.text.SimpleDateFormat +import java.util.* + + +class DateRangeValidation( + private val formatPattern: String? = null, + private val minDate: String? = null, + private val maxDate: String? = null +) : UiTronBaseValidation() { + override fun performValidation(inputText: String?): Boolean { + if (inputText.isNullOrEmpty()) return false + + return try { + val formatter = SimpleDateFormat(formatPattern ?: FORMAT_PATTERN, Locale.getDefault()) + formatter.isLenient = false + formatter.parse(inputText) + val minimumDate = minDate ?: return false + val maximumDate = maxDate ?: return false + + val minDateLong = formatter.parse(minimumDate) + val maxDateLong = formatter.parse(maximumDate) + val currentDate = formatter.parse(inputText) + + if (minDateLong != null && maxDateLong != null && currentDate != null) { + val minCalendar = Calendar.getInstance() + minCalendar.time = minDateLong + + val maxCalendar = Calendar.getInstance() + maxCalendar.time = maxDateLong + + val calendar = Calendar.getInstance() + calendar.time = currentDate + + calendar.after(minCalendar) && calendar.before(maxCalendar) + || calendar.equals(minCalendar) + || calendar.equals(maxCalendar) + } else { + false + } + } catch (ex: Exception) { + false + } + } + + companion object { + const val VALIDATION_NAME = "DATE_RANGE" + const val FORMAT_PATTERN = "ddMMyyyy" + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/validation/LengthRangeValidation.kt b/UiTron/src/main/java/com/navi/uitron/validation/LengthRangeValidation.kt new file mode 100644 index 0000000..81b7fe5 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/validation/LengthRangeValidation.kt @@ -0,0 +1,25 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.validation + +import orVal +import orZero + +class LengthRangeValidation( + private val minLength: Int? = null, + private val maxLength: Int? = null +) : UiTronBaseValidation() { + override fun performValidation(inputText: String?): Boolean { + return inputText?.length.orZero() >= minLength.orZero() && + inputText?.length.orZero() <= maxLength.orVal(Int.MAX_VALUE) + } + + companion object { + const val VALIDATION_NAME = "LENGTH_RANGE" + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/validation/NotBlankValidation.kt b/UiTron/src/main/java/com/navi/uitron/validation/NotBlankValidation.kt new file mode 100644 index 0000000..27fa7bf --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/validation/NotBlankValidation.kt @@ -0,0 +1,18 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.validation + +class NotBlankValidation : UiTronBaseValidation() { + override fun performValidation(inputText: String?): Boolean { + return inputText.isNullOrBlank().not() + } + + companion object { + const val VALIDATION_NAME = "NOT_BLANK" + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/validation/RegexValidation.kt b/UiTron/src/main/java/com/navi/uitron/validation/RegexValidation.kt new file mode 100644 index 0000000..30d0ad6 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/validation/RegexValidation.kt @@ -0,0 +1,25 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.validation + +import orFalse + +class RegexValidation(private val regex: String? = null) : UiTronBaseValidation() { + override fun performValidation(inputText: String?): Boolean { + regex?.let { + if (!inputText?.matches(regex = Regex(it)).orFalse()) { + return false + } + } + return true + } + + companion object { + const val VALIDATION_NAME = "REGEX" + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/validation/UiTronBaseValidation.kt b/UiTron/src/main/java/com/navi/uitron/validation/UiTronBaseValidation.kt new file mode 100644 index 0000000..51619c3 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/validation/UiTronBaseValidation.kt @@ -0,0 +1,14 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.validation + +abstract class UiTronBaseValidation { + val validationType: String? = null + val errorMessage: String? = null + abstract fun performValidation(inputText: String?): Boolean +} diff --git a/UiTron/src/main/java/com/navi/uitron/validation/ValueRangeValidation.kt b/UiTron/src/main/java/com/navi/uitron/validation/ValueRangeValidation.kt new file mode 100644 index 0000000..0b0a824 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/validation/ValueRangeValidation.kt @@ -0,0 +1,31 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.validation + +import orVal +import java.lang.Double.MAX_VALUE +import java.lang.Double.MIN_VALUE + +class ValueRangeValidation( + private val minValue: Double? = null, + private val maxValue: Double? = null +) : UiTronBaseValidation() { + override fun performValidation(inputText: String?): Boolean { + if (inputText.isNullOrEmpty()) return false + return try { + val inputData = inputText.toDouble() + inputData >= minValue.orVal(MIN_VALUE) && inputData <= maxValue.orVal(MAX_VALUE) + } catch (ex: Exception) { + false + } + } + + companion object { + const val VALIDATION_NAME = "VALUE_RANGE" + } +} diff --git a/UiTron/src/main/java/com/navi/uitron/viewmodel/ComposeStateHolder.kt b/UiTron/src/main/java/com/navi/uitron/viewmodel/ComposeStateHolder.kt new file mode 100644 index 0000000..a513986 --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/viewmodel/ComposeStateHolder.kt @@ -0,0 +1,44 @@ +package com.navi.uitron.viewmodel + +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.runtime.Composable +import com.google.accompanist.pager.ExperimentalPagerApi +import com.google.accompanist.pager.PagerState +import com.google.accompanist.pager.rememberPagerState +import javax.inject.Inject + +@OptIn(ExperimentalPagerApi::class) +class ComposeStateHolder +@Inject +constructor() { + + private val pagerStateMap: MutableMap by lazy { + mutableMapOf() + } + private val gridStateMap: MutableMap by lazy { + mutableMapOf() + } + + @Composable + fun getOrUpdatePagerState(key: String?): PagerState? { + return getOrUpdateState(key, pagerStateMap, rememberPagerState()) + } + + @Composable + fun getOrUpdateGridState(key: String?): LazyGridState? { + return getOrUpdateState(key, gridStateMap, rememberLazyGridState()) + } + + private fun getOrUpdateState(key: String?, map: MutableMap, defaultValue: T): T? { + return if (key != null) { + if (map[key] == null) { + map[key] = defaultValue + } + map[key] + } else { + defaultValue + } + } + +} \ No newline at end of file diff --git a/UiTron/src/main/java/com/navi/uitron/viewmodel/UiTronViewModel.kt b/UiTron/src/main/java/com/navi/uitron/viewmodel/UiTronViewModel.kt new file mode 100644 index 0000000..a6fcc9a --- /dev/null +++ b/UiTron/src/main/java/com/navi/uitron/viewmodel/UiTronViewModel.kt @@ -0,0 +1,116 @@ +/* + * + * * Copyright © 2023 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.uitron.viewmodel + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.navi.uitron.model.data.UiTronActionData +import com.navi.uitron.model.data.TextFieldInputWrapper +import com.navi.uitron.model.data.UiTronAction +import com.navi.uitron.utils.ActionHandler +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +@HiltViewModel +open class UiTronViewModel constructor( + val handle: SavedStateHandle = SavedStateHandle(), + val stateHolder: ComposeStateHolder = ComposeStateHolder() +) : ViewModel() { + + private val actionHandler = ActionHandler(handle) + private val layoutIdSet: MutableSet = mutableSetOf() + private val actionCallbackFlow: MutableSharedFlow = MutableSharedFlow() + private val _sliderFinishedFlow: MutableSharedFlow> = + MutableSharedFlow() + val sliderFinishedFlow = _sliderFinishedFlow.asSharedFlow() + private val _sliderProgressFlow: MutableSharedFlow> = + MutableSharedFlow() + val sliderProgressFlow = _sliderProgressFlow.asSharedFlow() + + fun onTextFieldValueChanged(layoutId: String, inputText: String) { + handle[layoutId] = handle.getStateFlow(layoutId, TextFieldInputWrapper()).value.copy( + inputText = inputText, + hasError = false + ) + } + + fun addKeyToSavedStateHandle(layoutId: String) { + layoutIdSet.add(layoutId) + } + + fun clearAllStates() { + layoutIdSet.forEach { + handle.remove(it) + } + layoutIdSet.clear() + } + + fun handleActions(actionData: UiTronActionData?) { + viewModelScope.launch { + actionHandler.handleActions(actionData, actionCallbackFlow) + } + } + fun getActionCallback() = actionCallbackFlow.asSharedFlow() + + @Deprecated("Was Done for a POC. Should be done using Actions") + fun onSliderValueProgressChanged( + layoutId: String, + sliderRawValue: String?, + sliderFormattedValue: String?, + uiTronFormattedValueIds: List, + uiTronRawValueIds: List + ) { + viewModelScope.launch { + sliderFormattedValue?.let { formatValue -> + uiTronFormattedValueIds.forEach { + handle[it] = handle.getStateFlow(it, TextFieldInputWrapper()).value.copy( + inputText = formatValue, + hasError = false + ) + } + } + sliderRawValue?.let { rawValue -> + uiTronRawValueIds.forEach { + handle[it] = handle.getStateFlow(it, TextFieldInputWrapper()).value.copy( + inputText = rawValue, + hasError = false + ) + } + } + sliderRawValue?.let { + _sliderProgressFlow.emit(Triple(layoutId, sliderRawValue, sliderFormattedValue)) + } + } + } + + @Deprecated("Was Done for a POC. Should be done using Actions") + fun onSliderValueChangeFinished( + layoutId: String, + sliderRawValue: String?, + sliderFormattedValue: String?, + uiTronIds: List + ) { + viewModelScope.launch { + uiTronIds.forEach { + sliderFormattedValue?.let { + handle[it] = handle.getStateFlow(it, TextFieldInputWrapper()).value.copy( + inputText = sliderFormattedValue, + hasError = false + ) + } + } + sliderRawValue?.let { + _sliderFinishedFlow.emit(Triple(layoutId, sliderRawValue, sliderFormattedValue)) + } + } + } + +} diff --git a/UiTron/src/main/res/font/tt_bold.otf b/UiTron/src/main/res/font/tt_bold.otf new file mode 100644 index 0000000..07f4105 Binary files /dev/null and b/UiTron/src/main/res/font/tt_bold.otf differ diff --git a/UiTron/src/main/res/font/tt_medium.ttf b/UiTron/src/main/res/font/tt_medium.ttf new file mode 100644 index 0000000..77eb3e2 Binary files /dev/null and b/UiTron/src/main/res/font/tt_medium.ttf differ diff --git a/UiTron/src/main/res/font/tt_regular.ttf b/UiTron/src/main/res/font/tt_regular.ttf new file mode 100644 index 0000000..09333ca Binary files /dev/null and b/UiTron/src/main/res/font/tt_regular.ttf differ diff --git a/UiTron/src/main/res/font/tt_semi_bold.otf b/UiTron/src/main/res/font/tt_semi_bold.otf new file mode 100644 index 0000000..609715d Binary files /dev/null and b/UiTron/src/main/res/font/tt_semi_bold.otf differ diff --git a/UiTron/src/test/java/com/navi/uitron/ExampleUnitTest.kt b/UiTron/src/test/java/com/navi/uitron/ExampleUnitTest.kt new file mode 100644 index 0000000..b839ee1 --- /dev/null +++ b/UiTron/src/test/java/com/navi/uitron/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.navi.uitron + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..3038242 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'com.navi.uitron' + compileSdk 32 + + defaultConfig { + applicationId "com.navi.uitron" + minSdk 21 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.2.0-beta03' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } +} + +dependencies { + + implementation project(":UiTron") + + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'androidx.core:core-ktx:1.8.0' + implementation 'com.google.android.material:material:1.8.0' + implementation 'androidx.activity:activity-compose:1.5.0' + implementation "androidx.compose.material:material:1.2.0-beta03" + implementation 'androidx.compose.ui:ui:1.2.0-beta03' + implementation 'androidx.compose.ui:ui-graphics:1.2.0-beta03' + implementation 'androidx.compose.ui:ui-tooling-preview:1.2.0-beta03' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' + implementation 'com.google.code.gson:gson:2.8.9' + + testImplementation "junit:junit:4.13.2" + androidTestImplementation "androidx.test.ext:junit:1.1.4" + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/navi/uitron/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/navi/uitron/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..9dacc69 --- /dev/null +++ b/app/src/androidTest/java/com/navi/uitron/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.navi.uitron + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.navi.uitron", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..50d0434 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/navi/uitron/MainActivity.kt b/app/src/main/java/com/navi/uitron/MainActivity.kt new file mode 100644 index 0000000..729d0f3 --- /dev/null +++ b/app/src/main/java/com/navi/uitron/MainActivity.kt @@ -0,0 +1,26 @@ +package com.navi.uitron + +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.navi.uitron.model.UiTronResponse +import com.navi.uitron.render.UiTronRenderer +import com.navi.uitron.viewmodel.UiTronViewModel + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val type = object : TypeToken() {}.type + val response = mockApiResponse(this, type, "mock") + Log.d("MainActivity", "Response : ${Gson().toJson(response)}") + setContent { + UiTronRenderer( + response.data, + UiTronViewModel() + ).Render(composeViews = response.parentComposeView!!) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/navi/uitron/Utils.kt b/app/src/main/java/com/navi/uitron/Utils.kt new file mode 100644 index 0000000..24e820a --- /dev/null +++ b/app/src/main/java/com/navi/uitron/Utils.kt @@ -0,0 +1,30 @@ +package com.navi.uitron + +import android.content.Context +import com.google.gson.GsonBuilder +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.navi.uitron.deserializer.ComposePropertyDeserializer +import com.navi.uitron.deserializer.UiTronApiDeserializer +import com.navi.uitron.deserializer.UiTronDataDeserializer +import com.navi.uitron.deserializer.UiTronValidationDeserializer +import com.navi.uitron.model.action.MakeApiAction +import com.navi.uitron.model.data.UiTronData +import com.navi.uitron.model.ui.BaseProperty +import com.navi.uitron.validation.UiTronBaseValidation +import java.lang.reflect.Type +import java.nio.charset.StandardCharsets + +fun mockApiResponse(context: Context, type: Type, jsonKey: String): T { + val inputStream = context.resources.openRawResource(com.navi.uitron.R.raw.mock) + val dataString = String(inputStream.readBytes(), StandardCharsets.UTF_8) + val jsonElement = (JsonParser.parseString(dataString) as? JsonObject)?.get(jsonKey) + val customGson = + GsonBuilder() + .registerTypeAdapter(UiTronData::class.java, UiTronDataDeserializer()) + .registerTypeAdapter(MakeApiAction::class.java, UiTronApiDeserializer()) + .registerTypeAdapter(UiTronBaseValidation::class.java, UiTronValidationDeserializer()) + .registerTypeAdapter(BaseProperty::class.java, ComposePropertyDeserializer()) + .create() + return customGson.fromJson(jsonElement, type) +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/raw/mock.json b/app/src/main/res/raw/mock.json new file mode 100644 index 0000000..d91d0be --- /dev/null +++ b/app/src/main/res/raw/mock.json @@ -0,0 +1,38 @@ +{ + "mock": { + "parentComposeView": [ + { + "property": { + "viewType": "Row", + "layoutId": "row" + }, + "childrenViews": [ + { + "property": { + "viewType": "Spacer", + "layoutId": "item_1", + "backgroundColor": "#ABABAB", + "height": "200", + "width": "200" + } + }, + { + "property": { + "viewType": "Spacer", + "layoutId": "item_2", + "backgroundColor": "#010101", + "height": "200", + "width": "200" + } + } + ] + } + ], + "data": { + "title": { + "viewType": "Text", + "text": "Testing deserializer's" + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..38f8d62 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..661af1f --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + UiTron + MainActivity + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..e10b896 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/navi/uitron/ExampleUnitTest.kt b/app/src/test/java/com/navi/uitron/ExampleUnitTest.kt new file mode 100644 index 0000000..b839ee1 --- /dev/null +++ b/app/src/test/java/com/navi/uitron/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.navi.uitron + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..f59cea7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,7 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +plugins { + id 'com.android.application' version '8.0.0-rc01' apply false + id 'com.android.library' version '8.0.0-rc01' apply false + id 'org.jetbrains.kotlin.android' version '1.6.21' apply false +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..3c5031e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8d2d17c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Apr 06 03:27:16 IST 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..4038fc5 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + maven { url 'https://jitpack.io' } + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} +rootProject.name = "UiTron" +include ':app' +include ':UiTron'