From 6edda721d1d13f8f7fe72102e1d40f4babdce701 Mon Sep 17 00:00:00 2001 From: Yang Date: Fri, 8 Dec 2023 20:32:09 +1100 Subject: [PATCH] Home feed item transformation, modules restructuring. --- .../android/common/ui/feed/KotlinBlogCard.kt | 2 + .../common/ui/feed/KotlinWeeklyCard.kt | 2 + .../common/ui/feed/KotlinYouTubeCard.kt | 2 + .../common/ui/feed/TalkingKotlinCard.kt | 2 + android/feature/home/build.gradle.kts | 1 - .../android/feature/home/HomeScreen.kt | 2 +- .../feature/saved-for-later/build.gradle.kts | 1 - .../savedforlater/SavedForLaterScreen.kt | 2 +- gradle/libs.versions.toml | 2 + kmp/model/build.gradle.kts | 1 + .../kmp/model}/feed/DisplayableFeedItem.kt | 3 +- kmp/presentation/common/build.gradle.kts | 4 + kmp/presentation/home/build.gradle.kts | 3 + .../kmp/presentation/home/HomeFeedItem.kt | 9 + .../presentation/home/HomeFeedItemMapper.kt | 32 +++ .../home/HomeFeedItemMapperTest.kt | 193 ++++++++++++++++++ .../saved-for-later/build.gradle.kts | 3 + .../kstreamlined/kmp/prettytime/PrettyTime.kt | 8 +- .../kmp/prettytime/PrettyTimeTest.kt | 20 +- settings.gradle.kts | 1 + 20 files changed, 277 insertions(+), 16 deletions(-) rename {android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui => kmp/model/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/model}/feed/DisplayableFeedItem.kt (74%) create mode 100644 kmp/presentation/common/build.gradle.kts create mode 100644 kmp/presentation/home/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItem.kt create mode 100644 kmp/presentation/home/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItemMapper.kt create mode 100644 kmp/presentation/home/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItemMapperTest.kt diff --git a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinBlogCard.kt b/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinBlogCard.kt index 9194725e..3dfb4d4f 100644 --- a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinBlogCard.kt +++ b/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinBlogCard.kt @@ -23,7 +23,9 @@ import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.KST import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.BookmarkAdd import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.BookmarkFill import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.KSIcons +import io.github.reactivecircus.kstreamlined.kmp.model.feed.DisplayableFeedItem import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.toDisplayable import kotlinx.datetime.toInstant @Composable diff --git a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinWeeklyCard.kt b/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinWeeklyCard.kt index 8d368a82..8e9a70d0 100644 --- a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinWeeklyCard.kt +++ b/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinWeeklyCard.kt @@ -22,7 +22,9 @@ import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.KST import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.BookmarkAdd import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.BookmarkFill import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.KSIcons +import io.github.reactivecircus.kstreamlined.kmp.model.feed.DisplayableFeedItem import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.toDisplayable import kotlinx.datetime.toInstant @Composable diff --git a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinYouTubeCard.kt b/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinYouTubeCard.kt index 83f06ae7..7e1cde65 100644 --- a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinYouTubeCard.kt +++ b/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/KotlinYouTubeCard.kt @@ -32,7 +32,9 @@ import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.KST import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.BookmarkAdd import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.BookmarkFill import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.KSIcons +import io.github.reactivecircus.kstreamlined.kmp.model.feed.DisplayableFeedItem import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.toDisplayable import kotlinx.datetime.toInstant @OptIn(ExperimentalFoundationApi::class) diff --git a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/TalkingKotlinCard.kt b/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/TalkingKotlinCard.kt index 7477e70e..a5874930 100644 --- a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/TalkingKotlinCard.kt +++ b/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/TalkingKotlinCard.kt @@ -23,7 +23,9 @@ import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.KST import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.BookmarkAdd import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.BookmarkFill import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.KSIcons +import io.github.reactivecircus.kstreamlined.kmp.model.feed.DisplayableFeedItem import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.toDisplayable import kotlinx.datetime.toInstant @Composable diff --git a/android/feature/home/build.gradle.kts b/android/feature/home/build.gradle.kts index 367b8625..7fd9e276 100644 --- a/android/feature/home/build.gradle.kts +++ b/android/feature/home/build.gradle.kts @@ -14,5 +14,4 @@ dependencies { implementation(project(":feature:common")) implementation(project(":common-ui:feed")) implementation(project(":kmp:presentation:home")) - implementation(project(":kmp:data")) } diff --git a/android/feature/home/src/main/java/io/github/reactivecircus/kstreamlined/android/feature/home/HomeScreen.kt b/android/feature/home/src/main/java/io/github/reactivecircus/kstreamlined/android/feature/home/HomeScreen.kt index 7964ccae..f2d024c4 100644 --- a/android/feature/home/src/main/java/io/github/reactivecircus/kstreamlined/android/feature/home/HomeScreen.kt +++ b/android/feature/home/src/main/java/io/github/reactivecircus/kstreamlined/android/feature/home/HomeScreen.kt @@ -27,7 +27,6 @@ import io.github.reactivecircus.kstreamlined.android.common.ui.feed.KotlinBlogCa import io.github.reactivecircus.kstreamlined.android.common.ui.feed.KotlinWeeklyCard import io.github.reactivecircus.kstreamlined.android.common.ui.feed.KotlinYouTubeCard import io.github.reactivecircus.kstreamlined.android.common.ui.feed.TalkingKotlinCard -import io.github.reactivecircus.kstreamlined.android.common.ui.feed.toDisplayable import io.github.reactivecircus.kstreamlined.android.designsystem.component.FilledIconButton import io.github.reactivecircus.kstreamlined.android.designsystem.component.Surface import io.github.reactivecircus.kstreamlined.android.designsystem.component.Text @@ -37,6 +36,7 @@ import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.ico import io.github.reactivecircus.kstreamlined.android.feature.home.component.FeedFilterChip import io.github.reactivecircus.kstreamlined.android.feature.home.component.SyncButton import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.toDisplayable import kotlinx.coroutines.delay import kotlinx.datetime.toInstant diff --git a/android/feature/saved-for-later/build.gradle.kts b/android/feature/saved-for-later/build.gradle.kts index af671ee9..6e51312f 100644 --- a/android/feature/saved-for-later/build.gradle.kts +++ b/android/feature/saved-for-later/build.gradle.kts @@ -14,5 +14,4 @@ dependencies { implementation(project(":feature:common")) implementation(project(":common-ui:feed")) implementation(project(":kmp:presentation:saved-for-later")) - implementation(project(":kmp:data")) } diff --git a/android/feature/saved-for-later/src/main/java/io/github/reactivecircus/kstreamlined/android/feature/savedforlater/SavedForLaterScreen.kt b/android/feature/saved-for-later/src/main/java/io/github/reactivecircus/kstreamlined/android/feature/savedforlater/SavedForLaterScreen.kt index f78ab990..e4644125 100644 --- a/android/feature/saved-for-later/src/main/java/io/github/reactivecircus/kstreamlined/android/feature/savedforlater/SavedForLaterScreen.kt +++ b/android/feature/saved-for-later/src/main/java/io/github/reactivecircus/kstreamlined/android/feature/savedforlater/SavedForLaterScreen.kt @@ -21,12 +21,12 @@ import io.github.reactivecircus.kstreamlined.android.common.ui.feed.KotlinBlogCa import io.github.reactivecircus.kstreamlined.android.common.ui.feed.KotlinWeeklyCard import io.github.reactivecircus.kstreamlined.android.common.ui.feed.KotlinYouTubeCard import io.github.reactivecircus.kstreamlined.android.common.ui.feed.TalkingKotlinCard -import io.github.reactivecircus.kstreamlined.android.common.ui.feed.toDisplayable import io.github.reactivecircus.kstreamlined.android.designsystem.component.FilledIconButton import io.github.reactivecircus.kstreamlined.android.designsystem.component.TopNavBar import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.KSTheme import io.github.reactivecircus.kstreamlined.android.designsystem.foundation.icon.KSIcons import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.toDisplayable import kotlinx.datetime.toInstant @Composable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 40f664c0..9e4ddbff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ androidx-test-ext-junit = "1.1.5" androidx-test-espresso = "3.5.1" androidx-test-uiautomator = "2.2.0" coil = "2.5.0" +jetbrainsCompose = "1.5.11" kermit = "2.0.2" radiography = "2.5" sqldelight = "2.0.1" @@ -110,6 +111,7 @@ androidx-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib" androidx-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "androidx-test-espresso" } coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" } +jetbrainsComposeRuntime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrainsCompose" } apollo-runtime = { module = "com.apollographql.apollo3:apollo-runtime", version.ref = "apollo" } apollo-normalizedCache = { module = "com.apollographql.apollo3:apollo-normalized-cache", version.ref = "apollo" } apollo-adapters = { module = "com.apollographql.apollo3:apollo-adapters", version.ref = "apollo" } diff --git a/kmp/model/build.gradle.kts b/kmp/model/build.gradle.kts index 95a772a5..1f5185ff 100644 --- a/kmp/model/build.gradle.kts +++ b/kmp/model/build.gradle.kts @@ -7,6 +7,7 @@ kotlin { commonMain { dependencies { api(libs.kotlinx.datetime) + implementation(libs.jetbrainsComposeRuntime) } } } diff --git a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/DisplayableFeedItem.kt b/kmp/model/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/model/feed/DisplayableFeedItem.kt similarity index 74% rename from android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/DisplayableFeedItem.kt rename to kmp/model/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/model/feed/DisplayableFeedItem.kt index 506fd81b..c0e31293 100644 --- a/android/common-ui/feed/src/main/java/io/github/reactivecircus/kstreamlined/android/common/ui/feed/DisplayableFeedItem.kt +++ b/kmp/model/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/model/feed/DisplayableFeedItem.kt @@ -1,7 +1,6 @@ -package io.github.reactivecircus.kstreamlined.android.common.ui.feed +package io.github.reactivecircus.kstreamlined.kmp.model.feed import androidx.compose.runtime.Immutable -import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem @Immutable public data class DisplayableFeedItem( diff --git a/kmp/presentation/common/build.gradle.kts b/kmp/presentation/common/build.gradle.kts new file mode 100644 index 00000000..964b426d --- /dev/null +++ b/kmp/presentation/common/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + id("kstreamlined.kmp.common") + id("kstreamlined.kmp.test") +} diff --git a/kmp/presentation/home/build.gradle.kts b/kmp/presentation/home/build.gradle.kts index 652d5ffe..21b76662 100644 --- a/kmp/presentation/home/build.gradle.kts +++ b/kmp/presentation/home/build.gradle.kts @@ -7,7 +7,10 @@ kotlin { sourceSets { commonMain { dependencies { + implementation(project(":kmp:presentation:common")) + implementation(project(":kmp:data")) implementation(project(":kmp:pretty-time")) + api(project(":kmp:model")) } } } diff --git a/kmp/presentation/home/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItem.kt b/kmp/presentation/home/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItem.kt new file mode 100644 index 00000000..af722621 --- /dev/null +++ b/kmp/presentation/home/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItem.kt @@ -0,0 +1,9 @@ +package io.github.reactivecircus.kstreamlined.kmp.presentation.home + +import io.github.reactivecircus.kstreamlined.kmp.model.feed.DisplayableFeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem + +internal sealed interface HomeFeedItem { + data class SectionHeader(val title: String) : HomeFeedItem + data class Item(val displayableFeedItem: DisplayableFeedItem) : HomeFeedItem +} diff --git a/kmp/presentation/home/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItemMapper.kt b/kmp/presentation/home/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItemMapper.kt new file mode 100644 index 00000000..21492034 --- /dev/null +++ b/kmp/presentation/home/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItemMapper.kt @@ -0,0 +1,32 @@ +package io.github.reactivecircus.kstreamlined.kmp.presentation.home + +import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.toDisplayable +import io.github.reactivecircus.kstreamlined.kmp.prettytime.timeAgo +import io.github.reactivecircus.kstreamlined.kmp.prettytime.weeksAgo +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone + +internal fun List.toHomeFeedItems( + clock: Clock = Clock.System, + timeZone: TimeZone = TimeZone.currentSystemDefault(), +): List { + val homeFeedItems = mutableListOf() + var currentSectionHeader: String? = null + + // assume items are already sorted by publish time in descending order + forEach { feedItem -> + val sectionHeader = feedItem.publishTime.weeksAgo(clock) + println("${feedItem.publishTime}: \"$sectionHeader\" for item: $feedItem") + if (sectionHeader != currentSectionHeader) { + homeFeedItems.add(HomeFeedItem.SectionHeader(sectionHeader)) + currentSectionHeader = sectionHeader + } + val displayableFeedItem = feedItem.toDisplayable( + feedItem.publishTime.timeAgo(clock, timeZone) + ) + homeFeedItems.add(HomeFeedItem.Item(displayableFeedItem)) + } + + return homeFeedItems +} diff --git a/kmp/presentation/home/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItemMapperTest.kt b/kmp/presentation/home/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItemMapperTest.kt new file mode 100644 index 00000000..61f8639a --- /dev/null +++ b/kmp/presentation/home/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmp/presentation/home/HomeFeedItemMapperTest.kt @@ -0,0 +1,193 @@ +package io.github.reactivecircus.kstreamlined.kmp.presentation.home + +import io.github.reactivecircus.kstreamlined.kmp.model.feed.DisplayableFeedItem +import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant +import kotlin.test.Test +import kotlin.test.assertEquals + +class HomeFeedItemMapperTest { + + @Test + fun `transformed HomeFeedItems are grouped by weeks with expected displayable time`() { + val fixedClock = object : Clock { + override fun now(): Instant { + return "2023-12-03T03:10:54Z".toInstant() + } + } + val timeZone = TimeZone.UTC + + val feedItems = listOf( + // this week + // moments ago + FeedItem.KotlinBlog( + id = "1", + title = "Kotlin Blog 1", + publishTime = "2023-12-03T03:10:00Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + featuredImageUrl = "feature-image-url", + ), + // 30 minutes ago + FeedItem.KotlinYouTube( + id = "2", + title = "Kotlin YouTube 1", + publishTime = "2023-12-03T02:40:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + thumbnailUrl = "thumbnail-url", + description = "description", + ), + // 5 hours ago + FeedItem.KotlinWeekly( + id = "3", + title = "Kotlin Weekly 1", + publishTime = "2023-12-02T22:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + ), + // yesterday + FeedItem.TalkingKotlin( + id = "4", + title = "Talking Kotlin 1", + publishTime = "2023-12-02T03:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + podcastLogoUrl = "podcast-logo-url", + ), + // 5 days ago + FeedItem.KotlinBlog( + id = "5", + title = "Kotlin Blog 2", + publishTime = "2023-11-28T03:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + featuredImageUrl = "feature-image-url", + ), + // last week + // 12 days ago + FeedItem.KotlinYouTube( + id = "6", + title = "Kotlin YouTube 2", + publishTime = "2023-11-21T03:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + thumbnailUrl = "thumbnail-url", + description = "description", + ), + // earlier + // 20 days ago + FeedItem.KotlinWeekly( + id = "7", + title = "Kotlin Weekly 2", + publishTime = "2023-11-13T03:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + ), + ) + + val expectedHomeFeedItems = listOf( + HomeFeedItem.SectionHeader("This week"), + HomeFeedItem.Item( + displayableFeedItem = DisplayableFeedItem( + FeedItem.KotlinBlog( + id = "1", + title = "Kotlin Blog 1", + publishTime = "2023-12-03T03:10:00Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + featuredImageUrl = "feature-image-url", + ), + displayablePublishTime = "Moments ago", + ) + ), + HomeFeedItem.Item( + displayableFeedItem = DisplayableFeedItem( + FeedItem.KotlinYouTube( + id = "2", + title = "Kotlin YouTube 1", + publishTime = "2023-12-03T02:40:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + thumbnailUrl = "thumbnail-url", + description = "description", + ), + displayablePublishTime = "30 minutes ago", + ) + ), + HomeFeedItem.Item( + displayableFeedItem = DisplayableFeedItem( + FeedItem.KotlinWeekly( + id = "3", + title = "Kotlin Weekly 1", + publishTime = "2023-12-02T22:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + ), + displayablePublishTime = "5 hours ago", + ) + ), + HomeFeedItem.Item( + displayableFeedItem = DisplayableFeedItem( + FeedItem.TalkingKotlin( + id = "4", + title = "Talking Kotlin 1", + publishTime = "2023-12-02T03:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + podcastLogoUrl = "podcast-logo-url", + ), + displayablePublishTime = "Yesterday", + ) + ), + HomeFeedItem.Item( + displayableFeedItem = DisplayableFeedItem( + FeedItem.KotlinBlog( + id = "5", + title = "Kotlin Blog 2", + publishTime = "2023-11-28T03:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + featuredImageUrl = "feature-image-url", + ), + displayablePublishTime = "5 days ago", + ) + ), + HomeFeedItem.SectionHeader("Last week"), + HomeFeedItem.Item( + displayableFeedItem = DisplayableFeedItem( + FeedItem.KotlinYouTube( + id = "6", + title = "Kotlin YouTube 2", + publishTime = "2023-11-21T03:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + thumbnailUrl = "thumbnail-url", + description = "description", + ), + displayablePublishTime = "21 Nov 2023", + ) + ), + HomeFeedItem.SectionHeader("Earlier"), + HomeFeedItem.Item( + displayableFeedItem = DisplayableFeedItem( + FeedItem.KotlinWeekly( + id = "7", + title = "Kotlin Weekly 2", + publishTime = "2023-11-13T03:10:54Z".toInstant(), + contentUrl = "content-url", + savedForLater = false, + ), + displayablePublishTime = "13 Nov 2023", + ) + ), + ) + + val actualHomeFeedItems = feedItems.toHomeFeedItems(fixedClock, timeZone) + + assertEquals(expectedHomeFeedItems, actualHomeFeedItems) + } +} diff --git a/kmp/presentation/saved-for-later/build.gradle.kts b/kmp/presentation/saved-for-later/build.gradle.kts index 652d5ffe..21b76662 100644 --- a/kmp/presentation/saved-for-later/build.gradle.kts +++ b/kmp/presentation/saved-for-later/build.gradle.kts @@ -7,7 +7,10 @@ kotlin { sourceSets { commonMain { dependencies { + implementation(project(":kmp:presentation:common")) + implementation(project(":kmp:data")) implementation(project(":kmp:pretty-time")) + api(project(":kmp:model")) } } } diff --git a/kmp/pretty-time/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/prettytime/PrettyTime.kt b/kmp/pretty-time/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/prettytime/PrettyTime.kt index 3854b238..1e012d52 100644 --- a/kmp/pretty-time/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/prettytime/PrettyTime.kt +++ b/kmp/pretty-time/src/commonMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/prettytime/PrettyTime.kt @@ -6,8 +6,10 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime @Suppress("MagicNumber") -public fun Instant.weeksAgo(): String { - val duration = Clock.System.now().minus(this) +public fun Instant.weeksAgo( + clock: Clock = Clock.System +): String { + val duration = clock.now().minus(this) val weekDifference = duration.inWholeDays / 7 return when (weekDifference) { 0L -> "This week" @@ -19,7 +21,7 @@ public fun Instant.weeksAgo(): String { @Suppress("MagicNumber") public fun Instant.timeAgo( clock: Clock = Clock.System, - timeZone: TimeZone = TimeZone.currentSystemDefault() + timeZone: TimeZone = TimeZone.currentSystemDefault(), ): String { val now = clock.now() val duration = now.minus(this) diff --git a/kmp/pretty-time/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmp/prettytime/PrettyTimeTest.kt b/kmp/pretty-time/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmp/prettytime/PrettyTimeTest.kt index 1a7c6ea0..5290a0e8 100644 --- a/kmp/pretty-time/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmp/prettytime/PrettyTimeTest.kt +++ b/kmp/pretty-time/src/commonTest/kotlin/io/github/reactivecircus/kstreamlined/kmp/prettytime/PrettyTimeTest.kt @@ -13,15 +13,21 @@ import kotlin.time.Duration.Companion.seconds class PrettyTimeTest { + private val fixedClock = object : Clock { + override fun now(): Instant { + return "2023-12-03T03:10:54Z".toInstant() + } + } + @Test fun weeksAgo() { - val now = Clock.System.now() - assertEquals("This week", now.minus(1.seconds).weeksAgo()) - assertEquals("This week", now.minus(7.days - 1.seconds).weeksAgo()) - assertEquals("Last week", now.minus(7.days).weeksAgo()) - assertEquals("Last week", now.minus(14.days - 1.seconds).weeksAgo()) - assertEquals("Earlier", now.minus(14.days).weeksAgo()) - assertEquals("Earlier", now.minus(100.days).weeksAgo()) + val now = fixedClock.now() + assertEquals("This week", now.minus(1.seconds).weeksAgo(fixedClock)) + assertEquals("This week", now.minus(7.days - 1.seconds).weeksAgo(fixedClock)) + assertEquals("Last week", now.minus(7.days).weeksAgo(fixedClock)) + assertEquals("Last week", now.minus(14.days - 1.seconds).weeksAgo(fixedClock)) + assertEquals("Earlier", now.minus(14.days).weeksAgo(fixedClock)) + assertEquals("Earlier", now.minus(100.days).weeksAgo(fixedClock)) } @Test diff --git a/settings.gradle.kts b/settings.gradle.kts index cb979edc..c3c1ac27 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -67,6 +67,7 @@ include(":kmp:feed-datasource:edge") include(":kmp:feed-datasource:testing") include(":kmp:model") include(":kmp:persistence") +include(":kmp:presentation:common") include(":kmp:presentation:home") include(":kmp:presentation:saved-for-later") include(":kmp:pretty-time")