refactor: core:data -> core:data:client

This commit is contained in:
Cilly Leang 2026-04-02 21:55:45 +11:00
parent 4cdb9a305c
commit e7caeca356
Signed by: cilly
GPG key ID: 6500251E087653C9
21 changed files with 162 additions and 78 deletions

View file

@ -0,0 +1,54 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.androidMultiplatformLibrary)
alias(libs.plugins.ksp)
}
kotlin {
android {
namespace = "moe.lava.banksia.core.data.client"
compileSdk = libs.versions.android.compileSdk.get().toInt()
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
}
iosArm64()
iosSimulatorArm64()
jvm()
sourceSets {
androidMain.dependencies {
implementation(libs.koin.compose)
implementation(libs.ktor.client.okhttp)
}
commonMain.dependencies {
api(projects.core.data)
implementation(libs.okio)
implementation(libs.koin.core)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentnegotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.serialization.protobuf)
implementation(projects.core)
implementation(projects.core.room)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
}
}

View file

@ -0,0 +1,66 @@
package moe.lava.banksia.core.data
import io.ktor.client.HttpClient
import io.ktor.client.plugins.HttpSend
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.plugin
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import moe.lava.banksia.core.Constants
import moe.lava.banksia.core.data.repositories.ClientRouteRepository
import moe.lava.banksia.core.data.repositories.ClientStopRepository
import moe.lava.banksia.core.data.repositories.ClientStopTimeRepository
import moe.lava.banksia.core.data.repositories.RouteRepository
import moe.lava.banksia.core.data.repositories.StopRepository
import moe.lava.banksia.core.data.repositories.StopTimeRepository
import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource
import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource
import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource
import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource
import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource
import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource
import moe.lava.banksia.core.room.roomDiModule
import moe.lava.banksia.core.util.log
import moe.lava.banksia.data.ptv.PtvService
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
import org.koin.dsl.module
val clientDataDiModule = module {
includes(roomDiModule)
// HTTP Clients
singleOf(::PtvService)
single {
HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
defaultRequest {
url(Constants.serverUrl)
}
}.also { client ->
client.plugin(HttpSend).intercept { req ->
val fullPath = req.url.build().encodedPathAndQuery
log("ktor.client", fullPath)
execute(req)
}
}
}
// Data sources
singleOf(::RouteLocalDataSource)
singleOf(::RouteRemoteDataSource)
singleOf(::StopLocalDataSource)
singleOf(::StopRemoteDataSource)
singleOf(::StopTimeLocalDataSource)
singleOf(::StopTimeRemoteDataSource)
// Repositories
singleOf(::ClientRouteRepository) bind RouteRepository::class
singleOf(::ClientStopRepository) bind StopRepository::class
singleOf(::ClientStopTimeRepository) bind StopTimeRepository::class
}

View file

@ -0,0 +1,25 @@
package moe.lava.banksia.core.data.repositories
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource
import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource
internal class ClientRouteRepository internal constructor(
private val local: RouteLocalDataSource,
private val remote: RouteRemoteDataSource,
) : RouteRepository {
private val mutex = Mutex()
override suspend fun getAll() = mutex.withLock {
local
.getAll()
.map { it.asModel() }
.ifEmpty {
remote
.getAll()
.also { local.save(*it.toTypedArray()) }
}
}
override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) }
}

View file

@ -0,0 +1,22 @@
package moe.lava.banksia.core.data.repositories
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource
import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource
internal class ClientStopRepository internal constructor(
private val local: StopLocalDataSource,
private val remote: StopRemoteDataSource,
) : StopRepository {
private val mutex = Mutex()
override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) }
override suspend fun getByRoute(id: String) = mutex.withLock {
local
.getByRoute(id)
.map { it.asModel() }
.ifEmpty { null }
?: remote.getByRoute(id)
}
}

View file

@ -0,0 +1,16 @@
package moe.lava.banksia.core.data.repositories
import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource
import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource
import moe.lava.banksia.core.model.StopTimeDated
internal class ClientStopTimeRepository internal constructor(
private val local: StopTimeLocalDataSource,
private val remote: StopTimeRemoteDataSource,
) : StopTimeRepository {
override suspend fun getForStop(id: String): List<StopTimeDated> {
return local
.getAtStop(id)
.ifEmpty { remote.getAtStop(id) }
}
}

