├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── themes.xml
│ │ │ └── colors.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── xml
│ │ │ ├── backup_rules.xml
│ │ │ └── data_extraction_rules.xml
│ │ └── drawable
│ │ │ ├── ic_launcher_foreground.xml
│ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ └── com
│ │ │ └── flexcode
│ │ │ └── sduicompose
│ │ │ ├── SDUIComposeApp.kt
│ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle.kts
├── data
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── flexcode
│ │ └── sduicompose
│ │ └── data
│ │ └── GamesRepositoryImpl.kt
├── build.gradle.kts
└── proguard-rules.pro
├── di
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── flexcode
│ │ └── sduicompose
│ │ └── di
│ │ ├── utils
│ │ └── SDUIDispatchers.kt
│ │ ├── DataModule.kt
│ │ ├── DispatchersModule.kt
│ │ └── NetworkModule.kt
├── proguard-rules.pro
└── build.gradle.kts
├── domain
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── flexcode
│ │ └── sduicompose
│ │ └── domain
│ │ ├── models
│ │ ├── UiComponent.kt
│ │ ├── OrderedUiComponent.kt
│ │ ├── ScreenUi.kt
│ │ ├── TextUi.kt
│ │ ├── DpSizeUi.kt
│ │ ├── GamesUi.kt
│ │ ├── ListUi.kt
│ │ ├── ComponentExtensions.kt
│ │ └── JsonElement.kt
│ │ └── repository
│ │ └── GamesRepository.kt
├── build.gradle.kts
└── proguard-rules.pro
├── navigation
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ └── AndroidManifest.xml
├── build.gradle.kts
└── proguard-rules.pro
├── designSystem
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── res
│ │ └── font
│ │ │ ├── urbanist_bold.ttf
│ │ │ ├── urbanist_thin.ttf
│ │ │ ├── urbanist_black.ttf
│ │ │ ├── urbanist_light.ttf
│ │ │ ├── urbanist_medium.ttf
│ │ │ ├── urbanist_regular.ttf
│ │ │ ├── urbanist_semi_bold.ttf
│ │ │ └── urbanist_extra_light.ttf
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── flexcode
│ │ └── sduicompose
│ │ └── designsystem
│ │ ├── preview
│ │ └── SDUIPreview.kt
│ │ ├── uiVersion
│ │ └── UiVersion.kt
│ │ ├── serverUiBuilders
│ │ └── toFontWeight.kt
│ │ ├── extension
│ │ └── UiExtensions.kt
│ │ ├── theme
│ │ ├── Color.kt
│ │ ├── Spacing.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ │ ├── components
│ │ ├── Image.kt
│ │ └── Text.kt
│ │ └── extensions
│ │ └── ShimmerCompose.kt
├── build.gradle.kts
└── proguard-rules.pro
├── presentation
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── flexcode
│ │ └── sduicompose
│ │ ├── HomeViewModel.kt
│ │ ├── HomeScreen.kt
│ │ └── components
│ │ └── ImageComponent.kt
├── build.gradle.kts
└── proguard-rules.pro
├── screenshots
└── sdui.png
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .gitignore
├── settings.gradle.kts
├── .github
└── workflows
│ └── build.yml
├── LICENSE
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/di/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/di/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/navigation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/designSystem/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/designSystem/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/navigation/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/presentation/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/screenshots/sdui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/screenshots/sdui.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SDUICompose
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/designSystem/src/main/res/font/urbanist_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/designSystem/src/main/res/font/urbanist_bold.ttf
--------------------------------------------------------------------------------
/designSystem/src/main/res/font/urbanist_thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/designSystem/src/main/res/font/urbanist_thin.ttf
--------------------------------------------------------------------------------
/designSystem/src/main/res/font/urbanist_black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/designSystem/src/main/res/font/urbanist_black.ttf
--------------------------------------------------------------------------------
/designSystem/src/main/res/font/urbanist_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/designSystem/src/main/res/font/urbanist_light.ttf
--------------------------------------------------------------------------------
/designSystem/src/main/res/font/urbanist_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/designSystem/src/main/res/font/urbanist_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/designSystem/src/main/res/font/urbanist_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/designSystem/src/main/res/font/urbanist_regular.ttf
--------------------------------------------------------------------------------
/designSystem/src/main/res/font/urbanist_semi_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/designSystem/src/main/res/font/urbanist_semi_bold.ttf
--------------------------------------------------------------------------------
/di/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/designSystem/src/main/res/font/urbanist_extra_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Felix-Kariuki/SDUI-Compose/HEAD/designSystem/src/main/res/font/urbanist_extra_light.ttf
--------------------------------------------------------------------------------
/navigation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/designSystem/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/presentation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flexcode/sduicompose/SDUIComposeApp.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class SDUIComposeApp : Application()
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/UiComponent.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.models
2 |
3 | import androidx.compose.runtime.Stable
4 | import kotlinx.serialization.Serializable
5 |
6 | @Stable
7 | @Serializable
8 | sealed interface UiComponent
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Sep 21 09:24:00 EAT 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | /local.properties
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/repository/GamesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.repository
2 |
3 | import com.flexcode.sduicompose.domain.models.GamesUi
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface GamesRepository {
7 | fun fetchGamesUi(): Flow>
8 | }
9 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/OrderedUiComponent.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.models
2 |
3 | import androidx.compose.runtime.Stable
4 | import kotlinx.serialization.Serializable
5 |
6 | @Stable
7 | @Serializable
8 | sealed interface OrderedUiComponent : UiComponent {
9 | val order: Int
10 | }
11 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/ScreenUi.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.models
2 |
3 | import androidx.compose.runtime.Immutable
4 | import kotlinx.serialization.Serializable
5 |
6 | @Immutable
7 | @Serializable
8 | data class ScreenUi(
9 | val version: Int?,
10 | val components: List
11 | )
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/TextUi.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.models
2 |
3 | import androidx.compose.runtime.Immutable
4 | import kotlinx.serialization.Serializable
5 |
6 | @Immutable
7 | @Serializable
8 | data class TextUi(
9 | val text: String,
10 | val size: Int,
11 | val fontWeight: String
12 | ) : UiComponent
--------------------------------------------------------------------------------
/di/src/main/java/com/flexcode/sduicompose/di/utils/SDUIDispatchers.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.di.utils
2 |
3 | import javax.inject.Qualifier
4 | import kotlin.annotation.AnnotationRetention.RUNTIME
5 |
6 | @Qualifier
7 | @Retention(RUNTIME)
8 | annotation class Dispatcher(val dispatchers: SDUIDispatchers)
9 |
10 | enum class SDUIDispatchers {
11 | IO, MAIN
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/preview/SDUIPreview.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.preview
2 |
3 | import android.content.res.Configuration
4 | import android.content.res.Configuration.UI_MODE_NIGHT_YES
5 | import android.content.res.Configuration.UI_MODE_TYPE_NORMAL
6 | import androidx.compose.ui.tooling.preview.Preview
7 |
8 | @Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
9 | @Preview(name = "Light Mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
10 | annotation class SDUIPreview
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("flexcode.android.library")
3 | id("flexcode.spotless")
4 | alias(libs.plugins.hilt)
5 | alias(libs.plugins.ksp)
6 | }
7 |
8 | android {
9 | namespace = "com.flexcode.sduicompose.data"
10 | }
11 |
12 | dependencies {
13 | api(projects.domain)
14 |
15 | implementation(libs.kotlinx.serialization.json)
16 |
17 | implementation(platform(libs.firebase.bom))
18 | api(libs.firebase.database.ktx)
19 | api(libs.firebase.database)
20 |
21 | api(libs.hilt.android)
22 | ksp(libs.hilt.compiler)
23 | }
--------------------------------------------------------------------------------
/presentation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("flexcode.android.library")
3 | id("flexcode.android.library.compose")
4 | id("flexcode.android.feature")
5 | alias(libs.plugins.hilt)
6 | alias(libs.plugins.ksp)
7 | id("flexcode.spotless")
8 | }
9 |
10 | android {
11 | namespace = "com.flexcode.sduicompose"
12 |
13 | }
14 |
15 | dependencies {
16 | api(projects.domain)
17 |
18 | api(libs.hilt.android)
19 | ksp(libs.hilt.compiler)
20 | implementation(libs.androidx.hilt.navigation.compose)
21 | implementation(libs.coil.compose)
22 | }
--------------------------------------------------------------------------------
/di/src/main/java/com/flexcode/sduicompose/di/DataModule.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.di
2 |
3 | import com.flexcode.sduicompose.data.GamesRepositoryImpl
4 | import com.flexcode.sduicompose.domain.repository.GamesRepository
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 |
10 | @[
11 | Module
12 | InstallIn(SingletonComponent::class)
13 | ]
14 | internal interface DataModule {
15 |
16 | @Binds
17 | fun bindsGamesRepository(gamesRepoImpl: GamesRepositoryImpl): GamesRepository
18 | }
19 |
--------------------------------------------------------------------------------
/navigation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("flexcode.android.library")
3 | id("flexcode.android.library.compose")
4 | id("flexcode.android.hilt")
5 | id(libs.plugins.kotlin.serialization.get().pluginId)
6 | id("flexcode.spotless")
7 | }
8 |
9 | android {
10 | namespace = "com.flexcode.sduicompose.navigation"
11 | }
12 |
13 | dependencies {
14 | implementation(projects.designSystem)
15 | api(projects.domain)
16 |
17 | implementation(libs.kotlinx.coroutines.android)
18 | api(libs.androidx.navigation.compose)
19 |
20 | // Serialization
21 | implementation(libs.kotlinx.serialization.json)
22 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/DpSizeUi.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.models
2 |
3 | import android.os.Parcelable
4 | import androidx.compose.runtime.Immutable
5 | import kotlinx.parcelize.Parcelize
6 | import kotlinx.serialization.Serializable
7 |
8 | /**
9 | * Represent the dp size of a [UiComponent] in Jetpack Compose.
10 | *
11 | * The value 0 is the same as the fill max to the parent.
12 | * The value -1 is the same as the wrap to the children
13 | */
14 | @Immutable
15 | @Parcelize
16 | @Serializable
17 | data class DpSizeUi(
18 | val width: Int,
19 | val height: Int
20 | ) : Parcelable
--------------------------------------------------------------------------------
/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("flexcode.android.library")
3 | id("kotlin-parcelize")
4 | id(libs.plugins.kotlin.serialization.get().pluginId)
5 | id("flexcode.spotless")
6 | alias(libs.plugins.hilt)
7 | alias(libs.plugins.ksp)
8 | }
9 |
10 | android {
11 | namespace = "com.flexcode.sduicompose.domain"
12 | }
13 |
14 | dependencies {
15 | implementation(libs.kotlinx.reflect)
16 | api(libs.kotlinx.serialization.json)
17 | implementation(libs.kotlinx.coroutines.android)
18 |
19 | api(libs.hilt.android)
20 | ksp(libs.hilt.compiler)
21 | implementation(libs.androidx.hilt.navigation.compose)
22 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/GamesUi.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class GamesUi(
7 | val version: Int,
8 | val games: TimelineBottomUi
9 | ) : UiComponent
10 |
11 | @Serializable
12 | data class ImageUi(
13 | val title: String = "",
14 | val short_description: String,
15 | val url: String,
16 | val size: DpSizeUi = DpSizeUi(0, 0),
17 | val scaleType: String,
18 | ) : UiComponent
19 |
20 | @Serializable
21 | data class TimelineBottomUi(
22 | override val order: Int,
23 | val title: TextUi,
24 | val list: ListUi
25 | ) : OrderedUiComponent
--------------------------------------------------------------------------------
/di/src/main/java/com/flexcode/sduicompose/di/DispatchersModule.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.di
2 |
3 | import com.flexcode.sduicompose.di.utils.Dispatcher
4 | import com.flexcode.sduicompose.di.utils.SDUIDispatchers
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import kotlinx.coroutines.CoroutineDispatcher
10 | import kotlinx.coroutines.Dispatchers
11 |
12 | @[
13 | Module
14 | InstallIn(SingletonComponent::class)
15 | ]
16 | object DispatchersModule {
17 |
18 | @[
19 | Provides
20 | Dispatcher(SDUIDispatchers.IO)
21 | ]
22 | fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
23 | }
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/uiVersion/UiVersion.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.uiVersion
2 |
3 | import com.flexcode.sduicompose.domain.models.ScreenUi
4 |
5 |
6 | enum class UiVersion(val value: Int) {
7 | VERSION_1_0(1),
8 | VERSION_2_0(2);
9 |
10 | companion object {
11 |
12 | fun toUiVersion(value: Int?): UiVersion {
13 | return when (value) {
14 | VERSION_1_0.value -> VERSION_1_0
15 | VERSION_2_0.value -> VERSION_2_0
16 | else -> VERSION_1_0
17 | }
18 | }
19 | }
20 | }
21 |
22 | val ScreenUi.uiVersion: UiVersion
23 | get() = UiVersion.toUiVersion(version)
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/flexcode/sduicompose/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.activity.enableEdgeToEdge
7 | import com.flexcode.sduicompose.designsystem.theme.SDUIComposeTheme
8 | import dagger.hilt.android.AndroidEntryPoint
9 |
10 | @AndroidEntryPoint
11 | class MainActivity : ComponentActivity() {
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | enableEdgeToEdge()
15 | setContent {
16 | SDUIComposeTheme {
17 | HomeRootScreen()
18 | }
19 | }
20 | }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/designSystem/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("flexcode.android.library")
3 | id("flexcode.android.library.compose")
4 | id("flexcode.spotless")
5 | }
6 |
7 | android {
8 | namespace = "com.flexcode.sduicompose.designsystem"
9 | }
10 |
11 | dependencies {
12 | api(projects.domain)
13 |
14 | api(libs.androidx.compose.runtime)
15 | api(libs.androidx.compose.ui)
16 | api(libs.androidx.compose.ui.tooling)
17 | api(libs.androidx.compose.ui.tooling.preview)
18 | api(libs.androidx.compose.material.iconsExtended)
19 | api(libs.androidx.compose.material3)
20 | api(libs.androidx.compose.foundation)
21 | api(libs.androidx.compose.foundation.layout)
22 | api(libs.androidx.compose.constraintlayout)
23 | implementation(libs.coil.compose)
24 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | includeBuild("build-logic")
3 | repositories {
4 | google {
5 | content {
6 | includeGroupByRegex("com\\.android.*")
7 | includeGroupByRegex("com\\.google.*")
8 | includeGroupByRegex("androidx.*")
9 | }
10 | }
11 | mavenCentral()
12 | gradlePluginPortal()
13 | }
14 | }
15 | dependencyResolutionManagement {
16 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
17 | repositories {
18 | google()
19 | mavenCentral()
20 | }
21 | }
22 |
23 | rootProject.name = "SDUICompose"
24 | include(":app")
25 | include(":navigation")
26 | include(":di")
27 | include(":designSystem")
28 | include(":data")
29 | include(":presentation")
30 | include(":domain")
31 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/di/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/designSystem/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/navigation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/presentation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/di/src/main/java/com/flexcode/sduicompose/di/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.di
2 |
3 | import com.google.firebase.Firebase
4 | import com.google.firebase.database.DatabaseReference
5 | import com.google.firebase.database.database
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import kotlinx.serialization.json.Json
11 | import javax.inject.Singleton
12 |
13 | @[
14 | Module
15 | InstallIn(SingletonComponent::class)
16 | ]
17 |
18 | internal object NetworkModule {
19 |
20 | @Provides
21 | @Singleton
22 | fun provideDatabaseReference(): DatabaseReference {
23 | return Firebase.database(BuildConfig.REALTIME_DATABASE_URL).reference
24 | }
25 |
26 | @Provides
27 | @Singleton
28 | fun provideJson(): Json {
29 | return Json {
30 | isLenient = true
31 | ignoreUnknownKeys = true
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/flexcode/sduicompose/data/GamesRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.data
2 |
3 | import com.flexcode.sduicompose.domain.models.GamesUi
4 | import com.flexcode.sduicompose.domain.repository.GamesRepository
5 | import com.google.firebase.database.DatabaseReference
6 | import com.skydoves.firebase.database.ktx.flow
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.serialization.json.Json
9 | import javax.inject.Inject
10 |
11 | class GamesRepositoryImpl @Inject constructor(
12 | private val databaseReference: DatabaseReference,
13 | private val json: Json
14 | ) : GamesRepository {
15 | override fun fetchGamesUi(): Flow> {
16 | return databaseReference.flow(
17 | path = { snapshot ->
18 | snapshot.child("gamesCompose")
19 | },
20 | decodeProvider = { jsonString ->
21 | json.decodeFromString(jsonString)
22 | }
23 | )
24 | }
25 | }
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/serverUiBuilders/toFontWeight.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.serverUiBuilders
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.layout.ContentScale
6 | import androidx.compose.ui.text.TextStyle
7 | import androidx.compose.ui.text.font.FontWeight
8 |
9 | fun String.toFontWeight(): FontWeight {
10 | return when (this) {
11 | "bold" -> FontWeight.Bold
12 | "medium" -> FontWeight.Medium
13 | else -> FontWeight.Normal
14 | }
15 | }
16 |
17 | @Composable
18 | fun String.toTypography(): TextStyle {
19 | return when (this) {
20 | "bold" -> MaterialTheme.typography.titleSmall
21 | "medium" -> MaterialTheme.typography.bodyLarge
22 | else -> MaterialTheme.typography.bodyLarge
23 | }
24 | }
25 |
26 | fun String.toContentScale(): ContentScale {
27 | return when (this) {
28 | "crop" -> ContentScale.Crop
29 | "fit" -> ContentScale.Fit
30 | "fill" -> ContentScale.FillBounds
31 | else -> ContentScale.None
32 | }
33 | }
--------------------------------------------------------------------------------
/di/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("flexcode.android.library")
3 | alias(libs.plugins.hilt)
4 | alias(libs.plugins.ksp)
5 | id(libs.plugins.kotlin.serialization.get().pluginId)
6 | id(libs.plugins.google.secrets.get().pluginId)
7 | id("flexcode.spotless")
8 | }
9 |
10 | android {
11 | namespace = "com.flexcode.sduicompose.di"
12 |
13 | buildFeatures {
14 | buildConfig = true
15 | }
16 | }
17 |
18 | dependencies {
19 | api(projects.domain)
20 | api(projects.data)
21 |
22 | implementation(libs.androidx.startup)
23 |
24 | api(libs.okhttp.logging)
25 | implementation(platform(libs.retrofit.bom))
26 | implementation(libs.retrofit)
27 | implementation(libs.retrofit.kotlinx.serialization)
28 |
29 | implementation(libs.kotlinx.serialization.json)
30 |
31 | implementation(platform(libs.firebase.bom))
32 | api(libs.firebase.database)
33 |
34 | api(libs.hilt.android)
35 | ksp(libs.hilt.compiler)
36 | }
37 |
38 | secrets {
39 | propertiesFileName = "local.properties"
40 | defaultPropertiesFileName = "local.properties"
41 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/ListUi.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.models
2 |
3 | import androidx.compose.runtime.Immutable
4 | import kotlinx.serialization.Serializable
5 | import kotlinx.serialization.json.JsonElement
6 |
7 | @Immutable
8 | @Serializable
9 | data class ListUi(
10 | val layout: String,
11 | val itemSize: DpSizeUi,
12 | val items: List,
13 | val extra: Map = mapOf(),
14 | val testStyles: ListTextUi
15 | ) : UiComponent
16 |
17 | @Immutable
18 | @Serializable
19 | data class ListTextUi(
20 | val subTitleFontWeight: String,
21 | val subTitleSize: Int,
22 | val titleFontWeight: String,
23 | val titleSize: Int,
24 | val heightImage: Int,
25 | val widthImage: Int
26 | ) : UiComponent
27 |
28 | fun String.toLayoutType(): LayoutType {
29 | return if (this == "grid") LayoutType.GRID
30 | else if (this == "column") LayoutType.COLUMN
31 | else LayoutType.ROW
32 | }
33 |
34 | enum class LayoutType(val value: String) {
35 | GRID("grid"), COLUMN("column"), ROW("row")
36 | }
37 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 | - name: setup JDK 17
16 | uses: actions/setup-java@v4
17 | with:
18 | distribution: 'zulu'
19 | java-version: '17'
20 | cache: 'gradle'
21 |
22 | - name: Grant execute permission for gradlew
23 | run: chmod +x gradlew
24 |
25 | - name: Cache Gradle
26 | uses: actions/cache@v4
27 | with:
28 | path: ~/.gradle/caches
29 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
30 | restore-keys: |
31 | ${{ runner.os }}-gradle-
32 | # - name: Run ktlintFormat
33 | # run: ./gradlew ktlintFormat
34 | #
35 | # - name: Run ktlintCheck
36 | # run: ./gradlew ktlintCheck
37 |
38 | - name: Run Unit Tests
39 | run: ./gradlew test
40 |
41 | - name: Run debug Build
42 | run: ./gradlew assembleDebug
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Felix Kariuki
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/extension/UiExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.extension
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.composed
7 | import androidx.compose.ui.platform.LocalConfiguration
8 | import androidx.compose.ui.unit.Dp
9 | import androidx.compose.ui.unit.dp
10 | import com.flexcode.sduicompose.domain.models.DpSizeUi
11 |
12 | fun DpSizeUi.widthDp(): Dp = width.dp
13 |
14 | fun DpSizeUi.heightDp(): Dp = height.dp
15 |
16 | @SuppressLint("ModifierFactoryUnreferencedReceiver")
17 | fun Modifier.size(dpSizeUi: DpSizeUi): Modifier = composed {
18 | val configuration = LocalConfiguration.current
19 | val width = if (dpSizeUi.width == 0) {
20 | configuration.screenWidthDp.dp
21 | } else {
22 | dpSizeUi.widthDp()
23 | }
24 | val height = if (dpSizeUi.height == 0) {
25 | configuration.screenHeightDp.dp
26 | } else {
27 | dpSizeUi.heightDp()
28 | }
29 | size(width = width, height = height)
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/ComponentExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.domain.models
2 |
3 | import kotlin.reflect.full.memberProperties
4 |
5 | fun UiComponent.buildUiComponentList(
6 | uiComponents: List = emptyList()
7 | ): List {
8 | val mutableUiLists = mutableListOf()
9 | mutableUiLists.addAll(uiComponents)
10 |
11 |
12 | this.javaClass.kotlin.memberProperties.sortedBy { member ->
13 | val uiComponent = member.get(this) as? OrderedUiComponent
14 | uiComponent?.order
15 | }.forEach { parent ->
16 | val uiComponent = parent.get(this) as? UiComponent ?: return@forEach
17 | var isNested = false
18 | uiComponent.javaClass.kotlin.memberProperties.reversed().forEach { child ->
19 | val childComponent = child.get(uiComponent) as? UiComponent
20 | if (childComponent != null) {
21 | mutableUiLists.add(childComponent)
22 | childComponent.buildUiComponentList(mutableUiLists)
23 | isNested = true
24 | }
25 | }
26 |
27 | if (!isNested) {
28 | mutableUiLists.add(uiComponent)
29 | }
30 | }
31 | return mutableUiLists
32 | }
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | internal val PrimaryPink = Color(0xFFFF3EA5)
6 | internal val PinkDisabled = Color(0xFFCC3284) // primary container
7 | internal val OnPrimary = Color(0xFFFFFFFF)
8 | internal val Red = Color(0xFFF75555)
9 | internal val SuccessGreen = Color(0XFF12D18E)
10 | internal val WarningOrange = Color(0XFFFACC15)
11 | internal val BackgroundDark = Color(0xFF000000)
12 | internal val BackgroundLight = Color(0xFFFFFFFF)
13 | internal val BackgroundVariantLight = Color(0xFFFAFAFA)
14 | internal val BackgroundVariantDark = Color(0xFF0A0A0E)
15 | internal val SecondaryContainer = Color(0xFF1F222A)
16 | internal val SecondaryContainerLight = Color(0xFFFAFAFA)
17 |
18 | internal val BorderGray = Color(0xFFF5F5F5)
19 | internal val BorderGrayDark = Color(0xFF35383E)
20 | internal val HintGray = Color(0xFF9E9E9E)
21 | internal val TextGray = Color(0xFF616161)
22 | internal val TextGrayDark = Color(0xFFEEEEEE)
23 |
24 | internal val ButtonSurfaceVariant = Color(0xFFFFF0F8)
25 | internal val ButtonOnSurfaceVariant = Color(0xFFFF3EA5)
26 | internal val ButtonSurfaceVariantDark = Color(0xFF141414)
27 | internal val ButtonOnSurfaceVariantDark = Color(0xFFFEFFFF)
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/components/Image.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.runtime.setValue
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.layout.ContentScale
12 | import androidx.compose.ui.platform.LocalContext
13 | import coil.compose.AsyncImage
14 | import coil.request.ImageRequest
15 | import com.flexcode.sduicompose.designsystem.extensions.shimmerEffect
16 |
17 | @Composable
18 | fun SDUIImage(
19 | image: Any?,
20 | modifier: Modifier = Modifier,
21 | ) {
22 | var showShimmer by remember { mutableStateOf(true) }
23 |
24 | AsyncImage(
25 | model =
26 | ImageRequest.Builder(LocalContext.current)
27 | .data(image)
28 | .crossfade(true)
29 | .build(),
30 | contentDescription = null,
31 | contentScale = ContentScale.Crop,
32 | modifier =
33 | modifier
34 | .fillMaxWidth()
35 | .background(shimmerEffect()),
36 | onSuccess = { showShimmer = false },
37 | )
38 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/presentation/src/main/java/com/flexcode/sduicompose/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.flexcode.sduicompose.domain.models.ScreenUi
6 | import com.flexcode.sduicompose.domain.models.buildUiComponentList
7 | import com.flexcode.sduicompose.domain.repository.GamesRepository
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.SharingStarted
10 | import kotlinx.coroutines.flow.StateFlow
11 | import kotlinx.coroutines.flow.filterNotNull
12 | import kotlinx.coroutines.flow.flatMapLatest
13 | import kotlinx.coroutines.flow.flowOf
14 | import kotlinx.coroutines.flow.map
15 | import kotlinx.coroutines.flow.stateIn
16 | import javax.inject.Inject
17 |
18 | @HiltViewModel
19 | class HomeViewModel @Inject constructor(
20 | repository: GamesRepository
21 | ) : ViewModel() {
22 |
23 | val gamesUi: StateFlow = repository.fetchGamesUi()
24 | .flatMapLatest { response -> flowOf(response.getOrNull()) }
25 | .filterNotNull()
26 | .map {
27 | ScreenUi(
28 | version = it.version,
29 | components = it.buildUiComponentList()
30 | )
31 | }
32 | .stateIn(
33 | scope = viewModelScope,
34 | started = SharingStarted.WhileSubscribed(5000L),
35 | initialValue = null
36 | )
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/flexcode/sduicompose/domain/models/JsonElement.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UNCHECKED_CAST")
2 |
3 | package com.flexcode.sduicompose.domain.models
4 |
5 | import kotlinx.serialization.json.JsonArray
6 | import kotlinx.serialization.json.JsonElement
7 | import kotlinx.serialization.json.JsonNull
8 | import kotlinx.serialization.json.JsonObject
9 | import kotlinx.serialization.json.JsonPrimitive
10 |
11 | private fun JsonPrimitive.toAnyValue(): Any? {
12 | val content = this.content
13 | if (this.isString) {
14 | // add custom string convert
15 | return content
16 | }
17 | if (content.equals("null", ignoreCase = true)) {
18 | return null
19 | }
20 | if (content.equals("true", ignoreCase = true)) {
21 | return true
22 | }
23 | if (content.equals("false", ignoreCase = true)) {
24 | return false
25 | }
26 | val intValue = content.toIntOrNull()
27 | if (intValue != null) {
28 | return intValue
29 | }
30 | val longValue = content.toLongOrNull()
31 | if (longValue != null) {
32 | return longValue
33 | }
34 | val doubleValue = content.toDoubleOrNull()
35 | if (doubleValue != null) {
36 | return doubleValue
37 | }
38 | throw Exception("Parsing exception")
39 | }
40 |
41 | fun JsonElement?.toAnyOrNull(): Any? {
42 | return when (this) {
43 | null -> null
44 | is JsonNull -> null
45 | is JsonPrimitive -> toAnyValue()
46 | // !!! key convert back custom object
47 | is JsonObject -> this.map { it.key to it.value.toAnyOrNull() }.toMap()
48 | is JsonArray -> this.map { it.toAnyOrNull() }
49 | }
50 | }
51 |
52 | fun JsonElement?.typedValue(default: T): T {
53 | return toAnyOrNull() as? T ?: default
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/components/Text.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.components
2 |
3 | import androidx.compose.foundation.layout.padding
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.text.TextStyle
10 | import androidx.compose.ui.text.style.TextAlign
11 | import androidx.compose.ui.text.style.TextDecoration
12 | import androidx.compose.ui.text.style.TextOverflow
13 | import androidx.compose.ui.unit.Dp
14 | import androidx.compose.ui.unit.dp
15 | import androidx.compose.ui.unit.sp
16 | import com.flexcode.sduicompose.designsystem.preview.SDUIPreview
17 | import com.flexcode.sduicompose.designsystem.theme.SDUIComposeTheme
18 |
19 | @Composable
20 | fun SduiText(
21 | modifier: Modifier = Modifier,
22 | text: String,
23 | textColor: Color = MaterialTheme.colorScheme.onBackground,
24 | style: TextStyle = MaterialTheme.typography.bodyLarge.copy(fontSize = 16.sp),
25 | overflow: TextOverflow = TextOverflow.Ellipsis,
26 | maxLines: Int = Int.MAX_VALUE,
27 | textPadding: Dp = 0.dp,
28 | textDecoration: TextDecoration = TextDecoration.None,
29 | textAlign: TextAlign = TextAlign.Start,
30 | ) {
31 | Text(
32 | text = text,
33 | style = style,
34 | color = textColor,
35 | maxLines = maxLines,
36 | overflow = overflow,
37 | modifier = modifier.padding(textPadding),
38 | textDecoration = textDecoration,
39 | textAlign = textAlign,
40 | )
41 | }
42 |
43 | @SDUIPreview
44 | @Composable
45 | private fun MingleTextPreview() {
46 | SDUIComposeTheme {
47 | SduiText(text = "SDUI Compose")
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/theme/Spacing.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.theme
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.ReadOnlyComposable
6 | import androidx.compose.runtime.compositionLocalOf
7 | import androidx.compose.ui.unit.Dp
8 | import androidx.compose.ui.unit.dp
9 |
10 | /**
11 | * Spacing for entire app to ensure consistency
12 | * @property [none] default spacing which is 0.dp
13 | * @property [extraSmall] the smalled type spacing 4.dp
14 | * @property [small] 8.dp spacing
15 | * @property[bigSmall] 12.dp
16 | * @property [medium] 16.dp spacing used for app's edge to edge (start,end,top,bottom) spacing
17 | * for holding container columns or surface
18 | * @property [extraMedium] 24.dp
19 | * @property [buttonSpacing] text padding for the [com.business.tripitaca.designSystem.compossables.CommonButton]
20 | * @property [extraLarge] 64.dp spacing
21 | * @property [large] 32.dp spacing
22 | * @property [bottomLarge] 120.dp the padding below in views to create a spacing at the bottom of
23 | * the screen especially in dashboard screen where there are is a bottom sheet
24 | */
25 | data class Spacing(
26 | val none: Dp = 0.dp,
27 | val extraSmall: Dp = 4.dp,
28 | val buttonSpacing: Dp = 6.dp,
29 | val small: Dp = 8.dp,
30 | val bigSmall: Dp = 12.dp,
31 | val medium: Dp = 16.dp,
32 | val extraMedium: Dp = 24.dp,
33 | val large: Dp = 32.dp,
34 | val extraLarge: Dp = 64.dp,
35 | val bottomLarge: Dp = 120.dp,
36 | )
37 |
38 | /**
39 | * composition local to allow passing down of spacing
40 | */
41 | internal val LocalSpacing = compositionLocalOf { Spacing() }
42 |
43 | /**
44 | * Retrieves the current [Spacing] at the call site's position in the hierarchy.
45 | */
46 | val MaterialTheme.spacing: Spacing
47 | @Composable
48 | @ReadOnlyComposable
49 | get() = LocalSpacing.current
50 |
51 | /**
52 | * function that returns [Spacing]
53 | * @return [Spacing] top avoid repetition of MaterialTheme.Spacing
54 | * @see spacing
55 | */
56 |
57 | @Composable
58 | internal fun spacing(): Spacing {
59 | return MaterialTheme.spacing
60 | }
61 |
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.theme
2 |
3 | import android.os.Build
4 | import androidx.compose.foundation.isSystemInDarkTheme
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.darkColorScheme
7 | import androidx.compose.material3.dynamicDarkColorScheme
8 | import androidx.compose.material3.dynamicLightColorScheme
9 | import androidx.compose.material3.lightColorScheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import androidx.compose.ui.platform.LocalContext
13 |
14 | private val DarkColorScheme = darkColorScheme(
15 | primary = PrimaryPink,
16 | onPrimary = OnPrimary,
17 | inversePrimary = TextGrayDark,
18 | primaryContainer = PinkDisabled,
19 | onPrimaryContainer = OnPrimary,
20 | secondary = SecondaryContainer,
21 | onSecondary = BackgroundLight,
22 | onSurface = BackgroundDark,
23 | error = Red,
24 | onError = OnPrimary,
25 | tertiaryContainer = SuccessGreen,
26 | background = BackgroundDark,
27 | surfaceVariant = BackgroundVariantDark,
28 | onSurfaceVariant = HintGray,
29 | outline = BorderGrayDark,
30 | inverseSurface = ButtonSurfaceVariantDark,
31 | inverseOnSurface = ButtonOnSurfaceVariantDark,
32 |
33 | )
34 |
35 | private val LightColorScheme = lightColorScheme(
36 | primary = PrimaryPink,
37 | onPrimary = OnPrimary,
38 | inversePrimary = TextGray,
39 | primaryContainer = PinkDisabled,
40 | onPrimaryContainer = OnPrimary,
41 | secondary = SecondaryContainerLight,
42 | onSecondary = BackgroundLight,
43 | onSurface = BackgroundLight,
44 | error = Red,
45 | onError = OnPrimary,
46 | tertiaryContainer = SuccessGreen,
47 | background = OnPrimary,
48 | surfaceVariant = BackgroundVariantLight,
49 | onSurfaceVariant = HintGray,
50 | outline = BorderGray,
51 | inverseSurface = ButtonSurfaceVariant,
52 | inverseOnSurface = ButtonOnSurfaceVariant,
53 |
54 | )
55 |
56 |
57 | @Composable
58 | fun SDUIComposeTheme(
59 | darkTheme: Boolean = isSystemInDarkTheme(),
60 | // Dynamic color is available on Android 12+
61 | dynamicColor: Boolean = false,
62 | content: @Composable () -> Unit
63 | ) {
64 | val colorScheme = when {
65 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
66 | val context = LocalContext.current
67 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
68 | }
69 |
70 | darkTheme -> DarkColorScheme
71 | else -> LightColorScheme
72 | }
73 |
74 | CompositionLocalProvider(
75 | LocalSpacing provides spacing()
76 | ) {
77 | MaterialTheme(
78 | colorScheme = colorScheme,
79 | typography = Typography,
80 | content = content
81 | )
82 | }
83 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("flexcode.android.application")
3 | id("flexcode.android.application.compose")
4 | id("flexcode.spotless")
5 | alias(libs.plugins.kotlin.serialization)
6 | alias(libs.plugins.hilt)
7 | alias(libs.plugins.ksp)
8 | }
9 |
10 | android {
11 | namespace = "com.flexcode.sduicompose"
12 | compileSdk = 35
13 |
14 | defaultConfig {
15 | applicationId = "com.flexcode.sduicompose"
16 | minSdk = 24
17 | targetSdk = 35
18 | versionCode = 1
19 | versionName = "1.0"
20 |
21 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
22 | vectorDrawables {
23 | useSupportLibrary = true
24 | }
25 | }
26 |
27 | buildTypes {
28 | release {
29 | isMinifyEnabled = false
30 | proguardFiles(
31 | getDefaultProguardFile("proguard-android-optimize.txt"),
32 | "proguard-rules.pro"
33 | )
34 | }
35 | }
36 | compileOptions {
37 | sourceCompatibility = JavaVersion.VERSION_17
38 | targetCompatibility = JavaVersion.VERSION_17
39 | }
40 |
41 | kotlinOptions {
42 | jvmTarget = "17"
43 | }
44 | buildFeatures {
45 | compose = true
46 | buildConfig = true
47 | }
48 | packaging {
49 | resources {
50 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
51 | }
52 | }
53 | }
54 |
55 | dependencies {
56 |
57 | implementation(projects.designSystem)
58 | implementation(projects.navigation)
59 | implementation(projects.presentation)
60 | implementation(projects.data)
61 | implementation(projects.domain)
62 | implementation(projects.di)
63 |
64 | implementation(libs.androidx.activity.compose)
65 | implementation(libs.androidx.navigation.compose)
66 | implementation(libs.androidx.compose.ui)
67 | implementation(libs.androidx.compose.ui.tooling.preview)
68 | implementation(libs.androidx.compose.runtime)
69 | implementation(libs.androidx.compose.foundation)
70 | implementation(libs.androidx.lifecycle.runtimeCompose)
71 | implementation(libs.androidx.lifecycle.viewModelCompose)
72 | implementation(libs.androidx.compose.material)
73 | implementation(libs.androidx.compose.material.iconsExtended)
74 |
75 |
76 |
77 | implementation(libs.kotlinx.coroutines.android)
78 |
79 | implementation(libs.okhttp.logging)
80 |
81 |
82 | implementation(libs.kotlinx.serialization.json)
83 |
84 |
85 | implementation(platform(libs.firebase.bom))
86 | implementation(libs.firebase.store)
87 | implementation(libs.firebase.remote.config)
88 |
89 | api(libs.hilt.android)
90 | ksp(libs.hilt.compiler)
91 | implementation(libs.androidx.hilt.navigation.compose)
92 |
93 |
94 | implementation(libs.coil.compose)
95 |
96 | }
97 |
98 | if (file("google-services.json").exists()) {
99 | apply(plugin = libs.plugins.gms.googleServices.get().pluginId)
100 | }
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/extensions/ShimmerCompose.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.extensions
2 |
3 | import androidx.compose.animation.core.RepeatMode
4 | import androidx.compose.animation.core.animateFloat
5 | import androidx.compose.animation.core.infiniteRepeatable
6 | import androidx.compose.animation.core.rememberInfiniteTransition
7 | import androidx.compose.animation.core.tween
8 | import androidx.compose.foundation.background
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.composed
16 | import androidx.compose.ui.geometry.Offset
17 | import androidx.compose.ui.graphics.Brush
18 | import androidx.compose.ui.graphics.Brush.Companion.linearGradient
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.layout.onGloballyPositioned
21 | import androidx.compose.ui.unit.IntSize
22 |
23 | fun Modifier.shimmerEffect(): Modifier =
24 | composed {
25 | var size by remember {
26 | mutableStateOf(IntSize.Zero)
27 | }
28 | val transition = rememberInfiniteTransition(label = "shimer_transition")
29 | val startOffsetX by transition.animateFloat(
30 | initialValue = -2 * size.width.toFloat(),
31 | targetValue = 2 * size.width.toFloat(),
32 | animationSpec =
33 | infiniteRepeatable(
34 | animation = tween(1500),
35 | ),
36 | label = "shimmer_modifier",
37 | )
38 |
39 | background(
40 | brush =
41 | linearGradient(
42 | colors =
43 | listOf(
44 | Color(0xFFF5F3F3),
45 | Color(0xFFF2EAEA),
46 | Color(0xFFF5F3F3),
47 | ),
48 | start = Offset(startOffsetX, 0f),
49 | end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat()),
50 | ),
51 | )
52 | .onGloballyPositioned {
53 | size = it.size
54 | }
55 | }
56 |
57 | @Composable
58 | fun shimmerEffect(
59 | showShimmer: Boolean = true,
60 | targetValue: Float = 1300f,
61 | ): Brush {
62 | return if (showShimmer) {
63 | val shimmerColors =
64 | listOf(
65 | Color(0xFFF5F3F3),
66 | Color(0xFFF2EAEA),
67 | Color(0xFFF5F3F3),
68 | )
69 |
70 | val transition = rememberInfiniteTransition(label = "shimmer_background_transition")
71 | val translateAnimation =
72 | transition.animateFloat(
73 | initialValue = 0f,
74 | targetValue = targetValue,
75 | animationSpec =
76 | infiniteRepeatable(
77 | animation = tween(2000),
78 | repeatMode = RepeatMode.Restart,
79 | ),
80 | label = "shimmer_background",
81 | )
82 | linearGradient(
83 | colors = shimmerColors,
84 | start = Offset.Zero,
85 | end = Offset(x = translateAnimation.value, y = translateAnimation.value),
86 | )
87 | } else {
88 | linearGradient(
89 | colors = listOf(Color.Transparent, Color.Transparent),
90 | start = Offset.Zero,
91 | end = Offset.Zero,
92 | )
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/flexcode/sduicompose/HomeScreen.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.PaddingValues
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.lazy.LazyColumn
11 | import androidx.compose.foundation.lazy.items
12 | import androidx.compose.material3.CircularProgressIndicator
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.unit.sp
20 | import androidx.hilt.navigation.compose.hiltViewModel
21 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
22 | import com.flexcode.sduicompose.components.ImageComponent
23 | import com.flexcode.sduicompose.designsystem.components.SduiText
24 | import com.flexcode.sduicompose.designsystem.serverUiBuilders.toTypography
25 | import com.flexcode.sduicompose.designsystem.theme.spacing
26 | import com.flexcode.sduicompose.domain.models.LayoutType
27 | import com.flexcode.sduicompose.domain.models.ListUi
28 | import com.flexcode.sduicompose.domain.models.ScreenUi
29 | import com.flexcode.sduicompose.domain.models.TextUi
30 | import com.flexcode.sduicompose.domain.models.UiComponent
31 | import com.flexcode.sduicompose.domain.models.toLayoutType
32 |
33 | @Composable
34 | fun HomeRootScreen(
35 | viewModel: HomeViewModel = hiltViewModel(),
36 | ) {
37 | val screenUi by viewModel.gamesUi.collectAsStateWithLifecycle()
38 |
39 | if (screenUi != null) {
40 | screenUi?.let {
41 | HomeScreen(
42 | screenUi = it,
43 | )
44 | }
45 |
46 | } else {
47 | Box(
48 | modifier = Modifier
49 | .fillMaxSize()
50 | .background(MaterialTheme.colorScheme.background)
51 | ) {
52 | CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
53 | }
54 | }
55 | }
56 |
57 | @Composable
58 | private fun HomeScreen(
59 | screenUi: ScreenUi,
60 | ) {
61 | Column(
62 | modifier = Modifier
63 | .background(MaterialTheme.colorScheme.background)
64 | .fillMaxSize()
65 | .padding(
66 | start = MaterialTheme.spacing.medium,
67 | end = MaterialTheme.spacing.medium,
68 | top = 40.dp
69 | ),
70 | ) {
71 | screenUi.components.forEach { uiComponent ->
72 | uiComponent.BuildHomeScreen()
73 | }
74 | }
75 | }
76 |
77 | @Composable
78 | fun UiComponent.BuildHomeScreen(
79 | modifier: Modifier = Modifier,
80 | ) {
81 | when (this) {
82 | is TextUi -> {
83 | SduiText(
84 | text = this.text,
85 | style = this.fontWeight.toTypography().copy(
86 | fontSize = this.size.sp
87 | )
88 | )
89 | }
90 |
91 | is ListUi -> {
92 | ImagesList(
93 | listUi = this,
94 | modifier = modifier,
95 | )
96 | }
97 |
98 | else -> {
99 |
100 | }
101 | }
102 |
103 | }
104 |
105 | @Composable
106 | fun ImagesList(
107 | listUi: ListUi,
108 | modifier: Modifier = Modifier,
109 | ) {
110 |
111 | val layoutType = listUi.layout.toLayoutType()
112 |
113 | when (layoutType) {
114 | LayoutType.COLUMN -> {
115 | LazyColumn(
116 | modifier = modifier
117 | .fillMaxWidth()
118 | .padding(top = MaterialTheme.spacing.medium),
119 | contentPadding = PaddingValues(MaterialTheme.spacing.none)
120 | ) {
121 | items(items = listUi.items, key = { it.url }) { imageUi ->
122 | ImageComponent(imageUi = imageUi, testStyles = listUi.testStyles)
123 | }
124 | }
125 | }
126 |
127 | else -> {
128 | // handle other types
129 | }
130 | }
131 |
132 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/flexcode/sduicompose/components/ImageComponent.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.components
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.material.icons.Icons
12 | import androidx.compose.material.icons.filled.Laptop
13 | import androidx.compose.material3.Icon
14 | import androidx.compose.material3.MaterialTheme
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.clip
18 | import androidx.compose.ui.text.font.FontWeight
19 | import androidx.compose.ui.unit.dp
20 | import androidx.compose.ui.unit.sp
21 | import com.flexcode.sduicompose.BuildHomeScreen
22 | import com.flexcode.sduicompose.designsystem.components.SDUIImage
23 | import com.flexcode.sduicompose.designsystem.components.SduiText
24 | import com.flexcode.sduicompose.designsystem.extension.heightDp
25 | import com.flexcode.sduicompose.designsystem.extension.size
26 | import com.flexcode.sduicompose.designsystem.preview.SDUIPreview
27 | import com.flexcode.sduicompose.designsystem.serverUiBuilders.toTypography
28 | import com.flexcode.sduicompose.designsystem.theme.SDUIComposeTheme
29 | import com.flexcode.sduicompose.designsystem.theme.spacing
30 | import com.flexcode.sduicompose.domain.models.DpSizeUi
31 | import com.flexcode.sduicompose.domain.models.ImageUi
32 | import com.flexcode.sduicompose.domain.models.ListTextUi
33 | import kotlin.random.Random
34 |
35 | @Composable
36 | fun ImageComponent(
37 | modifier: Modifier = Modifier,
38 | imageUi: ImageUi,
39 | testStyles: ListTextUi
40 | ) {
41 | Column(
42 | modifier = modifier
43 | .fillMaxWidth()
44 | .background(MaterialTheme.colorScheme.surfaceVariant),
45 | verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small)
46 | ) {
47 | SDUIImage(
48 | image = imageUi.url,
49 | modifier = modifier
50 | .clip(MaterialTheme.shapes.medium)
51 | .height(testStyles.heightImage.dp)
52 | )
53 |
54 | Row(
55 | modifier.fillMaxWidth(),
56 | horizontalArrangement = Arrangement.SpaceBetween
57 | ) {
58 | SduiText(
59 | text = imageUi.title,
60 | style = testStyles.titleFontWeight.toTypography().copy(
61 | fontSize = testStyles.titleSize.sp
62 | )
63 | )
64 | }
65 |
66 | SduiText(
67 | text = imageUi.short_description,
68 | style = testStyles.subTitleFontWeight.toTypography().copy(
69 | fontSize = testStyles.subTitleSize.sp
70 | )
71 | )
72 |
73 | Row(
74 | modifier
75 | .fillMaxWidth()
76 | .padding(end = MaterialTheme.spacing.small),
77 | horizontalArrangement = Arrangement.End
78 | ) {
79 |
80 | Row(
81 | horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.large)
82 | ) {
83 |
84 | Icon(
85 | imageVector = Icons.Filled.Laptop, contentDescription = null,
86 | tint = MaterialTheme.colorScheme.onBackground
87 | )
88 |
89 | }
90 | }
91 |
92 | Spacer(modifier = modifier.height(MaterialTheme.spacing.small))
93 | }
94 |
95 |
96 | }
97 |
98 | val sampleImageUi: ImageUi
99 | get() = ImageUi(
100 | url = Random.nextInt(10000).toString(),
101 | scaleType = "crop",
102 | size = DpSizeUi(0, 250),
103 | title = "PUBG",
104 | short_description = ""
105 |
106 | )
107 |
108 | private val sampleStyles: ListTextUi
109 | get() = ListTextUi(
110 | subTitleSize = 20,
111 | subTitleFontWeight = "bold",
112 | titleFontWeight = "bold",
113 | titleSize = 22,
114 | heightImage = 200,
115 | widthImage = 0
116 | )
117 |
118 | @SDUIPreview
119 | @Composable
120 | private fun ImageComponentPreview(modifier: Modifier = Modifier) {
121 | SDUIComposeTheme {
122 | ImageComponent(imageUi = sampleImageUi, testStyles = sampleStyles)
123 | }
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/designSystem/src/main/java/com/flexcode/sduicompose/designsystem/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.flexcode.sduicompose.designsystem.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.Font
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.text.style.LineHeightStyle
9 | import androidx.compose.ui.text.style.LineHeightStyle.Alignment
10 | import androidx.compose.ui.text.style.LineHeightStyle.Trim
11 | import androidx.compose.ui.unit.sp
12 | import com.flexcode.sduicompose.designsystem.R.font as AppFont
13 |
14 | val UrbanistBlack = FontFamily(Font(AppFont.urbanist_black, FontWeight.Black))
15 | val UrbanistBold = FontFamily(Font(AppFont.urbanist_bold, FontWeight.Bold))
16 | val UrbanistExtraLight = FontFamily(Font(AppFont.urbanist_extra_light, FontWeight.ExtraLight))
17 | val UrbanistLight = FontFamily(Font(AppFont.urbanist_light, FontWeight.Light))
18 | val UrbanistMedium = FontFamily(Font(AppFont.urbanist_medium, FontWeight.Medium))
19 | val UrbanistRegular = FontFamily(Font(AppFont.urbanist_regular, FontWeight.Normal))
20 | val UrbanistSemiBold = FontFamily(Font(AppFont.urbanist_semi_bold, FontWeight.SemiBold))
21 | val UrbanistThin = FontFamily(Font(AppFont.urbanist_thin, FontWeight.Thin))
22 |
23 | internal val Typography = Typography(
24 | displayLarge = TextStyle(
25 | fontFamily = UrbanistRegular,
26 | fontWeight = FontWeight.Normal,
27 | fontSize = 57.sp,
28 | lineHeight = 64.sp,
29 | letterSpacing = (-0.25).sp,
30 | ),
31 | displayMedium = TextStyle(
32 | fontFamily = UrbanistRegular,
33 | fontWeight = FontWeight.Normal,
34 | fontSize = 45.sp,
35 | lineHeight = 52.sp,
36 | letterSpacing = 0.sp,
37 | ),
38 | displaySmall = TextStyle(
39 | fontFamily = UrbanistRegular,
40 | fontWeight = FontWeight.Normal,
41 | fontSize = 36.sp,
42 | lineHeight = 44.sp,
43 | letterSpacing = 0.sp,
44 | ),
45 | headlineLarge = TextStyle(
46 | fontFamily = UrbanistRegular,
47 | fontWeight = FontWeight.Normal,
48 | fontSize = 32.sp,
49 | lineHeight = 40.sp,
50 | letterSpacing = 0.sp,
51 | ),
52 | headlineMedium = TextStyle(
53 | fontFamily = UrbanistRegular,
54 | fontWeight = FontWeight.Normal,
55 | fontSize = 28.sp,
56 | lineHeight = 36.sp,
57 | letterSpacing = 0.sp,
58 | ),
59 | headlineSmall = TextStyle(
60 | fontFamily = UrbanistRegular,
61 | fontWeight = FontWeight.Normal,
62 | fontSize = 24.sp,
63 | lineHeight = 32.sp,
64 | letterSpacing = 0.sp,
65 | lineHeightStyle = LineHeightStyle(
66 | alignment = Alignment.Bottom,
67 | trim = Trim.None,
68 | ),
69 | ),
70 | titleLarge = TextStyle(
71 | fontFamily = UrbanistBold,
72 | fontWeight = FontWeight.Bold,
73 | fontSize = 22.sp,
74 | lineHeight = 28.sp,
75 | letterSpacing = 0.sp,
76 | lineHeightStyle = LineHeightStyle(
77 | alignment = Alignment.Bottom,
78 | trim = Trim.LastLineBottom,
79 | ),
80 | ),
81 | titleMedium = TextStyle(
82 | fontFamily = UrbanistBold,
83 | fontWeight = FontWeight.Bold,
84 | fontSize = 18.sp,
85 | lineHeight = 24.sp,
86 | letterSpacing = 0.1.sp,
87 | ),
88 | titleSmall = TextStyle(
89 | fontFamily = UrbanistSemiBold,
90 | fontWeight = FontWeight.SemiBold,
91 | fontSize = 17.sp,
92 | lineHeight = 20.sp,
93 | letterSpacing = 0.1.sp,
94 | ),
95 | bodyLarge = TextStyle(
96 | fontFamily = UrbanistMedium,
97 | fontWeight = FontWeight.Normal,
98 | fontSize = 16.sp,
99 | lineHeight = 24.sp,
100 | letterSpacing = 0.5.sp,
101 | lineHeightStyle = LineHeightStyle(
102 | alignment = Alignment.Center,
103 | trim = Trim.None,
104 | ),
105 | ),
106 | bodyMedium = TextStyle(
107 | fontFamily = UrbanistRegular,
108 | fontWeight = FontWeight.Normal,
109 | fontSize = 14.sp,
110 | lineHeight = 20.sp,
111 | letterSpacing = 0.25.sp,
112 | ),
113 | bodySmall = TextStyle(
114 | fontFamily = UrbanistRegular,
115 | fontWeight = FontWeight.Normal,
116 | fontSize = 12.sp,
117 | lineHeight = 16.sp,
118 | letterSpacing = 0.4.sp,
119 | ),
120 | labelLarge = TextStyle(
121 | fontFamily = UrbanistMedium,
122 | fontWeight = FontWeight.Medium,
123 | fontSize = 14.sp,
124 | lineHeight = 20.sp,
125 | letterSpacing = 0.1.sp,
126 | lineHeightStyle = LineHeightStyle(
127 | alignment = Alignment.Center,
128 | trim = Trim.LastLineBottom,
129 | ),
130 | ),
131 | labelMedium = TextStyle(
132 | fontFamily = UrbanistMedium,
133 | fontWeight = FontWeight.Medium,
134 | fontSize = 12.sp,
135 | lineHeight = 16.sp,
136 | letterSpacing = 0.5.sp,
137 | lineHeightStyle = LineHeightStyle(
138 | alignment = Alignment.Center,
139 | trim = Trim.LastLineBottom,
140 | ),
141 | ),
142 | labelSmall = TextStyle(
143 | fontFamily = UrbanistMedium,
144 | fontWeight = FontWeight.Medium,
145 | fontSize = 10.sp,
146 | lineHeight = 14.sp,
147 | letterSpacing = 0.sp,
148 | lineHeightStyle = LineHeightStyle(
149 | alignment = Alignment.Center,
150 | trim = Trim.LastLineBottom,
151 | ),
152 | ),
153 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | androidGradlePlugin = "8.5.2"
3 | androidxActivity = "1.9.1"
4 | androidxCompose = "1.6.8"
5 | androidxComposeMaterial3 = "1.2.1"
6 | androidxComposeConstraintLayout = "1.0.1"
7 | androidxComposeNavigation = "2.8.0-rc01"
8 | androidxCore = "1.13.1"
9 | androidxHiltNavigationCompose = "1.2.0"
10 | androidxLifecycle = "2.8.4"
11 | androidxProfileinstaller = "1.3.1"
12 | androidxStartup = "1.1.1"
13 | androidxTest = "1.6.2"
14 | hilt = "2.51.1"
15 | kotlin = "2.0.20"
16 | kotlinxCoroutines = "1.8.1"
17 | kotlinxSerializationJson = "1.7.1"
18 | googleSecretPlugin = "2.0.1"
19 | ksp = "2.0.20-1.0.24"
20 | okhttp = "4.12.0"
21 | retrofit = "2.11.0"
22 | spotless = "6.7.0"
23 | firebaseBom = "33.2.0"
24 | firebaseKtx = "0.1.0"
25 | junit = "4.13.2"
26 | junitVersion = "1.2.1"
27 | espressoCore = "3.6.1"
28 | appcompat = "1.7.0"
29 | material = "1.12.0"
30 | coil-compose = "2.6.0"
31 | firebaseCrashlyticsGradle = "3.0.2"
32 |
33 | [libraries]
34 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
35 | androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxComposeNavigation" }
36 | androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "androidxCompose" }
37 | androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "androidxCompose" }
38 | androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "androidxCompose" }
39 | androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "androidxCompose" }
40 | androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidxComposeMaterial3" }
41 | androidx-compose-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "androidxComposeConstraintLayout" }
42 | androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "androidxCompose" }
43 | androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidxCompose" }
44 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "androidxCompose" }
45 | androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "androidxCompose" }
46 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
47 | androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
48 | androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
49 | androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
50 | androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidxProfileinstaller" }
51 | androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTest" }
52 | androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTest" }
53 | androidx-startup = { group = "androidx.startup", name = "startup-runtime", version.ref = "androidxStartup" }
54 | hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
55 | hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
56 | hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
57 | kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
58 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
59 | kotlinx-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" }
60 | okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
61 | retrofit-bom = { module = "com.squareup.retrofit2:retrofit-bom", version.ref = "retrofit" }
62 | retrofit = { module = "com.squareup.retrofit2:retrofit" }
63 | retrofit-kotlinx-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization" }
64 | firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
65 | firebase-store = { module = "com.google.firebase:firebase-firestore" }
66 | firebase-database = { module = "com.google.firebase:firebase-database-ktx" }
67 | firebase-remote-config = { module = "com.google.firebase:firebase-config" }
68 | firebase-database-ktx = { module = "com.github.skydoves:firebase-database-ktx", version.ref = "firebaseKtx" }
69 | coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose" }
70 | firebase-messaging = { module = "com.google.firebase:firebase-messaging-ktx" }
71 | firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
72 | firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" }
73 | firebase-crashlytics-gradle = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsGradle" }
74 |
75 | android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
76 | kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
77 | compose-compiler-gradlePlugin = { group = "org.jetbrains.kotlin", name = "compose-compiler-gradle-plugin", version.ref = "kotlin" }
78 | spotless-gradlePlugin = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotless" }
79 | junit = { group = "junit", name = "junit", version.ref = "junit" }
80 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
81 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
82 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
83 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
84 |
85 | [bundles]
86 | firebase = [ "firebase-bom", "firebase-messaging", "firebase-analytics", "firebase-crashlytics", "firebase-crashlytics-gradle"]
87 |
88 | [plugins]
89 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
90 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
91 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
92 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
93 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
94 | hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
95 | spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
96 | gms-googleServices = "com.google.gms.google-services:4.4.2"
97 | google-secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "googleSecretPlugin" }
98 | android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## SDUI - Compose
2 |
3 | [Server-Driven UI](SDUI) is an architectural pattern designed to minimize client-side logic and ensure consistency across multiple platforms (web, iOS, Android, etc.).
4 | Instead of returning raw domain data, the API delivers layout information, allowing the UI to be dynamically consumed and reducing the need for platform-specific code.
5 |
6 | This is a sample project created to show case the SDUI approach on android apps using jetpack compose.
7 |
8 |
9 | ## Resources
10 | 1. [Design Server-Driven UI with Jetpack Compose and Firebase](https://getstream.io/blog/server-driven-compose-firebase/)
11 |
12 | 2. [How to Build Server Driven UI w/ Firebase + Jetpack Compose](https://www.youtube.com/watch?v=tca-6yhWXNo)
13 |
14 |
15 | ## **Tech stack & Libraries 🛠️👨💻**
16 | - [Kotlin](https://kotlinlang.org/docs/reference/) - Kotlin is a cross-platform, statically typed, general-purpose programming language with type inference. Kotlin is designed to interoperate fully with Java, and the JVM version of Kotlin's standard library depends on the Java Class Library, but type inference allows its syntax to be more concise
17 | * [Jetpack Components:](https://developer.android.com/topic/architecture?gclid=Cj0KCQjw8O-VBhCpARIsACMvVLOH1satX45o9f4PMQ4Sxr7bG9myl6-KZL9nYda8PJsHV7m2uJL8bzgaAmqiEALw_wcB&gclsrc=aw.ds)
18 | * [Jetpack Compose](https://developer.android.com/jetpack/compose?gclid=Cj0KCQjwhqaVBhCxARIsAHK1tiMMwHsxQ8Z25jyEdtLha9erq11wROoEfL6RqpGMprgbDTNuMO3_Ri8aAu5EEALw_wcB&gclsrc=aw.ds) - Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android
19 | * [View Model](https://developer.android.com/topic/libraries/architecture/viewmodel)- store and manage UI-related data in a lifecycle conscious way.
20 | * [Lifecycle]( https://developer.android.com/topic/libraries/architecture/lifecycle) - Perform actions in response to a change in the lifecycle status of another component, such as activities and fragments.
21 | * [Android KTX](https://developer.android.com/kotlin/ktx.html) - Android KTX is a set of Kotlin extensions that are included with Android Jetpack and other Android libraries. KTX extensions provide concise, idiomatic Kotlin to Jetpack, Android platform, and other APIs.
22 | * [AndroidX](https://developer.android.com/jetpack/androidx) - Major improvement to the original Android [Support Library](https://developer.android.com/topic/libraries/support-library/index), which is no longer maintained.
23 |
24 |
25 | * [Dagger-Hilt](https://dagger.dev/hilt/)- a dependency injection library for Android that reduces the boilerplate of doing manual dependency injection in your project
26 |
27 | * [Coroutines](https://developer.android.com/kotlin/coroutines) - a concurrency design pattern that you can use on Android to simplify code that executes asynchronously
28 | * [Flow](https://developer.android.com/kotlin/flow)- In coroutines, a flow is a type that can emit multiple values sequentially, as opposed to suspend functions that return only a single value.
29 |
30 | * [CI/CD](https://codemagic.io/android-continuous-integration/) - Continuous integration systems let you automatically build and test your app every time you check in updates to your source control system.
31 |
32 | * [Firebase Android KTX](): Kotlin & Compose-friendly Firebase extensions designed to help you focus on your business logic.
33 |
34 | * [ksp]() Kotlin Symbol Processing API for code generation and analysis.
35 |
36 | * [Coil](https://coil-kt.github.io/coil/compose/) - Image Loader library.
37 |
38 | * [Compose Navigation]()
39 | * [Lottie Animation](https://lottiefiles.com/what-is-lottie) - A Lottie is a JSON-based animation file format that allows you to ship animations on any platform as easily as shipping static assets.
40 |
41 |
42 | ## Project Set Up
43 | To set up this project...
44 |
45 | 1. Set up firebase and add the `google-services.json` to the app directory of the project.
46 | 2. Copy this json to your machine and save it with `.json` extension e.g (games.json)
47 | ``` json
48 | {
49 | "gamesCompose": {
50 | "games": {
51 | "list": {
52 | "itemSize": {
53 | "height": 200,
54 | "width": 0
55 | },
56 | "items": [
57 | {
58 | "developer": "Level Infinite",
59 | "freetogame_profile_url": "https://www.freetogame.com/tarisland",
60 | "game_url": "https://www.freetogame.com/open/tarisland",
61 | "genre": "MMORPG",
62 | "platform": "PC (Windows)",
63 | "publisher": "Tencent",
64 | "release_date": "2024-06-22",
65 | "scaleType": "crop",
66 | "short_description": "A cross-platform MMORPG developed by Level Infinite and Published by Tencent.",
67 | "title": "Tarisland",
68 | "url": "https://www.freetogame.com/g/582/thumbnail.jpg"
69 | },
70 | {
71 | "developer": "Blizzard Entertainment",
72 | "freetogame_profile_url": "https://www.freetogame.com/overwatch-2",
73 | "game_url": "https://www.freetogame.com/open/overwatch-2",
74 | "genre": "Shooter",
75 | "platform": "PC (Windows)",
76 | "publisher": "Activision Blizzard",
77 | "release_date": "2022-10-04",
78 | "scaleType": "crop",
79 | "short_description": "A hero-focused first-person team shooter from Blizzard Entertainment.",
80 | "title": "Overwatch 2",
81 | "url": "https://www.freetogame.com/g/540/thumbnail.jpg"
82 | },
83 | {
84 | "developer": "KRAFTON, Inc.",
85 | "freetogame_profile_url": "https://www.freetogame.com/pubg",
86 | "game_url": "https://www.freetogame.com/open/pubg",
87 | "genre": "Shooter",
88 | "platform": "PC (Windows)",
89 | "publisher": "KRAFTON, Inc.",
90 | "release_date": "2022-01-12",
91 | "scaleType": "crop",
92 | "short_description": "Get into the action in one of the longest running battle royale games PUBG Battlegrounds.",
93 | "title": "PUBG: BATTLEGROUNDS",
94 | "url": "https://www.freetogame.com/g/516/thumbnail.jpg"
95 | },
96 | {
97 | "developer": "InnoGames",
98 | "freetogame_profile_url": "https://www.freetogame.com/forge-of-empires",
99 | "game_url": "https://www.freetogame.com/open/forge-of-empires",
100 | "genre": "Strategy",
101 | "platform": "Web Browser",
102 | "publisher": "InnoGames",
103 | "release_date": "2012-04-17",
104 | "scaleType": "crop",
105 | "short_description": "A free to play 2D browser-based online strategy game, become the leader and raise your city.",
106 | "title": "Forge of Empires",
107 | "url": "https://www.freetogame.com/g/345/thumbnail.jpg"
108 | },
109 | {
110 | "developer": "Mediatonic",
111 | "freetogame_profile_url": "https://www.freetogame.com/fall-guys",
112 | "game_url": "https://www.freetogame.com/open/fall-guys",
113 | "genre": "Battle Royale",
114 | "platform": "PC (Windows)",
115 | "publisher": "Mediatonic",
116 | "release_date": "2020-08-04",
117 | "scaleType": "crop",
118 | "short_description": "Play the most competitive massively multiplayer party royale game featuring beans ever for free on a variety of platforms. ",
119 | "title": "Genshin Impact",
120 | "url": "https://www.freetogame.com/g/523/thumbnail.jpg"
121 | }
122 | ],
123 | "layout": "column",
124 | "testStyles": {
125 | "heightImage": 200,
126 | "subTitleFontWeight": "medium",
127 | "subTitleSize": 15,
128 | "titleFontWeight": "bold",
129 | "titleSize": 17,
130 | "widthImage": 0
131 | }
132 | },
133 | "order": 2,
134 | "title": {
135 | "fontWeight": "bold",
136 | "size": 28,
137 | "text": "Free To Game"
138 | }
139 | },
140 | "version": 1
141 | }
142 | }
143 | ```
144 | ```
145 | 4. Set up [Firebase realtime Databsee]() and once done import the json as demonstrated:
146 | 5. Copy the realtime Database Url to and add it to your local.properties as:
147 |
148 | ```
149 | REALTIME_DATABASE_URL=URL_HERE
150 | ```
151 | 
152 |
153 |
154 | ## Screnshots
155 |
156 |
157 |
158 | ## Demo
159 |
160 |
161 |
162 | https://github.com/user-attachments/assets/ca53dbcd-1f9f-47f2-b3aa-44b20df6d6ab
163 |
164 |
165 |
166 | ## Author
167 | Felix Kariuki
168 |
169 |
170 |
171 | Do Reach Out :
172 |
173 | * [Twitter](https://twitter.com/felixkariuki_)
174 |
175 | * [LinkedIn](https://www.linkedin.com/in/felix-kariuki/)
176 |
177 | ## License and Copyright
178 |
179 |
180 | ```
181 | MIT License
182 |
183 | Copyright (c) 2024 Felix Kariuki
184 |
185 | Permission is hereby granted, free of charge, to any person obtaining a copy
186 | of this software and associated documentation files (the "Software"), to deal
187 | in the Software without restriction, including without limitation the rights
188 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
189 | copies of the Software, and to permit persons to whom the Software is
190 | furnished to do so, subject to the following conditions:
191 |
192 | The above copyright notice and this permission notice shall be included in all
193 | copies or substantial portions of the Software.
194 |
195 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
196 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
197 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
198 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
199 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
200 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
201 | SOFTWARE.
202 |
203 | ```
204 |
205 |
206 |
207 |
208 |
209 |
210 |
--------------------------------------------------------------------------------