Skip to content

Commit

Permalink
split timelineRepoModule into delegates
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalbuddha committed Dec 21, 2022
1 parent 3d7ba4c commit 00e7618
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,11 @@ internal class MastodonApiKtor(

override suspend fun getHomeFeed(domain: String, accessToken: String): Result<List<Status>> {
return runCatchingIgnoreCancelled<List<Status>> {
httpClient.get("https://$domain/api/v1/timelines/home") {
httpClient.get("https://$domain/api/v1/timelines/home") {
headers {
append(HttpHeaders.Authorization, "Bearer $accessToken")
}
}.body()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CREATE TABLE TimelineItem (
CREATE TABLE StatusDB (
type TEXT NOT NULL,
remoteId Text NOT NULL PRIMARY KEY,
uri Text NOT NULL,
Expand All @@ -18,13 +18,13 @@ CREATE TABLE TimelineItem (
);

insertFeedItem:
INSERT OR REPLACE INTO TimelineItem
INSERT OR REPLACE INTO StatusDB
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);

selectHomeItems:
SELECT * FROM TimelineItem
SELECT * FROM StatusDB
WHERE type = "HOME"
ORDER BY createdAt;

deleteAll:
DELETE FROM TimelineItem;
DELETE FROM StatusDB;
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import org.mobilenativefoundation.store.store5.StoreRequest
import org.mobilenativefoundation.store.store5.StoreResponse
import social.androiddev.domain.timeline.FeedType
import social.androiddev.domain.timeline.HomeTimelineRepository
import social.androiddev.domain.timeline.model.StatusUI
import social.androiddev.domain.timeline.model.StatusLocal


class RealHomeTimelineRepository(
private val store: Store<FeedType, List<StatusUI>>
private val store: Store<FeedType, List<StatusLocal>>
) : HomeTimelineRepository {
/**
* returns a flow of home feed items from a database
Expand All @@ -22,7 +22,7 @@ class RealHomeTimelineRepository(
override suspend fun read(
feedType: FeedType,
refresh: Boolean
): Flow<StoreResponse<List<StatusUI>>> {
): Flow<StoreResponse<List<StatusLocal>>> {
return store.stream(StoreRequest.cached(key = feedType, refresh = true))
.distinctUntilChanged() }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package social.androiddev.common.repository.timeline

import social.androiddev.common.network.model.Status
import social.androiddev.common.timeline.StatusDB
import social.androiddev.domain.timeline.FeedType
import social.androiddev.domain.timeline.model.StatusLocal

fun StatusDB.toLocal(
key: FeedType
) = StatusLocal(
remoteId = remoteId,
feedType = key,
createdAt = createdAt,
repliesCount = repliesCount,
reblogsCount = favouritesCount,
favoritesCount = favouritesCount,
content = content,
sensitive = sensitive ?: false,
spoilerText = spoilerText,
visibility = visibility,
avatarUrl = avatarUrl,
accountAddress = accountAddress,
userName = userName
)


fun Status.statusDB() =
StatusDB(
type = FeedType.Home.type,
remoteId = id,
uri = uri,
createdAt = createdAt,
content = content,
accountId = account?.id,
visibility = visibility.name,
sensitive = sensitive,
spoilerText = spoilerText,
applicationName = application?.name ?: "",
repliesCount = repliesCount?.toLong(),
reblogsCount = reblogsCount?.toLong(),
favouritesCount = favouritesCount?.toLong(),
avatarUrl = account?.avatar?:"",
accountAddress = account?.acct?:"",
userName = account?.username?:" "
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package social.androiddev.common.repository.timeline

import org.mobilenativefoundation.store.store5.Fetcher
import social.androiddev.common.network.MastodonApi
import social.androiddev.common.persistence.localstorage.DodoAuthStorage
import social.androiddev.common.timeline.StatusDB
import social.androiddev.domain.timeline.FeedType

/**
* Wrapper for [MastodonApi.getHomeFeed] while also getting an auth token from storage
* and mapping result to list of [StatusDB]
*/

fun MastodonApi.timelineFetcher(authStorage: DodoAuthStorage): Fetcher<FeedType, List<StatusDB>> =
Fetcher.of { key: FeedType ->
when (key) {
is FeedType.Home -> {
getHomeFeed(
authStorage.currentDomain!!,
authStorage.getAccessToken(authStorage.currentDomain!!)!!
)
.getOrThrow()
.map { it.statusDB() }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,17 @@
*/
package social.androiddev.common.repository.timeline

import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList
import kotlinx.coroutines.flow.map
import org.koin.core.module.Module
import org.koin.core.parameter.ParametersHolder
import org.koin.dsl.module
import org.mobilenativefoundation.store.store5.Fetcher
import org.mobilenativefoundation.store.store5.SourceOfTruth
import org.mobilenativefoundation.store.store5.StoreBuilder
import social.androiddev.common.network.MastodonApi
import social.androiddev.common.network.model.Status
import social.androiddev.common.persistence.localstorage.DodoAuthStorage
import social.androiddev.common.timeline.StatusDB
import social.androiddev.common.timeline.TimelineDatabase
import social.androiddev.common.timeline.TimelineItem
import social.androiddev.domain.timeline.FeedType
import social.androiddev.domain.timeline.HomeTimelineRepository
import social.androiddev.domain.timeline.model.StatusUI
import social.androiddev.domain.timeline.model.StatusLocal

/**
* Koin module containing all koin/bean definitions for
Expand All @@ -35,118 +29,21 @@ val timelineRepoModule: Module = module {

factory<HomeTimelineRepository> { RealHomeTimelineRepository(get()) }

factory<SourceOfTruth<FeedType, List<TimelineItem>, List<StatusUI>>> { it: ParametersHolder ->
val database = get<TimelineDatabase>()
factory { get<TimelineDatabase>().asSourceOfTruth() }

SourceOfTruth.of(
reader = { key: FeedType ->
when (key) {
is FeedType.Home -> get<TimelineDatabase>()
.timelineQueries
.selectHomeItems()
.asFlow()
.mapToList()
.map {
it.ifEmpty {
return@map null
}
it.map { item ->
StatusUI(
remoteId = item.remoteId,
feedType = key,
createdAt = item.createdAt,
repliesCount = item.repliesCount,
reblogsCount = item.favouritesCount,
favoritesCount = item.favouritesCount,
content = item.content,
sensitive = item.sensitive ?: false,
spoilerText = item.spoilerText,
visibility = item.visibility,
avatarUrl = item.avatarUrl,
accountAddress = item.accountAddress,
userName = item.userName
)
}
}
}
},
writer = { key, input ->
input.forEach { database.tryWriteItem(it, key) }
}
)
}
factory { get<MastodonApi>().timelineFetcher(authStorage = get()) }

factory<Fetcher<FeedType, List<TimelineItem>>> {
Fetcher.of { key: FeedType ->
when (key) {
is FeedType.Home -> {
val authStorage = get<DodoAuthStorage>()
get<MastodonApi>()
.getHomeFeed(
authStorage.currentDomain!!,
authStorage.getAccessToken(authStorage.currentDomain!!)!!
)
.getOrThrow()
.map(::timelineItem)
}
}
}
}

factory {
val fetcher = get<Fetcher<FeedType, List<TimelineItem>>>()
val sourceOfTruth = get<SourceOfTruth<FeedType, List<TimelineItem>, List<StatusUI>>>()
StoreBuilder
.from(
val fetcher = get<Fetcher<FeedType, List<StatusDB>>>()
val sourceOfTruth = get<SourceOfTruth<FeedType, List<StatusDB>, List<StatusLocal>>>()
StoreBuilder.from(
fetcher = fetcher,
sourceOfTruth = sourceOfTruth
)
.build()
}
}

private fun timelineItem(it: Status) =
TimelineItem(
type = FeedType.Home.type,
remoteId = it.id,
uri = it.uri,
createdAt = it.createdAt,
content = it.content,
accountId = it.account?.id,
visibility = it.visibility.name,
sensitive = it.sensitive,
spoilerText = it.spoilerText,
applicationName = it.application?.name ?: "",
repliesCount = it.repliesCount?.toLong(),
reblogsCount = it.reblogsCount?.toLong(),
favouritesCount = it.favouritesCount?.toLong(),
avatarUrl = it.account?.avatar?:"",
accountAddress = it.account?.acct?:"",
userName = it.account?.username?:" "
)


fun TimelineDatabase.tryWriteItem(it: TimelineItem, type: FeedType): Boolean = try {
timelineQueries.insertFeedItem(
type = type.type,
remoteId = it.remoteId,
uri = it.uri,
createdAt = it.createdAt,
content = it.content,
accountId = it.accountId,
visibility = it.visibility,
sensitive = it.sensitive,
spoilerText = it.spoilerText,
applicationName = it.applicationName,
repliesCount = it.repliesCount,
favouritesCount = it.favouritesCount,
reblogsCount = it.reblogsCount,
avatarUrl = it.avatarUrl,
accountAddress = it.accountAddress,
userName = it.userName
)
true
} catch (t: Throwable) {
throw RuntimeException(t)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package social.androiddev.common.repository.timeline

import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList
import kotlinx.coroutines.flow.map
import org.mobilenativefoundation.store.store5.SourceOfTruth
import social.androiddev.common.timeline.StatusDB
import social.androiddev.common.timeline.TimelineDatabase
import social.androiddev.common.timeline.TimelineQueries
import social.androiddev.domain.timeline.FeedType
import social.androiddev.domain.timeline.model.StatusLocal

fun TimelineDatabase.asSourceOfTruth(): SourceOfTruth<FeedType, List<StatusDB>, List<StatusLocal>> =
SourceOfTruth.of(
reader = reader(),
writer = { key, input ->
input.forEach { item -> tryWriteItem(item, key) }
}
)

private fun TimelineDatabase.reader() = { key: FeedType ->
when (key) {
is FeedType.Home ->
timelineQueries.homeItemsAsLocal(key)
}
}

private fun TimelineQueries.homeItemsAsLocal(key: FeedType) = selectHomeItems()
.asFlow()
.mapToList()
.map {
it.ifEmpty { return@map null } //treat empty list as no result otherwise
it.map { item -> item.toLocal(key) }
}

fun TimelineDatabase.tryWriteItem(it: StatusDB, type: FeedType): Boolean = try {
timelineQueries.insertFeedItem(
type = type.type,
remoteId = it.remoteId,
uri = it.uri,
createdAt = it.createdAt,
content = it.content,
accountId = it.accountId,
visibility = it.visibility,
sensitive = it.sensitive,
spoilerText = it.spoilerText,
applicationName = it.applicationName,
repliesCount = it.repliesCount,
favouritesCount = it.favouritesCount,
reblogsCount = it.reblogsCount,
avatarUrl = it.avatarUrl,
accountAddress = it.accountAddress,
userName = it.userName
)
true
} catch (t: Throwable) {
throw RuntimeException(t)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package social.androiddev.domain.timeline
import kotlinx.coroutines.flow.Flow

import org.mobilenativefoundation.store.store5.StoreResponse
import social.androiddev.domain.timeline.model.StatusUI
import social.androiddev.domain.timeline.model.StatusLocal

interface HomeTimelineRepository {
suspend fun read(feedType: FeedType, refresh: Boolean = false): Flow<StoreResponse<List<StatusUI>>>
suspend fun read(feedType: FeedType, refresh: Boolean = false): Flow<StoreResponse<List<StatusLocal>>>
}
sealed class FeedType(val type:String){
object Home: FeedType("HOME")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package social.androiddev.domain.timeline.model

import social.androiddev.domain.timeline.FeedType

data class StatusUI(
data class StatusLocal(
val remoteId:String,
val feedType: FeedType,
val createdAt: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children
import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
import kotlinx.coroutines.flow.StateFlow
import org.mobilenativefoundation.store.store5.StoreResponse
import social.androiddev.domain.timeline.model.StatusUI
import social.androiddev.domain.timeline.model.StatusLocal
import social.androiddev.signedin.navigation.SignedInRootComponent
import social.androiddev.timeline.TimelineContent

Expand Down Expand Up @@ -58,7 +58,7 @@ fun SignedInRootContent(

@Composable
private fun TimelineTab(
state: StateFlow<StoreResponse<List<StatusUI>>>
state: StateFlow<StoreResponse<List<StatusLocal>>>
) {
TimelineContent(
state = state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.StateFlow
import org.mobilenativefoundation.store.store5.StoreResponse
import social.androiddev.common.theme.DodoTheme
import social.androiddev.domain.timeline.model.StatusUI
import social.androiddev.domain.timeline.model.StatusLocal
import social.androiddev.timeline.navigation.TimelineComponent

/**
Expand All @@ -33,7 +33,7 @@ import social.androiddev.timeline.navigation.TimelineComponent
*/
@Composable
fun TimelineContent(
state: StateFlow<StoreResponse<List<StatusUI>>>,
state: StateFlow<StoreResponse<List<StatusLocal>>>,
modifier: Modifier = Modifier,
) {
val items = state.collectAsState()
Expand Down
Loading

0 comments on commit 00e7618

Please sign in to comment.