From ceaf143499fb4719d41b8fbceea930930c454f32 Mon Sep 17 00:00:00 2001 From: VaiTon Date: Mon, 5 Apr 2021 12:09:09 +0200 Subject: [PATCH] feat: added share button to list activity ref: use new Activity Result API in ProductListActivity ref: renamed YourListProductsViewHolder to ProductViewHolder in ProductListAdapter.kt ref: use one transaction for adding multiple lists in ProductListsActivity.kt ref: moved createNotification to FileUtils --- .../product/edit/ProductEditActivity.kt | 3 +- .../view/photos/ProductPhotosAdapter.kt | 2 +- .../view/summary/SummaryProductFragment.kt | 7 +- .../productlist/ProductListActivity.kt | 178 ++++++++++-------- .../productlist/ProductListAdapter.kt | 10 +- .../productlists/ProductListsActivity.kt | 52 ++--- .../scrachx/openfood/utils/FileUtils.kt | 36 +++- .../res/drawable/ic_baseline_share_24.xml | 10 + .../res/menu/menu_your_listed_products.xml | 6 + 9 files changed, 178 insertions(+), 126 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_share_24.xml diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditActivity.kt index 05d32dda4936..2fa67e9f82ff 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/edit/ProductEditActivity.kt @@ -35,7 +35,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.addTo -import okhttp3.MediaType import okhttp3.RequestBody import openfoodfacts.github.scrachx.openfood.AppFlavors.OBF import openfoodfacts.github.scrachx.openfood.AppFlavors.OFF @@ -261,7 +260,7 @@ class ProductEditActivity : AppCompatActivity() { } private fun createTextPlain(code: String) = - RequestBody.create(MediaType.parse(OpenFoodAPIClient.MIME_TEXT), code) + RequestBody.create(OpenFoodAPIClient.MIME_TEXT, code) private fun addLoginPasswordInfo(imgMap: MutableMap) { val settings = getLoginPreferences() diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/photos/ProductPhotosAdapter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/photos/ProductPhotosAdapter.kt index a194ac5e7e79..f41eccfd1c4a 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/photos/ProductPhotosAdapter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/photos/ProductPhotosAdapter.kt @@ -93,7 +93,7 @@ class ProductPhotosAdapter( R.id.report_image -> { context.startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply { data = Uri.parse("mailto:") - type = OpenFoodAPIClient.MIME_TEXT + type = "text/plain" putExtra(Intent.EXTRA_EMAIL, "Open Food Facts ") putExtra(Intent.EXTRA_SUBJECT, "Photo report for product ${product.code}") putExtra(Intent.EXTRA_TEXT, "I've spotted a problematic photo for product ${product.code}") diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/summary/SummaryProductFragment.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/summary/SummaryProductFragment.kt index 2542baba4ed2..0f6ddd7c6766 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/summary/SummaryProductFragment.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/product/view/summary/SummaryProductFragment.kt @@ -788,14 +788,11 @@ class SummaryProductFragment : BaseFragment(), ISummaryProductPresenter.View { private fun onShareProductButtonClick() { val shareUrl = "${getString(R.string.website_product)}${product.code}" val shareBody = "${getString(R.string.msg_share)} $shareUrl" - val shareSub = "\n\n" - val title = "Share using" startActivity(Intent.createChooser(Intent().apply { action = Intent.ACTION_SEND - type = OpenFoodAPIClient.MIME_TEXT - putExtra(Intent.EXTRA_SUBJECT, shareSub) + type = "text/plain" putExtra(Intent.EXTRA_TEXT, shareBody) - }, title)) + }, null)) } private fun onEditProductButtonClick() { diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlist/ProductListActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlist/ProductListActivity.kt index a03d8b3834f0..e942f9a1927c 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlist/ProductListActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlist/ProductListActivity.kt @@ -1,12 +1,9 @@ package openfoodfacts.github.scrachx.openfood.features.productlist import android.Manifest -import android.app.NotificationChannel -import android.app.NotificationManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment @@ -16,14 +13,16 @@ import android.view.MenuItem import android.view.View import android.widget.TextView import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat +import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale +import androidx.core.content.ContextCompat.checkSelfPermission +import androidx.core.net.toUri import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog import dagger.hilt.android.AndroidEntryPoint -import openfoodfacts.github.scrachx.openfood.AppFlavors +import openfoodfacts.github.scrachx.openfood.AppFlavors.OFF import openfoodfacts.github.scrachx.openfood.AppFlavors.isFlavors import openfoodfacts.github.scrachx.openfood.BuildConfig import openfoodfacts.github.scrachx.openfood.R @@ -86,33 +85,36 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { if (this.isDisableImageLoad() && this.isBatteryLevelLow()) isLowBatteryMode = true // OnClick - binding.scanFirstYourListedProduct.setOnClickListener { onFirstScan() } + binding.scanFirstYourListedProduct.setOnClickListener { checkPermsStartScan() } // Get listid and add product to list if bundle is present val bundle = intent.extras - var prodToAdd: Product? = null if (bundle != null) { listID = bundle.getLong(KEY_LIST_ID) listName = bundle.getString(KEY_LIST_NAME) title = listName - prodToAdd = bundle[KEY_PRODUCT_TO_ADD] as Product? - } - val locale = getLanguage(this) - if (prodToAdd?.code != null && prodToAdd.productName != null && prodToAdd.getImageSmallUrl(locale) != null) { - val barcode = prodToAdd.code - val productName = prodToAdd.productName - val productDetails = prodToAdd.getProductBrandsQuantityDetails() - val imageUrl = prodToAdd.getImageSmallUrl(locale) - val product = YourListedProduct() - product.barcode = barcode - product.listId = listID - product.listName = listName - product.productName = productName - product.productDetails = productDetails - product.imageUrl = imageUrl - daoSession.yourListedProductDao.insertOrReplace(product) + (bundle[KEY_PRODUCT_TO_ADD] as? Product)?.let { prodToAdd -> + val locale = getLanguage(this) + if (prodToAdd.productName != null && prodToAdd.getImageSmallUrl(locale) != null) { + val barcode = prodToAdd.code + val productName = prodToAdd.productName + val productDetails = prodToAdd.getProductBrandsQuantityDetails() + val imageUrl = prodToAdd.getImageSmallUrl(locale) + + val product = YourListedProduct().apply { + this.barcode = barcode + this.listId = this@ProductListActivity.listID + this.listName = this@ProductListActivity.listName + this.productName = productName + this.productDetails = productDetails + this.imageUrl = imageUrl + } + + daoSession.yourListedProductDao.insertOrReplace(product) + } + } } val productList = daoSession.productListsDao.load(listID) @@ -153,8 +155,13 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_your_listed_products, menu) - menu.findItem(R.id.action_export_all_listed_products).isVisible = adapter.products.isNotEmpty() - menu.findItem(R.id.action_sort_listed_products).isVisible = adapter.products.isNotEmpty() + listOf( + R.id.action_export_all_listed_products, + R.id.action_sort_listed_products, + R.id.action_share_list + ).forEach { + menu.findItem(it).isVisible = adapter.products.isNotEmpty() + } return true } @@ -212,6 +219,8 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { else -> sortWith { _, _ -> 0 } } + private val requestWriteLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) + { if (it) exportAsCSV() } override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { android.R.id.home -> { @@ -222,26 +231,35 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { true } R.id.action_export_all_listed_products -> { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + val perm = Manifest.permission.WRITE_EXTERNAL_STORAGE + when { + checkSelfPermission( + this, perm + ) == PackageManager.PERMISSION_GRANTED -> { + exportAsCSV() + matomoAnalytics.trackEvent(AnalyticsEvent.ShoppingListExported) + } + shouldShowRequestPermissionRationale(this, perm) -> { MaterialDialog.Builder(this) .title(R.string.action_about) .content(R.string.permision_write_external_storage) .neutralText(R.string.txtOk) + .onNeutral { _, _ -> + requestWriteLauncher.launch(perm) + } .show() - } else { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), MY_PERMISSIONS_REQUEST_STORAGE) } - } else { - exportAsCSV() - matomoAnalytics.trackEvent(AnalyticsEvent.ShoppingListExported) + else -> { + requestWriteLauncher.launch(perm) + } } + true } R.id.action_sort_listed_products -> { MaterialDialog.Builder(this).run { title(R.string.sort_by) - val sortTypes = if (isFlavors(AppFlavors.OFF)) { + val sortTypes = if (isFlavors(OFF)) { listOf( getString(R.string.by_title), getString(R.string.by_brand), @@ -263,7 +281,7 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { sortType = when (position) { 0 -> TITLE 1 -> BRAND - 2 -> if (isFlavors(AppFlavors.OFF)) GRADE else TIME + 2 -> if (isFlavors(OFF)) GRADE else TIME 3 -> BARCODE else -> TIME } @@ -275,17 +293,11 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { } true } - else -> super.onOptionsItemSelected(item) - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - if (requestCode == MY_PERMISSIONS_REQUEST_STORAGE && grantResults.isNotEmpty() - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - exportAsCSV() + R.id.action_share_list -> { + shareList() + true } - + else -> super.onOptionsItemSelected(item) } @@ -295,35 +307,47 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { view.setText(R.string.txt_info_your_listed_products) } - private fun onFirstScan() { + private val requestCameraLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) + { if (it) startScan() } + + private fun checkPermsStartScan() { if (!isHardwareCameraInstalled(this)) { Log.e(this::class.simpleName, "Device has no camera installed.") return } - if (ContextCompat.checkSelfPermission(baseContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { + when { + checkSelfPermission( + baseContext, Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED -> { + startScan() + } + shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) -> { MaterialDialog.Builder(this).run { title(R.string.action_about) content(R.string.permission_camera) neutralText(R.string.txtOk) - onNeutral { _, _ -> ActivityCompat.requestPermissions(this@ProductListActivity, arrayOf(Manifest.permission.CAMERA), MY_PERMISSIONS_REQUEST_CAMERA) } + onNeutral { _, _ -> + requestCameraLauncher.launch(Manifest.permission.CAMERA) + } show() } - } else { - ActivityCompat.requestPermissions(this@ProductListActivity, arrayOf(Manifest.permission.CAMERA), MY_PERMISSIONS_REQUEST_CAMERA) } - } else { - val intent = Intent(this, ContinuousScanActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - startActivity(intent) + else -> { + requestCameraLauncher.launch(Manifest.permission.CAMERA) + } } } - @RequiresApi(Build.VERSION_CODES.KITKAT) - val fileWriterLauncher = registerForActivityResult(CreateCSVContract()) { - writeListToFile(this, productList, it, contentResolver.openOutputStream(it) ?: error("File path must not be null.")) + private fun startScan() { + startActivity(Intent(this, ContinuousScanActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + }) } + @RequiresApi(Build.VERSION_CODES.KITKAT) + val fileWriterLauncher = registerForActivityResult(CreateCSVContract()) + { writeListToFile(this, productList, it) } + private fun exportAsCSV() { Toast.makeText(this, R.string.txt_exporting_your_listed_products, Toast.LENGTH_LONG).show() @@ -339,15 +363,25 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { val baseDir = File(Environment.getExternalStorageDirectory(), getCsvFolderName()) if (!baseDir.exists()) baseDir.mkdirs() val file = File(baseDir, fileName) - writeListToFile(this, productList, Uri.fromFile(file), file.outputStream()) + writeListToFile(this, productList, file.toUri()) } } + private fun shareList() { + val shareUrl = "${BuildConfig.OFWEBSITE}search?code=${productList.products.joinToString(",") { it.barcode }}" + + startActivity(Intent.createChooser(Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, shareUrl) + type = "text/plain" + }, null)) + } + override fun onRightClicked(position: Int) { if (adapter.products.isEmpty()) return - val productToRemove = adapter.products[position] - daoSession.yourListedProductDao!!.delete(productToRemove) + val productToRemove = adapter.products[position] + daoSession.yourListedProductDao.delete(productToRemove) adapter.remove(productToRemove) } @@ -366,29 +400,9 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions { }) } - 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) - val notificationManager = context.getSystemService(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) - val importance = NotificationManager.IMPORTANCE_DEFAULT - val notificationChannel = NotificationChannel(channelId, channelName, importance) - notificationChannel.description = context.getString(R.string.notify_channel_description) - notificationManager.createNotificationChannel(notificationChannel) - } - return notificationManager - } - const val KEY_LIST_ID = "listId" const val KEY_LIST_NAME = "listName" const val KEY_PRODUCT_TO_ADD = "product" } } + diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlist/ProductListAdapter.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlist/ProductListAdapter.kt index f8ad2aeeaa25..d83c03500bfd 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlist/ProductListAdapter.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlist/ProductListAdapter.kt @@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView import com.squareup.picasso.Callback import com.squareup.picasso.Picasso import openfoodfacts.github.scrachx.openfood.R -import openfoodfacts.github.scrachx.openfood.features.productlist.ProductListAdapter.YourListProductsViewHolder +import openfoodfacts.github.scrachx.openfood.features.productlist.ProductListAdapter.ProductViewHolder import openfoodfacts.github.scrachx.openfood.features.shared.views.CustomTextView import openfoodfacts.github.scrachx.openfood.models.entities.YourListedProduct import openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIClient @@ -23,13 +23,13 @@ class ProductListAdapter( private val client: OpenFoodAPIClient, val products: MutableList, private val isLowBatteryMode: Boolean -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = - YourListProductsViewHolder(LayoutInflater.from(context) + ProductViewHolder(LayoutInflater.from(context) .inflate(R.layout.your_listed_products_item, parent, false)) - override fun onBindViewHolder(holder: YourListProductsViewHolder, position: Int) { + override fun onBindViewHolder(holder: ProductViewHolder, position: Int) { holder.imgProgressBar.visibility = View.VISIBLE holder.tvTitle.text = products[position].productName @@ -69,7 +69,7 @@ class ProductListAdapter( override fun getItemCount() = products.size - class YourListProductsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imgProduct: AppCompatImageView = itemView.findViewById(R.id.imgProductYourListedProduct) val imgProgressBar: ProgressBar = itemView.findViewById(R.id.imageProgressbarYourListedProduct) val tvBarcode: CustomTextView = itemView.findViewById(R.id.barcodeYourListedProduct) diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlists/ProductListsActivity.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlists/ProductListsActivity.kt index b0b0aa181333..eb41a91230d7 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlists/ProductListsActivity.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/features/productlists/ProductListsActivity.kt @@ -39,7 +39,6 @@ import io.reactivex.schedulers.Schedulers import openfoodfacts.github.scrachx.openfood.R import openfoodfacts.github.scrachx.openfood.analytics.AnalyticsEvent import openfoodfacts.github.scrachx.openfood.analytics.MatomoAnalytics -import openfoodfacts.github.scrachx.openfood.app.OFFApplication import openfoodfacts.github.scrachx.openfood.databinding.ActivityProductListsBinding import openfoodfacts.github.scrachx.openfood.features.listeners.CommonBottomListenerInstaller.installBottomNavigation import openfoodfacts.github.scrachx.openfood.features.listeners.CommonBottomListenerInstaller.selectNavigationItem @@ -56,6 +55,7 @@ import openfoodfacts.github.scrachx.openfood.models.entities.ProductListsDao import openfoodfacts.github.scrachx.openfood.models.entities.YourListedProduct import openfoodfacts.github.scrachx.openfood.models.entities.YourListedProductDao import openfoodfacts.github.scrachx.openfood.utils.SwipeController +import openfoodfacts.github.scrachx.openfood.utils.isEmpty import org.apache.commons.csv.CSVFormat import org.apache.commons.csv.CSVParser import java.io.InputStream @@ -93,6 +93,7 @@ class ProductListsActivity : BaseActivity(), SwipeController.Actions { supportActionBar!!.setDisplayHomeAsUpEnabled(true) binding.bottomNavigation.bottomNavigation.installBottomNavigation(this) + binding.fabAdd.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_plus_blue_24, 0, 0, 0) productListsDao = daoSession.getProductListsDaoWithDefaultList(this) @@ -221,9 +222,7 @@ class ProductListsActivity : BaseActivity(), SwipeController.Actions { else -> super.onOptionsItemSelected(item) } - private fun openCSVToImport() { - chooseFileContract.launch(arrayOf("text/csv")) - } + private fun openCSVToImport() = chooseFileContract.launch(arrayOf("text/csv")) public override fun onResume() { super.onResume() @@ -235,35 +234,34 @@ class ProductListsActivity : BaseActivity(), SwipeController.Actions { progressDialog.show() Observable.create { emitter: ObservableEmitter -> Single.fromCallable { - val yourListedProductDao = daoSession.yourListedProductDao - val list = ArrayList() + val listProductDao = daoSession.yourListedProductDao + val list = mutableListOf() try { CSVParser(InputStreamReader(inputStream), CSVFormat.DEFAULT.withFirstRecordAsHeader()).use { csvParser -> val size = csvParser.records.size - var count = 0 - var id: Long - csvParser.records.forEach { record -> - var lists = productListsDao.queryBuilder().where(ProductListsDao.Properties.ListName.eq(record[2])).list() - if (lists.isEmpty()) { + csvParser.records.withIndex().forEach { (index, record) -> + val listName = record[2] + var daoList = productListsDao.queryBuilder() + .where(ProductListsDao.Properties.ListName.eq(listName)).unique() + if (daoList == null) { //create new list - val productList = ProductLists(record[2], 0) + val productList = ProductLists(listName, 0) adapter.lists.add(productList) productListsDao.insert(productList) - lists = productListsDao.queryBuilder().where(ProductListsDao.Properties.ListName.eq(record[2])).list() + daoList = productListsDao.queryBuilder() + .where(ProductListsDao.Properties.ListName.eq(listName)).unique() } - id = lists[0].id val yourListedProduct = YourListedProduct().apply { - barcode = record[0] - productName = record[1] - listName = record[2] - productDetails = record[3] - listId = id + this.barcode = record[0] + this.productName = record[1] + this.listName = listName + this.productDetails = record[3] + this.listId = daoList.id } list.add(yourListedProduct) - count++ - emitter.onNext(count * 100 / size) + emitter.onNext(index * 100 / size) } - yourListedProductDao.insertOrReplaceInTx(list) + listProductDao.insertOrReplaceInTx(list) return@fromCallable true } } catch (e: Exception) { @@ -285,7 +283,7 @@ class ProductListsActivity : BaseActivity(), SwipeController.Actions { private const val KEY_PRODUCT = "product" @JvmStatic - fun start(context: Context, productToAdd: Product?) = context.startActivity( + fun start(context: Context, productToAdd: Product) = context.startActivity( Intent(context, ProductListsActivity::class.java).apply { putExtra(KEY_PRODUCT, productToAdd) }) @@ -295,9 +293,11 @@ class ProductListsActivity : BaseActivity(), SwipeController.Actions { fun DaoSession.getProductListsDaoWithDefaultList(context: Context): ProductListsDao { val productListsDao = productListsDao - if (productListsDao.loadAll().isEmpty()) { - productListsDao.insert(ProductLists(context.getString(R.string.txt_eaten_products), 0)) - productListsDao.insert(ProductLists(context.getString(R.string.txt_products_to_buy), 0)) + if (productListsDao.isEmpty()) { + productListsDao.insertInTx( + ProductLists(context.getString(R.string.txt_eaten_products), 0), + ProductLists(context.getString(R.string.txt_products_to_buy), 0) + ) } return productListsDao } diff --git a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/FileUtils.kt b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/FileUtils.kt index 96446c431488..e52ead717749 100644 --- a/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/FileUtils.kt +++ b/app/src/main/java/openfoodfacts/github/scrachx/openfood/utils/FileUtils.kt @@ -1,9 +1,12 @@ package openfoodfacts.github.scrachx.openfood.utils +import android.app.NotificationChannel +import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.CheckResult @@ -38,8 +41,9 @@ fun getCsvFolderName() = when (BuildConfig.FLAVOR) { else -> "Open Food Facts" } -fun writeListToFile(context: Context, productList: ProductLists, csvUri: Uri,outputStream: OutputStream) { +fun writeListToFile(context: Context, productList: ProductLists, csvUri: Uri) { var success: Boolean + val outputStream = context.contentResolver.openOutputStream(csvUri) ?: error("File path must not be null.") try { CSVPrinter( outputStream.bufferedWriter(), @@ -58,7 +62,7 @@ fun writeListToFile(context: Context, productList: ProductLists, csvUri: Uri,out } val downloadIntent = Intent(Intent.ACTION_VIEW) - val notificationManager = ProductListActivity.createNotification(csvUri, downloadIntent, context) + val notificationManager = createNotification(csvUri, downloadIntent, context) if (success) { val builder = NotificationCompat.Builder(context, "export_channel") .setContentTitle(context.getString(R.string.notify_title)) @@ -69,7 +73,7 @@ fun writeListToFile(context: Context, productList: ProductLists, csvUri: Uri,out } } -fun writeHistoryToFile(context: Context, productList: List, csvUri: Uri,outputStream: OutputStream) { +fun writeHistoryToFile(context: Context, productList: List, csvUri: Uri, outputStream: OutputStream) { var success = false try { @@ -82,7 +86,7 @@ fun writeHistoryToFile(context: Context, productList: List, csvU Log.e(ScanHistoryActivity.LOG_TAG, "Can't export to $csvUri.", e) } val downloadIntent = Intent(Intent.ACTION_VIEW) - val notificationManager = ProductListActivity.createNotification(csvUri, downloadIntent, context) + val notificationManager = createNotification(csvUri, downloadIntent, context) if (success) { val builder = NotificationCompat.Builder(context, "export_channel") .setContentTitle(context.getString(R.string.notify_title)) @@ -93,4 +97,26 @@ fun writeHistoryToFile(context: Context, productList: List, csvU } } -const val LOCALE_FILE_SCHEME = "file://" \ No newline at end of file +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) + 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) + val importance = NotificationManager.IMPORTANCE_DEFAULT + val notificationChannel = NotificationChannel(channelId, channelName, importance) + notificationChannel.description = context.getString(R.string.notify_channel_description) + notificationManager.createNotificationChannel(notificationChannel) + } + return notificationManager +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_share_24.xml b/app/src/main/res/drawable/ic_baseline_share_24.xml new file mode 100644 index 000000000000..9f4ea7ec395c --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_share_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/menu_your_listed_products.xml b/app/src/main/res/menu/menu_your_listed_products.xml index cc1e21290f17..511beb1739e9 100644 --- a/app/src/main/res/menu/menu_your_listed_products.xml +++ b/app/src/main/res/menu/menu_your_listed_products.xml @@ -13,4 +13,10 @@ android:title="@string/sort" app:showAsAction="ifRoom" /> + + \ No newline at end of file