Skip to content

Commit

Permalink
feat: added share button to list activity
Browse files Browse the repository at this point in the history
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
  • Loading branch information
VaiTon committed Apr 5, 2021
1 parent 5b7f350 commit ceaf143
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 126 deletions.
Expand Up @@ -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
Expand Down Expand Up @@ -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<String, RequestBody?>) {
val settings = getLoginPreferences()
Expand Down
Expand Up @@ -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 <contact@openfoodfacts.org>")
putExtra(Intent.EXTRA_SUBJECT, "Photo report for product ${product.code}")
putExtra(Intent.EXTRA_TEXT, "I've spotted a problematic photo for product ${product.code}")
Expand Down
Expand Up @@ -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() {
Expand Down
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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 -> {
Expand All @@ -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),
Expand All @@ -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
}
Expand All @@ -275,17 +293,11 @@ class ProductListActivity : BaseActivity(), SwipeController.Actions {
}
true
}
else -> super.onOptionsItemSelected(item)
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, 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)
}


Expand All @@ -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()

Expand All @@ -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)
}

Expand All @@ -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"
}
}

Expand Up @@ -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
Expand All @@ -23,13 +23,13 @@ class ProductListAdapter(
private val client: OpenFoodAPIClient,
val products: MutableList<YourListedProduct>,
private val isLowBatteryMode: Boolean
) : RecyclerView.Adapter<YourListProductsViewHolder>() {
) : RecyclerView.Adapter<ProductViewHolder>() {

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
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit ceaf143

Please sign in to comment.