31 Commits

Author SHA1 Message Date
0fdcabca48 implemented units 2024-10-18 15:53:36 +03:00
e9ef4c44f9 fix unit 2024-10-18 15:08:26 +03:00
c112eec868 adding units to abstract product 2024-10-18 14:54:58 +03:00
a2662bf55b handling existing abstract products 2024-10-17 22:32:24 +03:00
6a49b573ca ability to edit products on shelf 2024-10-17 21:59:15 +03:00
b1b987e5f6 fixed adding months to product 2024-10-17 20:37:21 +03:00
4e11a7b4b9 replacing date to timestamp 2024-10-17 14:34:29 +03:00
28da5408a1 Added sorting, freshness precentage, fullscreen view for products on shelf 2024-10-17 03:01:58 +03:00
fd7feb72eb dialog on unknown barcode on adding product 2024-10-16 01:57:53 +03:00
224887422c darker outline of a productView, fixed duplications of abstract ones 2024-10-16 01:26:52 +03:00
b1030ac8c1 unneeded coloring 2024-10-15 13:47:31 +03:00
e8e2a50cad fix days evaluation 2024-10-15 13:43:23 +03:00
231419eb77 ability to delete a product 2024-10-15 13:26:04 +03:00
6c4f11bcb0 Added stroke that depends on product freshness 2024-10-15 13:13:40 +03:00
8ef72db4e4 fixed duplication of abstract products in storage fragment 2024-10-15 02:47:08 +03:00
f50e10e231 now deleting product together with abstract product 2024-10-15 02:39:23 +03:00
5066ded9c7 added productView and things around 2024-10-15 02:08:29 +03:00
efcd9361b2 Fixed parsing, working on adding products 2024-10-13 02:12:01 +03:00
a6ab2e305f added barcode to abstractproduct, working on adding products 2024-10-12 14:42:11 +03:00
646ea2b91a fixes 2024-10-12 04:52:21 +03:00
6dd48d0a8d added handler for non-existent products 2024-10-12 04:43:12 +03:00
4cad738f0e Requests added 2024-10-12 04:37:32 +03:00
3bfad0f6ad some refactor, renaming, started working with product on shelf 2024-10-11 04:58:31 +03:00
926403f9ea Implemented number of abstract products in category 2024-10-10 16:02:57 +03:00
332c7cd3fe a bit of remake, better UI and ability to update abstract products 2024-10-09 17:49:12 +03:00
a5a4c5db40 moved update and delete buttons of category view to a toolbar 2024-10-09 14:40:26 +03:00
068cd7846b ability to remove a category 2024-10-09 03:28:29 +03:00
fbfe68786e the most stupid problem 2024-10-08 18:32:25 +03:00
cfef250d38 cleanup 2024-10-08 02:28:49 +03:00
5fb481a0d5 categories 2024-10-08 02:24:16 +03:00
a6c21f05f9 cleanup 2024-10-07 18:22:40 +03:00
46 changed files with 2242 additions and 526 deletions

View File

