synchronizing
This commit is contained in:
parent
94d309c491
commit
cd299477d4
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
@ -98,4 +104,29 @@ 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()
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,12 +177,22 @@ 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()
|
||||
}
|
||||
update()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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())
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue