forked from openfoodfacts/openfoodfacts-androidapp
/
ScanHistoryViewModel.kt
169 lines (154 loc) 路 6.98 KB
/
ScanHistoryViewModel.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package openfoodfacts.github.scrachx.openfood.features.scanhistory
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import androidx.lifecycle.ViewModel
import com.gojuno.koptional.rxjava2.filterSome
import com.gojuno.koptional.toOptional
import com.jakewharton.rxrelay2.BehaviorRelay
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import io.reactivex.schedulers.Schedulers
import openfoodfacts.github.scrachx.openfood.R
import openfoodfacts.github.scrachx.openfood.models.DaoSession
import openfoodfacts.github.scrachx.openfood.models.HistoryProduct
import openfoodfacts.github.scrachx.openfood.models.HistoryProductDao
import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient
import openfoodfacts.github.scrachx.openfood.utils.LocaleHelper
import openfoodfacts.github.scrachx.openfood.utils.SortType
import javax.inject.Inject
@HiltViewModel
@SuppressLint("StaticFieldLeak")
class ScanHistoryViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val daoSession: DaoSession,
private val client: OpenFoodAPIClient
) : ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val fetchProductsStateRelay = BehaviorRelay.createDefault<FetchProductsState>(FetchProductsState.Loading)
private var sortType = SortType.TIME
init {
refreshItems()
}
override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
}
fun observeFetchProductState(): Observable<FetchProductsState> = fetchProductsStateRelay
fun refreshItems() {
fetchProductsStateRelay.accept(FetchProductsState.Loading)
Observable
.fromCallable {
daoSession.historyProductDao.queryBuilder().list()
}
.flatMapIterable { it }
.flatMap {
client.getProductStateFull(it.barcode)
.toObservable()
.map { it.product.toOptional() }
}
.filterSome()
.flatMap { product ->
Observable.fromCallable {
val historyProduct = daoSession.historyProductDao.queryBuilder()
.where(HistoryProductDao.Properties.Barcode.eq(product.code))
.build()
.unique()
product.productName?.let { historyProduct.title = it }
product.brands?.let { historyProduct.brands = it }
product.getImageSmallUrl(LocaleHelper.getLanguage(context))?.let { historyProduct.url = it }
product.quantity?.let { historyProduct.quantity = it }
product.nutritionGradeFr?.let { historyProduct.nutritionGrade = it }
product.ecoscore?.let { historyProduct.ecoscore = it }
product.novaGroups?.let { historyProduct.novaGroup = it }
daoSession.historyProductDao.update(historyProduct)
}
}
.toList()
.flatMap {
Single.fromCallable {
daoSession.historyProductDao.queryBuilder().list()
}
}
.map { it.customSort() }
.subscribeOn(Schedulers.io())
.subscribe(
{ items -> fetchProductsStateRelay.accept(FetchProductsState.Data(items)) },
{ fetchProductsStateRelay.accept(FetchProductsState.Error) }
)
.addTo(compositeDisposable)
}
fun clearHistory() {
fetchProductsStateRelay.accept(FetchProductsState.Loading)
Observable
.fromCallable {
daoSession.historyProductDao.deleteAll()
}
.subscribeOn(Schedulers.io())
.subscribe(
{ fetchProductsStateRelay.accept(FetchProductsState.Data(emptyList())) },
{ fetchProductsStateRelay.accept(FetchProductsState.Error) }
)
.addTo(compositeDisposable)
}
fun removeProductFromHistory(product: HistoryProduct) {
Observable
.fromCallable {
daoSession.historyProductDao.delete(product)
daoSession.historyProductDao.queryBuilder().list()
}
.map { it.customSort() }
.subscribeOn(Schedulers.io())
.subscribe(
{ products -> fetchProductsStateRelay.accept(FetchProductsState.Data(products)) },
{ fetchProductsStateRelay.accept(FetchProductsState.Error) }
)
.addTo(compositeDisposable)
}
fun updateSortType(type: SortType) {
sortType = type
fetchProductsStateRelay
.take(1)
.map {
(it as? FetchProductsState.Data)?.items?.customSort() ?: emptyList()
}
.filter { it.isNotEmpty() }
.subscribeOn(Schedulers.io())
.subscribe(
{ products -> fetchProductsStateRelay.accept(FetchProductsState.Data(products)) },
{ fetchProductsStateRelay.accept(FetchProductsState.Error) }
)
.addTo(compositeDisposable)
}
fun openProduct(barcode: String, activity: Activity) {
client.openProduct(barcode, activity)
}
/**
* Function to compare history items based on title, brand, barcode, time and nutrition grade
*/
private fun List<HistoryProduct>.customSort() = when (sortType) {
SortType.TITLE -> sortedWith { item1, item2 ->
if (item1.title.isNullOrEmpty()) item1.title = context.getString(R.string.no_title)
if (item2.title.isNullOrEmpty()) item2.title = context.getString(R.string.no_title)
item1.title.compareTo(item2.title, true)
}
SortType.BRAND -> sortedWith { item1, item2 ->
if (item1.brands.isNullOrEmpty()) item1.brands = context.getString(R.string.no_brand)
if (item2.brands.isNullOrEmpty()) item2.brands = context.getString(R.string.no_brand)
item1.brands!!.compareTo(item2.brands!!, true)
}
SortType.BARCODE -> sortedBy { it.barcode }
SortType.GRADE -> sortedBy { it.nutritionGrade }
SortType.TIME -> sortedByDescending { it.lastSeen }
SortType.NONE -> this
}
sealed class FetchProductsState {
object Loading : FetchProductsState()
data class Data(val items: List<HistoryProduct>) : FetchProductsState()
object Error : FetchProductsState()
}
}