Compare commits

...

8 Commits

23 changed files with 581 additions and 223 deletions

View File

@ -13,6 +13,7 @@
<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"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application <application
android:allowBackup="true" android:allowBackup="true"

View File

@ -1,5 +1,6 @@
package org.foxarmy.barcodescannerforemployees package org.foxarmy.barcodescannerforemployees
import android.content.Context
import okhttp3.* import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -17,6 +18,28 @@ class Net {
var server = "bsfe.foxarmy.org" var server = "bsfe.foxarmy.org"
var token = "" var token = ""
fun serverIsAvailable(context: Context): Boolean {
if (!isInternetConnectionAvailable(context)) {
return false
}
var flag = false
thread {
val client = OkHttpClient();
val request = Request.Builder()
.url("https://$server/status")
.get()
.build();
try {
val response = client.newCall(request).execute();
flag = response.code == 200;
} catch (e: Exception) {
flag = false;
}
}.join()
return flag
}
fun requestProductFromOnlineDB(barcode: String): String { fun requestProductFromOnlineDB(barcode: String): String {
var response = "" var response = ""
thread { thread {

View File

@ -14,8 +14,6 @@ class Parser constructor() {
text = text.replace(found, "") text = text.replace(found, "")
netWeight = stripNetWeight(found) netWeight = stripNetWeight(found)
return Triple(text, netWeight, found) return Triple(text, netWeight, found)
} else {
return Triple(text, 0.0, "")
} }
} }
return Triple("", 0.0, "") return Triple("", 0.0, "")

View File

@ -7,10 +7,15 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Matrix import android.graphics.Matrix
import android.media.ExifInterface import android.media.ExifInterface
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.google.firebase.components.BuildConfig import com.google.firebase.components.BuildConfig
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
@ -18,6 +23,7 @@ import java.io.FileOutputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.system.exitProcess
fun convertToUnixEpochTimestamp(dateString: String): Long { fun convertToUnixEpochTimestamp(dateString: String): Long {
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
@ -27,7 +33,11 @@ fun convertToUnixEpochTimestamp(dateString: String): Long {
} }
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
)
} }
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
@ -47,13 +57,13 @@ fun generateThumbnailForImage(context: Context, imageHash: String) {
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val matrix = Matrix() val matrix = Matrix()
when(orientation){ when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F) ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F) ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F)
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F) ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F)
} }
val rotated = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true) val rotated = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true)
val scaled = Bitmap.createScaledBitmap(rotated, rotated.width/4, rotated.height/4, true) val scaled = Bitmap.createScaledBitmap(rotated, rotated.width / 4, rotated.height / 4, true)
scaled.compress(Bitmap.CompressFormat.WEBP_LOSSY, 25, FileOutputStream(thumbnailFile)) scaled.compress(Bitmap.CompressFormat.WEBP_LOSSY, 25, FileOutputStream(thumbnailFile))
} }
@ -64,8 +74,11 @@ fun String.md5(): String {
return digest.toHexString() return digest.toHexString()
} }
fun stripNetWeight (netWeight: String): Double { fun stripNetWeight(netWeight: String): Double {
return removeSubstringsFromString(netWeight, arrayOf("Л", "л", "мл", "Мл", "г", "Г", "кг", "Кг", "шт", "Шт", ",", " ", ".")).toDouble() return removeSubstringsFromString(
netWeight,
arrayOf("Л", "л", "мл", "Мл", "г", "Г", "кг", "Кг", "шт", "Шт", ",", " ", ".")
).toDouble()
} }
fun removeSubstringsFromString(text: String, toRemove: Array<String>): String { fun removeSubstringsFromString(text: String, toRemove: Array<String>): String {
@ -99,14 +112,31 @@ fun calculateProductFreshness(dateOfProduction: Long, dateOfExpiry: Long): Doubl
return lifeSpanLeft / productLifeSpan.toDouble() return lifeSpanLeft / productLifeSpan.toDouble()
} }
fun getUnitNameById (context: Context, id: Int): String { fun getUnitNameById(context: Context, id: Int): String {
return when(id) { return when (id) {
0 -> { context.getString(R.string.kilogram) } 0 -> {
1 -> { context.getString(R.string.gram) } context.getString(R.string.kilogram)
2 -> { context.getString(R.string.liter) } }
3 -> { context.getString(R.string.milliliter) }
4 -> { context.getString(R.string.pieces) } 1 -> {
else -> { "" } context.getString(R.string.gram)
}
2 -> {
context.getString(R.string.liter)
}
3 -> {
context.getString(R.string.milliliter)
}
4 -> {
context.getString(R.string.pieces)
}
else -> {
""
}
} }
} }
@ -138,3 +168,48 @@ fun bytesToHex(bytes: ByteArray): String {
} }
return hexString.toString() return hexString.toString()
} }
fun isInternetConnectionAvailable(context: Context): Boolean {
if (context.getSystemService(Context.CONNECTIVITY_SERVICE) == null) return false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (connectivityManager != null) {
val capabilities =
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
if (capabilities != null) {
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return true
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return true
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
return true
}
}
}
return false
}
fun noInternetConnectionAvailableNotification(context: Context) {
(context as Activity).runOnUiThread {
AlertDialog.Builder(context)
.setMessage(context.getString(R.string.no_internet_connection))
.setPositiveButton(R.string.quit) { _, _ ->
exitProcess(0)
}
.setNeutralButton(R.string.logout) { _, _ ->
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences.edit().putString("token", "").apply()
sharedPreferences.edit().putString("server", "").apply()
sharedPreferences.edit().putStringSet("groups", emptySet()).apply()
sharedPreferences.edit().putString("currentGroup", "").apply()
exitProcess(0)
}.show()
}
}

