Skip to content

Commit

Permalink
#16: Currency pair alerts (#21)
Browse files Browse the repository at this point in the history
Animated bottom navigation


fix key 0 was used


nav replaced with compose-destinations; bump kotlin, compose version


minor fix


Fix change currency for existing conditions, fix ratio input


Check pair alerts every 8 hours


 refactoring condition item


minor fix
  • Loading branch information
mdrlzy authored Jun 28, 2023
1 parent d32843c commit 0e393c5
Show file tree
Hide file tree
Showing 32 changed files with 985 additions and 96 deletions.
28 changes: 17 additions & 11 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'kotlin-kapt'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp' version "1.8.21-1.0.11"
}

android {
Expand Down Expand Up @@ -77,20 +78,26 @@ android {
}

dependencies {
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui:1.4.3"
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.compose.material:material:1.4.3"
implementation "androidx.compose.ui:ui-tooling-preview:1.4.3"
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 "androidx.room:room-runtime:2.5.1"
implementation "androidx.room:room-ktx:2.5.1"
kapt "androidx.room:room-compiler:2.5.1"

implementation 'com.jakewharton.timber:timber:5.0.1'

implementation "androidx.work:work-runtime-ktx:2.8.1"

implementation 'io.github.raamcosta.compose-destinations:core:1.7.41-beta'
ksp 'io.github.raamcosta.compose-destinations:ksp:1.7.41-beta'

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
Expand All @@ -99,11 +106,10 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.4.3"
debugImplementation "androidx.compose.ui:ui-tooling:1.4.3"
debugImplementation "androidx.compose.ui:ui-test-manifest:1.4.3"

implementation 'ch.acra:acra-http:5.9.6'
implementation 'ch.acra:acra-dialog:5.9.6'
implementation 'com.github.SimpleMobileTools:Simple-Commons:7e0240b1e3'
}
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

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

<application
android:name=".presentation.App"
android:allowBackup="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package space.taran.arkrate.data.assets

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -18,20 +19,17 @@ 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
fun allCurrencyAmountFlow(): Flow<List<CurrencyAmount>> = local.allFlow()

suspend fun setCurrencyAmount(code: String, amount: Double) =
withContext(Dispatchers.IO) {
Expand All @@ -46,15 +44,13 @@ class AssetsRepo @Inject constructor(
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)
}
}
8 changes: 8 additions & 0 deletions app/src/main/java/space/taran/arkrate/data/db/AssetsDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import space.taran.arkrate.data.CurrencyAmount
import javax.inject.Inject

