-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7bf3cc9
commit 4b673a8
Showing
10 changed files
with
952 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 73 additions & 85 deletions
158
...onMain/kotlin/io/github/reactivecircus/kstreamlined/kmp/feed/datasource/FeedDataSource.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,118 +1,106 @@ | ||
package io.github.reactivecircus.kstreamlined.kmp.feed.datasource | ||
|
||
import app.cash.sqldelight.coroutines.asFlow | ||
import app.cash.sqldelight.coroutines.mapToList | ||
import app.cash.sqldelight.coroutines.mapToOneOrNull | ||
import io.github.reactivecircus.kstreamlined.kmp.database.KStreamlinedDatabase | ||
import io.github.reactivecircus.kstreamlined.kmp.feed.datasource.mapper.asExternalModel | ||
import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem | ||
import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedOrigin | ||
import io.github.reactivecircus.kstreamlined.kmp.model.feed.KotlinWeeklyIssueItem | ||
import io.github.reactivecircus.kstreamlined.kmp.networking.FeedService | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.CoroutineDispatcher | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.IO | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.distinctUntilChanged | ||
import kotlinx.coroutines.flow.mapLatest | ||
import kotlinx.datetime.toInstant | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.withContext | ||
|
||
public class FeedDataSource( | ||
private val feedService: FeedService, | ||
private val database: KStreamlinedDatabase, | ||
private val db: KStreamlinedDatabase, | ||
private val dbDispatcher: CoroutineDispatcher = Dispatchers.IO, | ||
) { | ||
public suspend fun selectFeedSource(feedOrigin: FeedOrigin) { | ||
feedOrigin.selected | ||
TODO() | ||
} | ||
|
||
public suspend fun unselectFeedSource(feedOrigin: FeedOrigin) { | ||
feedOrigin.selected | ||
TODO() | ||
} | ||
|
||
@Suppress("MaxLineLength") | ||
@OptIn(ExperimentalCoroutinesApi::class) | ||
public fun streamFeedItemById(id: String): Flow<FeedItem?> { | ||
database.feedItemEntityQueries | ||
return savedItemsIds.mapLatest { savedItemsIds -> | ||
when { | ||
id.contains("kotlin-weekly") -> { | ||
FeedItem.KotlinWeekly( | ||
id = id, | ||
title = "Kotlin Weekly #386", | ||
publishTime = "2023-11-05T08:13:58Z".toInstant(), | ||
contentUrl = id, | ||
savedForLater = savedItemsIds.contains(id), | ||
issueNumber = 386, | ||
) | ||
} | ||
|
||
id.contains("tag:soundcloud") -> fakeTalkingKotlinEpisode.copy( | ||
id = id, | ||
savedForLater = savedItemsIds.contains(id) | ||
) | ||
|
||
id.contains("blog.jetbrains.com") -> FeedItem.KotlinBlog( | ||
id = id, | ||
title = "Tackle Advent of Code 2023 With Kotlin and Win Prizes!", | ||
publishTime = "2023-11-23T17:00:38Z".toInstant(), | ||
contentUrl = "https://blog.jetbrains.com/kotlin/2023/11/advent-of-code-2023-with-kotlin/", | ||
savedForLater = savedItemsIds.contains(id), | ||
featuredImageUrl = "https://blog.jetbrains.com/wp-content/uploads/2023/11/DSGN-18072-Social-media-banners_Blog-Featured-image-1280x720-1.png", | ||
) | ||
|
||
id.contains("yt:video") -> FeedItem.KotlinYouTube( | ||
id = id, | ||
title = "Coil Goes Multiplatform with Colin White | Talking Kotlin #127", | ||
publishTime = "2023-11-29T17:30:08Z".toInstant(), | ||
contentUrl = "https://www.youtube.com/watch?v=apiVJfLvUBE", | ||
savedForLater = savedItemsIds.contains(id), | ||
thumbnailUrl = "https://i2.ytimg.com/vi/apiVJfLvUBE/hqdefault.jpg", | ||
description = "Welcome to another engaging episode of Talking Kotlin! In this edition, we dive into the dynamic world of Android development with Colin White, the creator of the widely acclaimed Coil library. Join us as we discuss the latest developments, insights, and the exciting roadmap for Coil. \uD83D\uDE80 Highlights from this Episode: Learn about Colin's journey in developing the Coil library. Discover the pivotal role Coil plays in simplifying image loading for Android developers. Get an exclusive sneak peek into the upcoming Coil 3.0, featuring multi-platform support and seamless integration with Jetpack Compose. \uD83D\uDD17 Helpful Links: Coil Library GitHub: https://coil-kt.github.io/coil/ Follow Colin White on Twitter: https://twitter.com/colinwhi \uD83C\uDF10 Connect with the Kotlin Community: https://kotlinlang.org/community/ Kotlin Foundation: https://kotlinfoundation.org/ \uD83D\uDC49 Don't miss out on the latest insights and updates from the Kotlin world! Subscribe, hit the bell icon, and join the conversation in the comments below. \uD83D\uDCC8 Help us reach 20,000 views by liking, sharing, and subscribing! Your support keeps the Kotlin conversation alive.", | ||
) | ||
|
||
else -> null | ||
public fun streamFeedOrigins(): Flow<List<FeedOrigin>> { | ||
return db.feedOriginEntityQueries.allFeedOrigins() | ||
.asFlow().mapToList(dbDispatcher) | ||
.map { origins -> | ||
origins.map { it.asExternalModel() } | ||
} | ||
}.distinctUntilChanged() | ||
} | ||
|
||
public suspend fun addSavedFeedItem(id: String) { | ||
savedItemsIds.value += id | ||
public fun streamFeedItemsForSelectedOrigins(): Flow<List<FeedItem>> { | ||
return db.feedItemEntityQueries.feedItemsForSelectedOrigins() | ||
.asFlow().mapToList(dbDispatcher) | ||
.map { items -> | ||
items.map { it.asExternalModel() } | ||
} | ||
} | ||
|
||
public suspend fun removeSavedFeedItem(id: String) { | ||
savedItemsIds.value -= id | ||
public fun streamSavedFeedItems(): Flow<List<FeedItem>> { | ||
return db.feedItemEntityQueries.savedFeedItems() | ||
.asFlow().mapToList(dbDispatcher) | ||
.map { items -> | ||
items.map { it.asExternalModel() } | ||
} | ||
} | ||
|
||
public suspend fun loadSavedFeedItems(): List<FeedItem> { | ||
TODO() | ||
public fun streamFeedItemById(id: String): Flow<FeedItem?> { | ||
return db.feedItemEntityQueries.feedItemById(id) | ||
.asFlow().mapToOneOrNull(dbDispatcher) | ||
.map { it?.asExternalModel() } | ||
} | ||
|
||
public suspend fun loadKotlinWeeklyIssue(url: String): List<KotlinWeeklyIssueItem> { | ||
// TODO persist fetched entry to DB | ||
return feedService.fetchKotlinWeeklyIssue(url).map { | ||
it.asExternalModel() | ||
} | ||
} | ||
|
||
public suspend fun saveTalkingKotlinEpisodeStartPosition(id: String, positionMillis: Long) { | ||
// TODO persist to DB | ||
fakeTalkingKotlinEpisode = fakeTalkingKotlinEpisode.copy( | ||
id = id, | ||
startPositionMillis = positionMillis, | ||
) | ||
public suspend fun selectFeedSource(feedOriginKey: FeedOrigin.Key): Unit = | ||
withContext(dbDispatcher) { | ||
db.feedOriginEntityQueries.updateSelection(selected = true, key = feedOriginKey.name) | ||
} | ||
|
||
public suspend fun unselectFeedSource(feedOriginKey: FeedOrigin.Key): Unit = | ||
withContext(dbDispatcher) { | ||
db.transaction { | ||
// select all other origins if the one being unselected is the last one selected | ||
val currentFeedOrigins = db.feedOriginEntityQueries.allFeedOrigins().executeAsList() | ||
val selectedOrigins = currentFeedOrigins.filter { it.selected } | ||
val isLastSelected = selectedOrigins.size == 1 && | ||
selectedOrigins.first().key == feedOriginKey.name | ||
if (isLastSelected) { | ||
currentFeedOrigins.filter { it.key != feedOriginKey.name }.forEach { origin -> | ||
db.feedOriginEntityQueries.updateSelection( | ||
selected = true, | ||
key = origin.key, | ||
) | ||
} | ||
} | ||
db.feedOriginEntityQueries.updateSelection( | ||
selected = false, | ||
key = feedOriginKey.name | ||
) | ||
} | ||
} | ||
|
||
public suspend fun addSavedFeedItem(id: String): Unit = withContext(dbDispatcher) { | ||
db.feedItemEntityQueries.updateSavedForLaterById(saved_for_later = true, id = id) | ||
} | ||
|
||
private val savedItemsIds = MutableStateFlow(emptySet<String>()) | ||
public suspend fun removeSavedFeedItem(id: String): Unit = withContext(dbDispatcher) { | ||
db.feedItemEntityQueries.updateSavedForLaterById(saved_for_later = false, id = id) | ||
} | ||
|
||
@Suppress("MaxLineLength") | ||
private var fakeTalkingKotlinEpisode = FeedItem.TalkingKotlin( | ||
id = "tag:soundcloud,2010:tracks/1689535512", | ||
title = "Coil Goes Multiplatform with Colin White", | ||
publishTime = "2023-11-29T22:00:00Z".toInstant(), | ||
contentUrl = "https://soundcloud.com/user-38099918/coil-goes-multiplatform-with-colin-white", | ||
savedForLater = false, | ||
audioUrl = "https://feeds.soundcloud.com/stream/1689535512-user-38099918-network-resilient-applications-with-store5-talking-kotlin-128.mp3", | ||
thumbnailUrl = "https://talkingkotlin.com/images/kotlin_talking_logo.png", | ||
summary = "Welcome to another engaging episode of Talking Kotlin! In this edition, we dive into the dynamic world of Android development with Colin White, the creator of the widely acclaimed Coil library. Join us as we discuss the latest developments, insights, and the exciting roadmap for Coil. \uD83D\uDE80 Highlights from this Episode: Learn about Colin's journey in developing the Coil library. Discover the pivotal role Coil plays in simplifying image loading for Android developers. Get an exclusive sneak peek into the upcoming Coil 3.0, featuring multi-platform support and seamless integration with Jetpack Compose. \uD83D\uDD17 Helpful Links: Coil Library GitHub: github.com/coilkt/coil Follow Colin White on Twitter: @colinwhi \uD83C\uDF10 Connect with the Kotlin Community: https://kotlinlang.org/community/ Kotlin Foundation: https://kotlinfoundation.org/", | ||
duration = "42min.", | ||
startPositionMillis = 0, | ||
) | ||
public suspend fun saveTalkingKotlinEpisodeStartPosition( | ||
id: String, | ||
positionMillis: Long, | ||
): Unit = withContext(dbDispatcher) { | ||
db.feedItemEntityQueries.updatePotcastStartPositionById( | ||
id = id, | ||
podcast_start_position = positionMillis, | ||
) | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
...otlin/io/github/reactivecircus/kstreamlined/kmp/feed/datasource/mapper/FeedItemMappers.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package io.github.reactivecircus.kstreamlined.kmp.feed.datasource.mapper | ||
|
||
import io.github.reactivecircus.kstreamlined.kmp.database.FeedItemEntity | ||
import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedItem | ||
import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedOrigin | ||
|
||
internal fun FeedItemEntity.asExternalModel(): FeedItem { | ||
return when (feed_origin_key) { | ||
FeedOrigin.Key.KotlinBlog.name -> FeedItem.KotlinBlog( | ||
id = id, | ||
title = title, | ||
publishTime = publish_time, | ||
contentUrl = content_url, | ||
savedForLater = saved_for_later, | ||
featuredImageUrl = image_url!!, | ||
) | ||
|
||
FeedOrigin.Key.KotlinYouTubeChannel.name -> FeedItem.KotlinYouTube( | ||
id = id, | ||
title = title, | ||
publishTime = publish_time, | ||
contentUrl = content_url, | ||
savedForLater = saved_for_later, | ||
thumbnailUrl = image_url!!, | ||
description = description!!, | ||
) | ||
|
||
FeedOrigin.Key.TalkingKotlinPodcast.name -> FeedItem.TalkingKotlin( | ||
id = id, | ||
title = title, | ||
publishTime = publish_time, | ||
contentUrl = content_url, | ||
savedForLater = saved_for_later, | ||
audioUrl = podcast_audio_url!!, | ||
thumbnailUrl = image_url!!, | ||
summary = description!!, | ||
duration = podcast_duration!!, | ||
startPositionMillis = podcast_start_position ?: 0, | ||
) | ||
|
||
FeedOrigin.Key.KotlinWeekly.name -> FeedItem.KotlinWeekly( | ||
id = id, | ||
title = title, | ||
publishTime = publish_time, | ||
contentUrl = content_url, | ||
savedForLater = saved_for_later, | ||
issueNumber = issue_number!!.toInt(), | ||
) | ||
|
||
else -> error("Unknown feed origin key: $feed_origin_key") | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...lin/io/github/reactivecircus/kstreamlined/kmp/feed/datasource/mapper/FeedOriginMappers.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.github.reactivecircus.kstreamlined.kmp.feed.datasource.mapper | ||
|
||
import io.github.reactivecircus.kstreamlined.kmp.database.FeedOriginEntity | ||
import io.github.reactivecircus.kstreamlined.kmp.model.feed.FeedOrigin | ||
|
||
internal fun FeedOriginEntity.asExternalModel(): FeedOrigin { | ||
return FeedOrigin( | ||
key = when (key) { | ||
FeedOrigin.Key.KotlinBlog.name -> FeedOrigin.Key.KotlinBlog | ||
FeedOrigin.Key.KotlinYouTubeChannel.name -> FeedOrigin.Key.KotlinYouTubeChannel | ||
FeedOrigin.Key.TalkingKotlinPodcast.name -> FeedOrigin.Key.TalkingKotlinPodcast | ||
FeedOrigin.Key.KotlinWeekly.name -> FeedOrigin.Key.KotlinWeekly | ||
else -> error("Unknown feed origin key: $key") | ||
}, | ||
title = title, | ||
description = description, | ||
selected = selected, | ||
) | ||
} |
Oops, something went wrong.