View file

@ -0,0 +1,11 @@
package moe.lava.banksia.core.data.sources.route
import moe.lava.banksia.core.model.Route
import moe.lava.banksia.core.room.dao.RouteDao
import moe.lava.banksia.core.room.entity.asEntity
internal class RouteLocalDataSource(private val dao: RouteDao) {
suspend fun get(id: String) = dao.get(id)
suspend fun getAll() = dao.getAll()
suspend fun save(vararg routes: Route) = dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray())
}

View file

@ -0,0 +1,11 @@
package moe.lava.banksia.core.data.sources.route
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import moe.lava.banksia.core.model.Route
internal class RouteRemoteDataSource(val client: HttpClient) {
suspend fun get(id: String) = client.get("routes/${id}").body<Route>()
suspend fun getAll() = client.get("routes").body<List<Route>>()
}

View file

@ -0,0 +1,12 @@
package moe.lava.banksia.core.data.sources.stop
import moe.lava.banksia.core.model.Stop
import moe.lava.banksia.core.room.dao.RouteDao
import moe.lava.banksia.core.room.dao.StopDao
import moe.lava.banksia.core.room.entity.asEntity
internal class StopLocalDataSource(private val dao: StopDao, private val routeDao: RouteDao) {
suspend fun get(id: String) = dao.get(id)
suspend fun getByRoute(id: String) = routeDao.stops(id)
suspend fun save(vararg stops: Stop) = dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray())
}

View file

@ -0,0 +1,11 @@
package moe.lava.banksia.core.data.sources.stop
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import moe.lava.banksia.core.model.Stop
internal class StopRemoteDataSource(val client: HttpClient) {
suspend fun get(id: String) = client.get("stops/${id}").body<Stop>()
suspend fun getByRoute(id: String) = client.get("route_stops/${id}").body<List<Stop>>()
}

View file

@ -0,0 +1,28 @@
package moe.lava.banksia.core.data.sources.stoptime
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.todayIn
import moe.lava.banksia.core.model.StopTimeDated
import moe.lava.banksia.core.model.atDate
import moe.lava.banksia.core.room.dao.StopTimeDao
import moe.lava.banksia.core.util.serialise
import kotlin.time.Clock
internal class StopTimeLocalDataSource(
private val stopTimeDao: StopTimeDao,
) {
suspend fun getAtStop(
stopId: String,
date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()),
): List<StopTimeDated> {
return stopTimeDao
.getForStopDated(
stopId,
listOf(date.dayOfWeek).serialise(),
date.toEpochDays().toInt(),
)
.map { it.asModel().atDate(date) }
.sortedBy { it.departureTime }
}
}

View file

@ -0,0 +1,36 @@
package moe.lava.banksia.core.data.sources.stoptime
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.todayIn
import moe.lava.banksia.core.model.StopTimeDated
import kotlin.time.Clock
internal class StopTimeRemoteDataSource(
private val client: HttpClient,
) {
suspend fun getAtStop(
stopId: String,
date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()),
): List<StopTimeDated> {
return client.get("stoptimes/by_stop/${stopId}") {
parameter("date", date)
}.body<List<StopTimeDated>>()
}
/*suspend fun get(
stop: String? = null,
trip: String? = null,
day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek,
): List<StopTime> {
return client.get("stoptimes") {
stop?.let { parameter("stop", it) }
trip?.let { parameter("trip", it) }
day?.let { parameter("day", it) }
}.body<List<StopTime>>()
}*/
}

View file

@ -0,0 +1,18 @@
package moe.lava.banksia.core.data.sources.trip
import io.ktor.client.HttpClient
import kotlinx.datetime.DayOfWeek
import kotlinx.datetime.TimeZone
import kotlinx.datetime.todayIn
import moe.lava.banksia.core.model.Trip
import kotlin.time.Clock
internal class TripRemoteDataSource(
private val client: HttpClient,
) {
suspend fun get(
day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek,
): List<Trip> {
return listOf()
}
}