diff --git a/app/build.gradle b/app/build.gradle index cc82eda..91c113a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,8 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' + id 'com.google.dagger.hilt.android' } android { @@ -42,11 +44,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' } buildFeatures { compose true @@ -62,7 +64,7 @@ android { } dependencies { - implementation platform("androidx.compose:compose-bom:2023.01.00") + implementation platform("androidx.compose:compose-bom:2023.06.01") implementation project(':navi-uitron') implementation 'androidx.appcompat:appcompat:1.6.1' @@ -82,4 +84,29 @@ dependencies { implementation "androidx.navigation:navigation-compose:$nav_version" implementation 'androidx.profileinstaller:profileinstaller:1.3.1' + // Retrofit + api "com.squareup.retrofit2:retrofit:2.9.0" + api 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:3.6.0' + implementation 'com.squareup.okhttp3:logging-interceptor:4.4.1' + + // compose with lifecycle + api "androidx.lifecycle:lifecycle-runtime-compose:2.6.1" + + // Pagination + implementation "androidx.paging:paging-runtime-ktx:3.1.1" + implementation "androidx.paging:paging-compose:1.0.0-alpha17" + + //Room + def room_version = "2.5.2" + implementation "androidx.room:room-runtime:$room_version" + kapt "androidx.room:room-compiler:$room_version" + implementation "androidx.room:room-ktx:$room_version" + implementation "androidx.room:room-paging:$room_version" + + // Hilt + implementation 'com.google.dagger:hilt-android:2.44' + kapt 'androidx.hilt:hilt-compiler:1.0.0' + kapt 'com.google.dagger:hilt-android-compiler:2.44' + implementation "androidx.hilt:hilt-navigation-compose:1.0.0" } \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/MainActivity.kt b/app/src/main/java/com/uitron/demo/MainActivity.kt index 6bb0541..1bf1df1 100644 --- a/app/src/main/java/com/uitron/demo/MainActivity.kt +++ b/app/src/main/java/com/uitron/demo/MainActivity.kt @@ -9,7 +9,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.compose.rememberNavController import com.uitron.demo.home.HomeScreen import com.uitron.demo.theme.UiTronTheme +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/uitron/demo/MainApplication.kt b/app/src/main/java/com/uitron/demo/MainApplication.kt index dd4f7d9..bb48631 100644 --- a/app/src/main/java/com/uitron/demo/MainApplication.kt +++ b/app/src/main/java/com/uitron/demo/MainApplication.kt @@ -1,14 +1,17 @@ package com.uitron.demo import android.app.Application -import android.content.Context import com.navi.uitron.UiTronSdkManager +import com.uitron.demo.dazzledesignsystem.db.SharedPreferences +import dagger.hilt.android.HiltAndroidApp +@HiltAndroidApp class MainApplication : Application() { override fun onCreate() { super.onCreate() UiTronSdkManager.init(UiTronDependencyProvider()) + SharedPreferences.init(this) instance = this } diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/CommonUtils.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/CommonUtils.kt new file mode 100644 index 0000000..1530e3d --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/CommonUtils.kt @@ -0,0 +1,5 @@ +package com.uitron.demo.dazzledesignsystem + +import isNull + +fun Any?.isNotNull(): Boolean = !this.isNull() diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/Constants.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/Constants.kt new file mode 100644 index 0000000..035b340 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/Constants.kt @@ -0,0 +1,4 @@ +package com.uitron.demo.dazzledesignsystem + +const val DB_LAST_REFRESHED_TIMESTAMP = "widgetListLastRefreshedTimestamp" +const val DB_REFRESH_TIMEOUT = 3600000L // 1 hour diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/DazzleDatabase.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/DazzleDatabase.kt new file mode 100644 index 0000000..6c734ca --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/DazzleDatabase.kt @@ -0,0 +1,22 @@ +package com.uitron.demo.dazzledesignsystem.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.uitron.demo.dazzledesignsystem.db.converters.ListConverter +import com.uitron.demo.dazzledesignsystem.db.converters.WidgetModelDefinitionConverter +import com.uitron.demo.dazzledesignsystem.db.dao.WidgetTemplatesDao +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate + +@Database( + entities = [WidgetTemplate::class], + version = 1, + exportSchema = false +) +@TypeConverters( + WidgetModelDefinitionConverter::class, + ListConverter::class +) +abstract class DazzleDatabase: RoomDatabase() { + abstract fun getWidgetTemplatesDao(): WidgetTemplatesDao +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/SharedPreferences.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/SharedPreferences.kt new file mode 100644 index 0000000..9b58329 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/SharedPreferences.kt @@ -0,0 +1,26 @@ +package com.uitron.demo.dazzledesignsystem.db + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +object SharedPreferences { + private const val PREFS_FILENAME = "dazzle.preferences" + private lateinit var sharedPreferences: SharedPreferences + fun init(application: Application) { + sharedPreferences = application.getSharedPreferences(PREFS_FILENAME, Context.MODE_PRIVATE) + } + + suspend fun setLongValue(key: String, value: Long) = withContext(Dispatchers.IO) { + val editor = sharedPreferences.edit() + editor.putLong(key, value) + editor.apply() + } + + suspend fun getLongValue(key: String, defValue: Long = -1L) = withContext(Dispatchers.IO) { + sharedPreferences.getLong(key, defValue) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/converters/ListConverter.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/converters/ListConverter.kt new file mode 100644 index 0000000..ccc21c5 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/converters/ListConverter.kt @@ -0,0 +1,40 @@ +package com.uitron.demo.dazzledesignsystem.db.converters + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.uitron.demo.dazzledesignsystem.db.entity.TemplateVariable + + +class ListConverter { + @TypeConverter + fun fromStringList(value: List): String { + return Gson().toJson(value) + } + + @TypeConverter + fun toStringList(value: String): List { + return try { + Gson().fromJson(value) + } catch (e: Exception) { + listOf() + } + } + + @TypeConverter + fun fromVariablesList(value: List): String { + return Gson().toJson(value) + } + + @TypeConverter + fun toVariablesList(value: String): List { + return try { + Gson().fromJson(value) + } catch (e: Exception) { + listOf() + } + } + + private inline fun Gson.fromJson(json: String): T = + fromJson(json, object : TypeToken() {}.type) +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/converters/WidgetModelDefinitionConverter.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/converters/WidgetModelDefinitionConverter.kt new file mode 100644 index 0000000..00fc81c --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/converters/WidgetModelDefinitionConverter.kt @@ -0,0 +1,18 @@ +package com.uitron.demo.dazzledesignsystem.db.converters + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetModelDefinition + +class WidgetModelDefinitionConverter { + + @TypeConverter + fun fromWidgetModelDefinition(widgetModelDefinition: WidgetModelDefinition): String { + return Gson().toJson(widgetModelDefinition) + } + + @TypeConverter + fun toWidgetModelDefinition(widgetModelDefinition: String): WidgetModelDefinition { + return Gson().fromJson(widgetModelDefinition, WidgetModelDefinition::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/dao/WidgetTemplatesDao.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/dao/WidgetTemplatesDao.kt new file mode 100644 index 0000000..5a509f5 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/dao/WidgetTemplatesDao.kt @@ -0,0 +1,22 @@ +package com.uitron.demo.dazzledesignsystem.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate + +@Dao +interface WidgetTemplatesDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(widgetTemplates: List) + + @Query("select * from widget_templates") + fun getWidgetTemplates(): List + + @Query("select * from widget_templates where name like :searchQuery") + fun searchWidgetTemplates(searchQuery: String): List + + @Query("delete from widget_templates") + suspend fun clearAllWidgetTemplates() +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/entity/WidgetTemplate.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/entity/WidgetTemplate.kt new file mode 100644 index 0000000..94bc791 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/db/entity/WidgetTemplate.kt @@ -0,0 +1,70 @@ +package com.uitron.demo.dazzledesignsystem.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import com.uitron.demo.dazzledesignsystem.db.converters.ListConverter +import com.uitron.demo.dazzledesignsystem.db.converters.WidgetModelDefinitionConverter + +@Entity(tableName = "widget_templates") +data class WidgetTemplate( + @PrimaryKey(autoGenerate = false) + val id: String, + @ColumnInfo(name = "name") + val name: String? = null, + @ColumnInfo(name = "version") + val version: Int? = null, + @ColumnInfo(name = "type") + val type: String? = null, + @ColumnInfo(name = "minAppVersion") + val minAppVersion: String? = null, + @ColumnInfo(name = "maxAppVersion") + val maxAppVersion: String? = null, + @ColumnInfo(name = "cta") + val cta: String? = null, + @ColumnInfo(name = "imageUrl") + val imageUrl: String? = null, + @ColumnInfo(name = "osType") + val osType: String? = null, + @ColumnInfo(name = "tags") @TypeConverters(ListConverter::class) + val tags: List? = null, + @ColumnInfo(name = "config") @TypeConverters(WidgetModelDefinitionConverter::class) + val config: WidgetModelDefinition? = null, + @ColumnInfo(name = "vars") @TypeConverters(ListConverter::class) + val vars: List? = null, + @ColumnInfo(name = "parentTemplateName") + val parentTemplateName: String? = null, + @ColumnInfo(name = "parentTemplateVersion") + val parentTemplateVersion: Int? = null, + @ColumnInfo(name = "hash") + val hash: String? = null +) + +data class TemplateVariable( + val name: String? = null, + val type: String? = null, + val description: String? = null +) + +data class WidgetModelDefinition( + val widgetId: String? = null, + val widgetType: String? = null, + val widgetName: String? = null, + val widgetData: Any? = null, + val widgetStates: List? = null, + val widgetOutput: List? = null, + val widgetRenderActions: RenderActions? = null, + val widgetEventListeners: List? = null +) + +data class RenderActions( + val preRenderAction: Any? = null, + val postRenderAction: Any? = null +) + +data class WidgetState(val stateId: String, val actionData: Any) + +data class WidgetOutput(val fieldName: String, val layoutId: String) + +data class WidgetEvent(val eventName: String, val stateId: String) diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/di/DazzleModule.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/di/DazzleModule.kt new file mode 100644 index 0000000..a67b135 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/di/DazzleModule.kt @@ -0,0 +1,43 @@ +package com.uitron.demo.dazzledesignsystem.di + +import android.content.Context +import androidx.room.Room +import com.uitron.demo.dazzledesignsystem.db.DazzleDatabase +import com.uitron.demo.dazzledesignsystem.db.dao.WidgetTemplatesDao +import com.uitron.demo.dazzledesignsystem.network.retrofit.RetrofitProvider +import com.uitron.demo.dazzledesignsystem.network.retrofit.RetrofitService +import com.uitron.demo.dazzledesignsystem.repository.DazzleRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class DazzleModule { + + @Provides + @Singleton + fun provideRetrofitService(): RetrofitService { + return RetrofitProvider.instance.service + } + + @Singleton + @Provides + fun providesNaviPayAppDatabase(@ApplicationContext context: Context) = + Room.databaseBuilder(context, DazzleDatabase::class.java, "dazzle_database").build() + + @Singleton + @Provides + fun providesWidgetTemplatesDao(dazzleDatabase: DazzleDatabase) = + dazzleDatabase.getWidgetTemplatesDao() + + @Provides + @Singleton + fun provideRepository(retrofitService: RetrofitService, widgetTemplatesDao: WidgetTemplatesDao + ): DazzleRepository { + return DazzleRepository(retrofitService, widgetTemplatesDao) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/BottomSheetData.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/BottomSheetData.kt new file mode 100644 index 0000000..45e80ac --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/BottomSheetData.kt @@ -0,0 +1,13 @@ +package com.uitron.demo.dazzledesignsystem.models + +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate + +data class BottomSheetData( + val bottomSheetType: BottomSheetType? = null, + val widgetTemplate: WidgetTemplate? = null +) + +enum class BottomSheetType { + FilterBottomSheet, + ShareWidgetConfigBottomSheet +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/WidgetTemplateState.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/WidgetTemplateState.kt new file mode 100644 index 0000000..3020a7b --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/WidgetTemplateState.kt @@ -0,0 +1,10 @@ +package com.uitron.demo.dazzledesignsystem.models + +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate + +sealed class WidgetTemplateState { + object Loading : WidgetTemplateState() + data class Error(val message: String? = null) : WidgetTemplateState() + data class Success(val data: List) : WidgetTemplateState() + object Nothing : WidgetTemplateState() +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/network/NetworkResult.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/network/NetworkResult.kt new file mode 100644 index 0000000..96adf88 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/network/NetworkResult.kt @@ -0,0 +1,6 @@ +package com.uitron.demo.dazzledesignsystem.models.network + +data class NetworkResult ( + var data: T? = null, + var errorMessage: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/network/WidgetTemplateResponse.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/network/WidgetTemplateResponse.kt new file mode 100644 index 0000000..6bbc065 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/models/network/WidgetTemplateResponse.kt @@ -0,0 +1,13 @@ +package com.uitron.demo.dazzledesignsystem.models.network + +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate + +data class WidgetTemplateResponse( + val content: List? = null, + val size: Int? = null, + val number: Int? = null, + val first: Boolean? = null, + val last: Boolean? = null, + val numberOfElements: Int? = null, + val empty: Boolean? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/HttpClient.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/HttpClient.kt new file mode 100644 index 0000000..3ecad3a --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/HttpClient.kt @@ -0,0 +1,19 @@ +package com.uitron.demo.dazzledesignsystem.network + +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor + +class HttpClient { + val httpClientBuilder: OkHttpClient.Builder + get() { + val okHttpClientBuilder = OkHttpClient.Builder() + with(okHttpClientBuilder) { + addInterceptor(loggingInterceptor()) + } + return okHttpClientBuilder + } + + + private fun loggingInterceptor() = + HttpLoggingInterceptor().apply { setLevel(HttpLoggingInterceptor.Level.BODY) } +} diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/retrofit/RetrofitProvider.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/retrofit/RetrofitProvider.kt new file mode 100644 index 0000000..b8f2f13 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/retrofit/RetrofitProvider.kt @@ -0,0 +1,28 @@ +package com.uitron.demo.dazzledesignsystem.network.retrofit + +import com.uitron.demo.dazzledesignsystem.network.HttpClient +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +class RetrofitProvider private constructor() { + val service: RetrofitService = defaultRetrofitClient.create(RetrofitService::class.java) + + private object Holder { + val INSTANCE = RetrofitProvider() + } + + companion object { + val instance: RetrofitProvider by lazy { Holder.INSTANCE } + val defaultRetrofitClient: Retrofit = createApRetrofitClient( + HttpClient().httpClientBuilder + ) + } +} + +fun createApRetrofitClient(okttpClientBuilder: OkHttpClient.Builder): Retrofit { + val baseUrl = "https://qa-dazzle.np.navi-sa.in/" + + return Retrofit.Builder().baseUrl(baseUrl).addConverterFactory(GsonConverterFactory.create()) + .client(okttpClientBuilder.build()).build() +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/retrofit/RetrofitService.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/retrofit/RetrofitService.kt new file mode 100644 index 0000000..49f5e01 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/network/retrofit/RetrofitService.kt @@ -0,0 +1,21 @@ +package com.uitron.demo.dazzledesignsystem.network.retrofit + +import com.uitron.demo.dazzledesignsystem.models.network.WidgetTemplateResponse +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Query + +interface RetrofitService { + @GET("/dazzle/v1/widget/templates") + suspend fun fetchWidgetTemplates( + @Query("pageNumber") pageNumber: Int, + @Query("pageSize") pageSize: Int + ): Response + + @POST("https://jsonblob.com/api/jsonBlob") + suspend fun postConfigAndGetJsonBlobUrl( + @Body body: Any + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/repository/DazzleRepository.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/repository/DazzleRepository.kt new file mode 100644 index 0000000..115a2bc --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/repository/DazzleRepository.kt @@ -0,0 +1,70 @@ +package com.uitron.demo.dazzledesignsystem.repository + +import com.uitron.demo.dazzledesignsystem.DB_LAST_REFRESHED_TIMESTAMP +import com.uitron.demo.dazzledesignsystem.DB_REFRESH_TIMEOUT +import com.uitron.demo.dazzledesignsystem.db.SharedPreferences +import com.uitron.demo.dazzledesignsystem.db.dao.WidgetTemplatesDao +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate +import com.uitron.demo.dazzledesignsystem.models.network.NetworkResult +import com.uitron.demo.dazzledesignsystem.network.retrofit.RetrofitService +import orFalse +import retrofit2.Response +import javax.inject.Inject + +class DazzleRepository @Inject constructor( + private val retrofitService: RetrofitService, private val widgetTemplatesDao: WidgetTemplatesDao +) { + suspend fun fetchWidgetTemplates(): NetworkResult> { + val lastRefreshTimeStamp = SharedPreferences.getLongValue( + key = DB_LAST_REFRESHED_TIMESTAMP, defValue = -1L + ) + + return if (System.currentTimeMillis() - lastRefreshTimeStamp < DB_REFRESH_TIMEOUT) { + NetworkResult( + data = widgetTemplatesDao.getWidgetTemplates(), errorMessage = null + ) + } else fetchWidgetTemplatesFromNetwork() + } + + suspend fun searchWidgetTemplates(searchQuery: String): NetworkResult> { + return NetworkResult( + data = widgetTemplatesDao.searchWidgetTemplates("%$searchQuery%"), errorMessage = null + ) + } + + private suspend fun fetchWidgetTemplatesFromNetwork(): NetworkResult> { + return try { + widgetTemplatesDao.clearAllWidgetTemplates() + val response = retrofitService.fetchWidgetTemplates( + pageNumber = 0, pageSize = 10000 + ) + val result = response.body()?.content?.filter { + it.tags?.isNotEmpty().orFalse() && it.type.equals("WIDGET") + } + SharedPreferences.setLongValue( + key = DB_LAST_REFRESHED_TIMESTAMP, value = System.currentTimeMillis() + ) + widgetTemplatesDao.insertAll(result ?: listOf()) + NetworkResult(data = result, errorMessage = null) + } catch (e: Exception) { + NetworkResult( + data = null, errorMessage = e.message + ) + } + } + + suspend fun postConfigAndGetJsonBlobUrl( + body: Any + ): Response { + return try { + retrofitService.postConfigAndGetJsonBlobUrl( + body = body + ) + } catch (e: Exception) { + e.printStackTrace() + return Response.error(500, null) + } + } +} + + diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/BottomSheetContent.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/BottomSheetContent.kt new file mode 100644 index 0000000..2ffaadd --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/BottomSheetContent.kt @@ -0,0 +1,315 @@ +package com.uitron.demo.dazzledesignsystem.ui + +import android.content.Context +import android.content.Intent +import android.widget.Toast +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Checkbox +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import androidx.core.content.ContextCompat.startActivity +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.demo.uitron.theme.Black191919 +import com.demo.uitron.theme.Black1C1C1C +import com.demo.uitron.theme.Blue0276FE +import com.demo.uitron.theme.Gray969696 +import com.demo.uitron.theme.GrayE8E8E8 +import com.demo.uitron.theme.Purple1F002A +import com.demo.uitron.theme.Purple3C0050 +import com.demo.uitron.theme.WhiteFCFCFD +import com.google.gson.Gson +import com.uitron.demo.R +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate +import com.uitron.demo.dazzledesignsystem.models.BottomSheetData +import com.uitron.demo.dazzledesignsystem.models.BottomSheetType +import com.uitron.demo.dazzledesignsystem.viewmodel.DazzleViewModel +import com.uitron.demo.theme.fontFamily + +@Composable +fun BottomSheetContent( + bottomSheetData: BottomSheetData?, + viewModel: DazzleViewModel, + context: Context, + clipboardManager: ClipboardManager +) { + when (bottomSheetData?.bottomSheetType) { + BottomSheetType.FilterBottomSheet -> { + FilterBottomSheet(viewModel = viewModel) + } + + BottomSheetType.ShareWidgetConfigBottomSheet -> { + ShareWidgetConfigBottomSheet( + viewModel = viewModel, + widgetTemplate = bottomSheetData.widgetTemplate, + context = context, + clipboardManager = clipboardManager + ) + } + + else -> Spacer(modifier = Modifier.height(1.dp)) + } +} + +@Composable +fun FilterBottomSheet(viewModel: DazzleViewModel) { + val selectedFilterType by viewModel.selectedFilterType.collectAsStateWithLifecycle() + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(id = R.string.filter_by), modifier = Modifier.padding(top = 16.dp)) + Image( + painter = painterResource(id = R.drawable.ic_close), + contentDescription = null, + modifier = Modifier + .padding(top = 12.dp) + .clickable { + viewModel.hideBottomSheet() + } + ) + } + Divider(modifier = Modifier.padding(top = 14.dp)) + ConstraintLayout { + val (column1, divider, column2) = createRefs() + Column(modifier = Modifier + .constrainAs(column1) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(divider.start) + } + .padding(top = 16.dp) + .verticalScroll(state = rememberScrollState(), enabled = true)) { + for (i in viewModel.getListOfFilterTypes()) { + TabItemView(modifier = Modifier.padding(bottom = 28.dp), + text = i, + isSelected = selectedFilterType == i, + onClick = { viewModel.updateFilterType(i) }) + } + } + Divider(modifier = Modifier + .constrainAs(divider) { + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + start.linkTo(column1.end) + end.linkTo(column2.start) + height = Dimension.fillToConstraints + } + .padding(start = 24.dp, end = 18.dp) + .width(1.dp), color = GrayE8E8E8) + Column(modifier = Modifier + .constrainAs(column2) { + top.linkTo(parent.top) + start.linkTo(divider.end) + end.linkTo(parent.end) + } + .padding(top = 10.dp) + .verticalScroll(state = rememberScrollState(), enabled = true)) { + FiltersList(selectedFilterType = selectedFilterType, viewModel = viewModel) + } + } + Divider(modifier = Modifier.background(Gray969696)) + Row(modifier = Modifier.fillMaxWidth()) { + Button( + onClick = { viewModel.removeAllFilters() }, + shape = RectangleShape, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.White + ), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp + ), + contentPadding = PaddingValues( + top = 12.dp, + bottom = 12.dp, + ), + modifier = Modifier + .weight(1f) + .height(48.dp) + ) { + Text( + text = stringResource(id = R.string.clear_all), style = TextStyle( + fontSize = 14.sp, + fontFamily = fontFamily, + fontWeight = FontWeight(600), + color = Purple3C0050, + textAlign = TextAlign.Center, + ) + ) + } + Button( + onClick = { viewModel.applySearchOrFilter() + viewModel.hideBottomSheet() }, + shape = RectangleShape, + colors = ButtonDefaults.buttonColors( + backgroundColor = Purple1F002A + ), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp + ), + contentPadding = PaddingValues( + top = 12.dp, + bottom = 12.dp, + ), + modifier = Modifier + .weight(1f) + .height(48.dp) + ) { + Text( + text = stringResource(id = R.string.apply_filters), style = TextStyle( + fontSize = 14.sp, + fontFamily = fontFamily, + fontWeight = FontWeight(600), + color = WhiteFCFCFD, + textAlign = TextAlign.Center, + ) + ) + } + } + } +} + +@Composable +fun ShareWidgetConfigBottomSheet( + viewModel: DazzleViewModel, + widgetTemplate: WidgetTemplate? = null, + context: Context, + clipboardManager: ClipboardManager +) { + LaunchedEffect(Unit) { + viewModel.jsonBlobUrl.collect { + it?.let { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, it) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(context, shareIntent, null) + viewModel.hideBottomSheet() + } + } + } + Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp)) { + Row(modifier = Modifier + .padding(bottom = 20.dp) + .clickable { + Toast + .makeText(context, "Widget Template Copied!", Toast.LENGTH_SHORT) + .show() + val text = Gson().toJson(widgetTemplate, WidgetTemplate::class.java) + clipboardManager.setText(AnnotatedString(text)) + viewModel.hideBottomSheet() + }) { + Image( + painter = painterResource(id = R.drawable.ic_copy_24x24), contentDescription = null + ) + Text(text = stringResource(id = R.string.copy_widget_config), modifier = Modifier.padding(start = 14.dp)) + } + Divider() + Row(modifier = Modifier + .padding(top = 20.dp) + .clickable { + widgetTemplate?.let { + viewModel.postConfigAndGetJsonBlobUrl(widgetTemplate) + } + }) { + Image(painter = painterResource(id = R.drawable.ic_share), contentDescription = null) + Text(text = stringResource(id = R.string.share_widget_config), modifier = Modifier.padding(start = 14.dp)) + } + } +} + +@Composable +fun TabItemView( + modifier: Modifier = Modifier, text: String, isSelected: Boolean, onClick: () -> Unit +) { + Box( + modifier = modifier + .wrapContentSize() + .clickable { onClick() }, + contentAlignment = Alignment.CenterStart + ) { + Text( + text = text, style = TextStyle( + fontSize = 14.sp, + fontFamily = fontFamily, + fontWeight = FontWeight(400), + color = Black1C1C1C + ), modifier = Modifier.padding(12.dp) + ) + if (isSelected) { + Box( + modifier = Modifier + .background(color = Blue0276FE) + .width(3.dp) + .height(40.dp) + ) + } + } +} + +@Composable +fun FiltersList(selectedFilterType: String, viewModel: DazzleViewModel) { + val filterList = viewModel.getListOfFiltersBasedOnFilterType(selectedFilterType) + val selectedFilters by viewModel.selectedFilters.collectAsStateWithLifecycle() + for (i in filterList) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox(checked = selectedFilters.contains(i), onCheckedChange = { checked -> + if (checked) { + viewModel.addFilter(i) + } else { + viewModel.removeFilter(i) + } + }) + Text( + text = i, modifier = Modifier.padding(start = 8.dp), style = TextStyle( + fontSize = 16.sp, + fontFamily = fontFamily, + fontWeight = FontWeight(400), + color = Black191919, + ) + ) + } + } +} diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/DazzleDesignSystemScreen.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/DazzleDesignSystemScreen.kt new file mode 100644 index 0000000..86fbf2d --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/DazzleDesignSystemScreen.kt @@ -0,0 +1,140 @@ +package com.uitron.demo.dazzledesignsystem.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Text +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.demo.uitron.theme.WhiteF7F7F7 +import com.uitron.demo.dazzledesignsystem.models.WidgetTemplateState +import com.uitron.demo.dazzledesignsystem.viewmodel.DazzleViewModel +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun DazzleDesignSystemScreen( + viewModel: DazzleViewModel = hiltViewModel() +) { + val widgetTemplates = viewModel.widgetTemplates.collectAsStateWithLifecycle() + val searchAndFilterResults = viewModel.searchAndFilterResults.collectAsStateWithLifecycle() + val applySearchOrFilter by viewModel.applySearchOrFilter.collectAsStateWithLifecycle() + val bottomSheetState = rememberModalBottomSheetState( + initialValue = ModalBottomSheetValue.Hidden, skipHalfExpanded = true + ) + val bottomSheetData by viewModel.bottomSheetData.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() + val context = LocalContext.current + val clipboardManager: ClipboardManager = LocalClipboardManager.current + + LaunchedEffect(Unit) { + viewModel.fetchWidgetTemplates() + } + + LaunchedEffect(Unit) { + viewModel.bottomSheetVisibilityState.collect { isVisible -> + if (isVisible) { + scope.launch { + bottomSheetState.show() + } + } else { + scope.launch { + bottomSheetState.hide() + } + } + } + } + + ModalBottomSheetLayout(sheetState = bottomSheetState, sheetContent = { + BottomSheetContent( + bottomSheetData = bottomSheetData, + viewModel = viewModel, + context = context, + clipboardManager = clipboardManager + ) + }, sheetShape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) { + Column( + modifier = Modifier + .fillMaxSize() + .background(color = WhiteF7F7F7) + ) { + Toolbar(viewModel = viewModel) + if (applySearchOrFilter) { + WidgetTemplatesList(widgetTemplates = searchAndFilterResults, viewModel = viewModel) + } else { + WidgetTemplatesList(widgetTemplates = widgetTemplates, viewModel = viewModel) + } + } + } +} + +@Composable +fun WidgetTemplatesList(widgetTemplates: State, viewModel: DazzleViewModel) { + LazyColumn( + modifier = Modifier.fillMaxWidth() + ) { + val widgetTemplatesList = (widgetTemplates.value as? WidgetTemplateState.Success)?.data + widgetTemplatesList?.let { + items(widgetTemplatesList) { widgetTemplate -> + WidgetItemView( + widgetTemplate = widgetTemplate, viewModel = viewModel + ) + } + } + when (widgetTemplates.value) { + is WidgetTemplateState.Loading -> { + item { LoadingView(modifier = Modifier.fillParentMaxSize()) } + } + + is WidgetTemplateState.Error -> { + item { ErrorView(errorMessage = (widgetTemplates.value as WidgetTemplateState.Error).message) } + } + + else -> Unit + } + } +} + +@Composable +fun LoadingView(modifier: Modifier = Modifier) { + Column( + modifier = modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + CircularProgressIndicator() + } +} + +@Composable +fun ErrorView(errorMessage: String?) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text(text = errorMessage ?: "Something went wrong!") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/SearchAndFilterToolBar.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/SearchAndFilterToolBar.kt new file mode 100644 index 0000000..c0fed3e --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/SearchAndFilterToolBar.kt @@ -0,0 +1,118 @@ +package com.uitron.demo.dazzledesignsystem.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.demo.uitron.theme.GrayA8A8A8 +import com.demo.uitron.theme.GrayE3E5E5 +import com.demo.uitron.theme.WhiteF5F5F5 +import com.uitron.demo.R +import com.uitron.demo.dazzledesignsystem.models.BottomSheetType +import com.uitron.demo.dazzledesignsystem.viewmodel.DazzleViewModel +import com.uitron.demo.theme.fontFamily + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun Toolbar(viewModel: DazzleViewModel) { + + val keyboardController = LocalSoftwareKeyboardController.current + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(76.dp) + .background(color = Color.White) + .padding(start = 8.dp, end = 8.dp, top = 14.dp, bottom = 14.dp) + ) { + val searchQuery by viewModel.searchQuery.collectAsStateWithLifecycle() + TextField(value = searchQuery, + onValueChange = { + viewModel.updateSearchQuery(searchQuery = it) + }, + maxLines = 1, + colors = TextFieldDefaults.textFieldColors( + textColor = Color.DarkGray, + disabledTextColor = Color.Transparent, + backgroundColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent + ), + textStyle = TextStyle.Default.copy( + fontSize = 14.sp, fontFamily = fontFamily, fontWeight = FontWeight(400) + ), + keyboardOptions = KeyboardOptions( + autoCorrect = false, imeAction = ImeAction.Search + ), + keyboardActions = KeyboardActions(onSearch = { + viewModel.applySearchOrFilter() + keyboardController?.hide() + }), + modifier = Modifier + .height(48.dp) + .weight(2f) + .border(width = 1.dp, shape = RoundedCornerShape(8.dp), color = GrayE3E5E5), + placeholder = { + Text( + modifier = Modifier.align(Alignment.CenterVertically), + text = stringResource(id = R.string.search), + fontSize = 14.sp, + fontFamily = fontFamily, + fontWeight = FontWeight(400), + color = GrayA8A8A8 + ) + }, + leadingIcon = { + Image( + painter = painterResource(id = R.drawable.ic_search), contentDescription = null + ) + }) + Button( + onClick = { + keyboardController?.hide() + viewModel.showBottomSheet(BottomSheetType.FilterBottomSheet) }, + shape = RoundedCornerShape(4.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = WhiteF5F5F5 + ), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp + ), + modifier = Modifier + .padding(start = 8.dp) + .fillMaxHeight() + ) { + Image( + painter = painterResource(id = R.drawable.ic_filter), contentDescription = null + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/WidgetItemView.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/WidgetItemView.kt new file mode 100644 index 0000000..e21169e --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/ui/WidgetItemView.kt @@ -0,0 +1,131 @@ +package com.uitron.demo.dazzledesignsystem.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.demo.uitron.theme.Black1C1C1C +import com.demo.uitron.theme.BlueE4EEFF +import com.demo.uitron.theme.GrayE8E8E8 +import com.demo.uitron.theme.Purple1F002A +import com.google.gson.Gson +import com.navi.uitron.render.UiTronRenderer +import com.uitron.demo.R +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate +import com.uitron.demo.dazzledesignsystem.models.BottomSheetType +import com.uitron.demo.dazzledesignsystem.viewmodel.DazzleViewModel +import com.uitron.demo.stringToUiTronResponse +import com.uitron.demo.theme.fontFamily + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun WidgetItemView(widgetTemplate: WidgetTemplate?, viewModel: DazzleViewModel) { + + val keyboardController = LocalSoftwareKeyboardController.current + + val widgetData = widgetTemplate?.config?.widgetData?.let { + try { + stringToUiTronResponse(Gson().toJson(it)) + } catch (e: Exception) { + null + } + } + widgetData?.let { uiTronResponse -> + Card(modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 16.dp)) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = Color.White) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = widgetTemplate.name.orEmpty(), + modifier = Modifier + .padding( + start = 16.dp, end = 16.dp, top = 13.dp + ) + .weight(1f), + fontSize = 16.sp, + fontFamily = fontFamily, + fontWeight = FontWeight.Medium, + color = Black1C1C1C + ) + Image( + painter = painterResource(id = R.drawable.ic_vertical_dots), + contentDescription = null, + modifier = Modifier + .padding(top = 10.dp, end = 12.dp) + .clickable { + keyboardController?.hide() + viewModel.showBottomSheet( + BottomSheetType.ShareWidgetConfigBottomSheet, widgetTemplate + ) + }, + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 8.dp, end = 8.dp, top = 18.dp) + ) { + widgetTemplate.tags?.forEach { tag -> + Card( + shape = RoundedCornerShape(100.dp), + backgroundColor = BlueE4EEFF, + modifier = Modifier.padding(end = 8.dp), + elevation = 0.dp + ) { + Text( + text = tag, + fontSize = 12.sp, + fontFamily = fontFamily, + fontWeight = FontWeight.Medium, + color = Purple1F002A, + modifier = Modifier.padding( + horizontal = 12.dp, vertical = 4.dp + ) + ) + } + } + } + Divider( + modifier = Modifier.padding(8.dp), color = GrayE8E8E8 + ) + Box( + modifier = Modifier.padding( + start = 8.dp, end = 8.dp, bottom = 8.dp + ) + ) { + UiTronRenderer( + uiTronResponse.data, viewModel + ).Render( + composeViews = uiTronResponse.parentComposeView.orEmpty() + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/dazzledesignsystem/viewmodel/DazzleViewModel.kt b/app/src/main/java/com/uitron/demo/dazzledesignsystem/viewmodel/DazzleViewModel.kt new file mode 100644 index 0000000..61f2d43 --- /dev/null +++ b/app/src/main/java/com/uitron/demo/dazzledesignsystem/viewmodel/DazzleViewModel.kt @@ -0,0 +1,144 @@ +package com.uitron.demo.dazzledesignsystem.viewmodel + +import androidx.lifecycle.viewModelScope +import com.navi.uitron.viewmodel.UiTronViewModel +import com.uitron.demo.dazzledesignsystem.db.entity.WidgetTemplate +import com.uitron.demo.dazzledesignsystem.isNotNull +import com.uitron.demo.dazzledesignsystem.models.BottomSheetData +import com.uitron.demo.dazzledesignsystem.models.BottomSheetType +import com.uitron.demo.dazzledesignsystem.models.WidgetTemplateState +import com.uitron.demo.dazzledesignsystem.repository.DazzleRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class DazzleViewModel @Inject constructor(private val repository: DazzleRepository) : UiTronViewModel() { + private val _searchQuery = MutableStateFlow("") + val searchQuery = _searchQuery.asStateFlow() + + private val _widgetTemplates = MutableStateFlow(WidgetTemplateState.Nothing) + val widgetTemplates = _widgetTemplates.asStateFlow() + + private val _searchAndFilterResults = MutableStateFlow(WidgetTemplateState.Nothing) + val searchAndFilterResults = _searchAndFilterResults.asStateFlow() + + private val _selectedFilterType = MutableStateFlow("Widget\nType") + val selectedFilterType = _selectedFilterType.asStateFlow() + + private val _selectedFilters = MutableStateFlow(listOf()) + val selectedFilters = _selectedFilters.asStateFlow() + + private val _applySearchOrFilter = MutableStateFlow(false) + val applySearchOrFilter = _applySearchOrFilter.asStateFlow() + + private val _bottomSheetVisibilityState = MutableSharedFlow() + val bottomSheetVisibilityState = _bottomSheetVisibilityState.asSharedFlow() + + private val _bottomSheetData = MutableStateFlow(null) + val bottomSheetData = _bottomSheetData.asStateFlow() + + private val _jsonBlobUrl = MutableStateFlow(null) + val jsonBlobUrl = _jsonBlobUrl.asStateFlow() + + fun fetchWidgetTemplates() { + viewModelScope.launch(Dispatchers.IO) { + _widgetTemplates.value = WidgetTemplateState.Loading + val response = repository.fetchWidgetTemplates() + _widgetTemplates.value = when { + response.data.isNotNull() -> WidgetTemplateState.Success(response.data!!) + else -> WidgetTemplateState.Error(response.errorMessage) + } + } + } + + private fun searchAndFilterWidgetTemplates() { + viewModelScope.launch(Dispatchers.IO) { + _searchAndFilterResults.value = WidgetTemplateState.Loading + when (_widgetTemplates.value) { + is WidgetTemplateState.Success -> { + val widgetTemplates = repository.searchWidgetTemplates(_searchQuery.value) + val results = widgetTemplates.data?.filter { it.tags?.containsAll(_selectedFilters.value) == true } + _searchAndFilterResults.value = WidgetTemplateState.Success(results.orEmpty()) + } + + else -> { + _searchAndFilterResults.value = WidgetTemplateState.Error("No data found") + } + } + } + } + + fun postConfigAndGetJsonBlobUrl(body: Any) { + viewModelScope.launch(Dispatchers.IO){ + val response = repository.postConfigAndGetJsonBlobUrl(body = body) + + if (response.isSuccessful) { + val splitUrl = response.headers()["location"]?.split("/") + val resultUrl = "https://jsonblob.com/${splitUrl?.get(splitUrl.size - 1)}" + _jsonBlobUrl.update { resultUrl } + } + } + } + + fun updateSearchQuery(searchQuery: String = "") { + _searchQuery.update { searchQuery } + } + + fun applySearchOrFilter() { + _applySearchOrFilter.update { true } + searchAndFilterWidgetTemplates() + } + + fun updateFilterType(filterType: String) { + _selectedFilterType.update { filterType } + } + + fun addFilter(filter: String) { + _selectedFilters.update { selectedFilters.value.plus(filter) } + } + + fun removeFilter(filter: String) { + _selectedFilters.update { selectedFilters.value.minus(filter) } + } + + fun removeAllFilters() { + _selectedFilters.update { listOf() } + } + + fun showBottomSheet(bottomSheetType: BottomSheetType?, widgetTemplate: WidgetTemplate? = null) { + viewModelScope.launch { + _bottomSheetData.emit(BottomSheetData(bottomSheetType = bottomSheetType, widgetTemplate = widgetTemplate)) + _bottomSheetVisibilityState.emit(true) + } + } + + fun hideBottomSheet() { + viewModelScope.launch { + _bottomSheetVisibilityState.emit(false) + } + } + + fun getListOfFilterTypes(): List { + val list = mutableListOf() + list.add("Widget\nType") + list.add("Widget\nSubtype") + list.add("Team Name") + return list + } + + fun getListOfFiltersBasedOnFilterType(filterType: String): List { + return when (filterType) { + "Widget\nType" -> mutableListOf("Header", "Footer", "Content", "Bottom sheet", "Toast") + "Widget\nSubtype" -> mutableListOf("Title", "Icon", "Radio pill", "Button", "Input") + "Team Name" -> mutableListOf("PL", "HL_PP_TR", "KYC", "AMC", "Navi Pay", "HI", "GI", "Digital Gold", "Rewards", "Referrals") + else -> mutableListOf() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/home/HomeScreen.kt b/app/src/main/java/com/uitron/demo/home/HomeScreen.kt index 39e5c53..b0758a5 100644 --- a/app/src/main/java/com/uitron/demo/home/HomeScreen.kt +++ b/app/src/main/java/com/uitron/demo/home/HomeScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -23,6 +24,7 @@ import androidx.constraintlayout.compose.Dimension import androidx.navigation.NavHostController import com.demo.uitron.theme.PurpleDCD1F4 import com.demo.uitron.theme.RedEF0000 +import com.uitron.demo.navigation.DAZZLE_DESIGN_SYSTEM import com.uitron.demo.navigation.DESIGN_SYSTEM import com.uitron.demo.navigation.MOCK import com.uitron.demo.navigation.PLAYGROUND @@ -65,7 +67,7 @@ fun HomeScreen(navHostController: NavHostController) { ConstraintLayout(modifier = Modifier.fillMaxSize()) { val startGuideline = createGuidelineFromAbsoluteLeft(.5f) - val (playgroundRef, mockRef, designSystemRef) = createRefs() + val (playgroundRef, mockRef, designSystemRef, dazzleDesignSystemRef) = createRefs() PlaygroundBubble( navHostController = navHostController, modifier = Modifier.constrainAs(playgroundRef) { @@ -91,6 +93,15 @@ fun HomeScreen(navHostController: NavHostController) { width = Dimension.fillToConstraints } ) + DazzleDesignSystemBubble( + navHostController = navHostController, + modifier = Modifier.constrainAs(dazzleDesignSystemRef) { + start.linkTo(startGuideline, 16.dp) + end.linkTo(parent.end, 16.dp) + top.linkTo(designSystemRef.bottom, 16.dp) + width = Dimension.fillToConstraints + } + ) } } } @@ -151,4 +162,23 @@ fun MockBubble(navHostController: NavHostController, modifier: Modifier) { fontFamily = fontFamily ) } +} + +@Composable +fun DazzleDesignSystemBubble(navHostController: NavHostController, modifier: Modifier) { + Box( + modifier = modifier + .height(150.dp) + .background(color = Color.Cyan, shape = RoundedCornerShape(40.dp)) + .clickable { + navHostController.navigate(DAZZLE_DESIGN_SYSTEM) + }, contentAlignment = Alignment.Center + ) { + Text( + text = "Dazzle\nDesign\nSystem", + fontSize = 22.sp, + fontWeight = FontWeight(800), + fontFamily = fontFamily + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/navigation/NavigationConstants.kt b/app/src/main/java/com/uitron/demo/navigation/NavigationConstants.kt index fd82f9e..d1c99f3 100644 --- a/app/src/main/java/com/uitron/demo/navigation/NavigationConstants.kt +++ b/app/src/main/java/com/uitron/demo/navigation/NavigationConstants.kt @@ -5,4 +5,5 @@ const val PLAYGROUND = "playground" const val MOCK = "mock" const val DESIGN_SYSTEM = "designSystem" const val DESIGN_REPO_WITH_NAV = "designRepo/{id}" -const val DESIGN_REPO = "designRepo/" \ No newline at end of file +const val DESIGN_REPO = "designRepo/" +const val DAZZLE_DESIGN_SYSTEM = "dazzle/" \ No newline at end of file diff --git a/app/src/main/java/com/uitron/demo/navigation/UiTronDemoNavGraph.kt b/app/src/main/java/com/uitron/demo/navigation/UiTronDemoNavGraph.kt index 1f006fc..cc43281 100644 --- a/app/src/main/java/com/uitron/demo/navigation/UiTronDemoNavGraph.kt +++ b/app/src/main/java/com/uitron/demo/navigation/UiTronDemoNavGraph.kt @@ -4,6 +4,7 @@ import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navArgument +import com.uitron.demo.dazzledesignsystem.ui.DazzleDesignSystemScreen import com.uitron.demo.home.HomeScreen import com.uitron.demo.navigation.* import com.uitron.demo.playground.PlayGroundScreen @@ -34,6 +35,9 @@ fun UiTronDemoNavGraph(navController: NavHostController) { ) { DesignSystemRepoScreen(designComponentId = it.arguments?.get("id").toString()) } + composable(route = DAZZLE_DESIGN_SYSTEM) { + DazzleDesignSystemScreen() + } } } diff --git a/app/src/main/java/com/uitron/demo/theme/Color.kt b/app/src/main/java/com/uitron/demo/theme/Color.kt index f391da6..5670522 100644 --- a/app/src/main/java/com/uitron/demo/theme/Color.kt +++ b/app/src/main/java/com/uitron/demo/theme/Color.kt @@ -19,4 +19,15 @@ val RedEF0000 = Color(0xFFEF0000) val WhiteF8F8F8 = Color(0xFFF8F8F8) val Black444444 = Color(0xFF444444) val PurpleDCD1F4 = Color(0xFFDCD1F4) +val WhiteF7F7F7 = Color(0xFFF7F7F7) +val Black1C1C1C = Color(0xFF1C1C1C) +val Blue0276FE = Color(0xFF0276FE) +val WhiteFCFCFD = Color(0xFFFCFCFD) +val Purple3C0050 = Color(0xFF3C0050) +val Gray969696 = Color(0xFF969696) +val GrayE8E8E8 = Color(0xFFE8E8E8) +val Purple1F002A = Color(0xFF1F002A) +val BlueE4EEFF = Color(0xFFE4EEFF) +val GrayA8A8A8 = Color(0xFFA8A8A8) +val WhiteF5F5F5 = Color(0xFFF5F5F5) diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000..436e22f --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_copy_24x24.xml b/app/src/main/res/drawable/ic_copy_24x24.xml new file mode 100644 index 0000000..71a1c47 --- /dev/null +++ b/app/src/main/res/drawable/ic_copy_24x24.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml new file mode 100644 index 0000000..240cb7e --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..78a48cd --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 0000000..33f803a --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_vertical_dots.xml b/app/src/main/res/drawable/ic_vertical_dots.xml new file mode 100644 index 0000000..9479bc1 --- /dev/null +++ b/app/src/main/res/drawable/ic_vertical_dots.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bf02afd..f01444f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,10 @@ UiTron UiTron Demo Home + FILTER BY > + Clear All + Apply Filters + Copy widget config + Share widget config + Search \ No newline at end of file diff --git a/build.gradle b/build.gradle index 2dfefca..223c458 100644 --- a/build.gradle +++ b/build.gradle @@ -9,4 +9,5 @@ plugins { id 'com.android.library' version '7.3.1' apply false id 'org.jetbrains.kotlin.android' version '1.8.10' apply false id 'com.android.test' version '7.3.1' apply false + id 'com.google.dagger.hilt.android' version '2.44' apply false }