From e021f482f8fff9b7b003bfa5512b18b4a871b5de Mon Sep 17 00:00:00 2001 From: Muh Isfhani Ghiath Date: Thu, 29 Aug 2024 12:49:54 +0700 Subject: [PATCH] [Platform] Add desktop support (#3) * feat: adjust UI and uses shimmer for loader * feat: add desktop app support * feat: add desktop for jvm linux target build on github workflows --- .fleet/receipt.json | 19 ------ .github/workflows/build.yml | 1 + app/build.gradle.kts | 21 +++++- .../kotlin/id/gdg/app/MainActivity.kt | 1 - app/src/commonMain/kotlin/id/gdg/app/App.kt | 15 ----- .../kotlin/id/gdg/app/{ui => }/AppContent.kt | 5 +- .../kotlin/id/gdg/app/AppViewModel.kt | 2 +- .../kotlin/id/gdg/app/ui/screen/MainScreen.kt | 30 ++++----- .../ui/screen/content/PreviousEventContent.kt | 28 ++++---- .../ui/screen/uimodel/EventContent.mapper.kt | 2 +- .../id/gdg/app/ui/state/ChapterUiModel.kt | 6 +- .../ui/state/partial/PreviousEventsUiModel.kt | 3 + .../id/gdg/app/robot/AppViewModelRobot.kt | 10 ++- app/src/desktopMain/kotlin/id/gdg/app/main.kt | 65 +++++++++++++++++++ .../kotlin/id/gdg/app/MainViewController.kt | 1 - gdg-chapter/build.gradle.kts | 1 + .../chapter/data/chapter.datastore.desktop.kt | 9 +++ gdg-events/build.gradle.kts | 2 + .../id/gdg/event/domain/mapper/mapper.kt | 16 ++++- .../kotlin/id/gdg/event/model/EventModel.kt | 4 +- gdg-network/build.gradle.kts | 1 + gdg-ui/build.gradle.kts | 2 + .../id/gdg/ui/component/HeadlineSection.kt | 20 ++++++ .../shimmer/EventSimpleCardShimmer.kt | 60 +++++++++++++++++ gradle/libs.versions.toml | 3 + 25 files changed, 242 insertions(+), 85 deletions(-) delete mode 100644 .fleet/receipt.json delete mode 100644 app/src/commonMain/kotlin/id/gdg/app/App.kt rename app/src/commonMain/kotlin/id/gdg/app/{ui => }/AppContent.kt (97%) create mode 100644 app/src/desktopMain/kotlin/id/gdg/app/main.kt create mode 100644 gdg-chapter/src/desktopMain/kotlin/id/gdg/chapter/data/chapter.datastore.desktop.kt create mode 100644 gdg-ui/src/commonMain/kotlin/id/gdg/ui/component/HeadlineSection.kt create mode 100644 gdg-ui/src/commonMain/kotlin/id/gdg/ui/component/shimmer/EventSimpleCardShimmer.kt diff --git a/.fleet/receipt.json b/.fleet/receipt.json deleted file mode 100644 index df69c8e..0000000 --- a/.fleet/receipt.json +++ /dev/null @@ -1,19 +0,0 @@ -// Project generated by Kotlin Multiplatform Wizard -{ - "spec": { - "template_id": "kmt", - "targets": { - "android": { - "ui": [ - "compose" - ] - }, - "ios": { - "ui": [ - "compose" - ] - } - } - }, - "timestamp": "2024-08-16T16:16:47.465955819Z" -} \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 978f2e1..2e40361 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,7 @@ jobs: config: [ { target: android, os: ubuntu-latest, tasks: testDebugUnitTest testReleaseUnitTest, continueOnError: false }, { target: apple, os: macos-latest, tasks: iosX64Test iosSimulatorArm64Test, continueOnError: false }, + { target: desktop, os: ubuntu-latest, tasks: desktopTest, continueOnError: false }, ] runs-on: ${{ matrix.config.os }} name: Build ${{ matrix.config.target }} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5fa142b..2bc0c80 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,7 @@ kotlin { } } jvmToolchain(17) + jvm("desktop") listOf( iosX64(), @@ -33,7 +34,8 @@ kotlin { } sourceSets { - + val desktopMain by getting + androidMain.dependencies { implementation(compose.preview) implementation(libs.androidx.activity.compose) @@ -42,6 +44,11 @@ kotlin { implementation(libs.androidx.compose.windowsizeclass) } + desktopMain.dependencies { + implementation(libs.kotlinx.coroutines.swing) + implementation(compose.desktop.currentOs) + } + commonMain.dependencies { // Libraries implementation(project(":gdg-ui")) @@ -68,6 +75,7 @@ kotlin { implementation(libs.common.koin) implementation(libs.util.constraintlayout) } + commonTest.dependencies { implementation(kotlin("test")) implementation(libs.common.koin.test) @@ -128,3 +136,14 @@ android { } } +compose.desktop { + application { + mainClass = "id.gdg.app.MainKt" + + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "id.gdg.app" + packageVersion = "1.0.0" + } + } +} \ No newline at end of file diff --git a/app/src/androidMain/kotlin/id/gdg/app/MainActivity.kt b/app/src/androidMain/kotlin/id/gdg/app/MainActivity.kt index e26b57c..f0ab704 100644 --- a/app/src/androidMain/kotlin/id/gdg/app/MainActivity.kt +++ b/app/src/androidMain/kotlin/id/gdg/app/MainActivity.kt @@ -9,7 +9,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier -import id.gdg.app.ui.AppContent import id.gdg.ui.DarkColorPalette import id.gdg.ui.LightColorPalette import id.gdg.ui.androidx.compose.material3.windowsizeclass.CommonWindowSizeClass diff --git a/app/src/commonMain/kotlin/id/gdg/app/App.kt b/app/src/commonMain/kotlin/id/gdg/app/App.kt deleted file mode 100644 index 4bc093a..0000000 --- a/app/src/commonMain/kotlin/id/gdg/app/App.kt +++ /dev/null @@ -1,15 +0,0 @@ -package id.gdg.app - -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import id.gdg.app.di.ViewModelFactory -import id.gdg.app.ui.AppContent -import org.jetbrains.compose.ui.tooling.preview.Preview - -@Composable -@Preview -fun App(viewModel: AppViewModel = ViewModelFactory.create()) { - MaterialTheme { - AppContent() - } -} \ No newline at end of file diff --git a/app/src/commonMain/kotlin/id/gdg/app/ui/AppContent.kt b/app/src/commonMain/kotlin/id/gdg/app/AppContent.kt similarity index 97% rename from app/src/commonMain/kotlin/id/gdg/app/ui/AppContent.kt rename to app/src/commonMain/kotlin/id/gdg/app/AppContent.kt index 82d1e07..ebf17cf 100644 --- a/app/src/commonMain/kotlin/id/gdg/app/ui/AppContent.kt +++ b/app/src/commonMain/kotlin/id/gdg/app/AppContent.kt @@ -1,4 +1,4 @@ -package id.gdg.app.ui +package id.gdg.app import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -11,8 +11,9 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument -import id.gdg.app.AppViewModel import id.gdg.app.di.ViewModelFactory +import id.gdg.app.ui.AppEvent +import id.gdg.app.ui.AppRouter import id.gdg.app.ui.screen.EventDetailScreen import id.gdg.app.ui.screen.MainScreen import id.gdg.app.ui.screen.OnboardingScreen diff --git a/app/src/commonMain/kotlin/id/gdg/app/AppViewModel.kt b/app/src/commonMain/kotlin/id/gdg/app/AppViewModel.kt index 30b0dd8..1d2ee7b 100644 --- a/app/src/commonMain/kotlin/id/gdg/app/AppViewModel.kt +++ b/app/src/commonMain/kotlin/id/gdg/app/AppViewModel.kt @@ -52,7 +52,7 @@ class AppViewModel( _upcomingEvent, _previousEvents ) { upcoming, previous -> - ChapterUiModel(upcoming, previous, true) + ChapterUiModel(upcoming, previous) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(5_000), diff --git a/app/src/commonMain/kotlin/id/gdg/app/ui/screen/MainScreen.kt b/app/src/commonMain/kotlin/id/gdg/app/ui/screen/MainScreen.kt index ad5d049..18d39c1 100644 --- a/app/src/commonMain/kotlin/id/gdg/app/ui/screen/MainScreen.kt +++ b/app/src/commonMain/kotlin/id/gdg/app/ui/screen/MainScreen.kt @@ -1,9 +1,7 @@ package id.gdg.app.ui.screen import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Button import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -32,29 +30,23 @@ fun MainScreen( var selectedEventId by rememberSaveable { mutableStateOf("") } val windowSizeClazz: CommonWindowSizeClass = LocalWindowSizeClass.current - var hasDetailOpened: Boolean? by rememberSaveable { mutableStateOf(null) } - var showSidePanel by rememberSaveable { - mutableStateOf(hasDetailOpened != null).also { - println("showsidepanel: ${it.value}") - } - } + var shouldPanelOpened: Boolean? by rememberSaveable { mutableStateOf(null) } + var panelVisibility by rememberSaveable { mutableStateOf(shouldPanelOpened != null) } - LaunchedEffect(!chapterUiState.isInitiated) { + LaunchedEffect(Unit) { viewModel.sendEvent(AppEvent.InitialContent) } LaunchedEffect(windowSizeClazz) { - hasDetailOpened = - hasDetailOpened.takeIf { windowSizeClazz.widthSizeClass != CommonWindowWidthSizeClass.Compact } - println("hasdetail: $hasDetailOpened") - println("apaini: ${windowSizeClazz.widthSizeClass != CommonWindowWidthSizeClass.Compact}") - showSidePanel = hasDetailOpened != null + shouldPanelOpened = + shouldPanelOpened.takeIf { windowSizeClazz.widthSizeClass != CommonWindowWidthSizeClass.Compact } + panelVisibility = shouldPanelOpened != null } TwoPanelScaffold( - panelVisibility = showSidePanel, + panelVisibility = panelVisibility, animationSpec = TwoPanelScaffoldAnimationSpec( - finishedListener = { fraction -> if (fraction == 1f) hasDetailOpened = null } + finishedListener = { fraction -> if (fraction == 1f) shouldPanelOpened = null } ), body = { MainScreenContent( @@ -68,8 +60,8 @@ fun MainScreen( } selectedEventId = it - hasDetailOpened = true - showSidePanel = true + shouldPanelOpened = true + panelVisibility = true }, onRefreshPreviousContentClicked = { viewModel.sendEvent(AppEvent.FetchPreviousEvent) @@ -78,7 +70,7 @@ fun MainScreen( }, panel = { Surface(tonalElevation = 1.dp) { - if (hasDetailOpened != null) { + if (shouldPanelOpened != null) { EventDetailScreen( viewModel = viewModel, eventId = selectedEventId diff --git a/app/src/commonMain/kotlin/id/gdg/app/ui/screen/content/PreviousEventContent.kt b/app/src/commonMain/kotlin/id/gdg/app/ui/screen/content/PreviousEventContent.kt index d0cf320..3cc0682 100644 --- a/app/src/commonMain/kotlin/id/gdg/app/ui/screen/content/PreviousEventContent.kt +++ b/app/src/commonMain/kotlin/id/gdg/app/ui/screen/content/PreviousEventContent.kt @@ -8,17 +8,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import id.gdg.app.ui.screen.uimodel.toEventContent import id.gdg.app.ui.state.partial.PreviousEventsUiModel -import id.gdg.ui.component.EventContent import id.gdg.ui.component.EventSimpleCard +import id.gdg.ui.component.HeadlineSection +import id.gdg.ui.component.shimmer.EventSimpleCardShimmerList @Composable fun PreviousEventContent( @@ -26,9 +23,12 @@ fun PreviousEventContent( onRefreshContent: () -> Unit, onEventClicked: (String) -> Unit, ) { - Box { + Box( + modifier = Modifier + .padding(12.dp) + ) { AnimatedVisibility(data.state.isLoading) { - CircularProgressIndicator() + EventSimpleCardShimmerList() } when { @@ -37,16 +37,12 @@ fun PreviousEventContent( modifier = Modifier .fillMaxWidth(), ) { - item { - Text( - text = "Previous Events", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.SemiBold - ) - } + item { HeadlineSection("Previous Events") } - items(data.previousEvents.map { it.toEventContent() }) { - EventSimpleCard(it) { onEventClicked(it) } + items(data.toEventContent()) { + EventSimpleCard(it) { eventId -> + onEventClicked(eventId) + } } } } diff --git a/app/src/commonMain/kotlin/id/gdg/app/ui/screen/uimodel/EventContent.mapper.kt b/app/src/commonMain/kotlin/id/gdg/app/ui/screen/uimodel/EventContent.mapper.kt index 8fab9de..21def42 100644 --- a/app/src/commonMain/kotlin/id/gdg/app/ui/screen/uimodel/EventContent.mapper.kt +++ b/app/src/commonMain/kotlin/id/gdg/app/ui/screen/uimodel/EventContent.mapper.kt @@ -9,6 +9,6 @@ fun EventModel.toEventContent(): EventContent { bannerUrl = eventImageUrl, eventName = title, date = startDate, - type = audienceType + type = audienceType.toString() ) } \ No newline at end of file diff --git a/app/src/commonMain/kotlin/id/gdg/app/ui/state/ChapterUiModel.kt b/app/src/commonMain/kotlin/id/gdg/app/ui/state/ChapterUiModel.kt index 3b1971e..2eef368 100644 --- a/app/src/commonMain/kotlin/id/gdg/app/ui/state/ChapterUiModel.kt +++ b/app/src/commonMain/kotlin/id/gdg/app/ui/state/ChapterUiModel.kt @@ -5,15 +5,13 @@ import id.gdg.app.ui.state.partial.UpcomingEventUiModel data class ChapterUiModel( val upcomingEvent: UpcomingEventUiModel, - val previousEvents: PreviousEventsUiModel, - val isInitiated: Boolean + val previousEvents: PreviousEventsUiModel ) { companion object { val Default get() = ChapterUiModel( upcomingEvent = UpcomingEventUiModel.Empty, - previousEvents = PreviousEventsUiModel.Empty, - isInitiated = false + previousEvents = PreviousEventsUiModel.Empty ) } } diff --git a/app/src/commonMain/kotlin/id/gdg/app/ui/state/partial/PreviousEventsUiModel.kt b/app/src/commonMain/kotlin/id/gdg/app/ui/state/partial/PreviousEventsUiModel.kt index de72aaa..41b80de 100644 --- a/app/src/commonMain/kotlin/id/gdg/app/ui/state/partial/PreviousEventsUiModel.kt +++ b/app/src/commonMain/kotlin/id/gdg/app/ui/state/partial/PreviousEventsUiModel.kt @@ -1,5 +1,6 @@ package id.gdg.app.ui.state.partial +import id.gdg.app.ui.screen.uimodel.toEventContent import id.gdg.app.ui.state.common.UiState import id.gdg.event.model.EventModel @@ -8,6 +9,8 @@ data class PreviousEventsUiModel( val previousEvents: List, ) { + fun toEventContent() = previousEvents.map { it.toEventContent() } + companion object { val Empty get() = PreviousEventsUiModel( state = UiState.Loading, diff --git a/app/src/commonTest/kotlin/id/gdg/app/robot/AppViewModelRobot.kt b/app/src/commonTest/kotlin/id/gdg/app/robot/AppViewModelRobot.kt index bb4b88a..5f91b55 100644 --- a/app/src/commonTest/kotlin/id/gdg/app/robot/AppViewModelRobot.kt +++ b/app/src/commonTest/kotlin/id/gdg/app/robot/AppViewModelRobot.kt @@ -12,6 +12,7 @@ import id.gdg.app.stub.GetPreviousEventUseCaseStub import id.gdg.app.stub.GetUpcomingEventUseCaseStub import id.gdg.chapter.domain.GetChapterIdUseCaseImpl import id.gdg.chapter.domain.SetChapterIdUseCaseImpl +import id.gdg.event.domain.mapper.AudienceType import id.gdg.event.model.EventModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job @@ -84,7 +85,8 @@ object AppViewModelRobot : KoinTest { descriptionShort = "Loren Ipsum", eventImageUrl = "https://sample.com/image.png", startDate = "", - timezoneAbbreviation = "WIB" + timezoneAbbreviation = "WIB", + audienceType = AudienceType("hybrid") ), EventModel( id = 1, @@ -93,7 +95,8 @@ object AppViewModelRobot : KoinTest { descriptionShort = "Loren Ipsum", eventImageUrl = "https://sample.com/image.png", startDate = "", - timezoneAbbreviation = "WIB" + timezoneAbbreviation = "WIB", + audienceType = AudienceType("in person") ), EventModel( id = 2, @@ -102,7 +105,8 @@ object AppViewModelRobot : KoinTest { descriptionShort = "Loren Ipsum", eventImageUrl = "https://sample.com/image.png", startDate = "", - timezoneAbbreviation = "WIB" + timezoneAbbreviation = "WIB", + audienceType = AudienceType("online") ) ) } \ No newline at end of file diff --git a/app/src/desktopMain/kotlin/id/gdg/app/main.kt b/app/src/desktopMain/kotlin/id/gdg/app/main.kt new file mode 100644 index 0000000..69b0c77 --- /dev/null +++ b/app/src/desktopMain/kotlin/id/gdg/app/main.kt @@ -0,0 +1,65 @@ +package id.gdg.app + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.window.WindowDraggableArea +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowScope +import androidx.compose.ui.window.WindowState +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import id.gdg.app.di.appModule +import id.gdg.chapter.data.dataStore +import id.gdg.ui.DarkColorPalette +import id.gdg.ui.LightColorPalette +import id.gdg.ui.androidx.compose.material3.windowsizeclass.CommonWindowSizeClass +import id.gdg.ui.androidx.compose.material3.windowsizeclass.LocalWindowSizeClass +import org.koin.core.context.startKoin +import org.koin.dsl.module + +fun main() = application { + val windowState: WindowState = rememberWindowState() + + initKoin() + + Window( + onCloseRequest = ::exitApplication, + title = "GDG Indonesia", + ) { + AppWindowTitleBar() + + val colors = if (isSystemInDarkTheme()) DarkColorPalette else LightColorPalette + + CompositionLocalProvider( + LocalWindowSizeClass provides CommonWindowSizeClass.calculateFromSize(windowState.size) + ) { + MaterialTheme(colorScheme = colors) { + AppContent() + } + } + } +} + +fun initKoin() { + val dataStoreModule = module { single> { dataStore() } } + + startKoin { + modules(appModule + dataStoreModule) + } +} + +@Composable +private fun WindowScope.AppWindowTitleBar() = WindowDraggableArea { + Box(Modifier.fillMaxWidth().height(48.dp).background(Color.DarkGray)) +} \ No newline at end of file diff --git a/app/src/iosMain/kotlin/id/gdg/app/MainViewController.kt b/app/src/iosMain/kotlin/id/gdg/app/MainViewController.kt index dfe7eab..2027b69 100644 --- a/app/src/iosMain/kotlin/id/gdg/app/MainViewController.kt +++ b/app/src/iosMain/kotlin/id/gdg/app/MainViewController.kt @@ -11,7 +11,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.unit.DpSize import androidx.compose.ui.window.ComposeUIViewController -import id.gdg.app.ui.AppContent import id.gdg.ui.DarkColorPalette import id.gdg.ui.LightColorPalette import id.gdg.ui.androidx.compose.material3.windowsizeclass.LocalWindowSizeClass diff --git a/gdg-chapter/build.gradle.kts b/gdg-chapter/build.gradle.kts index 43c4293..74a281c 100644 --- a/gdg-chapter/build.gradle.kts +++ b/gdg-chapter/build.gradle.kts @@ -9,6 +9,7 @@ plugins { kotlin { androidTarget() jvmToolchain(17) + jvm("desktop") val xcf = XCFramework() listOf( diff --git a/gdg-chapter/src/desktopMain/kotlin/id/gdg/chapter/data/chapter.datastore.desktop.kt b/gdg-chapter/src/desktopMain/kotlin/id/gdg/chapter/data/chapter.datastore.desktop.kt new file mode 100644 index 0000000..a63ab6f --- /dev/null +++ b/gdg-chapter/src/desktopMain/kotlin/id/gdg/chapter/data/chapter.datastore.desktop.kt @@ -0,0 +1,9 @@ +package id.gdg.chapter.data + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import id.gdg.chapter.ChapterDataStore + +fun dataStore(): DataStore = ChapterDataStore.create { + ChapterDataStore.PREF_NAME +} \ No newline at end of file diff --git a/gdg-events/build.gradle.kts b/gdg-events/build.gradle.kts index e1e62e8..4f123af 100644 --- a/gdg-events/build.gradle.kts +++ b/gdg-events/build.gradle.kts @@ -12,6 +12,7 @@ plugins { kotlin { androidTarget() jvmToolchain(17) + jvm("desktop") val xcf = XCFramework() listOf( @@ -43,6 +44,7 @@ kotlin { dependencies { with("de.jensklingenberg.ktorfit:ktorfit-ksp:2.0.0-beta1") { add("kspCommonMainMetadata", this) + add("kspDesktop", this) add("kspAndroid", this) add("kspAndroidTest", this) add("kspIosX64", this) diff --git a/gdg-events/src/commonMain/kotlin/id/gdg/event/domain/mapper/mapper.kt b/gdg-events/src/commonMain/kotlin/id/gdg/event/domain/mapper/mapper.kt index c31c4db..c35b878 100644 --- a/gdg-events/src/commonMain/kotlin/id/gdg/event/domain/mapper/mapper.kt +++ b/gdg-events/src/commonMain/kotlin/id/gdg/event/domain/mapper/mapper.kt @@ -6,6 +6,7 @@ import id.gdg.event.model.EventDetailModel import id.gdg.event.model.EventModel import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import kotlin.jvm.JvmInline fun List.toEventModels(): List { return map { it.toEventModel() } @@ -20,7 +21,7 @@ fun Event.toEventModel(): EventModel { eventImageUrl = picture, startDate = formatDate(startDate), timezoneAbbreviation = timezoneAbbreviation, - audienceType = audienceType.lowercase() + audienceType = AudienceType(audienceType) ) } @@ -28,6 +29,19 @@ fun EventDetail.toEventDetailModel(): EventDetailModel { return EventDetailModel(title) } +@JvmInline +value class AudienceType(private val value: String) { + + override fun toString(): String { + val typeInLowercase = value.lowercase() + return when { + typeInLowercase.contains("person") -> "In Person" + typeInLowercase.contains("hybrid") -> "Hybrid" + else -> "Online" + } + } +} + internal fun formatDate(dateString: String): String { val date = Instant.parse(dateString) val now = Clock.System.now() diff --git a/gdg-events/src/commonMain/kotlin/id/gdg/event/model/EventModel.kt b/gdg-events/src/commonMain/kotlin/id/gdg/event/model/EventModel.kt index 722b1e7..e98d996 100644 --- a/gdg-events/src/commonMain/kotlin/id/gdg/event/model/EventModel.kt +++ b/gdg-events/src/commonMain/kotlin/id/gdg/event/model/EventModel.kt @@ -1,5 +1,7 @@ package id.gdg.event.model +import id.gdg.event.domain.mapper.AudienceType + data class EventModel( val id: Long, val title: String, @@ -8,5 +10,5 @@ data class EventModel( val eventImageUrl: String, val startDate: String, val timezoneAbbreviation: String, - val audienceType: String + val audienceType: AudienceType ) \ No newline at end of file diff --git a/gdg-network/build.gradle.kts b/gdg-network/build.gradle.kts index 79682bb..93dcf41 100644 --- a/gdg-network/build.gradle.kts +++ b/gdg-network/build.gradle.kts @@ -9,6 +9,7 @@ plugins { kotlin { androidTarget() jvmToolchain(17) + jvm("desktop") val xcf = XCFramework() listOf( diff --git a/gdg-ui/build.gradle.kts b/gdg-ui/build.gradle.kts index d214b68..45e35b7 100644 --- a/gdg-ui/build.gradle.kts +++ b/gdg-ui/build.gradle.kts @@ -10,6 +10,7 @@ plugins { kotlin { androidTarget() jvmToolchain(17) + jvm("desktop") val xcf = XCFramework() listOf( @@ -29,6 +30,7 @@ kotlin { } commonMain.dependencies { api(libs.util.qdsfdhvh.image.loader) + api(libs.util.shimmer) implementation(compose.runtime) implementation(compose.foundation) diff --git a/gdg-ui/src/commonMain/kotlin/id/gdg/ui/component/HeadlineSection.kt b/gdg-ui/src/commonMain/kotlin/id/gdg/ui/component/HeadlineSection.kt new file mode 100644 index 0000000..eed64d1 --- /dev/null +++ b/gdg-ui/src/commonMain/kotlin/id/gdg/ui/component/HeadlineSection.kt @@ -0,0 +1,20 @@ +package id.gdg.ui.component + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +@Composable +fun HeadlineSection(title: String) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + modifier = Modifier + .padding(bottom = 2.dp) + ) +} \ No newline at end of file diff --git a/gdg-ui/src/commonMain/kotlin/id/gdg/ui/component/shimmer/EventSimpleCardShimmer.kt b/gdg-ui/src/commonMain/kotlin/id/gdg/ui/component/shimmer/EventSimpleCardShimmer.kt new file mode 100644 index 0000000..5fc878a --- /dev/null +++ b/gdg-ui/src/commonMain/kotlin/id/gdg/ui/component/shimmer/EventSimpleCardShimmer.kt @@ -0,0 +1,60 @@ +package id.gdg.ui.component.shimmer + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.valentinilk.shimmer.shimmer + +@Composable +fun EventSimpleCardShimmerList() { + Column( + modifier = Modifier.fillMaxWidth() + ) { + EventSimpleCardShimmer() + EventSimpleCardShimmer() + EventSimpleCardShimmer() + } +} + +@Composable +fun EventSimpleCardShimmer() { + Row( + modifier = Modifier + .shimmer() + .padding(4.dp) + ) { + Box( + modifier = Modifier + .size(48.dp, 48.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color.LightGray) + ) + + Column( + modifier = Modifier + .weight(1f) + .padding(vertical = 10.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color.LightGray) + ) + Box( + modifier = Modifier + .size(40.dp, 24.dp) + .background(Color.LightGray) + ) + } + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5a5f484..5cd0895 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ test-coroutines = "1.6.4" test-mockk = "1.13.12" test-turbine = "0.12.1" qdsfdhvh-image-loader = "1.7.1" +shimmer = "1.3.1" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -66,6 +67,7 @@ kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-co kotlin-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "common-datetime" } kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin-serializable-json" } +kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlin-coroutines" } common-koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } common-koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } @@ -78,6 +80,7 @@ test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve util-constraintlayout = { module = "tech.annexflow.compose:constraintlayout-compose-multiplatform", version.ref = "constraint-layout" } util-qdsfdhvh-image-loader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "qdsfdhvh-image-loader" } +util-shimmer = { module = "com.valentinilk.shimmer:compose-shimmer", version.ref = "shimmer" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }