Skip to content

Commit

Permalink
ref: made notification code more readable and efficient in FileUtils.kt
Browse files Browse the repository at this point in the history
feat: added compact search api in ProductsAPI.kt and OpenFoodAPIClient.kt

feat: use compact search api in ScanHistoryActivity refresh action

Closes #3873
  • Loading branch information
VaiTon committed Apr 5, 2021
1 parent ceaf143 commit b49652c
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 58 deletions.
Expand Up @@ -19,6 +19,7 @@ import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.app.NavUtils
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
Expand Down Expand Up @@ -92,7 +93,7 @@ class ScanHistoryActivity : BaseActivity() {

@RequiresApi(Build.VERSION_CODES.KITKAT)
val fileWriterLauncher = registerForActivityResult(CreateCSVContract()) {
writeHistoryToFile(this, adapter.products, it, contentResolver.openOutputStream(it) ?: error("File path must not be null."))
writeHistoryToFile(this, adapter.products, it)
}

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -242,7 +243,7 @@ class ScanHistoryActivity : BaseActivity() {
val baseDir = File(Environment.getExternalStorageDirectory(), getCsvFolderName())
if (!baseDir.exists()) baseDir.mkdirs()
val file = File(baseDir, fileName)
writeHistoryToFile(this, adapter.products, Uri.fromFile(file), file.outputStream())
writeHistoryToFile(this, adapter.products, file.toUri())
}
}

Expand Down
Expand Up @@ -4,13 +4,10 @@ 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
Expand Down Expand Up @@ -52,34 +49,27 @@ class ScanHistoryViewModel @Inject constructor(
.fromCallable {
daoSession.historyProductDao.queryBuilder().list()
}
.flatMapIterable { it }
.flatMap {
client.getProductStateFull(it.barcode)
.toObservable()
.map { it.product.toOptional() }
.flatMap { prods ->
client.getProductsByBarcode(prods.map { it.barcode }).toObservable()
}
.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)
}
.flatMapIterable { it }
.map { product ->
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 {
daoSession.historyProductDao.queryBuilder().list()
}
.map { it.customSort() }
.subscribeOn(Schedulers.io())
Expand All @@ -97,10 +87,8 @@ class ScanHistoryViewModel @Inject constructor(
daoSession.historyProductDao.deleteAll()
}
.subscribeOn(Schedulers.io())
.subscribe(
{ fetchProductsStateRelay.accept(FetchProductsState.Data(emptyList())) },
{ fetchProductsStateRelay.accept(FetchProductsState.Error) }
)
.doOnError { fetchProductsStateRelay.accept(FetchProductsState.Error) }
.subscribe { fetchProductsStateRelay.accept(FetchProductsState.Data(emptyList())) }
.addTo(compositeDisposable)
}

Expand All @@ -112,10 +100,8 @@ class ScanHistoryViewModel @Inject constructor(
}
.map { it.customSort() }
.subscribeOn(Schedulers.io())
.subscribe(
{ products -> fetchProductsStateRelay.accept(FetchProductsState.Data(products)) },
{ fetchProductsStateRelay.accept(FetchProductsState.Error) }
)
.doOnError { fetchProductsStateRelay.accept(FetchProductsState.Error) }
.subscribe { products -> fetchProductsStateRelay.accept(FetchProductsState.Data(products)) }
.addTo(compositeDisposable)

}
Expand All @@ -129,10 +115,8 @@ class ScanHistoryViewModel @Inject constructor(
}
.filter { it.isNotEmpty() }
.subscribeOn(Schedulers.io())
.subscribe(
{ products -> fetchProductsStateRelay.accept(FetchProductsState.Data(products)) },
{ fetchProductsStateRelay.accept(FetchProductsState.Error) }
)
.doOnError { fetchProductsStateRelay.accept(FetchProductsState.Error) }
.subscribe { products -> fetchProductsStateRelay.accept(FetchProductsState.Data(products)) }
.addTo(compositeDisposable)
}

Expand Down
Expand Up @@ -59,6 +59,11 @@ class OpenFoodAPIClient @Inject constructor(
return productsApi.getProductByBarcode(barcode, getAllFields(), getUserAgent(customHeader))
}

fun getProductsByBarcode(codes: List<String>, customHeader: String = Utils.HEADER_USER_AGENT_SEARCH): Single<List<Product>> {
return productsApi.getProductsByBarcode(codes.joinToString(","), getAllFields(), customHeader)
.map { it.products }
}