View File

@ -0,0 +1,168 @@
package org.foxarmy.barcodescannerforemployees
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import okhttp3.*
import org.foxarmy.barcodescannerforemployees.activities.MainActivity
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.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import org.json.JSONObject
import java.io.File
import java.util.concurrent.TimeUnit
class WebSocketClient(private val context: Context, private val server: String) {
private lateinit var webSocket: WebSocket
fun connect(token: String, currentGroup: String) {
val client = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(1, TimeUnit.MINUTES)
.writeTimeout(10, TimeUnit.SECONDS)
.callTimeout(1, TimeUnit.MINUTES)
.build()
val request = Request.Builder()
.url("wss://$server")
.build()
client.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
this@WebSocketClient.webSocket = webSocket
sendMessage("{\"token\":\"$token\",\"currentGroup\":\"$currentGroup\"}")
keepAlive()
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onMessage(webSocket: WebSocket, text: String) {
val payload = JSONObject(text)
val item = payload["item"] as String
val data = payload["data"] as JSONObject
val action = payload["action"] as String
val currentGroup = payload["groupId"] as String
val dbHelper = DBStorageController(context, currentGroup)
val abstractProductDAO = AbstractProductDAO(dbHelper)
val productDAO = ProductDAO(dbHelper)
val categoryDAO = CategoryDAO(dbHelper)
when (action) {
"create" -> {
when (item) {
"abstractproduct" -> {
val newAbstractProduct = AbstractProduct.createFromJSON(data)
val net = Net()
net.server = server
net.token = token
val picturesDir = File(context.filesDir, "pictures")
picturesDir.mkdirs()
val pictureFile =
File(picturesDir, "${data["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${data["local_id"]}"
net.downloadImage(url, pictureFile)
abstractProductDAO.addAbstractProduct(newAbstractProduct)
}
"product" -> {
val newProduct = Product.createFromJSON(data)
productDAO.insertNewProduct(newProduct)
}
"category" -> {
val newCategory = Category.createFromJSON(data)
categoryDAO.addCategory(newCategory)
}
}
}
"update" -> {
when (item) {
"abstractproduct" -> {
val updatedAbstractProduct = AbstractProduct.createFromJSON(data)
val net = Net()
net.server = server
net.token = token
val picturesDir = File(context.filesDir, "pictures")
picturesDir.mkdirs()
val pictureFile =
File(picturesDir, "${data["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${data["local_id"]}"
net.downloadImage(url, pictureFile)
abstractProductDAO.updateAbstractProduct(updatedAbstractProduct)
}
"product" -> {
val updatedProduct = Product.createFromJSON(data)
productDAO.updateProduct(updatedProduct)
}
"category" -> {
val updatedCategory = Category.createFromJSON(data)
categoryDAO.updateCategory(updatedCategory)
}
}
}
"delete" -> {
val id = data["local_id"] as String
when(item) {
"abstractproduct" -> {
abstractProductDAO.eraseAbstractProduct(id.toInt(), context)
}
"product" -> {
productDAO.eraseProduct(id.toInt())
}
"category" -> {
categoryDAO.eraseCategory(id.toInt(), context)
}
}
}
}
(context as MainActivity).updateAll()
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
Log.d("QWERTYUIOP", "Closing ws. Reason: $reason")
noInternetConnectionAvailableNotification(context)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
Log.d("QWERTYUIOP","Connection failed: ${t.message}")
noInternetConnectionAvailableNotification(context)
}
})
}
private fun keepAlive() {
Thread {
while (true) {
Thread.sleep(30000)
webSocket.send("keepalive")
}
}.start()
}
fun sendMessage(message: String) {
webSocket.send(message)
}
fun close() {
webSocket.close(1000, "Closing WebSocket connection")
}
}

View File