@@ -34,7 +34,6 @@ android {
viewBinding = true viewBinding = true
} }
} }
var cameraxVersion = "1.0.1"
dependencies { dependencies {
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
@@ -45,8 +44,13 @@ dependencies {
implementation(libs.androidx.navigation.ui.ktx) implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.firebase.crashlytics.buildtools) implementation(libs.firebase.crashlytics.buildtools)
implementation(libs.androidx.gridlayout) implementation(libs.androidx.gridlayout)
implementation(libs.androidx.activity)
implementation(libs.androidx.legacy.support.v4)
implementation(libs.androidx.fragment)
testImplementation(libs.junit) testImplementation(libs.junit)
implementation(libs.volley)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
implementation (libs.play.services.code.scanner)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
// implementation("com.google.android.material:1.2.0") // implementation("com.google.android.material:1.2.0")
@@ -55,8 +59,8 @@ dependencies {
implementation (libs.barcode.scanning) implementation (libs.barcode.scanning)
// CameraX library // CameraX library
implementation ("androidx.camera:camera-camera2:$cameraxVersion") implementation (libs.androidx.camera.camera2)
implementation ("androidx.camera:camera-lifecycle:$cameraxVersion") implementation (libs.androidx.camera.lifecycle)
implementation (libs.androidx.camera.view) implementation (libs.androidx.camera.view)
implementation ("com.google.android.gms:play-services-code-scanner:16.1.0")
} }

View File

@@ -12,6 +12,7 @@
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -24,10 +25,17 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.BarcodeScannerForEmployees" android:theme="@style/Theme.BarcodeScannerForEmployees"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".activities.AddCategoryActivity"
android:exported="false"/>
<activity <activity
android:name=".activities.AddProductActivity" android:name=".activities.AddProductActivity"
android:exported="false" android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/> android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.AddAbstractProductActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity <activity
android:name=".activities.FullscreenActivity" android:name=".activities.FullscreenActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
@@ -37,7 +45,7 @@
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="com.google.firebase.components.activities.MainActivity.provider;com.google.firebase.components.activities.FullscreenActivity.provider;com.google.firebase.components.activities.AddProductActivity.provider" android:authorities="com.google.firebase.components.activities.MainActivity.provider;com.google.firebase.components.activities.FullscreenActivity.provider;com.google.firebase.components.activities.AddAbstractProductActivity.provider;com.google.firebase.components.activities.AddProductActivity.provider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data

View File

@@ -1,11 +0,0 @@
package org.foxarmy.barcodescannerforemployees
class AbstractProduct(
public var id: Int,
public var name: String,
public var netWeight: Double,
public var imageHash: String,
public var category: Int
) {
}

View File

@@ -1,26 +1,24 @@
package org.foxarmy.barcodescannerforemployees package org.foxarmy.barcodescannerforemployees
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.DatabaseUtils
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import android.provider.BaseColumns import android.provider.BaseColumns
import android.util.Log import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import java.io.File import java.io.File
object ProductContract { object AbstractProductContract {
object ProductEntry : BaseColumns { object AbstractProductEntry : BaseColumns {
const val TABLE_NAME = "products" const val TABLE_NAME = "abstract_products"
const val BARCODE = "barcode"
const val PRODUCT_NAME = "name" const val PRODUCT_NAME = "name"
const val PRODUCT_NET_WEIGHT = "net_weight" const val PRODUCT_NET_WEIGHT = "net_weight"
const val IMAGE_FILENAME = "image_filename" const val IMAGE_FILENAME = "image_filename"
} const val CATEGORY = "category"
} const val UNIT = "unit"
object ShelfContract {
object ShelfEntry : BaseColumns {
const val TABLE_NAME = "shelf"
const val PRODUCT_ID = "product_id"
const val EXPIRE_DATE = "expire_date"
} }
} }
@@ -31,71 +29,253 @@ object CategoriesContract {
} }
} }
const val SQL_CREATE_PRODUCT_TABLE = object ProductContract {
object ProductEntry : BaseColumns {
const val TABLE_NAME = "products"
const val ABSTRACT_PRODUCT_ID = "abstract_product_id"
const val AMOUNT = "amount"
const val DATE_OF_PRODUCTION = "date_of_production"
const val EXPIRY_DATE = "expiry_date"
}
}
const val SQL_CREATE_ABSTRACT_PRODUCTS_TABLE =
"CREATE TABLE ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} (" +
"${BaseColumns._ID} INTEGER PRIMARY KEY," +
"${AbstractProductContract.AbstractProductEntry.BARCODE} TEXT," +
"${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} TEXT," +
"${AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT} REAL," +
"${AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME} TEXT," +
"${AbstractProductContract.AbstractProductEntry.CATEGORY} INTEGER," +
"${AbstractProductContract.AbstractProductEntry.UNIT} INTEGER)"
const val SQL_CREATE_PRODUCTS_TABLE =
"CREATE TABLE ${ProductContract.ProductEntry.TABLE_NAME} (" + "CREATE TABLE ${ProductContract.ProductEntry.TABLE_NAME} (" +
"${BaseColumns._ID} INTEGER PRIMARY KEY," + "${BaseColumns._ID} INTEGER PRIMARY KEY," +
"${ProductContract.ProductEntry.PRODUCT_NAME} TEXT," + "${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID} INTEGER," +
"${ProductContract.ProductEntry.PRODUCT_NET_WEIGHT} REAL," + "${ProductContract.ProductEntry.AMOUNT} INTEGER," +
"${ProductContract.ProductEntry.IMAGE_FILENAME} TEXT)" "${ProductContract.ProductEntry.DATE_OF_PRODUCTION} INTEGER," +
"${ProductContract.ProductEntry.EXPIRY_DATE} INTGER)"
const val SQL_CREATE_SHELF_TABLE =
"CREATE TABLE ${ShelfContract.ShelfEntry.TABLE_NAME} (" +
"${ShelfContract.ShelfEntry.PRODUCT_ID} INTEGER," +
"${ShelfContract.ShelfEntry.EXPIRE_DATE} DATE)"
const val SQL_CREATE_CATEGORIES_TABLE = const val SQL_CREATE_CATEGORIES_TABLE =
"CREATE TABLE ${CategoriesContract.CategoryEntry.TABLE_NAME} (" + "CREATE TABLE ${CategoriesContract.CategoryEntry.TABLE_NAME} (" +
"${BaseColumns._ID} INTEGER PRIMARY KEY," +
"${CategoriesContract.CategoryEntry.CATEGORY_NAME} TEXT)" "${CategoriesContract.CategoryEntry.CATEGORY_NAME} TEXT)"
class DBStorageController(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { class DBStorageController(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
override fun onCreate(db: SQLiteDatabase) { override fun onCreate(db: SQLiteDatabase) {
db.execSQL(SQL_CREATE_PRODUCT_TABLE) db.execSQL(SQL_CREATE_ABSTRACT_PRODUCTS_TABLE)
db.execSQL(SQL_CREATE_SHELF_TABLE) db.execSQL(SQL_CREATE_PRODUCTS_TABLE)
db.execSQL(SQL_CREATE_CATEGORIES_TABLE) db.execSQL(SQL_CREATE_CATEGORIES_TABLE)
} }
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) { override fun onUpgrade(db: SQLiteDatabase?, oldV: Int, newV: Int) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
public fun eraseAbstractProduct(db: SQLiteDatabase, id: Int, context: Context) { fun eraseCategory (db: SQLiteDatabase, id: Int, context: Context) {
val projection = arrayOf(BaseColumns._ID, val productsInCategory = getAllAbstractProductInCategory(db, id)
ProductContract.ProductEntry.IMAGE_FILENAME
)
// val cursor = db.query(ProductContract.ProductEntry.TABLE_NAME, projection, null, , null, null, null) for (product in productsInCategory.iterator()) {
eraseAbstractProduct(db, product.id, context)
}
db.delete(CategoriesContract.CategoryEntry.TABLE_NAME, BaseColumns._ID + "=" + id, null)
}
fun getCategoryNameById(db: SQLiteDatabase, id: Int) : String {
var result = ""
val projection = arrayOf(CategoriesContract.CategoryEntry.CATEGORY_NAME)
val selection = "${BaseColumns._ID} = ?" val selection = "${BaseColumns._ID} = ?"
val selectionArgs = arrayOf(id.toString()) val selectionArgs = arrayOf(id.toString())
val cursor = db.query(
ProductContract.ProductEntry.TABLE_NAME, val cursor = db.query(CategoriesContract.CategoryEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, null)
projection,
selection, with (cursor) {
selectionArgs, while (moveToNext()) {
result = getString(getColumnIndexOrThrow(CategoriesContract.CategoryEntry.CATEGORY_NAME))
}
}
return result
}
fun getAllAbstractProductInCategory(db: SQLiteDatabase, id: Int) : List<AbstractProduct> {
var result = mutableListOf<AbstractProduct>()
val projection = arrayOf(
BaseColumns._ID,
AbstractProductContract.AbstractProductEntry.BARCODE,
AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.UNIT
)
val selection = "${AbstractProductContract.AbstractProductEntry.CATEGORY} = ?"
val selectionArgs = arrayOf(id.toString())
val cursor = db.query(AbstractProductContract.AbstractProductEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, null)
with(cursor) {
while (moveToNext()) {
val abstractProductId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val abstractProductBarcode = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.BARCODE))
val abstractProductName = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val abstractProductNetWeight = getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val abstractProductImageHash = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val abstractProductUnit = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
val abstractProduct = AbstractProduct(abstractProductId, abstractProductBarcode, abstractProductName, abstractProductNetWeight, abstractProductImageHash, category = id, abstractProductUnit)
result.add(abstractProduct)
}
}
return result
}
fun getAmountOfAbstractProductsInCategory(db:SQLiteDatabase, id: Int) : Int {
return DatabaseUtils.longForQuery(db, "SELECT COUNT(*) FROM ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} WHERE ${AbstractProductContract.AbstractProductEntry.CATEGORY} = ?", arrayOf(id.toString())).toInt()
}
fun eraseAbstractProduct(db: SQLiteDatabase, id: Int, context: Context) {
val projectionForAbstractProduct = arrayOf(
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME
)
val selectionForAbstractProduct = "${BaseColumns._ID} = ?"
val selectionArgsForAbstractProduct = arrayOf(id.toString())
val cursorForAbstractProduct = db.query(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
projectionForAbstractProduct,
selectionForAbstractProduct,
selectionArgsForAbstractProduct,
null, null,
null, null,
null null
) )
// val cursor = db.rawQuery("SELECT image_filename FROM ${ProductContract.ProductEntry.TABLE_NAME} WHERE ${BaseColumns._ID}=$id", null)
var imageHash: String = "" var imageHash: String = ""
with (cursor) { with (cursorForAbstractProduct) {
while(moveToNext()) { while(moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(BaseColumns._ID)) val productImageHash = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val productImageHash = getString(getColumnIndexOrThrow(ProductContract.ProductEntry.IMAGE_FILENAME))
imageHash = productImageHash imageHash = productImageHash
} }
} }
Log.d("QWERTYUIOP", "Deleting image with hash: $imageHash and $id")
val picturesDir = File(context.filesDir, "pictures") val picturesDir = File(context.filesDir, "pictures")
val thumbnailsDir = File(context.cacheDir, "thumbnails") val thumbnailsDir = File(context.cacheDir, "thumbnails")
File(picturesDir, "$imageHash.png").delete() File(picturesDir, "$imageHash.png").delete()
File(thumbnailsDir, "$imageHash.webp").delete() File(thumbnailsDir, "$imageHash.webp").delete()
Log.d("QWERTYUIOP", BaseColumns._ID + "=" + id)
val projectionForProducts = arrayOf(BaseColumns._ID)
val selectionForProducts = "${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID} = ?"
val selectionArgsForProducts = arrayOf(id.toString())
val cursorForProducts = db.query(ProductContract.ProductEntry.TABLE_NAME, projectionForProducts, selectionForProducts, selectionArgsForProducts, null, null, null)
with (cursorForProducts) {
while(moveToNext()) {
eraseProduct(db, getInt(getColumnIndexOrThrow(BaseColumns._ID)))
}
}
db.delete(AbstractProductContract.AbstractProductEntry.TABLE_NAME, BaseColumns._ID + "=" + id, null)
}
fun eraseProduct(db:SQLiteDatabase, id: Int) {
db.delete(ProductContract.ProductEntry.TABLE_NAME, BaseColumns._ID + "=" + id, null) db.delete(ProductContract.ProductEntry.TABLE_NAME, BaseColumns._ID + "=" + id, null)
} }
fun insertNewProduct(db: SQLiteDatabase, product: Product) {
val values = ContentValues().apply {
put(ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID, product.abstractProductId)
put(ProductContract.ProductEntry.AMOUNT, product.amount)
put(ProductContract.ProductEntry.DATE_OF_PRODUCTION, product.dateOfProduction)
put(ProductContract.ProductEntry.EXPIRY_DATE, product.dateOfExpiry)
}
db.insert(ProductContract.ProductEntry.TABLE_NAME, null, values)
}
fun findAbstractProductByBarcode (db: SQLiteDatabase, barcode: String) : AbstractProduct? {
var abstractProduct: AbstractProduct? = null
val projection = arrayOf(
BaseColumns._ID,
AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.CATEGORY,
AbstractProductContract.AbstractProductEntry.UNIT
)
val selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = ?"
val selectionArgs = arrayOf(barcode)
val cursor = db.query(AbstractProductContract.AbstractProductEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, null)
with(cursor) {
while(moveToNext()) {
val id = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val productName = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val imageFilename = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val netWeight = getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val category = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.CATEGORY))
val unit = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
abstractProduct = AbstractProduct(id, barcode, productName, netWeight, imageFilename, category, unit)
}
}
return abstractProduct
}
fun findAbstractProductById(db: SQLiteDatabase, id: Int): AbstractProduct? {
var abstractProduct: AbstractProduct? = null
val projection = arrayOf(
BaseColumns._ID,
AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.CATEGORY,
AbstractProductContract.AbstractProductEntry.BARCODE,
AbstractProductContract.AbstractProductEntry.UNIT
)
val selection = "${BaseColumns._ID} = ?"
val selectionArgs = arrayOf(id.toString())
val cursor = db.query(AbstractProductContract.AbstractProductEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, null)
with (cursor) {
while (moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val barcode = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.BARCODE))
val name = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val netWeight = getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val imageHash = getString((getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME)))
val category = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.CATEGORY))
val unit = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
abstractProduct = AbstractProduct(productId, barcode, name, netWeight, imageHash, category, unit)
}
}
return abstractProduct
}
fun updateProduct(db: SQLiteDatabase, product: Product) {
val values = ContentValues().apply {
put(ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID, product.abstractProductId)
put(ProductContract.ProductEntry.AMOUNT, product.amount)
put(ProductContract.ProductEntry.DATE_OF_PRODUCTION, product.dateOfProduction)
put(ProductContract.ProductEntry.EXPIRY_DATE, product.dateOfExpiry)
}
db.update(ProductContract.ProductEntry.TABLE_NAME, values, "${BaseColumns._ID} = ?", arrayOf(product.id.toString()))
}
companion object { companion object {
const val DATABASE_VERSION = 1 const val DATABASE_VERSION = 1
const val DATABASE_NAME = "database.db" const val DATABASE_NAME = "database.db"

View File

@@ -1,45 +1,53 @@
package org.foxarmy.barcodescannerforemployees package org.foxarmy.barcodescannerforemployees
class Parser constructor(payloadStartRegex: String, payloadEndRegex: String, payloadRegex: String){ import android.util.Log
val payloadStartRegex: String = payloadStartRegex import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
val payloadEndRegex: String = payloadEndRegex
val payloadRegex: String = payloadRegex
fun parse(text: String): MutableList<AbstractProduct> { class Parser constructor() {
val payloadStart = Regex(payloadStartRegex) fun findNetWeightInText(text: String, regexes: List<Regex>): Triple<String, Double, String> {
val payloadEnd = Regex(payloadEndRegex) var text = text
val payload = Regex(payloadRegex) var netWeight = 0.0
for (regex in regexes.iterator()) {
val foundByRegex = regex.find(text)
if (foundByRegex != null) {
val found = foundByRegex.groupValues[0]
text = text.replace(found, "")
netWeight = stripNetWeight(found)
// when (found.lowercase().strip()) {
// "кг" -> netWeight *= 1000
// "мл" -> netWeight /= 1000
// }
return Triple(text, netWeight, found)
}
}
return Triple("", 0.0, "")
}
val startFound = payloadStart.find(text)?.value.toString() fun parse(text: String): AbstractProduct {
val payloadStartIndex = text.indexOf(startFound) val (name, netWeight, unit) = findNetWeightInText(
var clearText = text.removeRange( text,
0, listOf(
if (payloadStartIndex < 0) 0 else payloadStartIndex Regex("[0-9]+,?[0-9]*\\s*[лЛ]"),
Regex("[0-9]+,?[0-9]*\\s*((мл)|(МЛ)|(Мл))"),
Regex("[0-9]+,?[0-9]*\\s*((кг)|(Кг))"),
Regex("[0-9]+,?[0-9]*\\s*[гГ]"),
Regex("[0-9]+,?[0-9*]\\s*((шт)|(Шт))")
) )
val endFound = payloadEnd.find(clearText)?.value.toString()
val payloadEndIndex = clearText.indexOf(endFound)
clearText = clearText.removeRange(
if (payloadEndIndex < 0) 0 else payloadEndIndex - 1,
clearText.length
) )
var unitNumber = -1
val strippedUnit = unit.lowercase().replace(Regex("\\d"), "").strip()
unitNumber = when (strippedUnit) {
"кг" -> { 0 }
"г" -> { 1 }
"л" -> { 2 }
"мл" -> { 3 }
"шт" -> { 4 }
println(clearText) else -> { -1 }
var products = payload.findAll(clearText).toMutableList()
for (product in products) {
println(product.value)
} }
Log.d("QWERTYUIOP", "Unit: ${strippedUnit}, number: ${unitNumber}")
return mutableListOf() return AbstractProduct(0, "", name, netWeight, "", 0, unitNumber)
} }
} }
fun main () {
val p = Parser("""\<table\s*class\=\"randomBarcodes\"\s*>""", """\<\/table\>""", """[ёЁ\u0401\u0451\u0410-\u044f\d\w\s]{16,}""")
p.parse(Requester("https://barcode-list.ru", "barcode/RU/Поиск.htm?barcode=4680036915828", ).request("4680036915828"))
// println(Requester("https://barcode-list.ru", "barcode/RU/Поиск.htm?barcode=4680036915828", ).request("4680036915828"))
}

View File

@@ -1,28 +1,40 @@
package org.foxarmy.barcodescannerforemployees package org.foxarmy.barcodescannerforemployees
import java.net.HttpURLConnection import android.content.Context
import java.net.URL import android.widget.Toast
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
class Requester constructor(siteName:String, endpoint: String) { class Requester constructor(var siteName: String, var endpoint: String) {
var siteName: String = siteName var response = ""
var endpoint: String = endpoint
fun request (productId: String): String { fun request(context: Context, barcode: String) {
val url = URL("${siteName}/$endpoint") val url = "${siteName}/${endpoint}"
var response: String = "" val volleyQueue = Volley.newRequestQueue(context)
val stringRequest = object: StringRequest(
with(url.openConnection() as HttpURLConnection) { Method.POST, url, { resp ->
requestMethod = "GET" // optional default is GET run {
response =
println("\nSent 'GET' request to URL : $url; Response Code : $responseCode") if (resp == "") {
"Not found 404"
inputStream.bufferedReader().use { } else {
it.lines().forEach { line -> resp
response += line //+ '\n'
} }
} }
},
{
Toast.makeText(context, "Cannot make request", Toast.LENGTH_LONG).show()
} }
return response ) {
override fun getHeaders(): Map<String, String> {
return mapOf("referer" to "$siteName/")
}
public override fun getParams(): MutableMap<String, String> {
return mutableMapOf("barcode" to barcode)
}
}
volleyQueue.add(stringRequest)
} }
} }

View File

@@ -2,6 +2,7 @@ package org.foxarmy.barcodescannerforemployees
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.ContextWrapper
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
@@ -12,7 +13,9 @@ import androidx.core.graphics.scale
import com.google.firebase.components.BuildConfig import com.google.firebase.components.BuildConfig
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.net.URLEncoder
import java.security.MessageDigest import java.security.MessageDigest
import java.util.*
fun getImageUri(activity: Activity, imageFile: File): Uri? { fun getImageUri(activity: Activity, imageFile: File): Uri? {
return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider", imageFile) return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider", imageFile)
@@ -40,3 +43,51 @@ fun String.md5(): String {
val digest = md.digest(this.toByteArray()) val digest = md.digest(this.toByteArray())
return digest.toHexString() return digest.toHexString()
} }
fun stripNetWeight (netWeight: String): Double {
return removeSubstringsFromString(netWeight, arrayOf("Л", "л", "мл", "Мл", "г", "Г", "кг", "Кг", "шт", "Шт", ",", " ", ".")).toDouble()
}
fun removeSubstringsFromString(text: String, toRemove: Array<String>): String {
var result = text
for (candidate in toRemove.iterator()) {
result = result.replace(candidate, "")
}
return result
}
fun String.utf8(): String = URLEncoder.encode(this, "UTF-8")
fun getActivity(context: Context?): Activity? {
if (context == null) {
return null
} else if (context is ContextWrapper) {
return if (context is Activity) {
context
} else {
getActivity(context.baseContext)
}
}
return null
}
@RequiresApi(Build.VERSION_CODES.O)
fun calculateProductFreshness(dateOfProduction: Long, dateOfExpiry: Long): Double {
val productLifeSpan = dateOfExpiry - dateOfProduction;
val lifeSpanLeft: Long = dateOfExpiry - Date().time / 1000
return lifeSpanLeft / productLifeSpan.toDouble()
}
fun getUnitNameById (context: Context, id: Int): String {
return when(id) {
0 -> { context.getString(R.string.kilogram) }
1 -> { context.getString(R.string.gram) }
2 -> { context.getString(R.string.liter) }
3 -> { context.getString(R.string.milliliter) }
4 -> { context.getString(R.string.pieces) }
else -> { "" }
}
}

View File

@@ -7,10 +7,10 @@ import androidx.fragment.app.FragmentPagerAdapter
class ViewPagerAdapter class ViewPagerAdapter
public constructor(supportFragmentManager: FragmentManager) : FragmentPagerAdapter(supportFragmentManager) { public constructor(supportFragmentManager: FragmentManager) : FragmentPagerAdapter(supportFragmentManager) {
private final var fragmentList1: ArrayList<Fragment> = ArrayList() private var fragmentList1: ArrayList<Fragment> = ArrayList()
private final var fragmentTitleList1: ArrayList<String> = ArrayList() private var fragmentTitleList1: ArrayList<String> = ArrayList()
public override fun getItem(position: Int): Fragment { override fun getItem(position: Int): Fragment {
return fragmentList1.get(position) return fragmentList1.get(position)
} }

View File

@@ -0,0 +1,282 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.Manifest
import android.content.ContentValues
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.BaseColumns
import android.util.Log
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import org.foxarmy.barcodescannerforemployees.*
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import kotlin.concurrent.thread
class AddAbstractProductActivity : AppCompatActivity() {
private lateinit var imageView: ImageView
private lateinit var saveButton: Button
private lateinit var takePictureButton: Button
private lateinit var scanButton: Button
private lateinit var barcodeText: EditText
private lateinit var productNameText: TextView
private lateinit var netWeightText: TextView
private lateinit var unitTypeSpinner: Spinner
private lateinit var categorySpinner: Spinner
private var abstractProduct: AbstractProduct? = null
private lateinit var pictureFile: File
private lateinit var picturesPath: File
private var barcode: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_add_abstract_product)
val extras = intent.extras
abstractProduct = extras!!.get("abstractProduct") as AbstractProduct?
if (abstractProduct != null) {
barcode = abstractProduct!!.barcode
}
picturesPath = File(filesDir, "pictures")
val thumbnailsDir = File(cacheDir, "thumbnails")
thumbnailsDir.mkdirs()
picturesPath.mkdirs()
imageView = findViewById(R.id.imageView)
saveButton = findViewById(R.id.saveButton)
takePictureButton = findViewById(R.id.takePictureButton)
scanButton = findViewById(R.id.scan_button)
barcodeText = findViewById(R.id.barcodeTextEdit)
productNameText = findViewById(R.id.productName)
netWeightText = findViewById(R.id.netWeight)
unitTypeSpinner = findViewById(R.id.unitTypeSpinner)
categorySpinner = findViewById(R.id.categorySpinner)
fillupCategorySpinner()
fillupUnitsSpinner()
if (abstractProduct?.name == "" && abstractProduct?.barcode != "") {
performRequest(abstractProduct?.barcode!!)
}
if (abstractProduct != null) {
val imageThumbnailUri = getImageUri(this, File(thumbnailsDir, "${abstractProduct!!.imageHash}.webp"))
pictureFile = File(picturesPath, "${abstractProduct!!.imageHash}.png]")
imageView.setImageURI(imageThumbnailUri)
imageView.rotation = 90f
barcodeText.setText(abstractProduct!!.barcode)
productNameText.text = abstractProduct!!.name
netWeightText.text = abstractProduct!!.netWeight.toString()
categorySpinner.setSelection(abstractProduct!!.category)
unitTypeSpinner.setSelection(abstractProduct!!.unit)
}
saveButton.setOnClickListener {
val productName = productNameText.text.toString()
val netWeight = netWeightText.text
if (abstractProduct == null && (!this::pictureFile.isInitialized || !pictureFile.exists())) {
Toast.makeText(this, "Please, make a picture of a product!", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (barcode == "") {
Toast.makeText(this, "Please, scan barcode on a product", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (productName == "") {
Toast.makeText(this, "Please, write a name of a product!", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (netWeight.toString() == "" || netWeight.toString().toDoubleOrNull() == null) {
Toast.makeText(this, "Please, write a valid net weight of a product!", Toast.LENGTH_SHORT).show()
}
val db = DBStorageController(this).writableDatabase
val values = ContentValues().apply {
put(AbstractProductContract.AbstractProductEntry.BARCODE, barcode)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME, productName)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT, netWeight.toString())
put(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME, pictureFile.nameWithoutExtension)
put(AbstractProductContract.AbstractProductEntry.CATEGORY, categorySpinner.selectedItemPosition)
put(AbstractProductContract.AbstractProductEntry.UNIT, unitTypeSpinner.selectedItemPosition)
}
if (abstractProduct == null) {
db.insert(AbstractProductContract.AbstractProductEntry.TABLE_NAME, null, values)
} else {
db.update(AbstractProductContract.AbstractProductEntry.TABLE_NAME, values, "${BaseColumns._ID} = ?", arrayOf(abstractProduct!!.id.toString()))
}
finish()
}
takePictureButton.setOnClickListener {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
scanButton.setOnClickListener {
val options = GmsBarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_EAN_13
)
.build()
val scanner = GmsBarcodeScanning.getClient(this)
scanner.startScan()
.addOnSuccessListener { barcode ->
this.barcode = barcode.rawValue.toString()
performRequest(this.barcode)
}
.addOnFailureListener { e ->
Toast.makeText(
this,
"Failed to scan barcode. Please, try again or enter data manually",
Toast.LENGTH_LONG
).show()
}
}
}
fun performRequest(barcode: String) {
barcodeText.setText(this.barcode)
val requester = Requester("https://ean-online.ru", "match.php")
requester.request(this, barcode)
var abstractProduct: AbstractProduct
if (DBStorageController(this).findAbstractProductByBarcode(DBStorageController(this).readableDatabase, this.barcode) != null) {
AlertDialog.Builder(this)
.setMessage("You've got an abstract product with such barcode in your database")
.setPositiveButton("Quit") { _: DialogInterface, _: Int ->
finish()
}
.setNegativeButton("Edit existing") { _: DialogInterface, _: Int ->
val addProductIntent = Intent(this, AddAbstractProductActivity::class.java)
val extras = Bundle()
val existingAbstractProduct = DBStorageController(this).findAbstractProductByBarcode(DBStorageController(this).readableDatabase, this.barcode)
extras.putParcelable("abstractProduct", existingAbstractProduct)
addProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addProductIntent, extras)
finish()
}.show()
}
thread {
// Я сам в ахуях какой это костыль, пока хз как фиксить, потом придумаю :))
while (requester.response == "") { }
if (requester.response == "Not found 404") {
runOnUiThread {
Toast.makeText(this, "Product not found. Please, try again or type manually", Toast.LENGTH_LONG).show()
}
return@thread
}
abstractProduct = Parser().parse(requester.response)
requester.response = ""
runOnUiThread {
productNameText.text = abstractProduct.name
netWeightText.text = abstractProduct.netWeight.toString()
unitTypeSpinner.setSelection(abstractProduct.unit)
}
}
}
private fun fillupUnitsSpinner() {
val units = listOf(
getString(R.string.kilogram),
getString(R.string.gram),
getString(R.string.liter),
getString(R.string.milliliter),
getString(R.string.pieces)
)
val arrayAdapter = ArrayAdapter<String>(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, units)
arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
unitTypeSpinner.adapter = arrayAdapter
}
fun fillupCategorySpinner() {
val db = DBStorageController(this).readableDatabase
val categories = mutableListOf("")
val projection = arrayOf(
CategoriesContract.CategoryEntry.CATEGORY_NAME
)
val cursor = db.query(CategoriesContract.CategoryEntry.TABLE_NAME, projection, null, null, null, null, BaseColumns._ID+" ASC")
with (cursor) {
while (moveToNext()) {
categories.add(getString(getColumnIndexOrThrow(CategoriesContract.CategoryEntry.CATEGORY_NAME)))
}
}
val arrayAdapter = ArrayAdapter<String>(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, categories)
arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
categorySpinner.adapter = arrayAdapter
}
@RequiresApi(Build.VERSION_CODES.R)
val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success: Boolean ->
if (success) {
//Move picture to a proper directory according to its calculated hash
val tempfile = File(filesDir, "image.png")
val imageContent = tempfile.inputStream().readBytes()
val imageHash = imageContent.toString(Charsets.UTF_8).md5()
pictureFile = File(picturesPath, "$imageHash.png")
Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
tempfile.delete()
generateThumbnailForImage(this, imageHash)
imageView.setImageURI(getImageUri(this, pictureFile))
} else {
Log.e("QWERTYUIOP", "Cannot save a picture")
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun getPicture () {
//Saving picture to a temp file for further hash calculation and moving to a proper directory
val imageFile = File(this.filesDir, "image.png")
val imageUri = getImageUri(this, imageFile)
if (imageUri != null) {
takePicture.launch(imageUri)
}
}
@RequiresApi(Build.VERSION_CODES.R)
val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
getPicture()
} else {
Toast.makeText(this, "I need permission in order to take a picture", Toast.LENGTH_LONG).show()
}
}
}

View File

@@ -0,0 +1,46 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.content.ContentValues
import android.os.Bundle
import android.provider.BaseColumns
import android.widget.Button
import android.widget.EditText
import org.foxarmy.barcodescannerforemployees.CategoriesContract
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.R
class AddCategoryActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_category)
val extras = intent.extras
val category = extras!!.get("category") as Category?
val categoryNameTextEdit: EditText = findViewById(R.id.newCategoryName)
categoryNameTextEdit.setText(category!!.name)
findViewById<Button>(R.id.saveButton).setOnClickListener {
val db = DBStorageController(this).writableDatabase
if (category.id == 0) { // Inserting new category
val values = ContentValues().apply {
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, categoryNameTextEdit.text.toString())
}
db.insert(CategoriesContract.CategoryEntry.TABLE_NAME, null, values)
} else { // Updating existing category
val values = ContentValues().apply {
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, categoryNameTextEdit.text.toString())
}
db.update(CategoriesContract.CategoryEntry.TABLE_NAME, values, "${BaseColumns._ID} = ?", arrayOf(category.id.toString()))
}
finish()
}
}
}

