TP-35964 Implementation of Widget Discovery Feature in UiTron App (#139)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.uitron.demo.dazzledesignsystem
|
||||
|
||||
import isNull
|
||||
|
||||
fun Any?.isNotNull(): Boolean = !this.isNull()
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.uitron.demo.dazzledesignsystem
|
||||
|
||||
const val DB_LAST_REFRESHED_TIMESTAMP = "widgetListLastRefreshedTimestamp"
|
||||
const val DB_REFRESH_TIMEOUT = 3600000L // 1 hour
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>): String {
|
||||
return Gson().toJson(value)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun toStringList(value: String): List<String> {
|
||||
return try {
|
||||
Gson().fromJson(value)
|
||||
} catch (e: Exception) {
|
||||
listOf()
|
||||
}
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromVariablesList(value: List<TemplateVariable>): String {
|
||||
return Gson().toJson(value)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun toVariablesList(value: String): List<TemplateVariable> {
|
||||
return try {
|
||||
Gson().fromJson(value)
|
||||
} catch (e: Exception) {
|
||||
listOf()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T> Gson.fromJson(json: String): T =
|
||||
fromJson(json, object : TypeToken<T>() {}.type)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<WidgetTemplate>)
|
||||
|
||||
@Query("select * from widget_templates")
|
||||
fun getWidgetTemplates(): List<WidgetTemplate>
|
||||
|
||||
@Query("select * from widget_templates where name like :searchQuery")
|
||||
fun searchWidgetTemplates(searchQuery: String): List<WidgetTemplate>
|
||||
|
||||
@Query("delete from widget_templates")
|
||||
suspend fun clearAllWidgetTemplates()
|
||||
}
|
||||
@@ -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<String>? = null,
|
||||
@ColumnInfo(name = "config") @TypeConverters(WidgetModelDefinitionConverter::class)
|
||||
val config: WidgetModelDefinition? = null,
|
||||
@ColumnInfo(name = "vars") @TypeConverters(ListConverter::class)
|
||||
val vars: List<TemplateVariable>? = 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<WidgetState>? = null,
|
||||
val widgetOutput: List<WidgetOutput>? = null,
|
||||
val widgetRenderActions: RenderActions? = null,
|
||||
val widgetEventListeners: List<WidgetEvent>? = 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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<WidgetTemplate>) : WidgetTemplateState()
|
||||
object Nothing : WidgetTemplateState()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.uitron.demo.dazzledesignsystem.models.network
|
||||
|
||||
data class NetworkResult<T> (
|
||||
var data: T? = null,
|
||||
var errorMessage: String? = null
|
||||
)
|
||||
@@ -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<WidgetTemplate>? = 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
|
||||
)
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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<WidgetTemplateResponse>
|
||||
|
||||
@POST("https://jsonblob.com/api/jsonBlob")
|
||||
suspend fun postConfigAndGetJsonBlobUrl(
|
||||
@Body body: Any
|
||||
): Response<Any>
|
||||
}
|
||||
@@ -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<List<WidgetTemplate>> {
|
||||
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<List<WidgetTemplate>> {
|
||||
return NetworkResult(
|
||||
data = widgetTemplatesDao.searchWidgetTemplates("%$searchQuery%"), errorMessage = null
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchWidgetTemplatesFromNetwork(): NetworkResult<List<WidgetTemplate>> {
|
||||
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<Any> {
|
||||
return try {
|
||||
retrofitService.postConfigAndGetJsonBlobUrl(
|
||||
body = body
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return Response.error(500, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<WidgetTemplateState>, 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!")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>(WidgetTemplateState.Nothing)
|
||||
val widgetTemplates = _widgetTemplates.asStateFlow()
|
||||
|
||||
private val _searchAndFilterResults = MutableStateFlow<WidgetTemplateState>(WidgetTemplateState.Nothing)
|
||||
val searchAndFilterResults = _searchAndFilterResults.asStateFlow()
|
||||
|
||||
private val _selectedFilterType = MutableStateFlow("Widget\nType")
|
||||
val selectedFilterType = _selectedFilterType.asStateFlow()
|
||||
|
||||
private val _selectedFilters = MutableStateFlow(listOf<String>())
|
||||
val selectedFilters = _selectedFilters.asStateFlow()
|
||||
|
||||
private val _applySearchOrFilter = MutableStateFlow(false)
|
||||
val applySearchOrFilter = _applySearchOrFilter.asStateFlow()
|
||||
|
||||
private val _bottomSheetVisibilityState = MutableSharedFlow<Boolean>()
|
||||
val bottomSheetVisibilityState = _bottomSheetVisibilityState.asSharedFlow()
|
||||
|
||||
private val _bottomSheetData = MutableStateFlow<BottomSheetData?>(null)
|
||||
val bottomSheetData = _bottomSheetData.asStateFlow()
|
||||
|
||||
private val _jsonBlobUrl = MutableStateFlow<String?>(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<String> {
|
||||
val list = mutableListOf<String>()
|
||||
list.add("Widget\nType")
|
||||
list.add("Widget\nSubtype")
|
||||
list.add("Team Name")
|
||||
return list
|
||||
}
|
||||
|
||||
fun getListOfFiltersBasedOnFilterType(filterType: String): List<String> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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/"
|
||||
const val DESIGN_REPO = "designRepo/"
|
||||
const val DAZZLE_DESIGN_SYSTEM = "dazzle/"
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
14
app/src/main/res/drawable/ic_close.xml
Normal file
14
app/src/main/res/drawable/ic_close.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="25dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="25">
|
||||
<path
|
||||
android:pathData="M18.707,5.323C19.098,5.716 19.098,6.353 18.707,6.746L6.707,18.815C6.317,19.208 5.683,19.208 5.293,18.815C4.902,18.422 4.902,17.785 5.293,17.393L17.293,5.323C17.683,4.931 18.317,4.931 18.707,5.323Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M5.293,5.323C5.683,4.931 6.317,4.931 6.707,5.323L18.707,17.393C19.098,17.785 19.098,18.422 18.707,18.815C18.317,19.208 17.683,19.208 17.293,18.815L5.293,6.746C4.902,6.353 4.902,5.716 5.293,5.323Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_copy_24x24.xml
Normal file
14
app/src/main/res/drawable/ic_copy_24x24.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11,10C10.448,10 10,10.448 10,11V20C10,20.552 10.448,21 11,21H20C20.552,21 21,20.552 21,20V11C21,10.448 20.552,10 20,10H11ZM8,11C8,9.343 9.343,8 11,8H20C21.657,8 23,9.343 23,11V20C23,21.657 21.657,23 20,23H11C9.343,23 8,21.657 8,20V11Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M4,3C3.735,3 3.48,3.105 3.293,3.293C3.105,3.48 3,3.735 3,4V13C3,13.265 3.105,13.52 3.293,13.707C3.48,13.895 3.735,14 4,14H5C5.552,14 6,14.448 6,15C6,15.552 5.552,16 5,16H4C3.204,16 2.441,15.684 1.879,15.121C1.316,14.559 1,13.796 1,13V4C1,3.204 1.316,2.441 1.879,1.879C2.441,1.316 3.204,1 4,1H13C13.796,1 14.559,1.316 15.121,1.879C15.684,2.441 16,3.204 16,4V5C16,5.552 15.552,6 15,6C14.448,6 14,5.552 14,5V4C14,3.735 13.895,3.48 13.707,3.293C13.52,3.105 13.265,3 13,3H4Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_filter.xml
Normal file
10
app/src/main/res/drawable/ic_filter.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="25dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="25"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M1.593,2.579C1.757,2.226 2.111,2 2.5,2H22.5C22.889,2 23.243,2.226 23.407,2.579C23.571,2.932 23.515,3.348 23.264,3.646L15.5,12.826V21C15.5,21.347 15.321,21.668 15.026,21.851C14.731,22.033 14.363,22.049 14.053,21.894L10.053,19.894C9.714,19.725 9.5,19.379 9.5,19V12.826L1.736,3.646C1.485,3.348 1.429,2.932 1.593,2.579ZM4.655,4L11.264,11.814C11.416,11.995 11.5,12.224 11.5,12.46V18.382L13.5,19.382V12.46C13.5,12.224 13.584,11.995 13.736,11.814L20.345,4H4.655Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_search.xml
Normal file
14
app/src/main/res/drawable/ic_search.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11,4C7.134,4 4,7.134 4,11C4,14.866 7.134,18 11,18C14.866,18 18,14.866 18,11C18,7.134 14.866,4 11,4ZM2,11C2,6.029 6.029,2 11,2C15.971,2 20,6.029 20,11C20,15.971 15.971,20 11,20C6.029,20 2,15.971 2,11Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M15.943,15.943C16.333,15.552 16.966,15.552 17.357,15.943L21.707,20.293C22.097,20.683 22.097,21.316 21.707,21.707C21.316,22.097 20.683,22.097 20.293,21.707L15.943,17.357C15.552,16.966 15.552,16.333 15.943,15.943Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
26
app/src/main/res/drawable/ic_share.xml
Normal file
26
app/src/main/res/drawable/ic_share.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18,3C16.895,3 16,3.895 16,5C16,6.105 16.895,7 18,7C19.105,7 20,6.105 20,5C20,3.895 19.105,3 18,3ZM14,5C14,2.791 15.791,1 18,1C20.209,1 22,2.791 22,5C22,7.209 20.209,9 18,9C15.791,9 14,7.209 14,5Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M6,10C4.895,10 4,10.895 4,12C4,13.105 4.895,14 6,14C7.105,14 8,13.105 8,12C8,10.895 7.105,10 6,10ZM2,12C2,9.791 3.791,8 6,8C8.209,8 10,9.791 10,12C10,14.209 8.209,16 6,16C3.791,16 2,14.209 2,12Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M18,17C16.895,17 16,17.895 16,19C16,20.105 16.895,21 18,21C19.105,21 20,20.105 20,19C20,17.895 19.105,17 18,17ZM14,19C14,16.791 15.791,15 18,15C20.209,15 22,16.791 22,19C22,21.209 20.209,23 18,23C15.791,23 14,21.209 14,19Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M7.726,13.007C8.004,12.529 8.616,12.368 9.093,12.646L15.924,16.626C16.401,16.904 16.562,17.516 16.284,17.993C16.006,18.471 15.394,18.632 14.917,18.354L8.087,14.374C7.609,14.096 7.448,13.484 7.726,13.007Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M16.274,6.006C16.552,6.483 16.391,7.095 15.914,7.374L9.094,11.354C8.617,11.632 8.005,11.471 7.726,10.994C7.448,10.517 7.609,9.905 8.086,9.626L14.906,5.646C15.383,5.368 15.995,5.529 16.274,6.006Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
18
app/src/main/res/drawable/ic_vertical_dots.xml
Normal file
18
app/src/main/res/drawable/ic_vertical_dots.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M10,12C10,10.895 10.895,10 12,10C13.105,10 14,10.895 14,12C14,13.105 13.105,14 12,14C10.895,14 10,13.105 10,12Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M10,5C10,3.895 10.895,3 12,3C13.105,3 14,3.895 14,5C14,6.105 13.105,7 12,7C10.895,7 10,6.105 10,5Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M10,19C10,17.895 10.895,17 12,17C13.105,17 14,17.895 14,19C14,20.105 13.105,21 12,21C10.895,21 10,20.105 10,19Z"
|
||||
android:fillColor="#1F002A"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
@@ -1,4 +1,10 @@
|
||||
<resources>
|
||||
<string name="app_name">UiTron</string>
|
||||
<string name="title_activity_main">UiTron Demo Home</string>
|
||||
<string name="filter_by">FILTER BY ></string>
|
||||
<string name="clear_all">Clear All</string>
|
||||
<string name="apply_filters">Apply Filters</string>
|
||||
<string name="copy_widget_config">Copy widget config</string>
|
||||
<string name="share_widget_config">Share widget config</string>
|
||||
<string name="search">Search</string>
|
||||
</resources>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user