synchronizing

This commit is contained in:
leca 2024-11-10 12:43:23 +03:00
parent 94d309c491
commit cd299477d4
15 changed files with 608 additions and 42 deletions

View File

@ -5,7 +5,11 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlin.concurrent.thread
class Net {
@ -383,4 +387,190 @@ class Net {
return response
}
fun uploadCategory(groupId: Int, category: Category): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("localId", category.id.toString())
.add("categoryName", category.name)
.add("groupId", groupId.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/category/create")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun updateCategory(groupId: Int, category: Category): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("localId", category.id.toString())
.add("categoryName", category.name)
.add("groupId", groupId.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/category/update")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun uploadProduct(groupId: Int, product: Product): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("localId", product.id.toString())
.add("groupId", groupId.toString())
.add("abstract_product_id", product.abstractProductId.toString())
.add("amount", product.amount.toString())
.add("date_of_production", product.dateOfProduction.toString())
.add("expiry_date", product.dateOfExpiry.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/product/create")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun updateProduct(groupId: Int, product: Product): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("localId", product.id.toString())
.add("groupId", groupId.toString())
.add("abstract_product_id", product.abstractProductId.toString())
.add("amount", product.amount.toString())
.add("date_of_production", product.dateOfProduction.toString())
.add("expiry_date", product.dateOfExpiry.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/product/update")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun synchronize(groupId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/user/synchronize/$groupId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun getProduct(groupId: Int, localId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/product/$groupId/$localId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun getAbstractProduct(groupId: Int, localId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/abstractproduct/$groupId/$localId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun downloadImage(url: String, file: File) {
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val fos = FileOutputStream(file)
response.body?.byteStream()?.use { inputStream ->
fos.use {
inputStream.copyTo(fos)
}
}
}
}.join()
}
}

View File

@ -12,11 +12,19 @@ import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import com.google.firebase.components.BuildConfig
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URLEncoder
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*
fun convertToUnixEpochTimestamp(dateString: String): Long {
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
format.timeZone = TimeZone.getTimeZone("UTC")
val date = format.parse(dateString)
return date!!.time / 1000
}
fun getImageUri(activity: Activity, imageFile: File): Uri? {
return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider", imageFile)
}
@ -60,8 +68,6 @@ fun removeSubstringsFromString(text: String, toRemove: Array<String>): String {
return result
}
fun String.utf8(): String = URLEncoder.encode(this, "UTF-8")
fun getActivity(context: Context?): Activity? {
if (context == null) {
return null
@ -99,3 +105,28 @@ fun getUnitNameById (context: Context, id: Int): String {
fun parseArray(input: String): IntArray {
return input.trim('[', ']').split(",").map { it.trim().toInt() }.toIntArray()
}
fun calculateMd5Hash(file: File): String {
val digest = MessageDigest.getInstance("MD5")
val fis = FileInputStream(file)
val buffer = ByteArray(1024)
var bytesRead = fis.read(buffer)
while (bytesRead != -1) {
digest.update(buffer, 0, bytesRead)
bytesRead = fis.read(buffer)
}
fis.close()
return bytesToHex(digest.digest())
}
fun bytesToHex(bytes: ByteArray): String {
val hexString = StringBuilder()
for (byte in bytes) {
val hex = Integer.toHexString(0xff and byte.toInt())
if (hex.length == 1) {
hexString.append('0')
}
hexString.append(hex)
}
return hexString.toString()
}

View File

@ -154,7 +154,7 @@ class AddAbstractProductActivity : AppCompatActivity() {
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val currentGroup = sharedPreferences.getString("currentGroup", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!
lateinit var response: Response
@ -164,7 +164,7 @@ class AddAbstractProductActivity : AppCompatActivity() {
productName,
netWeight.toString().toDouble(),
pictureFile.nameWithoutExtension,
categorySpinner.selectedItemPosition,
categorySpinner.selectedItemPosition + 1,
unitTypeSpinner.selectedItemPosition
)
val pictureFile = File(File(filesDir, "pictures"), "${abstractProduct!!.imageHash}.png")
@ -269,8 +269,9 @@ class AddAbstractProductActivity : AppCompatActivity() {
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()
val imageHash = calculateMd5Hash(tempfile)
// 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)

View File

@ -8,6 +8,7 @@ import android.widget.EditText
import android.widget.Toast
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
@ -31,7 +32,7 @@ class AddCategoryActivity : Activity() {
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
DAO = CategoryDAO(dbHelper)
val extras = intent.extras
@ -41,6 +42,14 @@ class AddCategoryActivity : Activity() {
categoryNameTextEdit.setText(category!!.name)
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!.toInt()
findViewById<Button>(R.id.saveButton).setOnClickListener {
if (categoryNameTextEdit.text.toString() == "") {
Toast.makeText(this, getString(R.string.category_name_required), Toast.LENGTH_SHORT).show()
@ -48,9 +57,13 @@ class AddCategoryActivity : Activity() {
}
if (category.id == 0) { // Inserting new category
DAO.addCategory(Category(0, categoryNameTextEdit.text.toString()))
val newCategory = Category(0, categoryNameTextEdit.text.toString())
newCategory.id = DAO.addCategory(newCategory).toInt()
net.uploadCategory(currentGroup, newCategory)
} else { // Updating existing category
category.name = categoryNameTextEdit.text.toString()
DAO.updateCategory(category)
net.updateCategory(currentGroup, category)
}
finish()

View File

@ -18,6 +18,8 @@ import androidx.security.crypto.MasterKeys
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions
import okhttp3.Response
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
@ -67,10 +69,11 @@ class AddProductActivity : AppCompatActivity() {
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
productDAO = ProductDAO(dbHelper)
abstractProductDAO = AbstractProductDAO(dbHelper)
scanButton = findViewById(R.id.scanButton)
noBarcodeButton = findViewById(R.id.noBarcodeButton)
abstractProductView = findViewById(R.id.abstractProductView)
@ -137,6 +140,12 @@ class AddProductActivity : AppCompatActivity() {
product!!.amount = text.toInt()
}
val net = Net()
net.language = sharedPreferences.getString("language", "en-US")!!
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
saveProductButton.setOnClickListener {
if (abstractProduct == null) {
Toast.makeText(this, getString(R.string.abstract_product_request), Toast.LENGTH_SHORT).show()
@ -168,10 +177,20 @@ class AddProductActivity : AppCompatActivity() {
return@setOnClickListener
}
val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "")!! == "") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
var response: Response? = null
if (updatingExistentProduct) {
productDAO.updateProduct(product!!)
if (currentGroup > 0) response = net.updateProduct(currentGroup, product!!)
} else {
productDAO.insertNewProduct(product!!)
product!!.id = productDAO.insertNewProduct(product!!).toInt()
if (currentGroup > 0) response = net.uploadProduct(currentGroup, product!!)
}
if (response != null) {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
finish()

View File

@ -57,9 +57,11 @@ class LoginActivity : AppCompatActivity() {
val r = n.getMyGroups().body!!.string()
val myGroups = parseArray(r)
val myGroups = parseArray(r).map { a -> a.toString()}
sharedPreferences.edit().putStringSet("groups", myGroups.toSet()).apply()
sharedPreferences.edit().putString("currentGroup", myGroups[0]).apply()
sharedPreferences.edit().putStringSet("groups", myGroups.map { a -> a.toString()}.toSet())
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()

View File

@ -1,6 +1,5 @@
package org.foxarmy.barcodescannerforemployees.activities
//import android.R
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
@ -11,15 +10,28 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import androidx.viewpager.widget.ViewPager
import com.google.android.material.navigation.NavigationView
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.ViewPagerAdapter
import org.foxarmy.barcodescannerforemployees.convertToUnixEpochTimestamp
import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.database.ProductDAO
import org.foxarmy.barcodescannerforemployees.databinding.ActivityMainBinding
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import org.foxarmy.barcodescannerforemployees.fragments.CategoriesFragment
import org.foxarmy.barcodescannerforemployees.fragments.ShelfFragment
import org.foxarmy.barcodescannerforemployees.fragments.StorageFragment
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
@ -90,6 +102,234 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
}
}
synchronize()
}
private fun synchronize() {
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") return
val net = Net()
net.language = sharedPreferences.getString("language", "en-US")!!
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
val data = JSONObject(net.synchronize(currentGroup).body!!.string())
val remoteAbstractProducts = data["abstract_products"] as JSONArray
val remoteProducts = data["products"] as JSONArray
val remoteCategories = data["categories"] as JSONArray
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "")!!)
val abstractProductDAO = AbstractProductDAO(dbHelper)
val productDAO = ProductDAO(dbHelper)
val categoryDAO = CategoryDAO(dbHelper)
val localAbstractProducts = abstractProductDAO.getSortedListOfAbstractProducts(0, "", arrayOf(""))
val localProducts = productDAO.getSortedListOfProducts(0, "", "")
val localCategories = categoryDAO.getAllCategories()
syncAbstractProducts(net, currentGroup, abstractProductDAO, remoteAbstractProducts, localAbstractProducts)
syncProducts(productDAO, remoteProducts, localProducts)
syncCategories(categoryDAO, remoteCategories, localCategories)
}
private fun syncCategories(
categoryDAO: CategoryDAO,
remoteCategories: JSONArray,
localCategories: List<Category>
) {
for (i in 0 until remoteCategories.length()) {
var categoryInRemoteDB: JSONObject? = null
var categoryInLocalDB: Category? = null
val remoteCategory = remoteCategories.getJSONObject(i)
for (localCategory in localCategories) {
if (remoteCategory["local_id"] == localCategory.id) {
categoryInRemoteDB = remoteCategory
categoryInLocalDB = localCategory
break
}
}
if (categoryInLocalDB == null && remoteCategory != null) {
var newCategory: Category
with (remoteCategory) {
newCategory = Category(
this["local_id"].toString().toInt(),
this["name"].toString()
)
}
categoryDAO.addCategory(newCategory)
}
if (categoryInRemoteDB != null && categoryInLocalDB != null) {
if (categoryInRemoteDB["hash"] != categoryInLocalDB.calculateHash()) {
var updatedData: Category
with(categoryInRemoteDB) {
updatedData = Category(
this["local_id"].toString().toInt(),
this["name"].toString()
)
}
categoryDAO.updateCategory(updatedData)
}
}
}
}
private fun syncProducts(
productDAO: ProductDAO,
remoteProducts: JSONArray,
localProducts: List<Product>
) {
for (i in 0 until remoteProducts.length()) {
var productInRemoteDB: JSONObject? = null
var productInLocalDB: Product? = null
val remoteProduct = remoteProducts.getJSONObject(i)
for (localProduct in localProducts) {
if (remoteProduct["local_id"] == localProduct.id) {
productInRemoteDB = remoteProduct
productInLocalDB = localProduct
break
}
}
if (productInLocalDB == null && remoteProduct != null) {
var newProduct: Product
with(remoteProduct) {
newProduct = Product(
this["local_id"].toString().toInt(),
this["abstract_product_id"].toString().toInt(),
this["amount"].toString().toInt(),
convertToUnixEpochTimestamp(this["date_of_production"].toString()),
convertToUnixEpochTimestamp(this["expiry_date"].toString())
)
}
productDAO.insertNewProduct(newProduct)
}
if (productInRemoteDB != null && productInLocalDB != null) {
if (productInRemoteDB["hash"] != productInLocalDB.calculateHash()) {
var updatedData: Product
with(productInRemoteDB) {
updatedData = Product(
this["local_id"].toString().toInt(),
this["abstract_product_id"].toString().toInt(),
this["amount"].toString().toInt(),
convertToUnixEpochTimestamp(this["date_of_production"].toString()),
convertToUnixEpochTimestamp(this["expiry_date"].toString())
)
}
productDAO.updateProduct(updatedData)
}
}
}
}
private fun syncAbstractProducts(
net: Net,
currentGroup: Int,
abstractProductDAO: AbstractProductDAO,
remoteAbstractProducts: JSONArray,
localAbstractProducts: List<AbstractProduct>
) {
for (i in 0 until remoteAbstractProducts.length()) {
var abstractProductInRemoteDB: JSONObject? = null
var abstractProductInLocalDB: AbstractProduct? = null
val remoteAbstractProduct = remoteAbstractProducts.getJSONObject(i)
for (localAbstractProduct in localAbstractProducts) {
if (remoteAbstractProduct["local_id"] == localAbstractProduct.id) {
abstractProductInRemoteDB = remoteAbstractProduct
abstractProductInLocalDB = localAbstractProduct
break
}
}
if (abstractProductInLocalDB == null && remoteAbstractProduct != null) {
val localId = remoteAbstractProduct["local_id"].toString().toInt()
val picturesDir = File(filesDir, "pictures")
picturesDir.mkdirs()
val pictureFile =
File(picturesDir, "${remoteAbstractProduct["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}"
net.downloadImage(url, pictureFile)
var newAbstractProduct: AbstractProduct
with(remoteAbstractProduct) {
newAbstractProduct = AbstractProduct(
this["local_id"].toString().toInt(),
this["barcode"].toString(),
this["name"].toString(),
this["net_weight"].toString().toDouble(),
this["image_filename"].toString(),
this["category"].toString().toInt(),
this["unit"].toString().toInt()
)
}
abstractProductDAO.addAbstractProduct(newAbstractProduct)
}
if (abstractProductInRemoteDB != null && abstractProductInLocalDB != null) {
if (abstractProductInRemoteDB["hash"] != abstractProductInLocalDB.calculateHash()) {
val localId = abstractProductInRemoteDB["local_id"].toString().toInt()
val pictureFile =
File(File(filesDir, "pictures"), "${abstractProductInRemoteDB["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}"
net.downloadImage(url, pictureFile)
var updatedData: AbstractProduct
with(abstractProductInRemoteDB) {
updatedData = AbstractProduct(
this["local_id"].toString().toInt(),
this["barcode"].toString(),
this["name"].toString(),
this["net_weight"].toString().toDouble(),
this["image_filename"].toString(),
this["category"].toString().toInt(),
this["unit"].toString().toInt()
)
}
abstractProductDAO.updateAbstractProduct(updatedData)
}
}
}
}
private fun setupViewPager(viewpager: ViewPager) {
@ -117,9 +357,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
R.id.nav_account -> {
intent = Intent(this, AccountSettingsActivity::class.java)
}
R.id.nav_groups -> {
intent = Intent(this, MyGroupsActivity::class.java)
}
R.id.nav_settings -> {
//TODO: Settings
return false
@ -169,6 +411,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}.show()
}
"ShelfFragment" -> {
val shelfFragment = fragment as ShelfFragment
shelfFragment.removeSelected()

View File

@ -232,6 +232,7 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
if (abstractProduct.id > 0) put(BaseColumns._ID, abstractProduct.id)
put(AbstractProductContract.AbstractProductEntry.BARCODE, abstractProduct.barcode)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME, abstractProduct.name)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT, abstractProduct.netWeight.toString())
@ -249,6 +250,7 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
put(BaseColumns._ID, abstractProduct.id)
put(AbstractProductContract.AbstractProductEntry.BARCODE, abstractProduct.barcode)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME, abstractProduct.name)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT, abstractProduct.netWeight.toString())
@ -265,7 +267,11 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) {
)
}
fun getSortedListOfAbstractProducts(selectedSort: Int, filterBy: String, filter: Array<String>): List<AbstractProduct> {
fun getSortedListOfAbstractProducts(
selectedSort: Int,
filterBy: String,
filter: Array<String>
): List<AbstractProduct> {
val db = dbHelper.readableDatabase
@ -282,10 +288,11 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) {
)
var orderBy: String = ""
when(selectedSort) {
when (selectedSort) {
0 -> {
orderBy = "${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} ASC"
}
1 -> {
orderBy = "${AbstractProductContract.AbstractProductEntry.CATEGORY} ASC"
}
@ -299,6 +306,7 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) {
selection = "${AbstractProductContract.AbstractProductEntry.CATEGORY} = ?"
selectionArgs = filter
}
"barcodeless" -> {
selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = '' "
selectionArgs = null
@ -306,24 +314,35 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) {
}
val cursor = db.query(AbstractProductContract.AbstractProductEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy)
val cursor = db.query(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
orderBy
)
with (cursor) {
while(moveToNext()) {
with(cursor) {
while (moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID))
val barcode = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.BARCODE))
val productName = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val netWeight = getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val productImageHash = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val productName =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
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, barcode, productName, netWeight, productImageHash, category, unit)
val product =
AbstractProduct(productId, barcode, productName, netWeight, productImageHash, category, unit)
abstractProducts.add(product)
}
}
return abstractProducts
return abstractProducts
}
}

View File

@ -5,7 +5,7 @@ import android.content.Context
import android.provider.BaseColumns
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
class CategoryDAO (private val dbHelper: DBStorageController) {
class CategoryDAO(private val dbHelper: DBStorageController) {
fun eraseCategory(id: Int, context: Context) {
val abstractProductDAO = AbstractProductDAO(dbHelper)
@ -56,6 +56,7 @@ class CategoryDAO (private val dbHelper: DBStorageController) {
val categories = mutableListOf<Category>()
val projection = arrayOf(
BaseColumns._ID,
CategoriesContract.CategoryEntry.CATEGORY_NAME
)
@ -72,7 +73,7 @@ class CategoryDAO (private val dbHelper: DBStorageController) {
with(cursor) {
while (moveToNext()) {
val category = Category(
getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID)),
getInt(getColumnIndexOrThrow(BaseColumns._ID)),
getString(getColumnIndexOrThrow(CategoriesContract.CategoryEntry.CATEGORY_NAME))
)
categories.add(category)
@ -82,15 +83,18 @@ class CategoryDAO (private val dbHelper: DBStorageController) {
return categories
}
fun addCategory(category: Category) {
fun addCategory(category: Category): Long {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
if (category.id > 0) put(BaseColumns._ID, category.id)
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, category.name)
}
db.insert(CategoriesContract.CategoryEntry.TABLE_NAME, null, values)
val id = db.insert(CategoriesContract.CategoryEntry.TABLE_NAME, null, values)
return id
}
fun updateCategory(category: Category) {
@ -100,6 +104,11 @@ class CategoryDAO (private val dbHelper: DBStorageController) {
val values = ContentValues().apply {
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, category.name)
}
db.update(CategoriesContract.CategoryEntry.TABLE_NAME, values, "${BaseColumns._ID} = ?", arrayOf(category.id.toString()))
db.update(
CategoriesContract.CategoryEntry.TABLE_NAME,
values,
"${BaseColumns._ID} = ?",
arrayOf(category.id.toString())
)
}
}

View File

@ -13,18 +13,20 @@ class ProductDAO (private val dbHelper: DBStorageController) {
db.delete(ProductContract.ProductEntry.TABLE_NAME, BaseColumns._ID + "=" + id, null)
}
fun insertNewProduct(product: Product) {
fun insertNewProduct(product: Product) : Long{
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
if (product.id > 0) put(BaseColumns._ID, product.id)
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)
val id = db.insert(ProductContract.ProductEntry.TABLE_NAME, null, values)
return id
}
fun findAmountOfProductsWithExpiryDate(date: Long): Int {
@ -82,6 +84,7 @@ class ProductDAO (private val dbHelper: DBStorageController) {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
put(BaseColumns._ID, product.id)
put(ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID, product.abstractProductId)
put(ProductContract.ProductEntry.AMOUNT, product.amount)
put(ProductContract.ProductEntry.DATE_OF_PRODUCTION, product.dateOfProduction)
@ -157,9 +160,7 @@ class ProductDAO (private val dbHelper: DBStorageController) {
val product = Product(productId, abstractProductId, amount, dateOfProduction, dateOfExpiry)
if (selectedSort == 2) { //freshness
products.add(product)
}
products.add(product)
}
}

View File

@ -2,6 +2,7 @@ package org.foxarmy.barcodescannerforemployees.dataclasses
import android.os.Parcel
import android.os.Parcelable
import org.foxarmy.barcodescannerforemployees.md5
class AbstractProduct() : Parcelable {
var id: Int = 0
@ -32,6 +33,10 @@ class AbstractProduct() : Parcelable {
unit = parcel.readInt()
}
fun calculateHash(): String {
return "$id:$barcode:$name:${if (netWeight % 1 == 0.0) netWeight.toInt() else netWeight}:$imageHash:$category:$unit".md5()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeString(barcode)

View File

@ -2,6 +2,7 @@ package org.foxarmy.barcodescannerforemployees.dataclasses
import android.os.Parcel
import android.os.Parcelable
import org.foxarmy.barcodescannerforemployees.md5
class Category() : Parcelable {
var id = 0
@ -27,6 +28,10 @@ class Category() : Parcelable {
return 0
}
fun calculateHash(): String {
return "$id:$name".md5()
}
companion object CREATOR : Parcelable.Creator<Category> {
override fun createFromParcel(parcel: Parcel): Category {
return Category(parcel)

View File

@ -5,6 +5,7 @@ import android.os.Parcel
import android.os.Parcelable
import androidx.annotation.RequiresApi
import org.foxarmy.barcodescannerforemployees.calculateProductFreshness
import org.foxarmy.barcodescannerforemployees.md5
class Product() : Parcelable {
var id = 0
@ -46,6 +47,10 @@ class Product() : Parcelable {
return 0
}
fun calculateHash(): String {
return "$id:$abstractProductId:$amount:$dateOfProduction:$dateOfExpiry".md5()
}
companion object CREATOR : Parcelable.Creator<Product> {
override fun createFromParcel(parcel: Parcel): Product {
return Product(parcel)

View File

@ -37,13 +37,24 @@ class AbstractProductView: LinearLayout {
var isProductSelected = false
private var activity: Activity
private lateinit var categoryDAO: CategoryDAO
private lateinit var sharedPreferences: SharedPreferences
private var categoryDAO: CategoryDAO
private var sharedPreferences: SharedPreferences
constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!!
val inflater:LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.abstract_product_view, this)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
categoryDAO = CategoryDAO(dbHelper)
}
constructor(activity: Activity, context: Context, abstractProduct: AbstractProduct) : super(context) {

View File

@ -42,7 +42,7 @@ import kotlin.concurrent.thread
class ProductView: LinearLayout {
var product: Product = Product()
var isProductSelected = false
private lateinit var activity: Activity
private var activity: Activity
private lateinit var productImageView: ImageView
private lateinit var productNameTextView: TextView
@ -56,9 +56,9 @@ class ProductView: LinearLayout {
private var strokeColor: Int = 0x000000
private lateinit var outline: GradientDrawable
private lateinit var categoryDAO: CategoryDAO
private lateinit var abstractProductDAO: AbstractProductDAO
private lateinit var sharedPreferences: SharedPreferences
private var categoryDAO: CategoryDAO
private var abstractProductDAO: AbstractProductDAO
private var sharedPreferences: SharedPreferences
constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!!
@ -71,7 +71,7 @@ class ProductView: LinearLayout {
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
categoryDAO = CategoryDAO(dbHelper)
@ -86,6 +86,18 @@ class ProductView: LinearLayout {
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.product_view, this)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
categoryDAO = CategoryDAO(dbHelper)
productImageView = findViewById(R.id.productPicture)
productNameTextView = findViewById(R.id.productNameView)
productNetWeightTextView = findViewById(R.id.productNetWeightView)