private fun getAllFields(): String {
val allFields = ApiFields.Keys.PRODUCT_COMMON_FIELDS
val fieldsToLocalize = ApiFields.Keys.PRODUCT_LOCAL_FIELDS
Expand Down
Expand Up @@ -53,6 +53,16 @@ interface ProductsAPI {
@Header("User-Agent") header: String
): Single<ProductState>

/**
* @param barcodes String of comma separated barcodes
*/
@GET("$API_P/search")
fun getProductsByBarcode(
@Query("code") barcodes: String,
@Query("fields") fields: String,
@Header("User-Agent") header: String
): Single<Search>

@FormUrlEncoded
@POST("cgi/product_jqm2.pl")
fun saveProduct(
Expand Down
Expand Up @@ -5,6 +5,7 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.Intent.*
import android.net.Uri
import android.os.Build
import android.util.Log
Expand All @@ -25,7 +26,6 @@ import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVPrinter
import org.jetbrains.annotations.Contract
import java.io.IOException
import java.io.OutputStream

fun isLocaleFile(url: String?) = url?.startsWith(LOCALE_FILE_SCHEME) ?: false

Expand Down Expand Up @@ -54,15 +54,18 @@ fun writeListToFile(context: Context, productList: ProductLists, csvUri: Uri) {
}
}
Toast.makeText(context, R.string.txt_your_listed_products_exported, Toast.LENGTH_LONG).show()

success = true
} catch (e: IOException) {
success = false
Log.e(ProductListActivity::class.simpleName, "Can't export to $csvUri.", e)
}

val downloadIntent = Intent(Intent.ACTION_VIEW)
val notificationManager = createNotification(csvUri, downloadIntent, context)
val downloadIntent = Intent(ACTION_VIEW).apply {
flags = FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_NEW_TASK or FLAG_GRANT_READ_URI_PERMISSION
data = csvUri
type = "text/csv"
}
val notificationManager = createNotificationManager(context)
if (success) {
val builder = NotificationCompat.Builder(context, "export_channel")
.setContentTitle(context.getString(R.string.notify_title))
Expand All @@ -73,25 +76,32 @@ fun writeListToFile(context: Context, productList: ProductLists, csvUri: Uri) {
}
}

fun writeHistoryToFile(context: Context, productList: List<HistoryProduct>, csvUri: Uri, outputStream: OutputStream) {
fun writeHistoryToFile(context: Context, productList: List<HistoryProduct>, csvUri: Uri) {
var success = false

val outputStream = context.contentResolver.openOutputStream(csvUri) ?: error("File path must not be null.")
try {
CSVPrinter(outputStream.bufferedWriter(), CSVFormat.DEFAULT.withHeader(*context.resources.getStringArray(R.array.headers))).use { writer ->
CSVPrinter(
outputStream.bufferedWriter(),
CSVFormat.DEFAULT.withHeader(*context.resources.getStringArray(R.array.headers))
).use { writer ->
productList.forEach { writer.printRecord(it.barcode, it.title, it.brands) }
Toast.makeText(context, R.string.txt_history_exported, Toast.LENGTH_LONG).show()
success = true
}
Toast.makeText(context, R.string.txt_history_exported, Toast.LENGTH_LONG).show()
success = true
} catch (e: IOException) {
Log.e(ScanHistoryActivity.LOG_TAG, "Can't export to $csvUri.", e)
}
val downloadIntent = Intent(Intent.ACTION_VIEW)
val notificationManager = createNotification(csvUri, downloadIntent, context)
val downloadIntent = Intent(ACTION_VIEW).apply {
flags = FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_NEW_TASK or FLAG_GRANT_READ_URI_PERMISSION
data = csvUri
type = "text/csv"
}
val notificationManager = createNotificationManager(context)
if (success) {
val builder = NotificationCompat.Builder(context, "export_channel")
.setContentTitle(context.getString(R.string.notify_title))
.setContentText(context.getString(R.string.notify_content))
.setContentIntent(PendingIntent.getActivity(context, 4, downloadIntent, 0))
.setContentIntent(PendingIntent.getActivity(context, 0, downloadIntent, PendingIntent.FLAG_CANCEL_CURRENT))
.setSmallIcon(R.mipmap.ic_launcher)
notificationManager.notify(7, builder.build())
}
Expand All @@ -100,16 +110,15 @@ fun writeHistoryToFile(context: Context, productList: List<HistoryProduct>, csvU
const val LOCALE_FILE_SCHEME = "file://"

// TODO: Use constants and refactor
fun createNotification(csvUri: Uri, downloadIntent: Intent, context: Context): NotificationManager {
downloadIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
downloadIntent.setDataAndType(csvUri, "text/csv")
downloadIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fun createNotificationManager(context: Context): NotificationManager {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val importance = NotificationManager.IMPORTANCE_DEFAULT
val notificationChannel = NotificationChannel("downloadChannel", "ChannelCSV", importance)
notificationManager.createNotificationChannel(notificationChannel)
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = "export_channel"
val channelName = context.getString(R.string.notification_channel_name)
Expand Down

0 comments on commit b49652c

Please sign in to comment.