Skip to content

Commit

Permalink
Dev exp (#93)
Browse files Browse the repository at this point in the history
* Add client unit tests

* Fix build error

* Refactor KSP fix

* Add KPIs funnel

* Support funnel UI

* Run unit tests on JS

* Improve analytics

* Improve KPIs UI
  • Loading branch information
ILIYANGERMANOV authored Dec 27, 2024
1 parent d70a969 commit 749f26e
Show file tree
Hide file tree
Showing 20 changed files with 321 additions and 129 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci_clients.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,6 @@ jobs:
- name: Gradle cache
uses: gradle/actions/setup-gradle@v4

- name: Run unit tests
run: ./gradlew jvmTest
- name: Run unit tests on JS
run: ./gradlew jsTest

190 changes: 103 additions & 87 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,118 +1,134 @@
import com.google.devtools.ksp.gradle.KspTask

plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.ksp)
idea
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.ksp)
idea
}

kotlin {
js(IR) {
browser()
binaries.executable()
}
js(IR) {
browser()
binaries.executable()
}

androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
}

jvm("desktop")
jvm("desktop")

listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
isStatic = true
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
isStatic = true
}
}

sourceSets {
commonMain {
kotlin.srcDirs("build/generated/ksp/commonMain/kotlin")
}
sourceSets {
commonMain {
kotlin.srcDirs("build/generated/ksp/commonMain/kotlin")
}
commonTest {
kotlin.srcDir("build/generated/ksp/test/kotlin")
}
dependencies {
ksp(libs.arrow.optics.ksp)
}

val desktopMain by getting
val desktopMain by getting

androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
}
commonMain.dependencies {
implementation(projects.shared)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.kotlin.immutableCollections)
implementation(libs.thirdparty.lottieMultiplatform)
implementation(libs.thirdparty.kamel)
implementation(libs.bundles.arrow)
}
desktopMain.dependencies {
implementation(compose.desktop.currentOs)
}
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
}
}

dependencies {
ksp(libs.arrow.optics.ksp)
commonMain.dependencies {
implementation(projects.shared)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.kotlin.immutableCollections)
implementation(libs.thirdparty.lottieMultiplatform)
implementation(libs.thirdparty.kamel)
implementation(libs.bundles.arrow)
}
commonTest.dependencies {
implementation(libs.bundles.test.kmp)
}
desktopMain.dependencies {
implementation(compose.desktop.currentOs)
}
}
}