View File

@@ -1,154 +1,218 @@
package org.foxarmy.barcodescannerforemployees.activities package org.foxarmy.barcodescannerforemployees.activities
import android.Manifest import android.app.DatePickerDialog
import android.content.ContentValues
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.view.View
import android.widget.Button import android.widget.*
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.mlkit.vision.barcode.common.Barcode import androidx.core.widget.addTextChangedListener
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import org.foxarmy.barcodescannerforemployees.* import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.databinding.ActivityAddProductBinding import org.foxarmy.barcodescannerforemployees.R
import java.io.File import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import java.nio.file.Files import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import java.nio.file.StandardCopyOption import org.foxarmy.barcodescannerforemployees.views.AbstractProductView
import java.text.SimpleDateFormat
import java.util.*
class AddProductActivity : AppCompatActivity() { class AddProductActivity : AppCompatActivity() {
private lateinit var imageView: ImageView
private lateinit var saveButton: Button
private lateinit var takePictureButton: Button
private lateinit var scanButton: Button private lateinit var scanButton: Button
private lateinit var abstractProductView: AbstractProductView
private lateinit var expiryDateRadioButton: RadioButton
private lateinit var shelfLifeRadioButton: RadioButton
private lateinit var productNameText: TextView private lateinit var expiryDateTextView: TextView
private lateinit var netWeightText: TextView private lateinit var expiryDateSelectButton: Button
private lateinit var pictureFile: File private lateinit var shelfLifeTextView: TextView
private lateinit var picturesPath: File private lateinit var shelfLifeTextEdit: EditText
private lateinit var binding: ActivityAddProductBinding
private lateinit var amountTextEdit: EditText
private lateinit var dateOfProductionSelectButton: Button
private lateinit var saveProductButton: Button
private var expiryDateOverShelfLife: Boolean? = null
private var updatingExistentProduct = false
private var product: Product? = null
private var abstractProduct: AbstractProduct? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_add_product) setContentView(R.layout.fragment_add_product)
scanButton = findViewById(R.id.scanButton)
abstractProductView = findViewById(R.id.abstractProductView)
expiryDateRadioButton = findViewById(R.id.expiryDateRadio)
shelfLifeRadioButton = findViewById(R.id.shelfLifeRadio)
picturesPath = File(filesDir, "pictures") expiryDateTextView = findViewById(R.id.expiryDateTextView)
picturesPath.mkdirs() expiryDateSelectButton = findViewById(R.id.selectExpiryDateButton)
// imageView = binding.includeContent.addProductLayout
imageView = findViewById(R.id.imageView)
saveButton = findViewById(R.id.saveButton) shelfLifeTextView = findViewById(R.id.shelfLife)
takePictureButton = findViewById(R.id.takePictureButton) shelfLifeTextEdit = findViewById(R.id.shelfLifeTextEdit)
scanButton = findViewById(R.id.scan_button)
productNameText = findViewById(R.id.productName) amountTextEdit = findViewById(R.id.amountTextEdit)
netWeightText = findViewById(R.id.netWeight)
saveButton.setOnClickListener { dateOfProductionSelectButton = findViewById(R.id.selectDateOfProductionButton)
val productName = productNameText.text.toString() saveProductButton = findViewById(R.id.saveProductButton)
val netWeight = netWeightText.text
if (!this::pictureFile.isInitialized || !pictureFile.exists()) {
Toast.makeText(this, "Please, make a picture of a product!", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (productName == "") { val extras = intent.extras
Toast.makeText(this, "Please, write a name of a product!", Toast.LENGTH_SHORT).show() product = extras!!.get("product") as Product?
return@setOnClickListener
}
if (netWeight.toString() == "" || netWeight.toString().toDoubleOrNull() == null) {
Toast.makeText(this, "Please, write a valid net weight of a product!", Toast.LENGTH_SHORT).show()
}
val db = DBStorageController(this).writableDatabase if (product != null) {
Log.d("QWERTYUIOP", "Putting ${pictureFile.name}") abstractProduct = DBStorageController(this).findAbstractProductById(DBStorageController(this).readableDatabase, product!!.abstractProductId)
val values = ContentValues().apply { abstractProductView.abstractProduct = abstractProduct!!
put(ProductContract.ProductEntry.PRODUCT_NAME, productName) expiryDateRadioButton.isSelected = true
put(ProductContract.ProductEntry.PRODUCT_NET_WEIGHT, netWeight.toString()) shelfLifeRadioButton.isSelected = false
put(ProductContract.ProductEntry.IMAGE_FILENAME, pictureFile.nameWithoutExtension)
}
db.insert(ProductContract.ProductEntry.TABLE_NAME, null, values) expiryDateOverShelfLife = true
updatingExistentProduct = true
finish() update()
} } else {
product = Product(0, 0, 0, 0, 0)
takePictureButton.setOnClickListener { abstractProduct = AbstractProduct(0, "", "", 0.0, "", 0, 0)
Log.d("QWERTYUIOP", "test")
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
} }
scanButton.setOnClickListener { scanButton.setOnClickListener {
val options = GmsBarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_EAN_13
)
.build()
val scanner = GmsBarcodeScanning.getClient(this) val scanner = GmsBarcodeScanning.getClient(this)
scanner.startScan() scanner.startScan()
.addOnSuccessListener { barcode -> .addOnSuccessListener { bc ->
productNameText.setText(barcode.rawValue) abstractProduct = DBStorageController(this).findAbstractProductByBarcode(DBStorageController(this).readableDatabase, bc.rawValue.toString())
product!!.abstractProductId = abstractProduct!!.id
displayAbstractProduct(abstractProduct!!)
} }
.addOnFailureListener { e -> .addOnFailureListener {
Toast.makeText( Toast.makeText(this, "Cannot scan barcode", Toast.LENGTH_SHORT).show()
this,
"Failed to scan barcode. Please, try again or enter data manually",
Toast.LENGTH_LONG
).show()
} }
} }
// binding = ActivityAddProductBinding.inflate(layoutInflater) expiryDateRadioButton.setOnClickListener {
expiryDateOverShelfLife = true
// setContentView(binding.root) update()
} }
@RequiresApi(Build.VERSION_CODES.R)
val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success: Boolean ->
if (success) {
//Move picture to a proper directory according to its calculated hash
val tempfile = File(filesDir, "image.png")
val imageContent = tempfile.inputStream().readBytes()
val imageHash = imageContent.toString(Charsets.UTF_8).md5()
pictureFile = File(picturesPath, "$imageHash.png") shelfLifeRadioButton.setOnClickListener {
Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING) expiryDateOverShelfLife = false
tempfile.delete() update()
generateThumbnailForImage(this, imageHash) }
imageView.setImageURI(getImageUri(this, pictureFile)) dateOfProductionSelectButton.setOnClickListener {
Log.i("QWERTYUIOP", "Picture saved") val c = Calendar.getInstance()
val year = c.get(Calendar.YEAR)
val month = c.get(Calendar.MONTH)
val day = c.get(Calendar.DAY_OF_MONTH)
val dpd = DatePickerDialog(this, { _, y, m, d ->
product!!.dateOfProduction = SimpleDateFormat("dd.MM.yyyy").parse("$d.${m+1}.$y")!!.time / 1000
update()
}, year, month, day)
dpd.show()
}
expiryDateSelectButton.setOnClickListener {
val c = Calendar.getInstance()
val year = c.get(Calendar.YEAR)
val month = c.get(Calendar.MONTH)
val day = c.get(Calendar.DAY_OF_MONTH)
val dpd = DatePickerDialog(this, { _, y, m, d ->
product!!.dateOfExpiry = SimpleDateFormat("dd.MM.yyyy").parse("$d.${m+1}.$y")!!.time / 1000
update()
}, year, month, day)
dpd.show()
}
amountTextEdit.addTextChangedListener {
val text = amountTextEdit.text.toString()
if (text == "") return@addTextChangedListener
product!!.amount = text.toInt()
}
saveProductButton.setOnClickListener {
if (expiryDateOverShelfLife == null) {
Toast.makeText(this, "Please, choose and fill in shelf life or expiry date.", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (product!!.dateOfProduction == 0.toLong()) {
Toast.makeText(this, "Please, choose date of production.", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (!expiryDateOverShelfLife!!) {
if (shelfLifeTextEdit.text.toString() != "") {
evaluateDateOfExpiry(shelfLifeTextEdit.text.toString().toInt())
}
} else { } else {
Log.e("QWERTYUIOP", "Cannot save a picture") if (product!!.dateOfExpiry == 0.toLong()) {
Toast.makeText(this, "Please, choose date of expiry.", Toast.LENGTH_SHORT).show()
return@setOnClickListener
} }
} }
@RequiresApi(Build.VERSION_CODES.R) if (product!!.amount == 0) {
fun getPicture () { Toast.makeText(this, "Please, specify an amount of product.", Toast.LENGTH_SHORT).show()
//Saving picture to a temp file for further hash calculation and moving to a proper directory return@setOnClickListener
val imageFile = File(this.filesDir, "image.png")
val imageUri = getImageUri(this, imageFile)
takePicture.launch(imageUri)
} }
@RequiresApi(Build.VERSION_CODES.R) if (updatingExistentProduct) {
val requestPermissionLauncher = DBStorageController(this).updateProduct(DBStorageController(this).writableDatabase, product!!)
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
getPicture()
} else { } else {
Toast.makeText(this, "I need permission in order to take a picture", Toast.LENGTH_LONG).show() DBStorageController(this).insertNewProduct(DBStorageController(this).writableDatabase, product!!)
}
finish()
} }
} }
private fun update () {
if (expiryDateOverShelfLife == true) {
expiryDateTextView.visibility = View.VISIBLE
expiryDateSelectButton.visibility = View.VISIBLE
shelfLifeTextEdit.visibility = View.INVISIBLE
shelfLifeTextView.visibility = View.INVISIBLE
} else if (expiryDateOverShelfLife == false){
expiryDateTextView.visibility = View.INVISIBLE
expiryDateSelectButton.visibility = View.INVISIBLE
shelfLifeTextEdit.visibility = View.VISIBLE
shelfLifeTextView.visibility = View.VISIBLE
}
val dateOfProductionParsed = SimpleDateFormat("dd.MM.yyyy").format(Date(product!!.dateOfProduction * 1000))
findViewById<TextView>(R.id.dateOfProductionTextView).text = "Date of production: $dateOfProductionParsed"
val expiryDateParsed = SimpleDateFormat("dd.MM.yyyy").format(Date(product!!.dateOfExpiry * 1000))
expiryDateTextView.text = "Expiry date: $expiryDateParsed"
if (amountTextEdit.text.toString() == "") {
amountTextEdit.setText(product!!.amount.toString())
} else {
product!!.amount = amountTextEdit.text.toString().toInt()
}
abstractProductView.update()
}
private fun evaluateDateOfExpiry(shelfLifeMonths: Int) {
if (product!!.dateOfProduction == 0.toLong() ) {
return
}
val c = Calendar.getInstance()
c.timeInMillis = product!!.dateOfProduction * 1000
c.add(Calendar.MONTH, shelfLifeMonths)
product!!.dateOfExpiry = c.timeInMillis / 1000
}
private fun displayAbstractProduct(abstractProduct: AbstractProduct) {
abstractProductView.abstractProduct = abstractProduct
abstractProductView.update()
}
} }

