From 28da5408a18ef7c778077655fd27cc3feb3cb418 Mon Sep 17 00:00:00 2001 From: leca Date: Thu, 17 Oct 2024 03:01:58 +0300 Subject: [PATCH] Added sorting, freshness precentage, fullscreen view for products on shelf --- .../barcodescannerforemployees/Utils.kt | 17 +++ .../dataclasses/Product.kt | 8 ++ .../fragments/CategoriesFragment.kt | 4 +- .../fragments/ShelfFragment.kt | 103 +++++++++++++++--- .../fragments/StorageFragment.kt | 61 ++++++++--- .../views/ProductView.kt | 50 ++++----- .../main/res/layout/abstract_product_view.xml | 2 +- app/src/main/res/layout/fragment_shelf.xml | 36 ++++-- app/src/main/res/layout/fragment_storage.xml | 35 ++++-- app/src/main/res/layout/product_view.xml | 8 +- 10 files changed, 251 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/org/foxarmy/barcodescannerforemployees/Utils.kt b/app/src/main/java/org/foxarmy/barcodescannerforemployees/Utils.kt index 206dc9d..034851c 100644 --- a/app/src/main/java/org/foxarmy/barcodescannerforemployees/Utils.kt +++ b/app/src/main/java/org/foxarmy/barcodescannerforemployees/Utils.kt @@ -15,6 +15,9 @@ import java.io.File import java.io.FileOutputStream import java.net.URLEncoder import java.security.MessageDigest +import java.time.Duration +import java.time.LocalDate +import java.time.format.DateTimeFormatter fun getImageUri(activity: Activity, imageFile: File): Uri? { return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider", imageFile) @@ -69,4 +72,18 @@ fun getActivity(context: Context?): Activity? { } return null +} + +@RequiresApi(Build.VERSION_CODES.O) +fun calculateProductFreshness(dateOfProduction: String, dateOfExpiry: String): Double { + val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("d.M.yyyy") + + val fresh = LocalDate.parse(dateOfProduction, dateFormatter) + val expired = LocalDate.parse(dateOfExpiry, dateFormatter) + + val shelfLife = Duration.between(fresh.atStartOfDay(), expired.atStartOfDay()).toDays() + val today = LocalDate.parse(LocalDate.now().format(dateFormatter), dateFormatter) + val daysBeforeExpiry = Duration.between(today.atStartOfDay(), expired.atStartOfDay()).toDays() + + return daysBeforeExpiry / shelfLife.toDouble() } \ No newline at end of file diff --git a/app/src/main/java/org/foxarmy/barcodescannerforemployees/dataclasses/Product.kt b/app/src/main/java/org/foxarmy/barcodescannerforemployees/dataclasses/Product.kt index 4b97f0d..cfc5777 100644 --- a/app/src/main/java/org/foxarmy/barcodescannerforemployees/dataclasses/Product.kt +++ b/app/src/main/java/org/foxarmy/barcodescannerforemployees/dataclasses/Product.kt @@ -1,7 +1,10 @@ package org.foxarmy.barcodescannerforemployees.dataclasses +import android.os.Build import android.os.Parcel import android.os.Parcelable +import androidx.annotation.RequiresApi +import org.foxarmy.barcodescannerforemployees.calculateProductFreshness class Product() : Parcelable { var id = 0 @@ -9,13 +12,16 @@ class Product() : Parcelable { var amount = 0 var dateOfProduction = "01.01.1970" var dateOfExpiry = "01.01.1970" + var freshness: Double = 0.0 + @RequiresApi(Build.VERSION_CODES.O) constructor(id: Int, abstractProductId: Int, amount: Int, dateOfProduction: String, dateOfExpiry: String) : this() { this.id = id this.abstractProductId = abstractProductId this.amount = amount this.dateOfProduction = dateOfProduction this.dateOfExpiry = dateOfExpiry + this.freshness = calculateProductFreshness(dateOfProduction, dateOfExpiry) } constructor(parcel: Parcel) : this() { @@ -24,6 +30,7 @@ class Product() : Parcelable { amount = parcel.readInt() dateOfProduction = parcel.readString()!! dateOfExpiry = parcel.readString()!! + freshness = parcel.readDouble() } override fun writeToParcel(parcel: Parcel, flags: Int) { @@ -32,6 +39,7 @@ class Product() : Parcelable { parcel.writeInt(amount) parcel.writeString(dateOfProduction) parcel.writeString(dateOfExpiry) + parcel.writeDouble(freshness) } override fun describeContents(): Int { diff --git a/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/CategoriesFragment.kt b/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/CategoriesFragment.kt index 6363b16..a2db862 100644 --- a/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/CategoriesFragment.kt +++ b/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/CategoriesFragment.kt @@ -12,10 +12,10 @@ import androidx.core.content.ContextCompat import androidx.core.view.children import androidx.fragment.app.Fragment import org.foxarmy.barcodescannerforemployees.CategoriesContract -import org.foxarmy.barcodescannerforemployees.dataclasses.Category import org.foxarmy.barcodescannerforemployees.DBStorageController import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.activities.AddCategoryActivity +import org.foxarmy.barcodescannerforemployees.dataclasses.Category import org.foxarmy.barcodescannerforemployees.views.CategoryView class CategoriesFragment : Fragment() { @@ -61,8 +61,6 @@ class CategoriesFragment : Fragment() { ContextCompat.startActivity(context!!, addCategoryIntent, extras) } } - - } fun updateContent() { diff --git a/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/ShelfFragment.kt b/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/ShelfFragment.kt index 5829bb1..d7b9e9f 100644 --- a/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/ShelfFragment.kt +++ b/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/ShelfFragment.kt @@ -5,31 +5,57 @@ import android.provider.BaseColumns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.Toast import androidx.core.view.children import androidx.fragment.app.Fragment import androidx.gridlayout.widget.GridLayout +import org.foxarmy.barcodescannerforemployees.AbstractProductContract import org.foxarmy.barcodescannerforemployees.DBStorageController import org.foxarmy.barcodescannerforemployees.ProductContract import org.foxarmy.barcodescannerforemployees.R +import org.foxarmy.barcodescannerforemployees.databinding.FragmentShelfBinding import org.foxarmy.barcodescannerforemployees.dataclasses.Product import org.foxarmy.barcodescannerforemployees.views.ProductView import kotlin.concurrent.thread class ShelfFragment : Fragment() { + + private lateinit var binding: FragmentShelfBinding + private var updateInProgress = false + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { + binding = FragmentShelfBinding.inflate(layoutInflater) - return inflater.inflate(R.layout.fragment_shelf, container, false) - } + fillUpSortBySpinner() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) { + updateContent() + } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + updateContent() + } + } updateContent() + + return binding.root + } + + private fun fillUpSortBySpinner() { + val sorts = mutableListOf("Name", "Category", "Freshness", "Date of production", "Date of expiry") + + val arrayAdapter = + ArrayAdapter(requireContext(), androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, sorts) + arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item) + binding.spinner.adapter = arrayAdapter } override fun onResume() { @@ -40,6 +66,8 @@ class ShelfFragment : Fragment() { fun updateContent() { thread { + if (updateInProgress) return@thread + updateInProgress = true val grv = view?.findViewById(R.id.contentGridLayout) activity!!.runOnUiThread { grv?.removeAllViews() @@ -54,7 +82,37 @@ class ShelfFragment : Fragment() { ProductContract.ProductEntry.EXPIRY_DATE, ) - val cursor = db.query(ProductContract.ProductEntry.TABLE_NAME, projection, null, null, null, null, null) + var orderBy: String = "" + + when (binding.spinner.selectedItem) { + "Name" -> { + orderBy = + "(SELECT ${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} FROM ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} WHERE ${BaseColumns._ID} = ${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID}) ASC" + } + + "Category" -> { + orderBy = + "(SELECT ${AbstractProductContract.AbstractProductEntry.CATEGORY} FROM ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} WHERE ${BaseColumns._ID} = ${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID}) ASC" + } +// Wow, I wrote this on first try but unfortunately SQLite can't do that :( +// I'll leave it here as a memory. +// "Freshness" -> { +// orderBy = +// "(SELECT ( (julianday(${ProductContract.ProductEntry.EXPIRY_DATE}) - julianday('now', 'localtime')) / (julianday(${ProductContract.ProductEntry.EXPIRY_DATE}) - julianday(${ProductContract.ProductEntry.DATE_OF_PRODUCTION})) )) ASC" +// } + + "Date of production" -> { + orderBy = "${ProductContract.ProductEntry.DATE_OF_PRODUCTION} ASC" + } + + "Date of expiry" -> { + orderBy = "${ProductContract.ProductEntry.EXPIRY_DATE} ASC" + } + } + + val cursor = db.query(ProductContract.ProductEntry.TABLE_NAME, projection, null, null, null, null, orderBy) + + val products = mutableListOf() with(cursor) { while (moveToNext()) { @@ -68,16 +126,34 @@ class ShelfFragment : Fragment() { val product = Product(productId, abstractProductId, amount, dateOfProduction, dateOfExpiry) - val productView = ProductView( - requireActivity(), - requireContext(), - product - ) - activity!!.runOnUiThread { - grv?.addView(productView) + if (binding.spinner.selectedItem == "Freshness") { + products.add(product) + } else { + val productView = ProductView( + requireActivity(), + requireContext(), + product + ) + activity!!.runOnUiThread { + grv?.addView(productView) + } } } } + + products.sortWith(compareByDescending { it.freshness }) + + for (product in products.iterator()) { + val productView = ProductView( + requireActivity(), + requireContext(), + product + ) + activity!!.runOnUiThread { + grv?.addView(productView) + } + } + updateInProgress = false } } @@ -92,7 +168,6 @@ class ShelfFragment : Fragment() { view.findViewById(R.id.productPicture).setImageURI(null) } if (view.isProductSelected) { -// db.eraseAbstractProduct(db.writableDatabase, view.abstractProduct.id, requireContext()) db.eraseProduct(db.writableDatabase, view.product.id) deleted = true } diff --git a/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/StorageFragment.kt b/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/StorageFragment.kt index 09d45a4..e97184b 100644 --- a/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/StorageFragment.kt +++ b/app/src/main/java/org/foxarmy/barcodescannerforemployees/fragments/StorageFragment.kt @@ -3,19 +3,22 @@ package org.foxarmy.barcodescannerforemployees.fragments import android.content.Intent import android.os.Bundle import android.provider.BaseColumns +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.view.children import androidx.fragment.app.Fragment -import androidx.gridlayout.widget.GridLayout import org.foxarmy.barcodescannerforemployees.AbstractProductContract import org.foxarmy.barcodescannerforemployees.DBStorageController import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.activities.AddAbstractProductActivity +import org.foxarmy.barcodescannerforemployees.databinding.FragmentStorageBinding import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct import org.foxarmy.barcodescannerforemployees.generateThumbnailForImage import org.foxarmy.barcodescannerforemployees.views.AbstractProductView @@ -23,11 +26,27 @@ import kotlin.concurrent.thread class StorageFragment : Fragment() { + private lateinit var binding: FragmentStorageBinding + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_storage, container, false) + ): View { + binding = FragmentStorageBinding.inflate(inflater) + + fillUpSortBySpinner() + + binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) { + updateContent() + } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + updateContent() + } + } + + return binding.root } override fun onResume() { @@ -36,20 +55,21 @@ class StorageFragment : Fragment() { updateContent() } - override fun onStop() { - super.onStop() + private fun fillUpSortBySpinner() { + val sorts = mutableListOf("Name", "Category") - val grv = view?.findViewById(R.id.contentGridLayout) - grv?.removeAllViews() + val arrayAdapter = ArrayAdapter(requireContext(), androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, sorts) + arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item) + binding.spinner.adapter = arrayAdapter } fun removeSelected() { thread { - val grv = view?.findViewById(R.id.contentGridLayout) + val grv = binding.contentGridLayout val db = DBStorageController(requireContext()) var deleted = false - for (view: AbstractProductView in grv?.children!!.iterator() as Iterator) { + for (view: AbstractProductView in grv.children.iterator() as Iterator) { activity!!.runOnUiThread { view.findViewById(R.id.productPicture).setImageURI(null) } @@ -69,9 +89,9 @@ class StorageFragment : Fragment() { } fun updateSelected() { - val grv = view?.findViewById(R.id.contentGridLayout) + val grv = binding.contentGridLayout - for (view: AbstractProductView in grv?.children!!.iterator() as Iterator) { + for (view: AbstractProductView in grv.children.iterator() as Iterator) { if (view.isProductSelected) { val addProductIntent = Intent(requireContext(), AddAbstractProductActivity::class.java) val extras = Bundle() @@ -84,9 +104,9 @@ class StorageFragment : Fragment() { fun updateContent() { thread { - val grv:GridLayout? = view?.findViewById(R.id.contentGridLayout) + val grv = binding.contentGridLayout activity!!.runOnUiThread{ - grv?.removeAllViews() + grv.removeAllViews() } val db = DBStorageController(requireContext()).readableDatabase @@ -99,7 +119,18 @@ class StorageFragment : Fragment() { AbstractProductContract.AbstractProductEntry.CATEGORY ) - val cursor = db.query(AbstractProductContract.AbstractProductEntry.TABLE_NAME, projection, null, null, null, null, null) + var orderBy: String = "" + Log.d("QWERTYUIOP", binding.spinner.selectedItem.toString()) + when(binding.spinner.selectedItem) { + "Name" -> { + orderBy = "${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} ASC" + } + "Category" -> { + orderBy = "${AbstractProductContract.AbstractProductEntry.CATEGORY} ASC" + } + } + + val cursor = db.query(AbstractProductContract.AbstractProductEntry.TABLE_NAME, projection, null, null, null, null, orderBy) with (cursor) { while(moveToNext()) { @@ -121,7 +152,7 @@ class StorageFragment : Fragment() { ) activity!!.runOnUiThread{ - grv?.addView(abstractProduct) + grv.addView(abstractProduct) } } } diff --git a/app/src/main/java/org/foxarmy/barcodescannerforemployees/views/ProductView.kt b/app/src/main/java/org/foxarmy/barcodescannerforemployees/views/ProductView.kt index f9b0e3a..bf81d42 100644 --- a/app/src/main/java/org/foxarmy/barcodescannerforemployees/views/ProductView.kt +++ b/app/src/main/java/org/foxarmy/barcodescannerforemployees/views/ProductView.kt @@ -2,11 +2,12 @@ package org.foxarmy.barcodescannerforemployees.views import android.app.Activity import android.content.Context +import android.content.Intent import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.os.Build +import android.os.Bundle import android.util.AttributeSet -import android.util.Log import android.view.LayoutInflater import android.widget.ImageView import android.widget.LinearLayout @@ -14,20 +15,17 @@ import android.widget.TextView import androidx.annotation.RequiresApi import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat +import androidx.core.content.ContextCompat.startActivity import androidx.core.graphics.blue import androidx.core.graphics.green import androidx.core.graphics.red import androidx.core.math.MathUtils.clamp -import org.foxarmy.barcodescannerforemployees.DBStorageController -import org.foxarmy.barcodescannerforemployees.R +import org.foxarmy.barcodescannerforemployees.* +import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct import org.foxarmy.barcodescannerforemployees.dataclasses.Product -import org.foxarmy.barcodescannerforemployees.getActivity -import org.foxarmy.barcodescannerforemployees.getImageUri import java.io.File -import java.time.Duration -import java.time.LocalDate -import java.time.format.DateTimeFormatter +import java.text.DecimalFormat import kotlin.concurrent.thread class ProductView: LinearLayout { @@ -41,6 +39,8 @@ class ProductView: LinearLayout { private lateinit var productAmountTextView: TextView private lateinit var productCategoryView: TextView private lateinit var productLifeSpan: TextView + private lateinit var productFreshnessTextView: TextView + private var backgroundColor: Int = 0xffffff private var strokeColor: Int = 0x000000 private lateinit var outline: GradientDrawable @@ -64,6 +64,7 @@ class ProductView: LinearLayout { productAmountTextView = findViewById(R.id.amountView) productCategoryView = findViewById(R.id.categoryView) productLifeSpan = findViewById(R.id.dateSpan) + productFreshnessTextView = findViewById(R.id.freshnessPercentTextView) findViewById(R.id.productLayout).setOnLongClickListener { isProductSelected = !isProductSelected @@ -71,6 +72,15 @@ class ProductView: LinearLayout { true } + productImageView.setOnClickListener { + val fullscreenIntent = Intent(activity, FullscreenActivity::class.java) + val extras = Bundle() + val abstractProduct = DBStorageController(context).findAbstractProductById(DBStorageController(context).readableDatabase, product.abstractProductId) + extras.putString("imagehash", abstractProduct!!.imageHash) + fullscreenIntent.putExtras(extras) + startActivity(context, fullscreenIntent, extras) + } + update() } @@ -90,7 +100,12 @@ class ProductView: LinearLayout { productAmountTextView.text = product.amount.toString() productCategoryView.text = DBStorageController(activity).getCategoryNameById(DBStorageController(activity).readableDatabase, linkedAbstractProduct.category) productLifeSpan.text = "${product.dateOfProduction}-${product.dateOfExpiry}" - + productFreshnessTextView.text = + if (product.freshness == Double.NEGATIVE_INFINITY || product.freshness == Double.POSITIVE_INFINITY) { + "Expired" + } else { + "${DecimalFormat("#.#").format(product.freshness*100)}%" + } updateStroke() } @@ -118,17 +133,7 @@ class ProductView: LinearLayout { @RequiresApi(Build.VERSION_CODES.O) fun evaluateColor(): Int { - val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("d.M.yyyy") - - val fresh = LocalDate.parse(product.dateOfProduction, dateFormatter) - val expired = LocalDate.parse(product.dateOfExpiry, dateFormatter) - - val shelfLife = Duration.between(fresh.atStartOfDay(), expired.atStartOfDay()).toDays() - val today = LocalDate.parse(LocalDate.now().format(dateFormatter), dateFormatter) - val daysBeforeExpiry = Duration.between(today.atStartOfDay(), expired.atStartOfDay()).toDays() - - val freshnessPercentage: Double = daysBeforeExpiry / shelfLife.toDouble() - + val freshnessPercentage = calculateProductFreshness(product.dateOfProduction, product.dateOfExpiry) return calculateFreshnessGradient(freshnessPercentage) } @@ -153,9 +158,6 @@ class ProductView: LinearLayout { val colorString = "#$redHex$greenHex$blueHex" - Log.d("QWERTYUIOP", "$red:$green:$blue") - Log.d("QWERTYUIOP", "$redHex-$greenHex-$blueHex") - Log.d("QWERTYUIOP", colorString) return Color.parseColor(colorString) } @@ -166,8 +168,6 @@ class ProductView: LinearLayout { val red = c.red() * (1 - darkPercent) val green = c.green() * (1 - darkPercent) val blue = c.blue() * (1 - darkPercent) - Log.d("QWERTYUIOP", "....A:${c.red()}, ${c.green()}, ${c.blue()}") - Log.d("QWERTYUIOP", "....B:${red.toFloat()}, ${green.toFloat()}, ${blue.toFloat()}") return Color.rgb(red.toFloat(), green.toFloat(), blue.toFloat()) } diff --git a/app/src/main/res/layout/abstract_product_view.xml b/app/src/main/res/layout/abstract_product_view.xml index 2660ad5..f676b75 100644 --- a/app/src/main/res/layout/abstract_product_view.xml +++ b/app/src/main/res/layout/abstract_product_view.xml @@ -1,7 +1,7 @@ diff --git a/app/src/main/res/layout/fragment_shelf.xml b/app/src/main/res/layout/fragment_shelf.xml index 60ebf43..30fbbda 100644 --- a/app/src/main/res/layout/fragment_shelf.xml +++ b/app/src/main/res/layout/fragment_shelf.xml @@ -3,14 +3,36 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> - - - - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_storage.xml b/app/src/main/res/layout/fragment_storage.xml index 051820f..b2f88aa 100644 --- a/app/src/main/res/layout/fragment_storage.xml +++ b/app/src/main/res/layout/fragment_storage.xml @@ -5,14 +5,35 @@ android:layout_height="match_parent" android:id="@+id/fragment_storage" tools:context=".fragments.StorageFragment"> - - - - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/product_view.xml b/app/src/main/res/layout/product_view.xml index 6c5fdfb..38ac717 100644 --- a/app/src/main/res/layout/product_view.xml +++ b/app/src/main/res/layout/product_view.xml @@ -1,7 +1,7 @@ +