Expand All @@ -24,6 +26,9 @@ interface AssetsDao {
@Query("SELECT * FROM RoomCurrencyAmount")
suspend fun getAll(): List<RoomCurrencyAmount>

@Query("SELECT * FROM RoomCurrencyAmount")
fun allFlow(): Flow<List<RoomCurrencyAmount>>

@Query("DELETE FROM RoomCurrencyAmount where code = :code")
suspend fun delete(code: String)
}
Expand All @@ -34,6 +39,9 @@ class AssetsLocalDataSource @Inject constructor(val dao: AssetsDao) {

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

fun allFlow() =
dao.allFlow().map { list -> list.map { it.toCurrencyAmount() } }

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

Expand Down
11 changes: 8 additions & 3 deletions app/src/main/java/space/taran/arkrate/data/db/Database.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package space.taran.arkrate.data.db

import androidx.room.AutoMigration
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

@androidx.room.Database(
entities = [
RoomCurrencyAmount::class,
RoomCurrencyRate::class,
RoomFetchTimestamp::class
RoomFetchTimestamp::class,
RoomPairAlertCondition::class
],
version = 1,
exportSchema = false
version = 2,
exportSchema = true
)
abstract class Database : RoomDatabase() {
abstract fun assetsDao(): AssetsDao
abstract fun rateDao(): CurrencyRateDao
abstract fun fetchTimestampDao(): FetchTimestampDao
abstract fun pairAlertDao(): PairAlertConditionDao

companion object {
const val DB_NAME = "arkrate.db"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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.domain.PairAlertCondition
import javax.inject.Inject
import javax.inject.Singleton

@Entity
data class RoomPairAlertCondition(
@PrimaryKey(autoGenerate = true)
val id: Long,
val numeratorCode: String,
val denominatorCode: String,
val ratio: Float,
val moreNotLess: Boolean
)

@Dao
interface PairAlertConditionDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(pairAlert: RoomPairAlertCondition): Long

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

@Query("DELETE FROM RoomPairAlertCondition where id = :id")
suspend fun delete(id: Long)
}

private fun PairAlertCondition.toRoom() = RoomPairAlertCondition(
id, numeratorCode, denominatorCode, ratio, moreNotLess
)

private fun RoomPairAlertCondition.toCondition() = PairAlertCondition(
id, numeratorCode, denominatorCode, ratio, moreNotLess
)

@Singleton
class PairAlertConditionRepo @Inject constructor(
private val dao: PairAlertConditionDao
) {
suspend fun insert(pairAlertCondition: PairAlertCondition) =
dao.insert(pairAlertCondition.toRoom())

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

suspend fun delete(id: Long) = dao.delete(id)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package space.taran.arkrate.data.worker

import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import space.taran.arkrate.data.CurrencyRate
import space.taran.arkrate.data.GeneralCurrencyRepo
import space.taran.arkrate.data.db.PairAlertConditionRepo
import space.taran.arkrate.di.DIManager
import space.taran.arkrate.domain.PairAlertCondition
import space.taran.arkrate.presentation.utils.NotificationUtils
import javax.inject.Inject

class CurrencyMonitorWorker(val context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {

@Inject
lateinit var currencyRepo: GeneralCurrencyRepo

@Inject
lateinit var pairAlertRepo: PairAlertConditionRepo

override suspend fun doWork(): Result {
DIManager.component.inject(this)
val rates = currencyRepo.getCurrencyRate()
pairAlertRepo.getAll().forEach { pairAlert ->
val curRatio = curRatio(pairAlert, rates)
if (pairAlert.isConditionMet(curRatio)) {
notifyPair(pairAlert, curRatio)
}
}

return Result.success()
}

private fun curRatio(
pairAlertCondition: PairAlertCondition,
rates: List<CurrencyRate>
): Float {
val numeratorRate =
rates.find { it.code == pairAlertCondition.numeratorCode }!!.rate
val denominatorRate =
rates.find { it.code == pairAlertCondition.denominatorCode }!!.rate
return (numeratorRate / denominatorRate).toFloat()
}


private fun notifyPair(pairAlertCondition: PairAlertCondition, curRatio: Float) {
NotificationUtils.showPairAlert(pairAlertCondition, curRatio, context)
}

companion object {
const val name = "CurrencyMonitorWorker"
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/space/taran/arkrate/di/AppComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import dagger.BindsInstance
import dagger.Component
import space.taran.arkrate.data.GeneralCurrencyRepo
import space.taran.arkrate.data.assets.AssetsRepo
import space.taran.arkrate.data.worker.CurrencyMonitorWorker
import space.taran.arkrate.di.module.ApiModule
import space.taran.arkrate.di.module.DBModule
import space.taran.arkrate.presentation.summary.SummaryViewModelFactory
import space.taran.arkrate.presentation.addcurrency.AddCurrencyViewModelFactory
import space.taran.arkrate.presentation.assets.AssetsViewModelFactory
import space.taran.arkrate.presentation.shared.SharedViewModel
import space.taran.arkrate.presentation.shared.SharedViewModelFactory
import javax.inject.Singleton

@Singleton
Expand All @@ -24,8 +27,11 @@ interface AppComponent {
fun summaryViewModelFactory(): SummaryViewModelFactory
fun assetsVMFactory(): AssetsViewModelFactory
fun addCurrencyVMFactory(): AddCurrencyViewModelFactory
fun sharedVMFactory(): SharedViewModelFactory

fun generalCurrencyRepo(): GeneralCurrencyRepo
fun assetsRepo(): AssetsRepo
fun inject(currencyMonitorWorker: CurrencyMonitorWorker)

@Component.Factory
interface Factory {
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/space/taran/arkrate/di/NavDepContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package space.taran.arkrate.di

import androidx.lifecycle.SavedStateHandle
import space.taran.arkrate.presentation.MainActivity
import space.taran.arkrate.presentation.shared.SharedViewModel

class NavDepContainer(
val activity: MainActivity
) {
@Suppress("UNCHECKED_CAST")
fun <T> createViewModel(modelClass: Class<T>, handle: SavedStateHandle): T {
return when (modelClass) {
SharedViewModel::class.java -> DIManager.component.sharedVMFactory().create(modelClass)
else -> throw RuntimeException("Unknown view model $modelClass")
} as T
}
}
3 changes: 3 additions & 0 deletions app/src/main/java/space/taran/arkrate/di/module/DBModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class DBModule {
@Provides
fun rateDao(db: Database) = db.rateDao()

@Provides
fun pairAlertDao(db: Database) = db.pairAlertDao()

@Provides
fun fetchTimestampDao(db: Database) = db.fetchTimestampDao()
}
3 changes: 3 additions & 0 deletions app/src/main/java/space/taran/arkrate/domain/Alias.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package space.taran.arkrate.domain

typealias CurrencyCode = String
29 changes: 29 additions & 0 deletions app/src/main/java/space/taran/arkrate/domain/PairAlertCondition.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package space.taran.arkrate.domain

data class PairAlertCondition(
val id: Long,
val numeratorCode: String,
val denominatorCode: String,
val ratio: Float,
var moreNotLess: Boolean
) {
fun isConditionMet(currentRatio: Float) =
if (moreNotLess)
currentRatio >= ratio
else
currentRatio <= ratio

fun isCompleted() =
numeratorCode.isNotEmpty() &&
denominatorCode.isNotEmpty()

companion object {
fun defaultInstance() = PairAlertCondition(
id = 0,
numeratorCode = "",
denominatorCode = "",
ratio = 1f,
moreNotLess = true
)
}
}
Loading

0 comments on commit 0e393c5

Please sign in to comment.