View File

@@ -28,14 +28,12 @@ class FullscreenActivity : Activity() {
fullscreenImageView = findViewById(R.id.fullscreenImageView) fullscreenImageView = findViewById(R.id.fullscreenImageView)
val extras = intent.extras val extras = intent.extras
val imageHash = extras!!.get("imagehash") as String? //extras!!.getParcelable<Parcelable>("imagehash") as String? val imageHash = extras!!.get("imagehash") as String?
val picturesDir = File(filesDir, "pictures") val picturesDir = File(filesDir, "pictures")
picturesDir.mkdirs() picturesDir.mkdirs()
Log.d("QWERTYUIOP", imageHash.toString())
val fullscreenImageFile = File(picturesDir, "$imageHash.png") val fullscreenImageFile = File(picturesDir, "$imageHash.png")
// crash. change activity
fullscreenImageView.setImageURI(getImageUri(this, fullscreenImageFile)) fullscreenImageView.setImageURI(getImageUri(this, fullscreenImageFile))
fullscreenImageView.setOnClickListener { fullscreenImageView.setOnClickListener {
this.finish() this.finish()

View File

@@ -1,29 +1,26 @@
package org.foxarmy.barcodescannerforemployees.activities package org.foxarmy.barcodescannerforemployees.activities
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.ImageView import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.gridlayout.widget.GridLayout
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.ViewPagerAdapter import org.foxarmy.barcodescannerforemployees.ViewPagerAdapter
import org.foxarmy.barcodescannerforemployees.databinding.ActivityMainBinding import org.foxarmy.barcodescannerforemployees.databinding.ActivityMainBinding
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.fragments.CategoriesFragment import org.foxarmy.barcodescannerforemployees.fragments.CategoriesFragment
import org.foxarmy.barcodescannerforemployees.fragments.ShelfFragment
import org.foxarmy.barcodescannerforemployees.fragments.StorageFragment import org.foxarmy.barcodescannerforemployees.fragments.StorageFragment
import org.foxarmy.barcodescannerforemployees.views.AbstractProductView
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
// private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
public lateinit var adapter: ViewPagerAdapter lateinit var adapter: ViewPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -35,23 +32,47 @@ class MainActivity : AppCompatActivity() {
setupViewPager(binding.tabViewpager) setupViewPager(binding.tabViewpager)
binding.tabTablayout.setupWithViewPager(binding.tabViewpager) binding.tabTablayout.setupWithViewPager(binding.tabViewpager)
// val navController = findNavController(R.id.nav_host_fragment_content_storage) binding.newElementFab.setOnClickListener { view ->
// appBarConfiguration = AppBarConfiguration(navController.graph) val currentPosition = binding.tabTablayout.selectedTabPosition
// setupActionBarWithNavController(navController, appBarConfiguration) val fragment = adapter.getItem(currentPosition)
binding.addProductFab.setOnClickListener { view -> when (fragment::class.simpleName.toString()) {
"StorageFragment" -> {
val addAbstractProductIntent = Intent(this, AddAbstractProductActivity::class.java)
val extras = Bundle()
// I reuse the same stuff for editing and adding new product.
// if abstractProduct == null, it means that we need to create new object
extras.putParcelable("abstractProduct", null)
addAbstractProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addAbstractProductIntent, extras)
}
"CategoriesFragment" -> {
val addCategoryIntent = Intent(this, AddCategoryActivity::class.java)
val extras = Bundle()
extras.putParcelable("category", Category(0, "New category"))
addCategoryIntent.putExtras(extras)
ContextCompat.startActivity(this, addCategoryIntent, extras)
}
"ShelfFragment" -> {
val addProductIntent = Intent(this, AddProductActivity::class.java) val addProductIntent = Intent(this, AddProductActivity::class.java)
val extras = Bundle() val extras = Bundle()
extras.putParcelable("product", null)
addProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addProductIntent, extras) ContextCompat.startActivity(this, addProductIntent, extras)
} }
} }
}
}
private fun setupViewPager(viewpager: ViewPager) { private fun setupViewPager(viewpager: ViewPager) {
adapter = ViewPagerAdapter(supportFragmentManager) adapter = ViewPagerAdapter(supportFragmentManager)
adapter.addFragment(CategoriesFragment(), "Categories") adapter.addFragment(CategoriesFragment(), "Categories")
adapter.addFragment(StorageFragment(), "Storage") adapter.addFragment(StorageFragment(), "Storage")
//TODO: shelf and settings fragments adapter.addFragment(ShelfFragment(), "Shelf")
//TODO: settings fragments
// setting adapter to view pager. // setting adapter to view pager.
viewpager.setAdapter(adapter) viewpager.setAdapter(adapter)
@@ -63,43 +84,71 @@ class MainActivity : AppCompatActivity() {
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_settings -> {
true
}
R.id.action_delete -> {
// if there's no selected items, write a toast about it, otherwise delete those items.
val currentPosition = binding.tabTablayout.selectedTabPosition val currentPosition = binding.tabTablayout.selectedTabPosition
val fragment = adapter.getItem(currentPosition) val fragment = adapter.getItem(currentPosition)
return when (item.itemId) {
R.id.action_settings -> {
true
}
R.id.action_delete -> {
when (fragment::class.simpleName.toString()) {
"StorageFragment" -> {
AlertDialog.Builder(this)
.setMessage("Deleting an abstract product will also delete ALL the products, that belong to it. Do you want to proceed?")
.setPositiveButton("Yes") { _: DialogInterface, _: Int ->
val storageFragment = fragment as StorageFragment
storageFragment.removeSelected()
}
.setNegativeButton("No") { _: DialogInterface, _: Int ->
}.show()
}
"CategoriesFragment" -> {
AlertDialog.Builder(this)
.setMessage("Deleting a category will also delete ALL the products, that belong to that category. Do you want to proceed?")
.setPositiveButton("Yes") { _: DialogInterface, _: Int ->
val categoriesFragment = fragment as CategoriesFragment
categoriesFragment.removeSelected()
}
.setNegativeButton("No") { _: DialogInterface, _: Int ->
}.show()
}
"ShelfFragment" -> {
val shelfFragment = fragment as ShelfFragment
shelfFragment.removeSelected()
}
}
true
}
R.id.action_update -> {
when (fragment::class.simpleName.toString()) { when (fragment::class.simpleName.toString()) {
"StorageFragment" -> { "StorageFragment" -> {
val storageFragment = fragment as StorageFragment val storageFragment = fragment as StorageFragment
storageFragment.removeSelected() storageFragment.updateSelected()
// storageFragment.updateContent() }
"CategoriesFragment" -> {
val categoriesFragment = fragment as CategoriesFragment
categoriesFragment.updateSelected()
}
"ShelfFragment" -> {
val shelfFragment = fragment as ShelfFragment
shelfFragment.updateSelected()
} }
} }
// val storageFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_content_storage)?.childFragmentManager!!.fragments[0] as StorageFragment
// val amountOfViews = storageFragment.view?.findViewById<GridLayout>(R.id.contentGridLayout)?.childCount
//
// for (view: AbstractProductView in storageFragment.view?.findViewById<GridLayout>(R.id.contentGridLayout)?.children!!.iterator() as Iterator<AbstractProductView>) {
// val db = DBStorageController(this)
// if (view.isProductSelected) {
// db.eraseAbstractProduct(db.writableDatabase , view.product.id, this)
// Log.d("QWERTYUIOP", "Removing ${view.product.id}")
// }
// }
// storageFragment.updateContent()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
// override fun onSupportNavigateUp(): Boolean {
// val navController = findNavController(R.id.nav_host_fragment_content_storage)
// return navController.navigateUp(appBarConfiguration)
// || super.onSupportNavigateUp()
// }
} }

View File

@@ -0,0 +1,58 @@
package org.foxarmy.barcodescannerforemployees.dataclasses
import android.os.Parcel
import android.os.Parcelable
class AbstractProduct() : Parcelable {
var id: Int = 0
var barcode: String = ""
var name: String = ""
var netWeight: Double = 0.0
var imageHash: String = ""
var category: Int = 0
var unit: Int = 0
constructor(id: Int, barcode: String, name: String, netWeight: Double, imageHash: String, category: Int, unit: Int) : this() {
this.id = id
this.barcode = barcode
this.name = name
this.netWeight = netWeight
this.imageHash = imageHash
this.category = category
this.unit = unit
}
constructor(parcel: Parcel) : this() {
id = parcel.readInt()
barcode = parcel.readString()!!
name = parcel.readString()!!
netWeight = parcel.readDouble()
imageHash = parcel.readString()!!
category = parcel.readInt()
unit = parcel.readInt()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeString(barcode)
parcel.writeString(name)
parcel.writeDouble(netWeight)
parcel.writeString(imageHash)
parcel.writeInt(category)
parcel.writeInt(unit)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<AbstractProduct> {
override fun createFromParcel(parcel: Parcel): AbstractProduct {
return AbstractProduct(parcel)
}
override fun newArray(size: Int): Array<AbstractProduct?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -0,0 +1,39 @@
package org.foxarmy.barcodescannerforemployees.dataclasses
import android.os.Parcel
import android.os.Parcelable
class Category() : Parcelable {
var id = 0
var name = ""
constructor(id: Int, name: String) : this() {
this.id = id
this.name = name
}
constructor(parcel: Parcel) : this() {
id = parcel.readInt()
name = parcel.readString()!!
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeString(name)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Category> {
override fun createFromParcel(parcel: Parcel): Category {
return Category(parcel)
}
override fun newArray(size: Int): Array<Category?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -0,0 +1,58 @@
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
var abstractProductId = 0
var amount = 0
var dateOfProduction: Long = 0
var dateOfExpiry: Long = 0
var freshness: Double = 0.0
@RequiresApi(Build.VERSION_CODES.O)
constructor(id: Int, abstractProductId: Int, amount: Int, dateOfProduction: Long, dateOfExpiry: Long) : 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() {
id = parcel.readInt()
abstractProductId = parcel.readInt()
amount = parcel.readInt()
dateOfProduction = parcel.readLong()
dateOfExpiry = parcel.readLong()
freshness = parcel.readDouble()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeInt(abstractProductId)
parcel.writeInt(amount)
parcel.writeLong(dateOfProduction)
parcel.writeLong(dateOfExpiry)
parcel.writeDouble(freshness)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Product> {
override fun createFromParcel(parcel: Parcel): Product {
return Product(parcel)
}
override fun newArray(size: Int): Array<Product?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -0,0 +1,21 @@
package org.foxarmy.barcodescannerforemployees.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.FragmentAddAbstractProductBinding
class AddAbstractProductFragment : Fragment() {
private lateinit var binding: FragmentAddAbstractProductBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentAddAbstractProductBinding.inflate(layoutInflater)
return inflater.inflate(R.layout.fragment_add_abstract_product, container, false)
}
}

View File

@@ -1,29 +1,20 @@
package org.foxarmy.barcodescannerforemployees.fragments package org.foxarmy.barcodescannerforemployees.fragments
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.FragmentAddProductBinding import org.foxarmy.barcodescannerforemployees.databinding.FragmentAddProductBinding
import java.io.File
class AddProductFragment : Fragment() { class AddProductFragment : Fragment() {
private lateinit var binding: FragmentAddProductBinding private lateinit var binding: FragmentAddProductBinding
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
Log.d("QWERTYUIOP", "хуета1")
binding = FragmentAddProductBinding.inflate(layoutInflater) binding = FragmentAddProductBinding.inflate(layoutInflater)
return inflater.inflate(R.layout.fragment_add_product, container, false) return inflater.inflate(R.layout.fragment_add_product, container, false)
} }

View File

@@ -1,70 +1,97 @@
package org.foxarmy.barcodescannerforemployees.fragments package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.provider.BaseColumns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.CategoriesContract
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.R 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() { class CategoriesFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_categories, container, false) return inflater.inflate(R.layout.fragment_categories, container, false)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// updateContent() updateContent()
} }
// public fun updateContent() { fun removeSelected() {
// val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
// val grv = getView()?.findViewById<GridLayout>(R.id.contentGridLayout)
// grv?.removeAllViews() val db = DBStorageController(requireContext())
// var deleted = false
// val db = DBStorageController(requireContext()).readableDatabase for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) {
// val projection = arrayOf( if (view.isCategorySelected) {
// BaseColumns._ID, db.eraseCategory(db.writableDatabase, view.category.id, requireContext())
// ProductContract.ProductEntry.PRODUCT_NAME, deleted = true
// ProductContract.ProductEntry.PRODUCT_NET_WEIGHT, }
// ProductContract.ProductEntry.IMAGE_FILENAME }
// )
// if (!deleted) {
// val cursor = db.query(ProductContract.ProductEntry.TABLE_NAME, projection, null, null, null, null, null) Toast.makeText(requireContext(), "Nothing to delete", Toast.LENGTH_SHORT).show()
// }
// with (cursor) { updateContent()
// while(moveToNext()) { }
// val productId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
// val productName = getString(getColumnIndexOrThrow(ProductContract.ProductEntry.PRODUCT_NAME)) fun updateSelected() {
// val netWeight = getDouble(getColumnIndexOrThrow(ProductContract.ProductEntry.PRODUCT_NET_WEIGHT)) val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
// val productImageHash = getString(getColumnIndexOrThrow(ProductContract.ProductEntry.IMAGE_FILENAME)) for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) {
// if (view.isCategorySelected) {
// val product = AbstractProduct(productId, productName, netWeight, productImageHash, 1) val addCategoryIntent = Intent(context, AddCategoryActivity::class.java)
// val extras = Bundle()
// generateThumbnailForImage(context!!, productImageHash) extras.putParcelable("category", view.category)
// addCategoryIntent.putExtras(extras)
// val abstractProduct = AbstractProductView( ContextCompat.startActivity(context!!, addCategoryIntent, extras)
// requireActivity(), }
// requireContext(), }
// product }
// )
// grv?.addView(abstractProduct) fun updateContent() {
// } val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
// } layout?.removeAllViews()
// }
val db = DBStorageController(requireContext()).readableDatabase
val projection = arrayOf(
BaseColumns._ID,
CategoriesContract.CategoryEntry.CATEGORY_NAME
)
val cursor = db.query(CategoriesContract.CategoryEntry.TABLE_NAME, projection, null, null, null, null, null)
with (cursor) {
while(moveToNext()) {
val categoryId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val categoryName = getString(getColumnIndexOrThrow(CategoriesContract.CategoryEntry.CATEGORY_NAME))
val category = Category(categoryId, categoryName)
val categoryView = CategoryView(requireActivity(), requireContext(), category)
layout?.addView(categoryView)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// updateContent() updateContent()
} }
} }

View File

@@ -0,0 +1,209 @@
package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Intent
import android.os.Bundle
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.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.ProductContract
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.AddProductActivity
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 {
binding = FragmentShelfBinding.inflate(layoutInflater)
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()
}
}
updateContent()
return binding.root
}
fun updateSelected() {
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
var updated = false
for (view: ProductView in grv?.children!!.iterator() as Iterator<ProductView>) {
if (view.isProductSelected) {
val addProductIntent = Intent(requireContext(), AddProductActivity::class.java)
val extras = Bundle()
extras.putParcelable("product", view.product)
addProductIntent.putExtras(extras)
ContextCompat.startActivity(requireContext(), addProductIntent, extras)
updated = true
}
}
if (!updated) {
Toast.makeText(requireContext(), "Nothing to update", Toast.LENGTH_SHORT).show()
}
updateContent()
}
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() {
super.onResume()
updateContent()
}
fun updateContent() {
thread {
if (updateInProgress) return@thread
updateInProgress = true
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
activity!!.runOnUiThread {
grv?.removeAllViews()
}
val db = DBStorageController(requireContext()).readableDatabase
val projection = arrayOf(
BaseColumns._ID,
ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID,
ProductContract.ProductEntry.AMOUNT,
ProductContract.ProductEntry.DATE_OF_PRODUCTION,
ProductContract.ProductEntry.EXPIRY_DATE,
)
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<Product>()
with(cursor) {
while (moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val abstractProductId =
getInt(getColumnIndexOrThrow(ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID))
val amount = getInt(getColumnIndexOrThrow(ProductContract.ProductEntry.AMOUNT))
val dateOfProduction =
getLong(getColumnIndexOrThrow(ProductContract.ProductEntry.DATE_OF_PRODUCTION))
val dateOfExpiry = getLong(getColumnIndexOrThrow(ProductContract.ProductEntry.EXPIRY_DATE))
val product = Product(productId, abstractProductId, amount, dateOfProduction, dateOfExpiry)
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
}
}
fun removeSelected() {
thread {
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
val db = DBStorageController(requireContext())
var deleted = false
for (view: ProductView in grv?.children!!.iterator() as Iterator<ProductView>) {
activity!!.runOnUiThread {
view.findViewById<ImageView>(R.id.productPicture).setImageURI(null)
}
if (view.isProductSelected) {
db.eraseProduct(db.writableDatabase, view.product.id)
deleted = true
}
}
if (!deleted) {
activity!!.runOnUiThread {
Toast.makeText(requireContext(), "Nothing to delete", Toast.LENGTH_SHORT).show()
}
}
updateContent()
}
}
}

View File

@@ -1,37 +1,52 @@
package org.foxarmy.barcodescannerforemployees.fragments package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.provider.BaseColumns import android.provider.BaseColumns
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.gridlayout.widget.GridLayout import org.foxarmy.barcodescannerforemployees.AbstractProductContract
import org.foxarmy.barcodescannerforemployees.* 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 import org.foxarmy.barcodescannerforemployees.views.AbstractProductView
import kotlin.concurrent.thread
/**
* A simple [Fragment] subclass.
* Use the [StorageFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class StorageFragment : Fragment() { class StorageFragment : Fragment() {
public var test_value = 1 private lateinit var binding: FragmentStorageBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
// Inflate the layout for this fragment binding = FragmentStorageBinding.inflate(inflater)
return inflater.inflate(R.layout.fragment_storage, container, false)
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() { override fun onResume() {
@@ -40,45 +55,95 @@ class StorageFragment : Fragment() {
updateContent() updateContent()
} }
private fun fillUpSortBySpinner() {
val sorts = mutableListOf("Name", "Category")
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() { fun removeSelected() {
val grv = getView()?.findViewById<GridLayout>(R.id.contentGridLayout) thread {
val grv = binding.contentGridLayout
val db = DBStorageController(requireContext()) val db = DBStorageController(requireContext())
var deleted = false
for (view: AbstractProductView in grv?.children!!.iterator() as Iterator<AbstractProductView>) { for (view: AbstractProductView in grv.children.iterator() as Iterator<AbstractProductView>) {
activity!!.runOnUiThread {
view.findViewById<ImageView>(R.id.productPicture).setImageURI(null) view.findViewById<ImageView>(R.id.productPicture).setImageURI(null)
}
if (view.isProductSelected) { if (view.isProductSelected) {
db.eraseAbstractProduct(db.writableDatabase, view.product.id, requireContext()) db.eraseAbstractProduct(db.writableDatabase, view.abstractProduct.id, requireContext())
Log.d("QWERTYUIOP", "Removing ${view.product.id}") deleted = true
} else { }
Log.d("QWERTYUIOP", "Not ${view.product.id}") }
if (!deleted) {
activity!!.runOnUiThread {
Toast.makeText(requireContext(), "Nothing to delete", Toast.LENGTH_SHORT).show()
} }
} }
updateContent() updateContent()
} }
}
fun updateSelected() {
val grv = binding.contentGridLayout
for (view: AbstractProductView in grv.children.iterator() as Iterator<AbstractProductView>) {
if (view.isProductSelected) {
val addProductIntent = Intent(requireContext(), AddAbstractProductActivity::class.java)
val extras = Bundle()
extras.putParcelable("abstractProduct", view.abstractProduct)
addProductIntent.putExtras(extras)
ContextCompat.startActivity(requireContext(), addProductIntent, extras)
}
}
}
fun updateContent() { fun updateContent() {
thread {
val grv = getView()?.findViewById<GridLayout>(R.id.contentGridLayout) val grv = binding.contentGridLayout
grv?.removeAllViews() activity!!.runOnUiThread{
grv.removeAllViews()
}
val db = DBStorageController(requireContext()).readableDatabase val db = DBStorageController(requireContext()).readableDatabase
val projection = arrayOf(BaseColumns._ID, val projection = arrayOf(
ProductContract.ProductEntry.PRODUCT_NAME, BaseColumns._ID,
ProductContract.ProductEntry.PRODUCT_NET_WEIGHT, AbstractProductContract.AbstractProductEntry.BARCODE,
ProductContract.ProductEntry.IMAGE_FILENAME AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.CATEGORY,
AbstractProductContract.AbstractProductEntry.UNIT
) )
val cursor = db.query(ProductContract.ProductEntry.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) { with (cursor) {
while(moveToNext()) { while(moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(BaseColumns._ID)) val productId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val productName = getString(getColumnIndexOrThrow(ProductContract.ProductEntry.PRODUCT_NAME)) val barcode = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.BARCODE))
val netWeight = getDouble(getColumnIndexOrThrow(ProductContract.ProductEntry.PRODUCT_NET_WEIGHT)) val productName = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val productImageHash = getString(getColumnIndexOrThrow(ProductContract.ProductEntry.IMAGE_FILENAME)) val netWeight = getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val productImageHash = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val category = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.CATEGORY))
val unit = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
val product = AbstractProduct(productId, productName, netWeight, productImageHash, 1) val product = AbstractProduct(productId, barcode, productName, netWeight, productImageHash, category, unit)
generateThumbnailForImage(context!!, productImageHash) generateThumbnailForImage(context!!, productImageHash)
@@ -87,10 +152,14 @@ class StorageFragment : Fragment() {
requireContext(), requireContext(),
product product
) )
grv?.addView(abstractProduct)
activity!!.runOnUiThread{
grv.addView(abstractProduct)
} }
} }
} }
}.join()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@@ -1,4 +0,0 @@
package org.foxarmy.barcodescannerforemployees
import android.provider.BaseColumns

View File

@@ -4,7 +4,7 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
@@ -12,29 +12,38 @@ import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import org.foxarmy.barcodescannerforemployees.AbstractProduct import org.foxarmy.barcodescannerforemployees.*
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity
import org.foxarmy.barcodescannerforemployees.getImageUri import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import java.io.File import java.io.File
class AbstractProductView: LinearLayout { class AbstractProductView: LinearLayout {
private var productLayout: ConstraintLayout private var productLayout: ConstraintLayout? = null
private var productPicture: ImageView private var productPicture: ImageView? = null
private var productNameField: TextView private var productNameField: TextView? = null
private var netWeightField: TextView private var netWeightField: TextView? = null
private var categoryField: TextView private var categoryField: TextView? = null
private var unitField: TextView private var unitField: TextView? = null
public var product: AbstractProduct var abstractProduct: AbstractProduct = AbstractProduct()
var isProductSelected = false var isProductSelected = false
private lateinit var activity: Activity
constructor(activity: Activity, context: Context, product: AbstractProduct) : super(context) { constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!!
this.product = product val inflater:LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.abstract_product_view, this)
}
constructor(activity: Activity, context: Context, abstractProduct: AbstractProduct) : super(context) {
this.abstractProduct = abstractProduct
this.activity = activity
val inflater:LayoutInflater = activity.layoutInflater val inflater:LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.abstract_product_view, this) inflater.inflate(R.layout.abstract_product_view, this)
update()
}
fun init () {
productLayout = findViewById(R.id.productLayout) productLayout = findViewById(R.id.productLayout)
productPicture = findViewById(R.id.productPicture) productPicture = findViewById(R.id.productPicture)
productNameField = findViewById(R.id.productNameView) productNameField = findViewById(R.id.productNameView)
@@ -42,34 +51,39 @@ class AbstractProductView: LinearLayout {
categoryField = findViewById(R.id.categoryView) categoryField = findViewById(R.id.categoryView)
unitField = findViewById(R.id.unitView) unitField = findViewById(R.id.unitView)
val thumbnailsDir = File(context.cacheDir, "thumbnails")
thumbnailsDir.mkdirs() productPicture!!.setOnClickListener {
val imageUri = getImageUri(activity, File(thumbnailsDir, "${product.imageHash}.webp"))
productPicture.setImageURI(imageUri)
productPicture.rotation = 90f
productPicture.setOnClickListener {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java) val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle() val extras = Bundle()
extras.putString("imagehash", product.imageHash) extras.putString("imagehash", abstractProduct.imageHash)
fullscreenIntent.putExtras(extras) fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras) startActivity(context, fullscreenIntent, extras)
} }
productLayout.setOnClickListener { productLayout!!.setOnClickListener {
} }
productNameField.text = product.name unitField?.text = getUnitNameById(context, abstractProduct.unit)
netWeightField.text = product.netWeight.toString()
//TODO: category and units productLayout!!.setOnLongClickListener {
productLayout.setOnLongClickListener {
isProductSelected = !isProductSelected isProductSelected = !isProductSelected
Log.d("QWERTYUIOP", "Changed to value $isProductSelected")
this.background = ContextCompat.getDrawable(context, if (isProductSelected) R.drawable.outline_selected else R.drawable.outline) this.background = ContextCompat.getDrawable(context, if (isProductSelected) R.drawable.outline_selected else R.drawable.outline)
true true
} }
} }
fun update() {
init()
val thumbnailsDir = File(context.cacheDir, "thumbnails")
thumbnailsDir.mkdirs()
val imageUri = getImageUri(activity, File(thumbnailsDir, "${abstractProduct.imageHash}.webp"))
productPicture!!.setImageURI(imageUri)
productPicture!!.rotation = 90f
productNameField!!.text = abstractProduct.name
netWeightField!!.text = abstractProduct.netWeight.toString()
categoryField!!.text = DBStorageController(context).getCategoryNameById(DBStorageController(context).readableDatabase, abstractProduct.category)
}
} }

View File

@@ -0,0 +1,39 @@
package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.R
class CategoryView : LinearLayout {
var category: Category
val categoryName: TextView
val amountOfProducts: TextView
var isCategorySelected = false
constructor(activity: Activity, context: Context, category: Category) : super(context) {
this.category = category
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.category_view, this)
this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline)
categoryName = findViewById(R.id.categoryNameTextView)
amountOfProducts = findViewById(R.id.amountOfProducts)
categoryName.text = category.name
amountOfProducts.text = DBStorageController(context).getAmountOfAbstractProductsInCategory(DBStorageController(context).readableDatabase, category.id).toString()
setOnLongClickListener {
isCategorySelected = !isCategorySelected
this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline)
true
}
}
}

View File

@@ -0,0 +1,176 @@
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.view.LayoutInflater
import android.widget.ImageView
import android.widget.LinearLayout
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.*
import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import java.io.File
import java.text.DecimalFormat
import java.text.SimpleDateFormat
import java.util.*
import kotlin.concurrent.thread
class ProductView: LinearLayout {
var product: Product = Product()
var isProductSelected = false
private lateinit var activity: Activity
private lateinit var productImageView: ImageView
private lateinit var productNameTextView: TextView
private lateinit var productNetWeightTextView: TextView
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
constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!!
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.product_view, this)
}
@RequiresApi(Build.VERSION_CODES.O)
constructor(activity: Activity, context: Context, product: Product) : super(context) {
this.product = product
this.activity = activity
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.product_view, this)
productImageView = findViewById(R.id.productPicture)
productNameTextView = findViewById(R.id.productNameView)
productNetWeightTextView = findViewById(R.id.productNetWeightView)
productAmountTextView = findViewById(R.id.amountView)
productCategoryView = findViewById(R.id.categoryView)
productLifeSpan = findViewById(R.id.dateSpan)
productFreshnessTextView = findViewById(R.id.freshnessPercentTextView)
findViewById<ConstraintLayout>(R.id.productLayout).setOnLongClickListener {
isProductSelected = !isProductSelected
updateStroke()
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()
}
@RequiresApi(Build.VERSION_CODES.O)
fun update () {
val linkedAbstractProduct: AbstractProduct = DBStorageController(activity).findAbstractProductById(DBStorageController(activity).readableDatabase, product.abstractProductId)!!
val thumbnailsDir = File(activity.cacheDir, "thumbnails")
thumbnailsDir.mkdirs()
val pictureFile = File(thumbnailsDir, "${linkedAbstractProduct.imageHash}.webp")
val imageUri = getImageUri(activity, pictureFile)
productImageView.setImageURI(imageUri)
productImageView.rotation = 90f
productNameTextView.text = linkedAbstractProduct.name
productNetWeightTextView.text = linkedAbstractProduct.netWeight.toString()
productAmountTextView.text = product.amount.toString()
productCategoryView.text = DBStorageController(activity).getCategoryNameById(DBStorageController(activity).readableDatabase, linkedAbstractProduct.category)
productLifeSpan.text = "${SimpleDateFormat("dd.MM.yyyy").format(Date(product.dateOfProduction*1000))}-${SimpleDateFormat("dd.MM.yyyy").format(Date(product.dateOfExpiry*1000))}"
productFreshnessTextView.text =
if (product.freshness == Double.NEGATIVE_INFINITY || product.freshness == Double.POSITIVE_INFINITY) {
"Expired"
} else {
"${DecimalFormat("#.#").format(product.freshness*100)}%"
}
updateStroke()
}
@RequiresApi(Build.VERSION_CODES.O)
fun updateStroke() {
if (isProductSelected) {
this.background = ContextCompat.getDrawable(context, R.drawable.outline_selected)
} else {
if (product.id != 0) {
thread {
this.outline = GradientDrawable()
backgroundColor = evaluateColor()
strokeColor = darkenColor(backgroundColor, 0.25) // (backgroundColor and 0xfefefe ) shr 1
this.outline.setColor(backgroundColor)
this.outline.setStroke(4, strokeColor)
this.outline.alpha = 84
this.background = outline
}
} else {
this.background = ContextCompat.getDrawable(context, R.drawable.outline)
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
fun evaluateColor(): Int {
val freshnessPercentage = calculateProductFreshness(product.dateOfProduction, product.dateOfExpiry)
return calculateFreshnessGradient(freshnessPercentage)
}
fun calculateFreshnessGradient(percentage: Double): Int {
val startColor = ContextCompat.getColor(context, if (percentage > 0.5) R.color.full_freshness else R.color.half_freshness)
val endColor = ContextCompat.getColor(context, if (percentage > 0.5) R.color.half_freshness else R.color.expired_freshness)
val gradientPosition = 1 - if (percentage > 0.5) (percentage - 0.5) * 2 else percentage * 2
val red = clamp((startColor.red + gradientPosition * (endColor.red - startColor.red )).toInt(), 0, 255)
val green = clamp((startColor.green + gradientPosition * (endColor.green - startColor.green)).toInt(), 0, 255)
val blue = clamp((startColor.blue + gradientPosition * (endColor.blue - startColor.blue)).toInt(), 0, 255)
var redHex = Integer.toHexString(red)
if (redHex.length == 1) redHex = "0$redHex"
var greenHex = Integer.toHexString(green)
if (greenHex.length == 1) greenHex = "0$greenHex"
var blueHex = Integer.toHexString(blue)
if (blueHex.length == 1 ) blueHex = "0$blueHex"
val colorString = "#$redHex$greenHex$blueHex"
return Color.parseColor(colorString)
}
@RequiresApi(Build.VERSION_CODES.O)
fun darkenColor(color: Int, darkPercent: Double) : Int {
val c = Color.valueOf(color)
val red = c.red() * (1 - darkPercent)
val green = c.green() * (1 - darkPercent)
val blue = c.blue() * (1 - darkPercent)
return Color.rgb(red.toFloat(), green.toFloat(), blue.toFloat())
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@android:color/transparent" /> <solid android:color="@android:color/transparent" />
<stroke android:width="1dip" android:color="#28283f"/> <stroke android:width="3dip" android:color="#28283f" android:id="@+id/test"/>
<padding android:bottom="1dp" android:top="1dp" android:left="1dp" android:right="1dp" /> <padding android:bottom="1dp" android:top="1dp" android:left="1dp" android:right="1dp" />
</shape> </shape>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@android:color/transparent" /> <solid android:color="@android:color/transparent" />
<stroke android:width="1dip" android:color="#840d15"/> <stroke android:width="3dip" android:color="#840d15"/>
<padding android:bottom="1dp" android:top="1dp" android:left="1dp" android:right="1dp" /> <padding android:bottom="1dp" android:top="1dp" android:left="1dp" android:right="1dp" />
</shape> </shape>

View File

@@ -1,8 +1,8 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="200dp" xmlns:tools="http://schemas.android.com/tools" android:layout_width="190dp"
android:layout_height="wrap_content" app:barrierMargin="1dp" android:clickable="false" android:layout_height="330dp" app:barrierMargin="1dp" android:clickable="false"
android:outlineProvider="background" android:background="@drawable/outline"> android:outlineProvider="background" android:background="@drawable/outline">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@@ -11,7 +11,7 @@
android:background="#00FFFEFE" android:clickable="true"> android:background="#00FFFEFE" android:clickable="true">
<ImageView <ImageView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="200dp" app:srcCompat="@android:drawable/ic_btn_speak_now" android:layout_height="200dp" app:srcCompat="@android:drawable/ic_menu_gallery"
android:id="@+id/productPicture" android:id="@+id/productPicture"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
@@ -45,7 +45,8 @@
android:layout_height="wrap_content" android:id="@+id/categoryView" android:layout_height="wrap_content" android:id="@+id/categoryView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="25dp" android:fontFamily="monospace" android:textSize="10sp" android:layout_marginEnd="25dp" android:fontFamily="monospace" android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="20dp"/> app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="20dp"
app:layout_constraintTop_toBottomOf="@+id/productNameView" android:layout_marginTop="10dp"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.AddAbstractProductFragment">
<include layout="@layout/content_add_abstract_product" android:id="@+id/include_content"/>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.AddCategoryActivity">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text"
android:text="@string/sample_category"
android:ems="10"
android:id="@+id/newCategoryName" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"/>
<Button
android:text="@string/saveButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/saveButton"
app:layout_constraintTop_toBottomOf="@+id/newCategoryName" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="40dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -43,7 +43,7 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_product_fab" android:id="@+id/new_element_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="@string/sample_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/categoryNameTextView" android:layout_weight="1"
android:textSize="25sp"/>
<TextView
android:text="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/amountOfProducts" android:layout_weight="1"
android:textAlignment="viewEnd" android:textSize="25sp"/>
</LinearLayout>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/addAbstractProductLayout">
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" app:navGraph="@navigation/nav_graph_add_abstract_product"
app:defaultNavHost="true" android:id="@+id/fragmentContainerView"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.AddAbstractProductFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp">
<Button
android:id="@+id/scan_button"
android:layout_width="100dp"
android:layout_height="50dp"
android:text="@string/scan_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/categoryTextView"
android:layout_marginTop="15dp"/>
<ImageView
android:src="@android:drawable/ic_menu_camera"
android:layout_width="356dp"
android:layout_height="303dp" android:id="@+id/imageView"
android:layout_marginBottom="25dp"
app:layout_constraintBottom_toTopOf="@+id/productName" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="15dp"/>
<EditText
android:layout_width="350dp"
android:layout_height="50dp"
android:inputType="text"
android:ems="10"
android:id="@+id/productName"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp" android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/netWeight"
app:layout_constraintHorizontal_bias="0.5"
android:visibility="visible" android:hint="@string/product_name_label" android:textColorHint="#737373"
android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@+id/barcodeTextEdit"/>
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
android:inputType="text"
android:ems="10"
android:id="@+id/netWeight"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
android:visibility="visible" android:hint="@string/netWeight" android:textColorHint="#737373"
app:layout_constraintTop_toBottomOf="@+id/productName"
/>
<Spinner
android:layout_width="130dp"
android:layout_height="50dp" android:id="@+id/unitTypeSpinner"
app:layout_constraintStart_toEndOf="@+id/netWeight"
app:layout_constraintTop_toBottomOf="@+id/productName" app:layout_constraintEnd_toEndOf="parent"/>
<EditText
android:layout_width="350dp"
android:layout_height="wrap_content"
android:inputType="text"
android:ems="10"
android:id="@+id/barcodeTextEdit" app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="8dp" android:hint="Barcode" android:textColorHint="#737373"/>
<TextView
android:text="@string/category"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/categoryTextView"
app:layout_constraintTop_toBottomOf="@+id/netWeight"
android:layout_marginTop="20dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"/>
<Spinner
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/categorySpinner"
app:layout_constraintStart_toEndOf="@+id/categoryTextView"
app:layout_constraintTop_toBottomOf="@+id/netWeight" android:layout_marginStart="8dp"
android:layout_marginTop="18dp"/>
<Button
android:text="@string/saveButton"
android:layout_width="100dp"
android:layout_height="50dp" android:id="@+id/saveButton"
app:layout_constraintTop_toBottomOf="@+id/categoryTextView"
android:layout_marginTop="15dp" app:layout_constraintEnd_toEndOf="parent"/>
<Button
android:text="@string/takePicture"
android:layout_width="100dp"
android:layout_height="55dp" android:id="@+id/takePictureButton"
app:layout_constraintTop_toBottomOf="@+id/categoryTextView"
android:layout_marginTop="15dp" app:layout_constraintStart_toEndOf="@+id/scan_button"
android:layout_marginStart="33dp" app:layout_constraintEnd_toStartOf="@+id/saveButton"
android:layout_marginEnd="6dp" app:layout_constraintHorizontal_bias="0.0"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -9,62 +9,95 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp"> android:padding="16dp" android:layout_gravity="center_horizontal" android:id="@+id/addProductConstraintLayout">
<org.foxarmy.barcodescannerforemployees.views.AbstractProductView
android:layout_width="300dp"
android:layout_height="300dp" android:id="@+id/abstractProductView"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="32dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Button <Button
android:id="@+id/scan_button"
android:layout_width="100dp"
android:layout_height="50dp"
android:text="@string/scan_label" android:text="@string/scan_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/scanButton" android:layout_weight="1"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/netWeight"
android:layout_marginTop="25dp"/>
<ImageView
android:src="@android:drawable/ic_menu_camera"
android:layout_width="356dp"
android:layout_height="303dp" android:id="@+id/imageView"
android:layout_marginBottom="25dp"
app:layout_constraintBottom_toTopOf="@+id/productName" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="15dp"/>
<EditText
android:layout_width="350dp"
android:layout_height="50dp"
android:inputType="text"
android:ems="10"
android:id="@+id/productName"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp" android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/netWeight" app:layout_constraintTop_toBottomOf="@+id/abstractProductView" android:layout_marginTop="16dp"/>
app:layout_constraintHorizontal_bias="0.5" <TextView
android:visibility="visible" android:hint="@string/product_name_label" android:textColorHint="#737373" android:text="Date of production:"
app:layout_constraintTop_toBottomOf="@+id/imageView"/> android:layout_width="wrap_content"
<EditText android:layout_height="wrap_content" android:id="@+id/dateOfProductionTextView"
android:layout_width="350dp" app:layout_constraintTop_toBottomOf="@+id/scanButton"
android:layout_height="50dp" android:layout_marginTop="16dp" app:layout_constraintStart_toStartOf="parent"/>
android:inputType="text" <Button
android:ems="10" android:text="Select"
android:id="@+id/netWeight" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/selectDateOfProductionButton"
app:layout_constraintTop_toBottomOf="@+id/scanButton"
app:layout_constraintStart_toEndOf="@+id/dateOfProductionTextView"
app:layout_constraintEnd_toEndOf="parent"/>
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@+id/selectDateOfProductionButton"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="16dp"
android:layout_marginEnd="8dp" android:orientation="horizontal" android:id="@+id/radioGroup">
app:layout_constraintEnd_toEndOf="parent" <RadioButton
android:visibility="visible" android:hint="@string/netWeight" android:textColorHint="#737373" android:text="Expiry date"
app:layout_constraintTop_toBottomOf="@+id/productName"/> android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/expiryDateRadio"/>
<RadioButton
android:text="Shelf life"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/shelfLifeRadio"/>
</RadioGroup>
<TextView
android:text="Expiry date: "
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/expiryDateTextView"
app:layout_constraintTop_toBottomOf="@+id/radioGroup" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:visibility="invisible"/>
<Button
android:text="Select"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/selectExpiryDateButton"
app:layout_constraintTop_toBottomOf="@+id/radioGroup"
app:layout_constraintStart_toEndOf="@+id/expiryDateTextView" android:layout_marginStart="16dp"
android:visibility="invisible"/>
<TextView
android:text="Shelf life:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/shelfLife"
app:layout_constraintTop_toBottomOf="@+id/radioGroup"
android:layout_marginTop="16dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp" android:visibility="invisible"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:ems="10"
android:id="@+id/shelfLifeTextEdit" app:layout_constraintTop_toBottomOf="@+id/radioGroup"
app:layout_constraintStart_toEndOf="@+id/expiryDateTextView" android:layout_marginStart="16dp"
android:visibility="invisible"/>
<TextView
android:text="Amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/amountText"
app:layout_constraintTop_toBottomOf="@+id/shelfLifeTextEdit"
app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="16dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberSigned"
android:ems="10"
android:id="@+id/amountTextEdit"
app:layout_constraintTop_toBottomOf="@+id/selectExpiryDateButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/amountText"/>
<Button <Button
android:text="@string/saveButton" android:text="@string/saveButton"
android:layout_width="100dp" android:layout_width="wrap_content"
android:layout_height="50dp" android:id="@+id/saveButton" android:layout_height="wrap_content" android:id="@+id/saveProductButton"
app:layout_constraintTop_toBottomOf="@+id/netWeight" app:layout_constraintTop_toBottomOf="@+id/amountTextEdit" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="25dp" app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="16dp"/>
<Button
android:text="@string/takePicture"
android:layout_width="100dp"
android:layout_height="55dp" android:id="@+id/takePictureButton"
app:layout_constraintTop_toBottomOf="@+id/netWeight"
android:layout_marginTop="24dp" app:layout_constraintStart_toEndOf="@+id/scan_button"
android:layout_marginStart="33dp" app:layout_constraintEnd_toStartOf="@+id/saveButton"
android:layout_marginEnd="6dp" app:layout_constraintHorizontal_bias="0.0"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -5,9 +5,9 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/fragment_storage" android:id="@+id/fragment_storage"
tools:context=".fragments.CategoriesFragment"> tools:context=".fragments.CategoriesFragment">
<ScrollView <LinearLayout
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" android:id="@+id/categoriesLayout">
android:id="@+id/categoriesLayout"> </LinearLayout>
</ScrollView>
</FrameLayout> </FrameLayout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="Sort by"
android:layout_width="wrap_content"
android:layout_height="0dp" android:id="@+id/sortByTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/scrollView" android:textAlignment="center"
/>
<Spinner
android:layout_width="0dp"
android:layout_height="32dp" android:id="@+id/spinner"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/sortByTextView"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginStart="16dp"
/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" app:layout_constraintTop_toBottomOf="@+id/spinner"
android:layout_marginTop="32dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" android:id="@+id/scrollView">
<androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/contentGridLayout" app:columnCount="2"
app:rowCount="100" android:nestedScrollingEnabled="true">
</androidx.gridlayout.widget.GridLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@@ -5,9 +5,29 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/fragment_storage" android:id="@+id/fragment_storage"
tools:context=".fragments.StorageFragment"> tools:context=".fragments.StorageFragment">
<ScrollView <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView
android:text="Sort by"
android:layout_width="wrap_content"
android:layout_height="0dp" android:id="@+id/sortByTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="@+id/scrollView2"/>
<Spinner
android:layout_width="0dp"
android:layout_height="32dp" android:id="@+id/spinner"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/sortByTextView"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginStart="16dp"
/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@+id/spinner"
android:id="@+id/scrollView2">
<androidx.gridlayout.widget.GridLayout <androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/contentGridLayout" app:columnCount="2" android:layout_height="wrap_content" android:id="@+id/contentGridLayout" app:columnCount="2"
@@ -15,9 +35,5 @@
</androidx.gridlayout.widget.GridLayout> </androidx.gridlayout.widget.GridLayout>
</ScrollView> </ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/storageLayout">
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical"
android:layout_width="190dp"
android:layout_height="345dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/productLayout" android:outlineProvider="bounds"
android:background="#00FFFEFE" android:clickable="true">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp" app:srcCompat="@android:drawable/ic_menu_gallery"
android:id="@+id/productPicture"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:text="0%"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/freshnessPercentTextView"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="10dp"
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="5dp"/>
<TextView
android:text="@string/sample_product_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/productNameView"
app:layout_constraintTop_toBottomOf="@+id/productPicture"
android:layout_marginTop="15dp" app:layout_constraintStart_toStartOf="parent"
android:fontFamily="monospace" android:textSize="20sp"
android:maxLines="2" android:layout_marginStart="10dp"/>
<TextView
android:text="@string/sample_product_net_weight"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/productNetWeightView" android:layout_weight="1"
app:layout_constraintTop_toBottomOf="@+id/productNameView"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="25dp"
android:layout_marginTop="10dp" android:textSize="12sp" android:fontFamily="monospace"
app:layout_constraintEnd_toStartOf="@+id/unitView" android:layout_marginEnd="2dp"
/>
<TextView
android:text="@string/sample_unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/unitView"
app:layout_constraintStart_toEndOf="@+id/productNetWeightView"
app:layout_constraintTop_toBottomOf="@+id/productNameView" android:layout_marginTop="10dp"
android:fontFamily="monospace" android:textSize="12sp" android:layout_marginStart="8dp"/>
<TextView
android:text="10"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/amountView"
app:layout_constraintStart_toEndOf="@+id/unitView"
app:layout_constraintTop_toBottomOf="@+id/productNameView" android:layout_marginTop="10dp"
android:layout_marginStart="16dp"/>
<TextView
android:text="@string/sample_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/categoryView"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="25dp" android:fontFamily="monospace" android:textSize="10sp"
app:layout_constraintTop_toBottomOf="@+id/amountView" android:layout_marginTop="5dp"/>
<TextView
android:text="01.01.1970 - 01.01.1971"
android:layout_width="wrap_content"
android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@+id/categoryView"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="8dp" app:layout_constraintEnd_toEndOf="parent" android:id="@+id/dateSpan"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@@ -7,4 +7,6 @@
android:orderInCategory="100" android:orderInCategory="100"
app:showAsAction="never"/> app:showAsAction="never"/>
<item android:id="@+id/action_delete" android:title="@string/delete_menu"/> <item android:id="@+id/action_delete" android:title="@string/delete_menu"/>
<item android:id="@+id/action_update" android:title="@string/update_menu"/>
</menu> </menu>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph_add_abstract_product"
app:startDestination="@id/addAbstractProductFragment">
<fragment android:id="@+id/addAbstractProductFragment"
android:name="org.foxarmy.barcodescannerforemployees.fragments.AddAbstractProductFragment"
android:label="activity_add_abstract_product" tools:layout="@layout/fragment_add_abstract_product"/>
</navigation>

View File

@@ -5,6 +5,7 @@
android:id="@+id/nav_graph_add_product" android:id="@+id/nav_graph_add_product"
app:startDestination="@id/addProductFragment"> app:startDestination="@id/addProductFragment">
<fragment android:id="@+id/addProductFragment" android:name="org.foxarmy.barcodescannerforemployees.fragments.AddProductFragment" <fragment android:id="@+id/addProductFragment"
android:label="add_product_fragment" tools:layout="@layout/fragment_add_product"/> android:name="org.foxarmy.barcodescannerforemployees.fragments.AddProductFragment"
android:label="activity_add_product" tools:layout="@layout/fragment_add_product"/>
</navigation> </navigation>

View File

@@ -10,4 +10,8 @@
<color name="light_blue_A200">#FF40C4FF</color> <color name="light_blue_A200">#FF40C4FF</color>
<color name="light_blue_A400">#FF00B0FF</color> <color name="light_blue_A400">#FF00B0FF</color>
<color name="black_overlay">#66000000</color> <color name="black_overlay">#66000000</color>
<color name="full_freshness">#5AFF30</color>
<color name="half_freshness">#FFF200</color>
<color name="expired_freshness">#ff0000</color>
</resources> </resources>

View File

@@ -1,7 +1,6 @@
<resources> <resources>
<string name="app_name">BarcodeScannerForEmployees</string> <string name="app_name">BarcodeScannerForEmployees</string>
<string name="action_settings">Settings</string> <string name="action_settings">Settings</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">Add new product</string> <string name="first_fragment_label">Add new product</string>
<string name="second_fragment_label">Products</string> <string name="second_fragment_label">Products</string>
<string name="scan_label">Scan</string> <string name="scan_label">Scan</string>
@@ -13,8 +12,8 @@
<string name="productName">Product name</string> <string name="productName">Product name</string>
<string name="productType">Product type</string> <string name="productType">Product type</string>
<string name="imageOfAProduct">Image of a product</string> <string name="imageOfAProduct">Image of a product</string>
<string name="sample_product_name">Уззкое название товара</string> <string name="sample_product_name">Sample product name</string>
<string name="sample_product_net_weight">1998</string> <string name="sample_product_net_weight">100</string>
<string name="sample_unit">g</string> <string name="sample_unit">g</string>
<string name="sample_category">Sample category</string> <string name="sample_category">Sample category</string>
<string name="title_activity_fullscreen">FullscreenActivity</string> <string name="title_activity_fullscreen">FullscreenActivity</string>
@@ -22,36 +21,17 @@
<string name="dummy_content">DUMMY\nCONTENT</string> <string name="dummy_content">DUMMY\nCONTENT</string>
<string name="fullscreen_image">Fullscreen image</string> <string name="fullscreen_image">Fullscreen image</string>
<string name="next">Next</string> <string name="next">Next</string>
<string name="delete_menu">Delete item(s)…</string>
<string name="update_menu">Update item</string>
<string name="update">update</string>
<string name="delete">delete</string>
<string name="category">Category</string>
<string name="date_of_production">Date of production</string>
<string name="lorem_ipsum"> <!-- Unit names -->
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris volutpat, dolor id <string name="kilogram">kg</string>
interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus dui nec risus. Maecenas non sodales <string name="gram">g</string>
nisi, vel dictum dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos <string name="liter">l</string>
himenaeos. Suspendisse blandit eleifend diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, <string name="milliliter">ml</string>
imperdiet nisl a, ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n <string name="pieces">pc</string>
Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus egestas, est a
condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed neque. Morbi tellus erat, dapibus ut
sem a, iaculis tincidunt dui. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur et eros
porttitor, ultricies urna vitae, molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl
nec dolor bibendum, vel congue leo egestas.\n\n
Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit amet auctor at,
mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel, molestie quam. Fusce blandit tincidunt
nulla, quis sollicitudin libero facilisis et. Integer interdum nunc ligula, et fermentum metus hendrerit id.
Vestibulum lectus felis, dictum at lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse
sodales nunc ligula, in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
est.\n\n
Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh. Morbi laoreet, tortor
sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui non lorem. Nam mollis ipsum quis auctor
varius. Quisque elementum eu libero sed commodo. In eros nisl, imperdiet vel imperdiet et, scelerisque a mauris.
Pellentesque varius ex nunc, quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non
viverra ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a placerat
diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus convallis.\n\n
Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis
egestas. In volutpat arcu ut felis sagittis, in finibus massa gravida. Pellentesque id tellus orci. Integer
dictum, lorem sed efficitur ullamcorper, libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec
maximus ullamcorper sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus vestibulum. Fusce
dictum libero quis erat maximus, vitae volutpat diam dignissim.
</string>
<string name="delete_menu">Delete item(s)...</string>
</resources> </resources>

View File

@@ -14,8 +14,15 @@ navigationFragmentKtx = "2.8.0"
navigationUiKtx = "2.8.0" navigationUiKtx = "2.8.0"
firebaseCrashlyticsBuildtools = "3.0.2" firebaseCrashlyticsBuildtools = "3.0.2"
gridlayout = "1.0.0" gridlayout = "1.0.0"
activity = "1.9.2"
legacySupportV4 = "1.0.0"
fragment = "1.8.4"
playServicesCodeScanner = "16.1.0"
volley = "1.2.1"
[libraries] [libraries]
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraView" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraView" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraView" } androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraView" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" } barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" }
@@ -29,6 +36,11 @@ androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navi
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" } androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
firebase-crashlytics-buildtools = { group = "com.google.firebase", name = "firebase-crashlytics-buildtools", version.ref = "firebaseCrashlyticsBuildtools" } firebase-crashlytics-buildtools = { group = "com.google.firebase", name = "firebase-crashlytics-buildtools", version.ref = "firebaseCrashlyticsBuildtools" }
androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" } androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" }
androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" }
play-services-code-scanner = { module = "com.google.android.gms:play-services-code-scanner", version.ref = "playServicesCodeScanner" }
volley = { module = "com.android.volley:volley", version.ref = "volley" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }