Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.github.ai.simplesplit.android.data.api

import arrow.core.Either
import arrow.core.Some
import com.github.ai.simplesplit.android.data.json.JsonSerializer
import com.github.ai.simplesplit.android.data.settings.Settings
import com.github.ai.simplesplit.android.model.exception.ApiException
Expand All @@ -14,6 +13,7 @@ import com.github.ai.split.api.request.PutGroupRequest
import com.github.ai.split.api.request.PutMemberRequest
import com.github.ai.split.api.response.DeleteExpenseResponse
import com.github.ai.split.api.response.DeleteMemberResponse
import com.github.ai.split.api.response.GetCurrenciesResponse
import com.github.ai.split.api.response.GetGroupsResponse
import com.github.ai.split.api.response.PostExpenseResponse
import com.github.ai.split.api.response.PostGroupResponse
Expand Down Expand Up @@ -69,7 +69,7 @@ class ApiClient(
httpClient.sendRequest<PostGroupRequest, PostGroupResponse>(
type = RequestType.POST,
url = "$baseUrl/group",
body = Some(request)
body = request
)

suspend fun postExpense(
Expand All @@ -79,7 +79,7 @@ class ApiClient(
httpClient.sendRequest<PostExpenseRequest, PostExpenseResponse>(
type = RequestType.POST,
url = "$baseUrl/expense?password=$password",
body = Some(request)
body = request
)

suspend fun putExpense(
Expand All @@ -90,7 +90,7 @@ class ApiClient(
httpClient.sendRequest(
type = RequestType.PUT,
url = "$baseUrl/expense/$expenseUid?password=$password",
body = Some(request)
body = request
)

suspend fun removeExpense(
Expand All @@ -110,7 +110,7 @@ class ApiClient(
httpClient.sendRequest<PutGroupRequest, PutGroupResponse>(
type = RequestType.PUT,
url = "$baseUrl/group/$uid?password=$password",
body = Some(request)
body = request
)

suspend fun postMember(
Expand All @@ -120,7 +120,7 @@ class ApiClient(
httpClient.sendRequest(
type = RequestType.POST,
url = "$baseUrl/member?password=$password",
body = Some(request)
body = request
)

suspend fun removeMember(
Expand All @@ -140,7 +140,13 @@ class ApiClient(
httpClient.sendRequest<PutMemberRequest, PutMemberResponse>(
type = RequestType.PUT,
url = "$baseUrl/member/$memberUid?password=$password",
body = Some(request)
body = request
)

suspend fun getCurrencies(): Either<ApiException, GetCurrenciesResponse> =
httpClient.sendRequest<Unit, GetCurrenciesResponse>(
type = RequestType.GET,
url = "$baseUrl/currency"
)

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.github.ai.simplesplit.android.data.api

import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.raise.either
import com.github.ai.simplesplit.android.model.exception.ApiException
import com.github.ai.simplesplit.android.model.exception.InvalidResponseException
Expand Down Expand Up @@ -30,7 +28,7 @@ enum class RequestType {
suspend inline fun <reified Request, reified Response> HttpClient.sendRequest(
type: RequestType,
url: String,
body: Option<Request> = None
body: Request? = null
): Either<ApiException, Response> {
val client = this

Expand All @@ -41,12 +39,16 @@ suspend inline fun <reified Request, reified Response> HttpClient.sendRequest(

RequestType.POST -> client.post(url) {
contentType(ContentType.Application.Json)
setBody(body.getOrNull())
if (body != null) {
setBody(body)
}
}

RequestType.PUT -> client.put(url) {
contentType(ContentType.Application.Json)
setBody(body.getOrNull())
if (body != null) {
setBody(body)
}
}

RequestType.DELETE -> client.delete(url)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.ai.simplesplit.android.data.api.coverters

import com.github.ai.simplesplit.android.data.database.model.CurrencyEntity
import com.github.ai.split.api.CurrencyDto

fun List<CurrencyDto>.toCurrencies(): List<CurrencyEntity> {
return this.map { dto -> dto.toCurrency() }
}

fun CurrencyDto.toCurrency(): CurrencyEntity =
CurrencyEntity(
isoCode = isoCode,
name = name,
symbol = symbol
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.github.ai.simplesplit.android.data.database.dao.CurrencyEntityDao
import com.github.ai.simplesplit.android.data.database.dao.GroupCredentialsDao
import com.github.ai.simplesplit.android.data.database.model.CurrencyEntity
import com.github.ai.simplesplit.android.data.database.model.GroupCredentials

@Database(
entities = [GroupCredentials::class],
entities = [
GroupCredentials::class,
CurrencyEntity::class
],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {

abstract fun groupCredentialsDao(): GroupCredentialsDao
abstract fun currencyEntityDao(): CurrencyEntityDao

companion object {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.github.ai.simplesplit.android.data.database.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.github.ai.simplesplit.android.data.database.model.CurrencyEntity

@Dao
interface CurrencyEntityDao {

@Query("SELECT * FROM currencies WHERE isoCode = :isoCode")
fun findByIsoCode(isoCode: String): CurrencyEntity?

@Query("SELECT * FROM currencies")
fun getAll(): List<CurrencyEntity>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(currency: CurrencyEntity)

@Update
fun update(currency: CurrencyEntity)

@Query("DELETE FROM currencies WHERE isoCode = :isoCode")
fun deleteByIsoCode(isoCode: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.ai.simplesplit.android.data.database.model

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity("currencies")
data class CurrencyEntity(
@PrimaryKey
val isoCode: String,
val name: String,
val symbol: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.github.ai.simplesplit.android.data.repository

import arrow.core.Either
import arrow.core.raise.either
import com.github.ai.simplesplit.android.data.api.ApiClient
import com.github.ai.simplesplit.android.data.api.coverters.toCurrencies
import com.github.ai.simplesplit.android.data.database.dao.CurrencyEntityDao
import com.github.ai.simplesplit.android.data.database.model.CurrencyEntity
import com.github.ai.simplesplit.android.model.exception.AppException

class CurrencyRepository(
private val api: ApiClient,
private val dao: CurrencyEntityDao
) {

fun getAllCached(): Either<AppException, List<CurrencyEntity>> =
either {
val currencies = dao.getAll()
if (currencies.isEmpty()) {
raise(AppException(message = "Unable to load currencies"))
}

currencies
}

suspend fun getAllOrDownload(): Either<AppException, List<CurrencyEntity>> =
either {
val localCurrencies = dao.getAll()
if (localCurrencies.isNotEmpty()) {
localCurrencies
} else {
downloadAndSave().bind()
}
}

private suspend fun downloadAndSave(): Either<AppException, List<CurrencyEntity>> =
either {
val getCurrenciesResult = api.getCurrencies().bind()

val remoteCurrencies = getCurrenciesResult.currencies.toCurrencies()

mergeEntities(
localEntities = dao.getAll(),
remoteEntities = remoteCurrencies,
entityToUidMapper = { currency -> currency.isoCode },
isEqual = { local, remote -> local == remote },
onInsert = { remote -> dao.insert(remote) },
onUpdate = { _, remote -> dao.update(remote) },
onDelete = { local -> dao.deleteByIsoCode(local.isoCode) }
)

dao.getAll()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.github.ai.simplesplit.android.data.repository

inline fun <T> mergeEntities(
localEntities: List<T>,
remoteEntities: List<T>,
entityToUidMapper: (T) -> String,
isEqual: (local: T, remote: T) -> Boolean,
onInsert: (T) -> Unit,
onUpdate: (local: T, remote: T) -> Unit,
onDelete: (T) -> Unit
): Boolean {
val uidToLocalEntityMap = localEntities
.associateBy { entity -> entityToUidMapper.invoke(entity) }
.toMutableMap()

var isDataChanged = false

for (remote in remoteEntities) {
val uid = entityToUidMapper.invoke(remote)
val local = uidToLocalEntityMap.remove(uid)
if (local != null) {
if (!isEqual.invoke(local, remote)) {
onUpdate.invoke(local, remote)
isDataChanged = true
}
} else {
onInsert.invoke(remote)
isDataChanged = true
}
}

for (entity in uidToLocalEntityMap.values) {
onDelete.invoke(entity)
isDataChanged = true
}

return isDataChanged
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.github.ai.simplesplit.android.di
import com.github.ai.simplesplit.android.data.api.ApiClient
import com.github.ai.simplesplit.android.data.database.AppDatabase
import com.github.ai.simplesplit.android.data.json.JsonSerializer
import com.github.ai.simplesplit.android.data.repository.CurrencyRepository
import com.github.ai.simplesplit.android.data.repository.ExpenseRepository
import com.github.ai.simplesplit.android.data.repository.GroupCredentialsRepository
import com.github.ai.simplesplit.android.data.repository.GroupRepository
Expand All @@ -25,6 +26,10 @@ import com.github.ai.simplesplit.android.presentation.dialogs.expenseDetails.Exp
import com.github.ai.simplesplit.android.presentation.dialogs.expenseDetails.model.ExpenseDetailsDialogArgs
import com.github.ai.simplesplit.android.presentation.dialogs.menuDialog.MenuDialogViewModel
import com.github.ai.simplesplit.android.presentation.dialogs.menuDialog.model.MenuDialogArgs
import com.github.ai.simplesplit.android.presentation.dialogs.selectCurrency.SelectCurrencyDialogCellFactory
import com.github.ai.simplesplit.android.presentation.dialogs.selectCurrency.SelectCurrencyDialogViewModel
import com.github.ai.simplesplit.android.presentation.dialogs.selectCurrency.SelectCurrencyInteractor
import com.github.ai.simplesplit.android.presentation.dialogs.selectCurrency.model.SelectCurrencyDialogArgs
import com.github.ai.simplesplit.android.presentation.screens.checkoutGroup.CheckoutGroupInteractor
import com.github.ai.simplesplit.android.presentation.screens.checkoutGroup.CheckoutGroupViewModel
import com.github.ai.simplesplit.android.presentation.screens.checkoutGroup.model.CheckoutGroupArgs
Expand Down Expand Up @@ -59,6 +64,7 @@ object AndroidAppModule {
// Database
single { AppDatabase.buildDatabase(get()) }
single { get<AppDatabase>().groupCredentialsDao() }
single { get<AppDatabase>().currencyEntityDao() }

// Api
singleOf(::JsonSerializer)
Expand All @@ -69,6 +75,7 @@ object AndroidAppModule {
singleOf(::GroupCredentialsRepository)
singleOf(::ExpenseRepository)
singleOf(::MemberRepository)
singleOf(::CurrencyRepository)

// UseCases
singleOf(::ParseGroupUrlUseCase)
Expand All @@ -82,10 +89,12 @@ object AndroidAppModule {
singleOf(::ExpenseEditorInteractor)
singleOf(::CheckoutGroupInteractor)
singleOf(::SettingsInteractor)
singleOf(::SelectCurrencyInteractor)

// CellFactories
singleOf(::GroupDetailsCellFactory)
singleOf(::ExpenseDetailsDialogCellFactory)
singleOf(::SelectCurrencyDialogCellFactory)
singleOf(::SettingsCellFactory)

// Router
Expand Down Expand Up @@ -163,5 +172,14 @@ object AndroidAppModule {
args
)
}
factory { (args: SelectCurrencyDialogArgs) ->
SelectCurrencyDialogViewModel(
get(),
get(),
get(),
get(),
args
)
}
}
}
Loading