Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#8: Crash upon [add_new] clicking #13

Merged
merged 9 commits into from
Nov 12, 2022
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
20 changes: 14 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,25 @@ android {
}

dependencies {
implementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.14.0'
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.3"
implementation "androidx.compose.ui:ui:$compose_version"
implementation 'io.coil-kt:coil:2.2.1'
implementation 'io.coil-kt:coil-gif:2.2.1'
implementation 'io.coil-kt:coil-compose:2.2.1'
implementation "androidx.navigation:navigation-compose:2.5.2"
implementation "com.google.accompanist:accompanist-navigation-animation:0.27.0"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.5.1'

implementation "com.google.dagger:dagger:2.42"
kapt "com.google.dagger:dagger-compiler:2.42"

implementation "androidx.room:room-runtime:2.4.3"
implementation "androidx.room:room-ktx:2.4.3"
kapt "androidx.room:room-compiler:2.4.3"

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
xmlns:tools="http://schemas.android.com/tools"
package="space.taran.arkrate">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".App"
android:name=".presentation.App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -18,7 +19,7 @@
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".presentation.MainActivity"
android:exported="true"
android:theme="@style/Theme.Exchange">
<intent-filter>
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/CurrencyAmount.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package space.taran.arkrate.data

data class CurrencyAmount(
val code: String,
var amount: Double
)
6 changes: 6 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/CurrencyName.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package space.taran.arkrate.data

data class CurrencyName(
val code: String,
val name: String
)
6 changes: 6 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/CurrencyRate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package space.taran.arkrate.data

data class CurrencyRate(
val code: String,
val rate: Double
)
56 changes: 56 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/CurrencyRepo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package space.taran.arkrate.data

import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import space.taran.arkrate.data.db.CurrencyRateLocalDataSource
import space.taran.arkrate.data.db.FetchTimestampDataSource
import space.taran.arkrate.data.network.NetworkStatus
import space.taran.arkrate.utils.withContextAndLock
import java.util.concurrent.TimeUnit

abstract class CurrencyRepo(
private val local: CurrencyRateLocalDataSource,
private val networkStatus: NetworkStatus,
private val fetchTimestampDataSource: FetchTimestampDataSource
) {
protected abstract val type: CurrencyType
private var currencyRates: List<CurrencyRate>? = null
private var updatedTS: Long? = null
private val mutex = Mutex()

suspend fun getCurrencyRate(): List<CurrencyRate> =
withContextAndLock(Dispatchers.IO, mutex) {
if (!networkStatus.isOnline()) {
currencyRates = local.getByType(type)
return@withContextAndLock currencyRates!!
}

updatedTS ?: let {
updatedTS = fetchTimestampDataSource.getTimestamp(type)
}

if (
updatedTS == null ||
updatedTS!! + dayInMillis < System.currentTimeMillis()
) {
currencyRates = fetchRemote()
launch { fetchTimestampDataSource.rememberTimestamp(type) }
launch { local.insert(currencyRates!!, type) }
updatedTS = System.currentTimeMillis()
}

currencyRates ?: let {
currencyRates = local.getByType(type)
}
Log.d("Test", "${currencyRates!!.sortedBy { it.code }}")
return@withContextAndLock currencyRates!!
}

protected abstract suspend fun fetchRemote(): List<CurrencyRate>

abstract suspend fun getCurrencyName(): List<CurrencyName>

private val dayInMillis = TimeUnit.DAYS.toMillis(1)
}
5 changes: 5 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/CurrencyType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package space.taran.arkrate.data

enum class CurrencyType {
FIAT, CRYPTO
}
27 changes: 27 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/GeneralCurrencyRepo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package space.taran.arkrate.data

import space.taran.arkrate.data.crypto.CryptoCurrencyRepo
import space.taran.arkrate.data.fiat.FiatCurrencyRepo
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class GeneralCurrencyRepo @Inject constructor(
val fiatRepo: FiatCurrencyRepo,
val cryptoRepo: CryptoCurrencyRepo
) {
private val currencyRepos = listOf(
fiatRepo,
cryptoRepo
)

suspend fun getCurrencyRate(): List<CurrencyRate> =
currencyRepos.fold(emptyList()) { codeToRate, repo ->
codeToRate + repo.getCurrencyRate()
}

suspend fun getCurrencyName(): List<CurrencyName> =
currencyRepos.fold(emptyList()) { currencyName, repo ->
currencyName + repo.getCurrencyName()
}
}
60 changes: 60 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/assets/AssetsRepo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package space.taran.arkrate.data.assets

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import space.taran.arkrate.data.CurrencyAmount
import space.taran.arkrate.data.db.AssetsLocalDataSource
import space.taran.arkrate.utils.replace
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AssetsRepo @Inject constructor(
private val local: AssetsLocalDataSource
) {
private var currencyAmountList = listOf<CurrencyAmount>()
private val currencyAmountFlow =
MutableStateFlow<List<CurrencyAmount>>(emptyList())
private val scope = CoroutineScope(Dispatchers.IO)

init {
scope.launch {
currencyAmountList = local.getAll()
currencyAmountFlow.emit(currencyAmountList)
}
}

fun allCurrencyAmount(): List<CurrencyAmount> = currencyAmountList

fun allCurrencyAmountFlow(): StateFlow<List<CurrencyAmount>> = currencyAmountFlow

suspend fun setCurrencyAmount(code: String, amount: Double) =
withContext(Dispatchers.IO) {
currencyAmountList.find {
it.code == code
}?.let { currencyAmount ->
currencyAmountList = currencyAmountList.replace(
currencyAmount,
currencyAmount.copy(amount = amount)
)
} ?: let {
currencyAmountList =
currencyAmountList + CurrencyAmount(code, amount)
}
currencyAmountFlow.emit(currencyAmountList.toList())
local.insert(CurrencyAmount(code, amount))
}

suspend fun removeCurrency(code: String) = withContext(Dispatchers.IO) {
currencyAmountList.find { it.code == code }?.let {
currencyAmountList = currencyAmountList - it
}
currencyAmountFlow.emit(currencyAmountList)
local.delete(code)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package space.taran.arkrate.data.crypto

import retrofit2.http.GET

interface CryptoAPI {
@GET("/ARK-Builders/ark-exchange-rates/main/crypto-rates.json")
suspend fun getCryptoRates(): List<CryptoRateResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package space.taran.arkrate.data.crypto

import space.taran.arkrate.data.CurrencyName
import space.taran.arkrate.data.CurrencyRate
import space.taran.arkrate.data.CurrencyRepo
import space.taran.arkrate.data.CurrencyType
import space.taran.arkrate.data.network.NetworkStatus
import space.taran.arkrate.data.db.CurrencyRateLocalDataSource
import space.taran.arkrate.data.db.FetchTimestampDataSource
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class CryptoCurrencyRepo @Inject constructor(
private val cryptoAPI: CryptoAPI,
private val local: CurrencyRateLocalDataSource,
private val networkStatus: NetworkStatus,
private val fetchTimestampDataSource: FetchTimestampDataSource
) : CurrencyRepo(local, networkStatus, fetchTimestampDataSource) {
override val type = CurrencyType.CRYPTO

override suspend fun fetchRemote(): List<CurrencyRate> =
cryptoAPI.getCryptoRates().findUSDTPairs()

override suspend fun getCurrencyName(): List<CurrencyName> =
getCurrencyRate().map {
CurrencyName(it.code, name = "")
}

// api returns pairs like ETHBTC, ETHBNB, ETHTRX, ETHUSDT
// we only take USDT pairs
kirillt marked this conversation as resolved.
Show resolved Hide resolved
private fun List<CryptoRateResponse>.findUSDTPairs() =
mapNotNull { (code, price) ->
if (code.takeLast(4) == "USDT") {
CurrencyRate(code.dropLast(4), price)
} else
null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package space.taran.arkrate.data.crypto

data class CryptoRateResponse(
val symbol: String,
val price: Double
)
41 changes: 41 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/db/AssetsDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package space.taran.arkrate.data.db

import androidx.room.Dao
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import space.taran.arkrate.data.CurrencyAmount
import javax.inject.Inject

@Entity
data class RoomCurrencyAmount(
@PrimaryKey
val code: String,
val amount: Double
)

@Dao
interface AssetsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(currencyAmount: RoomCurrencyAmount)

@Query("SELECT * FROM RoomCurrencyAmount")
suspend fun getAll(): List<RoomCurrencyAmount>

@Query("DELETE FROM RoomCurrencyAmount where code = :code")
suspend fun delete(code: String)
}

class AssetsLocalDataSource @Inject constructor(val dao: AssetsDao) {
suspend fun insert(currencyAmount: CurrencyAmount) =
dao.insert(currencyAmount.toRoom())

suspend fun getAll() = dao.getAll().map { it.toCurrencyAmount() }

suspend fun delete(code: String) = dao.delete(code)
}

private fun RoomCurrencyAmount.toCurrencyAmount() = CurrencyAmount(code, amount)
private fun CurrencyAmount.toRoom() = RoomCurrencyAmount(code, amount)
42 changes: 42 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/db/CurrencyRateDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package space.taran.arkrate.data.db

import androidx.room.Dao
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import space.taran.arkrate.data.CurrencyRate
import space.taran.arkrate.data.CurrencyType
import javax.inject.Inject

@Entity
data class RoomCurrencyRate(
@PrimaryKey
val code: String,
val currencyType: String,
val rate: Double
)

@Dao
interface CurrencyRateDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(currencyRate: List<RoomCurrencyRate>)

@Query("SELECT * FROM RoomCurrencyRate where currencyType = :currencyType")
suspend fun getByType(currencyType: String): List<RoomCurrencyRate>
}

class CurrencyRateLocalDataSource @Inject constructor(val dao: CurrencyRateDao) {
suspend fun insert(
currencyRate: List<CurrencyRate>,
currencyType: CurrencyType
) = dao.insert(currencyRate.map { it.toRoom(currencyType) })

suspend fun getByType(currencyType: CurrencyType) =
dao.getByType(currencyType.name).map { it.toCurrencyRate() }
}

private fun RoomCurrencyRate.toCurrencyRate() = CurrencyRate(code, rate)
private fun CurrencyRate.toRoom(currencyType: CurrencyType) =
RoomCurrencyRate(code, currencyType.name, rate)
Loading