@ -33,6 +33,7 @@ import java.io.FileOutputStream
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.math.abs
class AddAbstractProductActivity : AppCompatActivity() { class AddAbstractProductActivity : AppCompatActivity() {
private lateinit var imageView: ImageView private lateinit var imageView: ImageView
@ -73,7 +74,7 @@ class AddAbstractProductActivity : AppCompatActivity() {
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
) )
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!) val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
DAO = AbstractProductDAO(dbHelper) DAO = AbstractProductDAO(dbHelper)
picturesPath = File(filesDir, "pictures") picturesPath = File(filesDir, "pictures")
@ -109,25 +110,28 @@ class AddAbstractProductActivity : AppCompatActivity() {
action = extras!!.get("action") as String action = extras!!.get("action") as String
when (action) { when (action) {
"update" -> { "update" -> {
abstractProduct = extras.get("abstractProduct") as AbstractProduct? abstractProduct = extras.get("abstractProduct") as AbstractProduct
} }
"new_from_barcode" -> { "new_from_barcode" -> {
abstractProduct = extras.get("abstractProduct") as AbstractProduct? abstractProduct = extras.get("abstractProduct") as AbstractProduct
barcode = abstractProduct!!.barcode barcode = abstractProduct!!.barcode
performRequest(abstractProduct!!.barcode) performRequest(abstractProduct!!.barcode)
} }
} }
if (abstractProduct != null) { if (abstractProduct != null && action == "update") {
val imageThumbnailUri = getImageUri(this, File(thumbnailsDir, "${abstractProduct!!.imageHash}.webp")) val imageThumbnailUri = getImageUri(this, File(thumbnailsDir, "${abstractProduct!!.imageHash}.webp"))
pictureFile = File(picturesPath, "${abstractProduct!!.imageHash}.png]") pictureFile = File(picturesPath, "${abstractProduct!!.imageHash}.png")
imageView.setImageURI(imageThumbnailUri) imageView.setImageURI(imageThumbnailUri)
barcodeText.setText(abstractProduct!!.barcode) barcodeText.setText(abstractProduct!!.barcode)
productNameText.text = abstractProduct!!.name productNameText.text = abstractProduct!!.name
netWeightText.text = abstractProduct!!.netWeight.toString() netWeightText.text = abstractProduct!!.netWeight.toString()
categorySpinner.setSelection(abstractProduct!!.category) categorySpinner.setSelection(abstractProduct!!.category - 1)
unitTypeSpinner.setSelection(abstractProduct!!.unit) unitTypeSpinner.setSelection(abstractProduct!!.unit)
if (abstractProduct!!.barcode == "" || abstractProduct!!.barcode == " ") {
noBarcodeCheckBox.isChecked = true
}
} }
saveButton.setOnClickListener { saveButton.setOnClickListener {
@ -149,20 +153,27 @@ class AddAbstractProductActivity : AppCompatActivity() {
} }
if (netWeight.toString() == "" || netWeight.toString().toDoubleOrNull() == null) { if (netWeight.toString() == "" || netWeight.toString().toDoubleOrNull() == null) {
Toast.makeText(this, getString(R.string.product_net_weight_request), Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.product_net_weight_request), Toast.LENGTH_SHORT).show()
return@setOnClickListener
} }
val currentGroup: Int
val net = Net() val currentGroupString = sharedPreferences.getString("currentGroup", "offline")!!
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!
lateinit var response: Response lateinit var response: Response
val net = Net()
if (currentGroupString != "offline") {
currentGroup = currentGroupString.toInt()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
} else {
currentGroup = 0
}
abstractProduct = AbstractProduct( abstractProduct = AbstractProduct(
if(abstractProduct == null) 0 else abstractProduct!!.id, if (abstractProduct == null) 0 else abstractProduct!!.id,
if (noBarcodeCheckBox.isChecked) "" else barcode, if (noBarcodeCheckBox.isChecked) "" else barcode,
productName, productName,
netWeight.toString().toDouble(), netWeight.toString().toDouble(),
@ -170,16 +181,18 @@ class AddAbstractProductActivity : AppCompatActivity() {
categorySpinner.selectedItemPosition + 1, categorySpinner.selectedItemPosition + 1,
unitTypeSpinner.selectedItemPosition unitTypeSpinner.selectedItemPosition
) )
val pictureFile = File(File(filesDir, "pictures"), "${abstractProduct!!.imageHash}.png") val pictureFile = File(File(filesDir, "pictures"), "${abstractProduct!!.imageHash}.png")
if (action == "update") { if (action == "update") {
DAO.updateAbstractProduct(abstractProduct!!) DAO.updateAbstractProduct(abstractProduct!!)
response = net.updateAbstractProduct(currentGroup.toInt(), abstractProduct!!, pictureFile) if (currentGroup > 0) response = net.updateAbstractProduct(currentGroup, abstractProduct!!, pictureFile)
} else if (action == "new" || action == "new_from_barcode") { } else if (action == "new" || action == "new_from_barcode") {
abstractProduct!!.id = DAO.addAbstractProduct(abstractProduct!!).toInt() abstractProduct!!.id = DAO.addAbstractProduct(abstractProduct!!).toInt()
response = net.uploadAbstractProduct(currentGroup.toInt(), abstractProduct!!, pictureFile); if (currentGroup > 0) response =
net.uploadAbstractProduct(currentGroup, abstractProduct!!, pictureFile)
} }
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show() if (currentGroup > 0) Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
finish() finish()
} }
@ -198,7 +211,6 @@ class AddAbstractProductActivity : AppCompatActivity() {
barcodeText.setText(this.barcode) barcodeText.setText(this.barcode)
val net = Net(); val net = Net();
val result = net.requestProductFromOnlineDB(barcode) val result = net.requestProductFromOnlineDB(barcode)
Log.d("QWERTYUIOP", "Result of request: $result")
var abstractProduct: AbstractProduct var abstractProduct: AbstractProduct
@ -257,7 +269,8 @@ class AddAbstractProductActivity : AppCompatActivity() {
fun fillupCategorySpinner() { fun fillupCategorySpinner() {
val categoriesDAO = CategoryDAO(DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)) val categoriesDAO =
CategoryDAO(DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!))
val categories = categoriesDAO.getAllCategories().map { category -> category.name } val categories = categoriesDAO.getAllCategories().map { category -> category.name }
@ -272,22 +285,15 @@ class AddAbstractProductActivity : AppCompatActivity() {
if (success) { if (success) {
//Move picture to a proper directory according to its calculated hash //Move picture to a proper directory according to its calculated hash
val tempfile = File(filesDir, "image.png") val tempfile = File(filesDir, "image.png")
val imageHash = calculateMd5Hash(tempfile)
// val imageContent = tempfile.inputStream().readBytes()
// val imageHash = imageContent.toString(Charsets.UTF_8).md5()
pictureFile = File(picturesPath, "$imageHash.png") val imageContent = tempfile.inputStream().readBytes()
Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
tempfile.delete()
val imageContent = pictureFile.inputStream().readBytes()
var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size) var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size)
val exif = ExifInterface(pictureFile.absoluteFile.toString()) val exif = ExifInterface(tempfile.absoluteFile.toString())
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val matrix = Matrix() val matrix = Matrix()
when(orientation){ when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F) ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F) ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F)
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F) ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F)
@ -298,9 +304,15 @@ class AddAbstractProductActivity : AppCompatActivity() {
rotated.compress( rotated.compress(
Bitmap.CompressFormat.WEBP_LOSSY, Bitmap.CompressFormat.WEBP_LOSSY,
100 * (15 / (16 - compressionFactor)), 100 * (15 / (16 - compressionFactor)),
FileOutputStream(pictureFile) FileOutputStream(tempfile)
) )
val imageHash = calculateMd5Hash(tempfile)
pictureFile = File(picturesPath, "$imageHash.png")
Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
tempfile.delete()
generateThumbnailForImage(this, imageHash) generateThumbnailForImage(this, imageHash)
imageView.setImageURI(getImageUri(this, pictureFile)) imageView.setImageURI(getImageUri(this, pictureFile))

View File

@ -48,7 +48,7 @@ class AddCategoryActivity : Activity() {
net.server = sharedPreferences.getString("server", "")!! net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "")!! net.language = sharedPreferences.getString("language", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!.toInt() val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
findViewById<Button>(R.id.saveButton).setOnClickListener { findViewById<Button>(R.id.saveButton).setOnClickListener {
if (categoryNameTextEdit.text.toString() == "") { if (categoryNameTextEdit.text.toString() == "") {
@ -59,11 +59,11 @@ class AddCategoryActivity : Activity() {
if (category.id == 0) { // Inserting new category if (category.id == 0) { // Inserting new category
val newCategory = Category(0, categoryNameTextEdit.text.toString()) val newCategory = Category(0, categoryNameTextEdit.text.toString())
newCategory.id = DAO.addCategory(newCategory).toInt() newCategory.id = DAO.addCategory(newCategory).toInt()
net.uploadCategory(currentGroup, newCategory) if (currentGroup > 0) net.uploadCategory(currentGroup, newCategory)
} else { // Updating existing category } else { // Updating existing category
category.name = categoryNameTextEdit.text.toString() category.name = categoryNameTextEdit.text.toString()
DAO.updateCategory(category) DAO.updateCategory(category)
net.updateCategory(currentGroup, category) if (currentGroup > 0) net.updateCategory(currentGroup, category)
} }
finish() finish()

View File

@ -177,7 +177,7 @@ class AddProductActivity : AppCompatActivity() {
return@setOnClickListener return@setOnClickListener
} }
val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "")!! == "") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt() val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
var response: Response? = null var response: Response? = null
@ -200,8 +200,11 @@ class AddProductActivity : AppCompatActivity() {
val today = SimpleDateFormat("dd.MM.yyyy").format(Calendar.getInstance().time).split(".") val today = SimpleDateFormat("dd.MM.yyyy").format(Calendar.getInstance().time).split(".")
dateOfProductionDatePicker.updateDate(today[2].toInt(), today[1].toInt(), today[0].toInt()) dateOfProductionDatePicker.updateDate(today[2].toInt(), today[1].toInt() - 1, today[0].toInt())
expiryDatePicker.updateDate(today[2].toInt(), today[1].toInt(), today[0].toInt()) expiryDatePicker.updateDate(today[2].toInt(), today[1].toInt() - 1, today[0].toInt())
product!!.dateOfProduction = SimpleDateFormat("dd.MM.yyyy").parse("${today[0]}.${today[1]}.${today[2]}")!!.time / 1000
} }
private val intentLauncher = private val intentLauncher =

View File

@ -8,6 +8,7 @@ import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.databinding.ActivityGroupBinding import org.foxarmy.barcodescannerforemployees.databinding.ActivityGroupBinding
import org.foxarmy.barcodescannerforemployees.noInternetConnectionAvailableNotification
class GroupActivity : AppCompatActivity() { class GroupActivity : AppCompatActivity() {
@ -30,25 +31,28 @@ class GroupActivity : AppCompatActivity() {
binding.createGroupButton.setOnClickListener { binding.createGroupButton.setOnClickListener {
val groupName = binding.groupNameTextEdit.text.toString() val groupName = binding.groupNameTextEdit.text.toString()
val groupPassword = binding.groupPasswordTextEdit.text.toString() val groupPassword = binding.groupPasswordTextEdit.text.toString()
// group is set to "successful" val net = Net()
val n = Net() net.language = sharedPreferences.getString("language", "en-US")!!
n.language = sharedPreferences.getString("language", "en-US")!! net.server = sharedPreferences.getString("server", "")!!
n.server = sharedPreferences.getString("server", "")!! net.token = sharedPreferences.getString("token", "")!!
n.token = sharedPreferences.getString("token", "")!!
val response = n.createGroup(groupName, groupPassword) if (!net.serverIsAvailable(this)) {
val responseText = response.body!!.string() noInternetConnectionAvailableNotification(this)
if (response.code == 200) {
val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf())
currentGroups!!.add(responseText)
sharedPreferences.edit().putStringSet("groups", currentGroups).apply()
sharedPreferences.edit().putString("currentGroup", responseText).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else { } else {
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show() val response = net.createGroup(groupName, groupPassword)
val responseText = response.body!!.string()
if (response.code == 200) {
val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf())
currentGroups!!.add(responseText)
sharedPreferences.edit().putStringSet("groups", currentGroups).apply()
sharedPreferences.edit().putString("currentGroup", responseText).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else {
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show()
}
} }
} }
@ -56,25 +60,36 @@ class GroupActivity : AppCompatActivity() {
val groupName = binding.groupNameTextEdit.text.toString() val groupName = binding.groupNameTextEdit.text.toString()
val groupPassword = binding.groupPasswordTextEdit.text.toString() val groupPassword = binding.groupPasswordTextEdit.text.toString()
val n = Net() val net = Net()
n.language = sharedPreferences.getString("language", "en-US")!! net.language = sharedPreferences.getString("language", "en-US")!!
n.server = sharedPreferences.getString("server", "")!! net.server = sharedPreferences.getString("server", "")!!
n.token = sharedPreferences.getString("token", "")!! net.token = sharedPreferences.getString("token", "")!!
val groupId = n.getGroupId(groupName).toInt() if (!net.serverIsAvailable(this)) {
val response = n.joinGroup(groupId, groupPassword) noInternetConnectionAvailableNotification(this)
val responseText = response.body!!.string()
if (response.code == 200) {
val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf())
currentGroups!!.add(groupId.toString())
sharedPreferences.edit().putStringSet("groups", currentGroups).apply()
sharedPreferences.edit().putString("currentGroup", groupId.toString()).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else { } else {
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show() val requestGroupIdResponse = net.getGroupId(groupName)
var groupId: Int
try {
groupId = requestGroupIdResponse.toInt()
} catch (e: Exception) {
Toast.makeText(this, requestGroupIdResponse, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val response = net.joinGroup(groupId, groupPassword)
val responseText = response.body!!.string()
if (response.code == 200) {
val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf())
currentGroups!!.add(groupId.toString())
sharedPreferences.edit().putStringSet("groups", currentGroups).apply()
sharedPreferences.edit().putString("currentGroup", groupId.toString()).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else {
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show()
}
} }
} }
} }

View File

@ -10,6 +10,7 @@ import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityLoginBinding import org.foxarmy.barcodescannerforemployees.databinding.ActivityLoginBinding
import org.foxarmy.barcodescannerforemployees.noInternetConnectionAvailableNotification
import org.foxarmy.barcodescannerforemployees.parseArray import org.foxarmy.barcodescannerforemployees.parseArray
import org.json.JSONObject import org.json.JSONObject
@ -40,31 +41,35 @@ class LoginActivity : AppCompatActivity() {
val language = resources.getStringArray(R.array.languages)[binding.languageSpinner.selectedItemPosition] val language = resources.getStringArray(R.array.languages)[binding.languageSpinner.selectedItemPosition]
sharedPreferences.edit().putString("language", language).apply() sharedPreferences.edit().putString("language", language).apply()
val n = Net() val net = Net()
n.language = sharedPreferences.getString("language", "en-US")!! net.language = sharedPreferences.getString("language", "en-US")!!
n.server = server net.server = server
val response = n.login(username, password) if (!net.serverIsAvailable(this)) {
val responseText = response.body!!.string() noInternetConnectionAvailableNotification(this)
if (response.code != 200) {
Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show()
} else { } else {
val json = JSONObject(responseText) val response = net.login(username, password)
sharedPreferences.edit().putString("token", json["token"].toString()).apply() val responseText = response.body!!.string()
n.token = json["token"].toString() if (response.code != 200) {
sharedPreferences.edit().putInt("userId", json["id"].toString().toInt()).apply() Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show()
sharedPreferences.edit().putString("server", server).apply() } else {
val json = JSONObject(responseText)
sharedPreferences.edit().putString("token", json["token"].toString()).apply()
net.token = json["token"].toString()
sharedPreferences.edit().putInt("userId", json["id"].toString().toInt()).apply()
sharedPreferences.edit().putString("server", server).apply()
val r = n.getMyGroups().body!!.string() val r = net.getMyGroups().body!!.string()
val myGroups = parseArray(r).map { a -> a.toString()} val myGroups = parseArray(r).map { a -> a.toString()}
sharedPreferences.edit().putStringSet("groups", myGroups.toSet()).apply() sharedPreferences.edit().putStringSet("groups", myGroups.toSet()).apply()
sharedPreferences.edit().putString("currentGroup", myGroups[0]).apply() sharedPreferences.edit().putString("currentGroup", myGroups[0]).apply()
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
startActivity(intent) startActivity(intent)
finish() finish()
}
} }
} }
@ -76,24 +81,38 @@ class LoginActivity : AppCompatActivity() {
sharedPreferences.edit().putString("language", language).apply() sharedPreferences.edit().putString("language", language).apply()
sharedPreferences.edit().putString("server", server).apply() sharedPreferences.edit().putString("server", server).apply()
val n = Net() val net = Net()
n.language = language net.language = language
n.server = server net.server = server
val response = n.registerAccount(username, password);
val responseText = response.body!!.string()
if (response.code != 200) {
Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show();
} else {
sharedPreferences.edit().putInt("userId", responseText.toInt()).apply()
val token = JSONObject(n.login(username, password).body!!.string())["token"].toString()
sharedPreferences.edit().putString("token", token).apply()
sharedPreferences.edit().putString("server", server).apply() if (!net.serverIsAvailable(this)) {
val intent = Intent(this, GroupActivity::class.java) noInternetConnectionAvailableNotification(this)
startActivity(intent) } else {
finish() val response = net.registerAccount(username, password);
val responseText = response.body!!.string()
if (response.code != 200) {
Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show();
} else {
sharedPreferences.edit().putInt("userId", responseText.toInt()).apply()
val token = JSONObject(net.login(username, password).body!!.string())["token"].toString()
sharedPreferences.edit().putString("token", token).apply()
sharedPreferences.edit().putString("server", server).apply()
val intent = Intent(this, GroupActivity::class.java)
startActivity(intent)
finish()
}
} }
} }
binding.offlineButton.setOnClickListener {
sharedPreferences.edit().putString("currentGroup", "offline").apply()
sharedPreferences.edit().putStringSet("groups", setOf("offline")).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
} }
private fun fillUpLanguagesSpinner() { private fun fillUpLanguagesSpinner() {

View File

@ -3,9 +3,12 @@ package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
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.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -16,10 +19,7 @@ import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys import androidx.security.crypto.MasterKeys
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import org.foxarmy.barcodescannerforemployees.Net import org.foxarmy.barcodescannerforemployees.*
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.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController import org.foxarmy.barcodescannerforemployees.database.DBStorageController
@ -39,9 +39,12 @@ import java.io.File
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
lateinit var adapter: ViewPagerAdapter private lateinit var adapter: ViewPagerAdapter
var drawerLayout: DrawerLayout? = null private var drawerLayout: DrawerLayout? = null
var actionBarDrawerToggle: ActionBarDrawerToggle? = null private var actionBarDrawerToggle: ActionBarDrawerToggle? = null
private lateinit var ws: WebSocketClient
private lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -49,6 +52,14 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
setupViewPager(binding.tabViewpager) setupViewPager(binding.tabViewpager)
binding.tabTablayout.setupWithViewPager(binding.tabViewpager) binding.tabTablayout.setupWithViewPager(binding.tabViewpager)
@ -60,9 +71,16 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
actionBarDrawerToggle!!.syncState() actionBarDrawerToggle!!.syncState()
binding.navView.setNavigationItemSelectedListener(this) binding.navView.setNavigationItemSelectedListener(this)
// to make the Navigation drawer icon always appear on the action bar
supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setDisplayHomeAsUpEnabled(true)
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
binding.expiryCalendarFab.setOnClickListener { _ -> binding.expiryCalendarFab.setOnClickListener { _ ->
val expiryCalendarIntent = Intent(this, ExpiryCalendarActivity::class.java) val expiryCalendarIntent = Intent(this, ExpiryCalendarActivity::class.java)
val extras = Bundle() val extras = Bundle()
@ -78,8 +96,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
"StorageFragment" -> { "StorageFragment" -> {
val addAbstractProductIntent = Intent(this, AddAbstractProductActivity::class.java) val addAbstractProductIntent = Intent(this, AddAbstractProductActivity::class.java)
val extras = Bundle() 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) extras.putParcelable("abstractProduct", null)
extras.putString("action", "new") extras.putString("action", "new")
addAbstractProductIntent.putExtras(extras) addAbstractProductIntent.putExtras(extras)
@ -104,19 +120,20 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
} }
} }
} }
synchronize() val net = Net()
net.server = sharedPreferences.getString("server", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!
if ( currentGroup != "offline" && !net.serverIsAvailable(this)) {
noInternetConnectionAvailableNotification(this)
} else if (currentGroup != "offline" && net.serverIsAvailable(this)) {
synchronize()
ws = WebSocketClient(this, sharedPreferences.getString("server", "")!!)
val token = sharedPreferences.getString("token", "")!!
ws.connect(token, currentGroup)
}
} }
private fun 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 if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") return
val net = Net() val net = Net()
@ -194,15 +211,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
if (categoryInRemoteDB != null && categoryInLocalDB != null) { if (categoryInRemoteDB != null && categoryInLocalDB != null) {
if (categoryInRemoteDB["hash"] != categoryInLocalDB.calculateHash()) { if (categoryInRemoteDB["hash"] != categoryInLocalDB.calculateHash()) {
val updatedData = Category.createFromJSON(categoryInRemoteDB)
var updatedData: Category
with(categoryInRemoteDB) {
updatedData = Category(
this["local_id"].toString().toInt(),
this["name"].toString()
)
}
categoryDAO.updateCategory(updatedData) categoryDAO.updateCategory(updatedData)
} }
@ -241,35 +250,14 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
} }
if (productInLocalDB == null && remoteProduct != null) { if (productInLocalDB == null && remoteProduct != null) {
var newProduct: Product val newProduct = Product.createFromJSON(remoteProduct)
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) productDAO.insertNewProduct(newProduct)
} }
if (productInRemoteDB != null && productInLocalDB != null) { if (productInRemoteDB != null && productInLocalDB != null) {
if (productInRemoteDB["hash"] != productInLocalDB.calculateHash()) { if (productInRemoteDB["hash"] != productInLocalDB.calculateHash()) {
val updatedData = Product.createFromJSON(productInRemoteDB)
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) productDAO.updateProduct(updatedData)
} }
@ -320,19 +308,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}" val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}"
net.downloadImage(url, pictureFile) net.downloadImage(url, pictureFile)
var newAbstractProduct: AbstractProduct val newAbstractProduct = AbstractProduct.createFromJSON(remoteAbstractProduct)
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) abstractProductDAO.addAbstractProduct(newAbstractProduct)
@ -347,20 +323,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}" val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}"
net.downloadImage(url, pictureFile) net.downloadImage(url, pictureFile)
var updatedData: AbstractProduct val updatedData = AbstractProduct.createFromJSON(abstractProductInRemoteDB)
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) abstractProductDAO.updateAbstractProduct(updatedData)
} }
@ -384,22 +347,43 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
return true return true
} }
private fun isOffline(): Boolean {
return sharedPreferences.getString("currentGroup", "") == "offline"
}
override fun onNavigationItemSelected(item: MenuItem): Boolean { override fun onNavigationItemSelected(item: MenuItem): Boolean {
lateinit var intent: Intent lateinit var intent: Intent
when (item.itemId) { when (item.itemId) {
R.id.nav_account -> { R.id.nav_account -> {
if (isOffline()) {
Toast.makeText(this, getString(R.string.online_only_feature), Toast.LENGTH_SHORT).show()
return false
}
intent = Intent(this, AccountSettingsActivity::class.java) intent = Intent(this, AccountSettingsActivity::class.java)
settingsActivityLauncher.launch(intent) settingsActivityLauncher.launch(intent)
return true return true
} }
R.id.nav_groups -> { R.id.nav_groups -> {
if (isOffline()) {
Toast.makeText(this, getString(R.string.online_only_feature), Toast.LENGTH_SHORT).show()
return false
}
intent = Intent(this, MyGroupsActivity::class.java) intent = Intent(this, MyGroupsActivity::class.java)
} }
R.id.nav_settings -> { R.id.nav_settings -> {
intent = Intent(this, SettingsActivity::class.java) intent = Intent(this, SettingsActivity::class.java)
} }
R.id.nav_logout -> {
sharedPreferences.edit().putString("currentGroup", "").apply()
sharedPreferences.edit().putStringSet("groups", emptySet()).apply()
sharedPreferences.edit().putString("token", "").apply()
sharedPreferences.edit().putString("server", "").apply()
intent = Intent(this, LoginActivity::class.java)
}
} }
startActivity(intent) startActivity(intent)
@ -416,13 +400,19 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
} }
fun updateAll() { fun updateAll() {
val storageFragment: StorageFragment = adapter.getItem(0) as StorageFragment runOnUiThread {
val shelfFragment: ShelfFragment = adapter.getItem(1) as ShelfFragment try {
val categoriesFragment: CategoriesFragment = adapter.getItem(2) as CategoriesFragment val storageFragment: StorageFragment = adapter.getItem(0) as StorageFragment
val shelfFragment: ShelfFragment = adapter.getItem(1) as ShelfFragment
val categoriesFragment: CategoriesFragment = adapter.getItem(2) as CategoriesFragment
storageFragment.updateContent() storageFragment.updateContent()
shelfFragment.updateContent() shelfFragment.updateContent()
categoriesFragment.updateContent() categoriesFragment.updateContent()
} catch (e:Exception) {
Log.e("BSFE/MainActivity", e.message!!)
}
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {

View File

@ -24,19 +24,22 @@ class NavigatorActivity : Activity() {
var intent = Intent(this, MainActivity::class.java) var intent = Intent(this, MainActivity::class.java)
if (!isInGroup()) { if (!isInGroup() && !isOffline()) {
intent = Intent(this, GroupActivity::class.java) intent = Intent(this, GroupActivity::class.java)
} }
if (!isAuthenticated()) { if (!isAuthenticated() && !isOffline()) {
intent = Intent(this, LoginActivity::class.java); intent = Intent(this, LoginActivity::class.java);
} }
startActivity(intent); startActivity(intent);
finish(); finish();
} }
private fun isOffline(): Boolean {
return sharedPreferences.getString("currentGroup", "")!! == "offline"
}
private fun isAuthenticated(): Boolean { private fun isAuthenticated(): Boolean {
return sharedPreferences.getString("token", "") != "" return sharedPreferences.getString("token", "") != ""
} }

View File

@ -37,17 +37,23 @@ class SettingsActivity : AppCompatActivity() {
net.language = sharedPreferences.getString("language", "")!! net.language = sharedPreferences.getString("language", "")!!
net.server = sharedPreferences.getString("server", "")!! net.server = sharedPreferences.getString("server", "")!!
groupsNames = mutableListOf()
for (myGroup in myGroups) {
groupsNames.add(net.getGroupName(myGroup.toInt()))
}
binding = ActivitySettingsBinding.inflate(layoutInflater) binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setUpImageCompressionSeekBar() setUpImageCompressionSeekBar()
fillUpCurrentGroupSpinner()
if (isOffline()) {
binding.currentGroupSetting.visibility = View.GONE
} else {
groupsNames = mutableListOf()
for (myGroup in myGroups) {
groupsNames.add(net.getGroupName(myGroup.toInt()))
}
fillUpCurrentGroupSpinner()
}
binding.saveButton.setOnClickListener { binding.saveButton.setOnClickListener {
sharedPreferences.edit().putString("currentGroup", net.getGroupId(binding.currentGroupSpinner.selectedItem.toString())).apply() sharedPreferences.edit().putString("currentGroup", net.getGroupId(binding.currentGroupSpinner.selectedItem.toString())).apply()
@ -62,14 +68,16 @@ class SettingsActivity : AppCompatActivity() {
} }
} }
private fun isOffline(): Boolean {
return sharedPreferences.getString("currentGroup", "offline") == "offline"
}
private fun setUpImageCompressionSeekBar() { private fun setUpImageCompressionSeekBar() {
val compressionFactor = sharedPreferences.getInt("imageCompression", 1) val compressionFactor = sharedPreferences.getInt("imageCompression", 1)
binding.imageCompressionFactorSeekBar.progress = compressionFactor - 1 binding.imageCompressionFactorSeekBar.progress = compressionFactor - 1
} }
private fun fillUpCurrentGroupSpinner() { private fun fillUpCurrentGroupSpinner() {
if (currentGroup == "offline") { if (currentGroup == "offline") {
binding.currentGroupSetting.visibility = View.GONE binding.currentGroupSetting.visibility = View.GONE
} }

View File

@ -163,7 +163,7 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) {
with(cursor) { with(cursor) {
while (moveToNext()) { while (moveToNext()) {
val id = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID)) val id = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val productName = val productName =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME)) getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val imageFilename = val imageFilename =

View File

@ -3,6 +3,7 @@ package org.foxarmy.barcodescannerforemployees.dataclasses
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import org.foxarmy.barcodescannerforemployees.md5 import org.foxarmy.barcodescannerforemployees.md5
import org.json.JSONObject
class AbstractProduct() : Parcelable { class AbstractProduct() : Parcelable {
var id: Int = 0 var id: Int = 0
@ -59,5 +60,17 @@ class AbstractProduct() : Parcelable {
override fun newArray(size: Int): Array<AbstractProduct?> { override fun newArray(size: Int): Array<AbstractProduct?> {
return arrayOfNulls(size) return arrayOfNulls(size)
} }
fun createFromJSON(json: JSONObject): AbstractProduct {
return AbstractProduct(
json["local_id"].toString().toInt(),
json["barcode"].toString(),
json["name"].toString(),
json["net_weight"].toString().toDouble(),
json["image_filename"].toString(),
json["category"].toString().toInt(),
json["unit"].toString().toInt()
)
}
} }
} }

View File

@ -3,6 +3,7 @@ package org.foxarmy.barcodescannerforemployees.dataclasses
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import org.foxarmy.barcodescannerforemployees.md5 import org.foxarmy.barcodescannerforemployees.md5
import org.json.JSONObject
class Category() : Parcelable { class Category() : Parcelable {
var id = 0 var id = 0
@ -40,5 +41,12 @@ class Category() : Parcelable {
override fun newArray(size: Int): Array<Category?> { override fun newArray(size: Int): Array<Category?> {
return arrayOfNulls(size) return arrayOfNulls(size)
} }
fun createFromJSON(json: JSONObject): Category {
return Category(
json["local_id"].toString().toInt(),
json["name"].toString()
)
}
} }
} }

View File

@ -5,7 +5,9 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import org.foxarmy.barcodescannerforemployees.calculateProductFreshness import org.foxarmy.barcodescannerforemployees.calculateProductFreshness
import org.foxarmy.barcodescannerforemployees.convertToUnixEpochTimestamp
import org.foxarmy.barcodescannerforemployees.md5 import org.foxarmy.barcodescannerforemployees.md5
import org.json.JSONObject
class Product() : Parcelable { class Product() : Parcelable {
var id = 0 var id = 0
@ -59,5 +61,16 @@ class Product() : Parcelable {
override fun newArray(size: Int): Array<Product?> { override fun newArray(size: Int): Array<Product?> {
return arrayOfNulls(size) return arrayOfNulls(size)
} }
@RequiresApi(Build.VERSION_CODES.O)
fun createFromJSON(json: JSONObject): Product {
return Product(
json["local_id"].toString().toInt(),
json["abstract_product_id"].toString().toInt(),
json["amount"].toString().toInt(),
convertToUnixEpochTimestamp(json["date_of_production"].toString()),
convertToUnixEpochTimestamp(json["expiry_date"].toString())
)
}
} }
} }

View File

@ -37,6 +37,8 @@ class StorageFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentStorageBinding.inflate(inflater)
sharedPreferences = EncryptedSharedPreferences.create( sharedPreferences = EncryptedSharedPreferences.create(
"sensitive", "sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
@ -47,8 +49,6 @@ class StorageFragment : Fragment() {
prepareDatabaseConnection() prepareDatabaseConnection()
binding = FragmentStorageBinding.inflate(inflater)
fillUpSortBySpinner() fillUpSortBySpinner()
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {

View File

@ -62,7 +62,7 @@
android:ems="10" android:ems="10"
android:id="@+id/barcodeTextEdit" app:layout_constraintTop_toBottomOf="@+id/imageView" android:id="@+id/barcodeTextEdit" app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="8dp" android:hint="Barcode" android:textColorHint="#737373" android:layout_marginTop="8dp" android:hint="@string/barcode" android:textColorHint="#737373"
android:layout_marginStart="5dp"/> android:layout_marginStart="5dp"/>
<CheckBox <CheckBox
android:text="@string/no_barcode" android:text="@string/no_barcode"

View File

@ -2,7 +2,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#141218"> >
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -12,4 +12,5 @@
<item <item
android:id="@+id/nav_settings" android:id="@+id/nav_settings"
android:title="@string/settings"/> android:title="@string/settings"/>
<item android:title="@string/logout" android:id="@+id/nav_logout"/>
</menu> </menu>

View File

@ -114,6 +114,10 @@
<string name="image_compress_factor">Степень сжатия изображения</string> <string name="image_compress_factor">Степень сжатия изображения</string>
<string name="current_group">Текущая группа</string> <string name="current_group">Текущая группа</string>
<string name="cancel">Отмена</string> <string name="cancel">Отмена</string>
<string name="no_internet_connection">Не могу установить соединение с сервером. Возможно, вы отключены от сети?</string>
<string name="ok">Ок</string>
<string name="logout">Выйти</string>
<string name="online_only_feature">Эта возможность доступна только с онлайн аккаунтом</string>
<string-array name="languages"> <string-array name="languages">
<item>en-US</item> <item>en-US</item>
<item>ru-RU</item> <item>ru-RU</item>

View File

@ -112,6 +112,10 @@
<string name="image_compress_factor">Image compression factor</string> <string name="image_compress_factor">Image compression factor</string>
<string name="current_group">Current group</string> <string name="current_group">Current group</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="no_internet_connection">Cannot reach the server. Maybe, you lost internet connection?</string>
<string name="ok">OK</string>
<string name="logout">Log out</string>
<string name="online_only_feature">This feature is online only</string>
<string-array name="languages"> <string-array name="languages">
<item>en-US</item> <item>en-US</item>
<item>ru-RU</item> <item>ru-RU</item>