android {
namespace = "ivy.learn"
compileSdk = libs.versions.android.compileSdk.get().toInt()
namespace = "ivy.learn"
compileSdk = libs.versions.android.compileSdk.get().toInt()

sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources")

defaultConfig {
applicationId = "ivy.learn"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
defaultConfig {
applicationId = "ivy.learn"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

compose.experimental {
web.application {}
web.application {}
}

// Configure KSP to output to the commonMain directory
tasks.withType<KspTask> {
doLast {
copy {
from("build/generated/ksp/js/jsMain/kotlin")
into("build/generated/ksp/commonMain/kotlin")
include("**/*.kt")
}
delete("build/generated/ksp/js/jsMain/kotlin")
doLast {
fixKspConflicts()
}
}

fun fixKspConflicts() {
fun fixTarget(target: String) {
copy {
from("build/generated/ksp/$target/${target}Main/kotlin")
into("build/generated/ksp/commonMain/kotlin")
include("**/*.kt")
}
delete("build/generated/ksp/$target/${target}Main/kotlin")
}
fixTarget("js")
fixTarget("desktop")
fixTarget("android")
fixTarget("ios")
fixTarget("native")
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,8 @@ class Analytics(
eventName: String,
params: Map<String, String>?,
) {
if (!enabled) return

appScope.launch {
if (sessionManager.getSession() is Session.LoggedIn) {
if (enabled && sessionManager.getSession() is Session.LoggedIn) {
trackLoggedAnalyticsEvent(
eventName = eventName,
params = params,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.compose.runtime.LaunchedEffect
import domain.GoogleAuthenticationUseCase
import domain.SessionManager
import domain.analytics.Analytics
import domain.analytics.Metrics
import domain.analytics.Source
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
Expand All @@ -15,7 +14,6 @@ import ui.screen.home.HomeScreen

class IntroViewModel(
private val googleAuthenticationUseCase: GoogleAuthenticationUseCase,
private val metrics: Metrics,
private val sessionManager: SessionManager,
private val analytics: Analytics,
private val scope: CoroutineScope,
Expand All @@ -24,7 +22,10 @@ class IntroViewModel(
@Composable
override fun viewState(): IntroViewState {
LaunchedEffect(Unit) {
metrics.logMetric(name = "intro__view")
analytics.logEvent(
source = Source.Intro,
event = "intro__view"
)
}
return IntroViewState()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import kotlinx.collections.immutable.ImmutableList
sealed interface KpiViewState {
data object Loading : KpiViewState
data class Error(val errMsg: String) : KpiViewState
data class Content(val kpis: ImmutableList<KpiDto>) : KpiViewState
data class Content(
val funnel: ImmutableList<KpiDto>,
val kpis: ImmutableList<KpiDto>
) : KpiViewState
}

sealed interface KpiViewEvent
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ class KpiViewModel(

return when (val result = res) {
is Either.Left -> KpiViewState.Error(result.value)
is Either.Right -> KpiViewState.Content(result.value.kpis.toImmutableList())
is Either.Right -> {
val kpisResponse = result.value
KpiViewState.Content(
funnel = kpisResponse.funnel.toImmutableList(),
kpis = kpisResponse.kpis.toImmutableList()
)
}
null -> KpiViewState.Loading
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package ui.screen.kpi.composable

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
Expand Down Expand Up @@ -48,9 +50,17 @@ private fun Content(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.Start,
) {
sectionDivider(name = "Funnel")
itemsIndexed(
items = viewState.kpis,
key = { index, item -> "${index}_${item.name}" }
key = { index, item -> "funnel_${index}_${item.name}" }
) { _, item ->
KpiItem(item = item)
}
sectionDivider(name = "KPIs")
itemsIndexed(
items = viewState.kpis,
key = { index, item -> "kpi_${index}_${item.name}" }
) { _, item ->
KpiItem(item = item)
}
Expand All @@ -65,13 +75,29 @@ private fun KpiItem(
Column(modifier = modifier) {
Text(
text = item.name,
style = IvyTheme.typography.b1,
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.SemiBold,
)
Spacer(Modifier.height(4.dp))
Text(
text = item.value,
style = IvyTheme.typography.b2
style = MaterialTheme.typography.body2,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colors.secondary,
)
}
}


private fun LazyListScope.sectionDivider(
name: String,
) {
item(name) {
Spacer(Modifier.height(12.dp))
Text(
text = name,
style = IvyTheme.typography.h1,
color = MaterialTheme.colors.primary,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import data.SoundRepository
import domain.DeleteUserDataUseCase
import domain.SessionManager
import domain.analytics.Analytics
import domain.analytics.Metrics
import domain.analytics.Source
import ivy.IvyUrls
import kotlinx.coroutines.CoroutineScope
Expand All @@ -27,7 +26,6 @@ class SettingsViewModel(
private val analytics: Analytics,
private val toaster: Toaster,
private val soundRepository: SoundRepository,
private val metrics: Metrics,
) : ComposeViewModel<SettingsViewState, SettingsViewEvent> {
private var soundEnabled by mutableStateOf(true)
private var deleteDialog by mutableStateOf<DeleteDialogViewState?>(null)
Expand Down Expand Up @@ -130,7 +128,7 @@ class SettingsViewModel(
deleteDialog = DeleteDialogViewState(ctaLoading = true)
deleteUserDataUseCase.execute()
deleteDialog = DeleteDialogViewState(ctaLoading = false)
metrics.logMetric(name = "account__deleted")
logEvent(event = "account__deleted")
}
}

Expand Down
Loading

0 comments on commit 749f26e

Please sign in to comment.