Skip to content

Commit

Permalink
feed data flow
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalbuddha committed Dec 21, 2022
1 parent 03e178a commit 08cbaae
Show file tree
Hide file tree
Showing 25 changed files with 393 additions and 29 deletions.
1 change: 1 addition & 0 deletions data/network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ kotlin {
implementation(libs.io.ktor.client.serialization)
implementation(libs.io.ktor.serialization.kotlinx.json)
implementation(libs.io.ktor.client.content.negotiation)
implementation(libs.io.ktor.client.auth)
implementation(libs.io.ktor.client.logging)
implementation(libs.org.jetbrains.kotlinx.serialization.json)
implementation(libs.io.insert.koin.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import social.androiddev.common.network.model.Application
import social.androiddev.common.network.model.AvailableInstance
import social.androiddev.common.network.model.Instance
import social.androiddev.common.network.model.NewOauthApplication
import social.androiddev.common.network.model.Status
import social.androiddev.common.network.model.Token

interface MastodonApi {
Expand Down Expand Up @@ -77,4 +78,11 @@ interface MastodonApi {
* @return an instance entity
*/
suspend fun getInstance(domain: String? = null): Result<Instance>

/**
* Fetch home feed for a particular user
* @param accessToken representing the user
* @return a list of [Status]
*/
suspend fun getHomeFeed(domain: String, accessToken: String): Result<List<Status>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import social.androiddev.common.network.model.Application
import social.androiddev.common.network.model.AvailableInstance
import social.androiddev.common.network.model.Instance
import social.androiddev.common.network.model.NewOauthApplication
import social.androiddev.common.network.model.Status
import social.androiddev.common.network.model.Token
import social.androiddev.common.network.model.request.CreateAccessTokenBody
import social.androiddev.common.network.model.request.CreateApplicationBody
Expand Down Expand Up @@ -124,4 +125,21 @@ internal class MastodonApiKtor(
Result.failure(exception = exception)
}
}

override suspend fun getHomeFeed(domain: String, accessToken: String): Result<List<Status>> {
return try {
val url = "https://$domain/api/v1/timelines/home"
val body = httpClient.get(url) {
headers {
append(HttpHeaders.Authorization, "Bearer $accessToken")
}
}.body<List<Status>>()
Result.success(
body
)
} catch (exception: SerializationException) {
Result.failure(exception = exception)
} catch (exception: ResponseException) {
Result.failure(exception = exception)
} }
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ val networkModule: Module = module {
}
)
}
// install(Auth) {
// bearer {
// loadTokens {
// // Load tokens from a local storage and return them as the 'BearerTokens' instance
// BearerTokens("abc123", "xyz111")
// }
// }
// }
defaultRequest {
url {
protocol = URLProtocol.HTTPS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import kotlinx.serialization.Serializable
*/
@Serializable
data class Application(
val id: String,
val id: String?=null,
val name: String,
@SerialName("vapid_key") val vapidKey: String,
@SerialName("vapid_key") val vapidKey: String?=null,

// optional attributes
val website: String? = null,
Expand Down
4 changes: 4 additions & 0 deletions data/persistence/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ sqldelight {
packageName = "social.androiddev.common.persistence"
sourceFolders = listOf("sqldelight")
}
database("TimelineDatabase") {
packageName = "social.androiddev.common.timeline"
sourceFolders = listOf("sqldelightTimeline")
}
}

android {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.koin.dsl.module
import social.androiddev.common.persistence.AuthenticationDatabase
import social.androiddev.common.persistence.localstorage.DodoAuthStorage
import social.androiddev.common.persistence.localstorage.DodoAuthStorageImpl
import social.androiddev.common.timeline.TimelineDatabase

/**
* Koin DI module for all android specific persistence dependencies
Expand Down Expand Up @@ -44,4 +45,13 @@ actual val persistenceModule: Module = module {
)
AuthenticationDatabase(driver)
}

single {
val driver = AndroidSqliteDriver(
schema = TimelineDatabase.Schema,
context = get(),
name = FEED_DB_NAME,
)
TimelineDatabase(driver)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ import org.koin.core.module.Module
expect val persistenceModule: Module

internal const val AUTH_DB_NAME = "authentication.db"
internal const val FEED_DB_NAME = "feed.db"
internal const val AUTH_SETTINGS_NAME = "DodoAuthSettings"
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
CREATE TABLE failedWrite (
key TEXT NOT NULL PRIMARY KEY,
datetime INTEGER AS Long
);

get:
SELECT *
FROM failedWrite
WHERE key = ?;

upsert:
INSERT OR REPLACE INTO failedWrite VALUES ?;

delete:
DELETE FROM failedWrite
WHERE key = ?;

deleteAll:
DELETE FROM failedWrite;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE TABLE TimelineItem (
statusId Text NOT NULL PRIMARY KEY,
type TEXT NOT NULL,
createdAt TEXT NOT NULL
);

insertFeedItem:
INSERT OR REPLACE INTO TimelineItem
VALUES ?;

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

deleteAll:
DELETE FROM TimelineItem;
2 changes: 2 additions & 0 deletions data/repository/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ kotlin {
implementation(projects.domain.authentication)
implementation(libs.io.insert.koin.core)
implementation(libs.kotlinx.coroutines.core)
api(libs.store)
implementation ("com.squareup.sqldelight:coroutines-extensions:1.5.4")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,23 @@
*/
package social.androiddev.common.repository.di

import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList
import kotlinx.coroutines.Dispatchers
import org.koin.core.module.Module
import org.koin.dsl.module
import org.mobilenativefoundation.store.store5.Bookkeeper
import org.mobilenativefoundation.store.store5.Market
import org.mobilenativefoundation.store.store5.NetworkFetcher
import org.mobilenativefoundation.store.store5.NetworkUpdater
import org.mobilenativefoundation.store.store5.OnNetworkCompletion
import org.mobilenativefoundation.store.store5.Store
import social.androiddev.common.network.MastodonApi
import social.androiddev.common.network.model.Status
import social.androiddev.common.persistence.localstorage.DodoAuthStorage
import social.androiddev.common.repository.AuthenticationRepositoryImpl
import social.androiddev.common.timeline.TimelineDatabase
import social.androiddev.common.timeline.TimelineItem
import social.androiddev.domain.authentication.repository.AuthenticationRepository

/**
Expand All @@ -31,3 +44,4 @@ val repositoryModule: Module = module {
)
}
}

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

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import org.mobilenativefoundation.store.store5.Market
import org.mobilenativefoundation.store.store5.MarketResponse
import org.mobilenativefoundation.store.store5.ReadRequest
import social.androiddev.common.timeline.TimelineItem

interface HomeTimelineRepository {
suspend fun read(): Flow<MarketResponse<List<TimelineItem>>>
}

class RealHomeTimelineRepository(
private val market: Market<FeedType, List<TimelineItem>, List<TimelineItem>>
) : HomeTimelineRepository {
/**
* returns a flow of home feed items from a database
* anytime table rows are created/updated will return a new list of timeline items
* on first return will also call network fetcher to get
* latest from network and update local storage with it]
*/


override suspend fun read(): Flow<MarketResponse<List<TimelineItem>>> {
return market.read(ReadRequest.of(
FeedType.Home,
emptyList(),
null,
true))
.distinctUntilChanged()
}


}


Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* This file is part of Dodo.
*
* Dodo is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* Dodo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Dodo. If not, see <https://www.gnu.org/licenses/>.
*/
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.dsl.module
import org.mobilenativefoundation.store.store5.Bookkeeper
import org.mobilenativefoundation.store.store5.Market
import org.mobilenativefoundation.store.store5.NetworkFetcher
import org.mobilenativefoundation.store.store5.NetworkUpdater
import org.mobilenativefoundation.store.store5.OnNetworkCompletion
import org.mobilenativefoundation.store.store5.Store
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.TimelineDatabase
import social.androiddev.common.timeline.TimelineItem

/**
* Koin module containing all koin/bean definitions for
* timeline repository delegates.
*/
val timelineRepoModule: Module = module {

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

factory<Store<FeedType, List<TimelineItem>, List<TimelineItem>>> {
val database = get<TimelineDatabase>()
Store.by(
reader = { key: FeedType ->
when(key){
is FeedType.Home -> get<TimelineDatabase>()
.timelineQueries
.selectHomeItems()
.asFlow()
.mapToList().map {
it.ifEmpty { throw Exception("Empty list") }
}
}

},
writer = { _, input ->
input.forEach(database::tryWriteItem)
true
},
deleter = { TODO() },
clearer = { TODO() }
)
}
factory {
//Todo Add logic for conflict resolution handling for when we start posting toots
Bookkeeper.by(
read = { _: FeedType -> null },
write = { _, _ -> true },
delete = { TODO() },
deleteAll = { TODO() }
)
}

factory {
NetworkFetcher.by(
get = { key: FeedType ->
when(key) {
is FeedType.Home -> {
val authStorage = get<DodoAuthStorage>()
get<MastodonApi>()
.getHomeFeed(authStorage.currentDomain!!, authStorage.getAccessToken(authStorage.currentDomain!!)!!)
.getOrThrow()
.map(::timelineItem)
}
}
},
post = { key, item -> TODO() },
converter = { it }
)
}


factory {
NetworkUpdater.by(
post = { key: FeedType, _: List<Status> ->
get<MastodonApi>()
TODO()
},
onCompletion = OnNetworkCompletion(
onSuccess = {},
onFailure = {}
),
converter = { TODO() }
)
}

factory<Market<FeedType, List<TimelineItem>, List<TimelineItem>>> {
Market.of<FeedType, List<TimelineItem>, List<TimelineItem>>(
stores = listOf(get()), //TODO MIKE: ADD memory cache
bookkeeper = get(),
fetcher = get(),
updater = get()
)
}
}

private fun timelineItem(it: Status) =
TimelineItem(it.id, FeedType.Home.type, it.createdAt)

fun TimelineDatabase.tryWriteItem(timelineItem: TimelineItem): Boolean = try {
timelineQueries.insertFeedItem(timelineItem)
true
} catch (t: Throwable) {
throw RuntimeException(t)
}

sealed class FeedType(val type:String){
object Home: FeedType("HOME")
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package social.androiddev.common.di
import social.androiddev.common.network.di.networkModule
import social.androiddev.common.persistence.di.persistenceModule
import social.androiddev.common.repository.di.repositoryModule
import social.androiddev.common.repository.timeline.timelineRepoModule
import social.androiddev.domain.authentication.di.domainAuthModule

/**
Expand All @@ -23,4 +24,5 @@ fun appModule() = listOf(
persistenceModule,
domainAuthModule,
repositoryModule,
timelineRepoModule
)
Loading

0 comments on commit 08cbaae

Please sign in to comment.