Compare commits

...

11 Commits

Author SHA1 Message Date
leca edd9c666ac bugfixes and translation 2024-11-20 22:00:32 +03:00
leca 6bbf436f45 Мимо собственного кода
Я без шуток не хожу -
То комменты не коммичу,
То весь класс перепишу

Кволити - не кволити,
Хуй меня уволите
2024-11-20 15:42:40 +03:00
leca a860e4d59f added functionality to group rename & leave buttons, added ability to transfer ownership of a group 2024-11-18 14:20:34 +03:00
leca 37e52b3f2f Fixed category filter 2024-11-17 22:37:54 +03:00
leca f18d7ad1e0 performance fixes 2024-11-17 19:26:25 +03:00
leca 1d7050c687 some ui improvements 2024-11-17 17:56:30 +03:00
leca 153506fc83 fixed some layout overflows 2024-11-16 15:40:08 +03:00
leca abe2a850d6 logging out on user data changes 2024-11-15 11:36:44 +03:00
leca 58a7ea7357 added ability to log out 2024-11-14 13:38:33 +03:00
leca 2ebcfff51a fixed using network in offline mode 2024-11-14 13:32:56 +03:00
leca 2676d8083e fixed net weight parsing 2024-11-14 13:11:13 +03:00
38 changed files with 1360 additions and 881 deletions

View File

@ -0,0 +1,14 @@
package org.foxarmy.barcodescannerforemployees
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
class LoadingDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState)
}
}

View File

@ -1,6 +1,9 @@
package org.foxarmy.barcodescannerforemployees
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -11,19 +14,19 @@ import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlin.concurrent.thread
import java.util.concurrent.TimeUnit
class Net {
var language = "en-US"
var server = "bsfe.foxarmy.org"
var token = ""
fun serverIsAvailable(context: Context): Boolean {
fun serverIsAvailable(context: Context, callback: (Boolean) -> Unit) {
if (!isInternetConnectionAvailable(context)) {
return false
callback(false)
}
CoroutineScope(Dispatchers.IO).launch {
var flag = false
thread {
val client = OkHttpClient();
val request = Request.Builder()
.url("https://$server/status")
@ -36,13 +39,13 @@ class Net {
} catch (e: Exception) {
flag = false;
}
}.join()
return flag
callback(flag)
}
}
fun requestProductFromOnlineDB(barcode: String): String {
var response = ""
thread {
fun requestProductFromOnlineDB(barcode: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val url = "https://ean-online.ru/match.php"
val client = OkHttpClient()
@ -58,19 +61,15 @@ class Net {
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute().body!!.string()
}.join()
return if (response == "") {
"Not found 404"
} else {
response
response = client.newCall(request).execute()
callback(response)
}
}
fun registerAccount(username: String, password: String): Response {
fun registerAccount(username: String, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -86,14 +85,13 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun login(username: String, password: String): Response {
fun login(username: String, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -107,15 +105,19 @@ class Net {
.addHeader("accept-language", language)
.build()
response = client.newCall(requestLogin).execute()
}.join()
return response
callback(response)
}
}
fun uploadAbstractProduct(groupId: Int, abstractProduct: AbstractProduct, imageFile: File): Response {
fun uploadAbstractProduct(
groupId: Int,
abstractProduct: AbstractProduct,
imageFile: File,
callback: (Response) -> Unit,
) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = MultipartBody.Builder()
@ -138,16 +140,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun changeUsername(newUsername: String): Response {
fun changeUsername(newUsername: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -162,16 +162,14 @@ class Net {
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun changePassword(newPassword: String): Response {
fun changePassword(newPassword: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -186,15 +184,12 @@ class Net {
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun createGroup(name: String, password: String): Response {
lateinit var response: Response
thread {
fun createGroup(name: String, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -207,19 +202,19 @@ class Net {
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
val response = client.newCall(request).execute()
changeGroupPassword(name, password, { responseFromPassword ->
callback(response)
})
changeGroupPassword(name, password)
return response
}
}
fun joinGroup(id: Int, password: String): Response {
fun joinGroup(id: Int, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -233,18 +228,15 @@ class Net {
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun changeGroupPassword(name: String, password: String): Response {
fun changeGroupPassword(name: String, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val groupId = getGroupId(name);
getGroupId(name, { groupId ->
val client = OkHttpClient()
val body = FormBody.Builder()
@ -259,15 +251,15 @@ class Net {
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
});
}
}
fun getGroupId(name: String): String {
fun getGroupId(name: String, callback: (String) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -277,17 +269,14 @@ class Net {
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
val responseText = response.body!!.string()
return responseText
callback(response.body!!.string())
}
}
fun getGroupName(id: Int): String {
fun getGroupName(id: Int, callback: (String) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -297,17 +286,14 @@ class Net {
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
val responseText = response.body!!.string()
return responseText
callback(response.body!!.string())
}
}
fun getUsersInGroup(groupId: Int): Response {
fun getUsersInGroup(groupId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -318,15 +304,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun getMyGroups(): Response {
fun getMyGroups(callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -337,15 +322,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun getUsernameById(userId: Int): Response {
fun getUsernameById(userId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -356,15 +340,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun getGroupAdminId(groupId: Int): Response {
fun getGroupAdminId(groupId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -375,15 +358,19 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun updateAbstractProduct(groupId: Int, abstractProduct: AbstractProduct, imageFile: File): Response {
fun updateAbstractProduct(
groupId: Int,
abstractProduct: AbstractProduct,
imageFile: File,
callback: (Response) -> Unit,
) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = MultipartBody.Builder()
@ -406,15 +393,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun uploadCategory(groupId: Int, category: Category): Response {
fun uploadCategory(groupId: Int, category: Category, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -431,15 +417,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun updateCategory(groupId: Int, category: Category): Response {
fun updateCategory(groupId: Int, category: Category, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -456,15 +441,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun uploadProduct(groupId: Int, product: Product): Response {
fun uploadProduct(groupId: Int, product: Product, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -484,15 +468,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun updateProduct(groupId: Int, product: Product): Response {
fun updateProduct(groupId: Int, product: Product, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
@ -512,15 +495,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun synchronize(groupId: Int): Response {
fun synchronize(groupId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -531,59 +513,24 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(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)
fun downloadImage(url: String, file: File, callback: () -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.MINUTES)
.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 ->
val response = client.newCall(request).execute()
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val fos = FileOutputStream(file)
@ -593,14 +540,14 @@ class Net {
inputStream.copyTo(fos)
}
}
callback()
}
}.join()
}
fun deleteCategory(groupId: Int, localId: Int): Response {
fun deleteCategory(groupId: Int, localId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -611,15 +558,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun deleteAbstractProduct(groupId: Int, localId: Int): Response {
fun deleteAbstractProduct(groupId: Int, localId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -630,15 +576,14 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
return response
callback(response)
}
}
fun deleteProduct(groupId: Int, localId: Int): Response {
fun deleteProduct(groupId: Int, localId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
@ -649,8 +594,69 @@ class Net {
.build()
response = client.newCall(request).execute()
}.join()
callback(response)
}
}
return response
fun renameGroup(groupId: Int, newName: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val client = OkHttpClient()
val body = FormBody.Builder()
.add("name", newName)
.build()
val request = Request.Builder()
.url("https://$server/api/group/rename/$groupId")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
callback(response)
}
}
fun transfer_ownership(groupId: Int, userId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val client = OkHttpClient()
val body = FormBody.Builder()
.add("userId", userId.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/group/transferOwnership/$groupId")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
callback(response)
}
}
fun leaveGroup(groupId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/group/leave/$groupId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
callback(response)
}
}
}

View File

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

View File

@ -3,6 +3,8 @@ package org.foxarmy.barcodescannerforemployees
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
@ -17,6 +19,7 @@ import androidx.core.content.FileProvider
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.google.firebase.components.BuildConfig
import org.foxarmy.barcodescannerforemployees.activities.LoginActivity
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
@ -114,36 +117,28 @@ fun calculateProductFreshness(dateOfProduction: Long, dateOfExpiry: Long): Doubl
fun getUnitNameById(context: Context, id: Int): String {
return when (id) {
0 -> {
context.getString(R.string.kilogram)
}
0 -> { context.getString(R.string.kilogram) }
1 -> {
context.getString(R.string.gram)
}
1 -> { context.getString(R.string.gram) }
2 -> {
context.getString(R.string.liter)
}
2 -> { context.getString(R.string.liter) }
3 -> {
context.getString(R.string.milliliter)
}
3 -> { context.getString(R.string.milliliter) }
4 -> {
context.getString(R.string.pieces)
}
4 -> { context.getString(R.string.pieces) }
else -> {
""
}
else -> { "" }
}
}
fun parseArray(input: String): IntArray {
fun parseIntArray(input: String): IntArray {
return input.trim('[', ']').split(",").map { it.trim().toInt() }.toIntArray()
}
fun parseStringList(input: String): List<String> {
return input.trim('[', ']').split(",").map { it.trim() }.toList()
}
fun calculateMd5Hash(file: File): String {
val digest = MessageDigest.getInstance("MD5")
val fis = FileInputStream(file)
@ -193,23 +188,30 @@ fun noInternetConnectionAvailableNotification(context: Context) {
(context as Activity).runOnUiThread {
AlertDialog.Builder(context)
.setMessage(context.getString(R.string.no_internet_connection))
.setCancelable(false)
.setPositiveButton(R.string.quit) { _, _ ->
exitProcess(0)
}
.setNeutralButton(R.string.logout) { _, _ ->
val sharedPreferences = EncryptedSharedPreferences.create(
val sharedPreferences = getPreferences(context)
sharedPreferences.edit().putString("token", "").apply()
sharedPreferences.edit().putString("server", "").apply()
sharedPreferences.edit().putStringSet("groups", emptySet()).apply()
sharedPreferences.edit().putString("currentGroup", "").apply()
val intent = Intent(context, LoginActivity::class.java)
context.startActivity(intent)
context.finish()
}.show()
}
}
fun getPreferences(context: Context): SharedPreferences {
return 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

@ -20,8 +20,13 @@ import java.util.concurrent.TimeUnit
class WebSocketClient(private val context: Context, private val server: String) {
private lateinit var webSocket: WebSocket
private lateinit var token: String
private lateinit var currentGroup: String
fun connect(token: String, currentGroup: String) {
this.token = token
this.currentGroup = currentGroup
val client = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(1, TimeUnit.MINUTES)
@ -70,19 +75,22 @@ class WebSocketClient(private val context: Context, private val server: String)
val pictureFile =
File(picturesDir, "${data["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${data["local_id"]}"
net.downloadImage(url, pictureFile)
net.downloadImage(url, pictureFile, {
abstractProductDAO.addAbstractProduct(newAbstractProduct)
(context as MainActivity).updateAll()
})
}
"product" -> {
val newProduct = Product.createFromJSON(data)
productDAO.insertNewProduct(newProduct)
(context as MainActivity).updateAll()
}
"category" -> {
val newCategory = Category.createFromJSON(data)
categoryDAO.addCategory(newCategory)
(context as MainActivity).updateAll()
}
}
}
@ -102,17 +110,21 @@ class WebSocketClient(private val context: Context, private val server: String)
val pictureFile =
File(picturesDir, "${data["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${data["local_id"]}"
net.downloadImage(url, pictureFile)
net.downloadImage(url, pictureFile, {
abstractProductDAO.updateAbstractProduct(updatedAbstractProduct)
(context as MainActivity).updateAll()
})
}
"product" -> {
val updatedProduct = Product.createFromJSON(data)
productDAO.updateProduct(updatedProduct)
(context as MainActivity).updateAll()
}
"category" -> {
val updatedCategory = Category.createFromJSON(data)
categoryDAO.updateCategory(updatedCategory)
(context as MainActivity).updateAll()
}
}
}
@ -123,23 +135,25 @@ class WebSocketClient(private val context: Context, private val server: String)
when(item) {
"abstractproduct" -> {
abstractProductDAO.eraseAbstractProduct(id.toInt(), context)
(context as MainActivity).updateAll()
}
"product" -> {
productDAO.eraseProduct(id.toInt())
(context as MainActivity).updateAll()
}
"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)
// noInternetConnectionAvailableNotification(context)
this@WebSocketClient.connect(token, currentGroup)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {

View File

@ -1,11 +1,11 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityAccountSettingsBinding
class AccountSettingsActivity : AppCompatActivity() {
@ -19,15 +19,7 @@ class AccountSettingsActivity : AppCompatActivity() {
setContentView(binding.root)
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
masterKeyAlias,
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val net = Net()
@ -36,13 +28,39 @@ class AccountSettingsActivity : AppCompatActivity() {
net.token = sharedPreferences.getString("token", "")!!
binding.saveUsernameButton.setOnClickListener {
val response = net.changeUsername(binding.newUsernameTextEdit.text.toString())
net.changeUsername(binding.newUsernameTextEdit.text.toString(), { response ->
if (response.code == 200) {
runOnUiThread {
Toast.makeText(this, getString(R.string.username_changed), Toast.LENGTH_LONG).show()
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
}
} else {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
}
})
}
binding.savePasswordButton.setOnClickListener {
val response = net.changePassword(binding.newPasswordTextEdit.text.toString())
net.changePassword(binding.newPasswordTextEdit.text.toString(), { response ->
if (response.code == 200) {
runOnUiThread {
Toast.makeText(this, getString(R.string.password_changed), Toast.LENGTH_LONG).show()
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
}
} else {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
}
})
}
}
}

View File

@ -1,5 +1,6 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.ProgressDialog
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
@ -17,8 +18,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions
@ -32,7 +31,6 @@ import java.io.File
import java.io.FileOutputStream
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import kotlin.concurrent.thread
class AddAbstractProductActivity : AppCompatActivity() {
private lateinit var imageView: ImageView
@ -60,18 +58,19 @@ class AddAbstractProductActivity : AppCompatActivity() {
private lateinit var DAO: AbstractProductDAO
private lateinit var sharedPreferences: SharedPreferences
private lateinit var loadingDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_add_abstract_product)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
sharedPreferences = getPreferences(this)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
DAO = AbstractProductDAO(dbHelper)
@ -128,6 +127,9 @@ class AddAbstractProductActivity : AppCompatActivity() {
netWeightText.text = abstractProduct!!.netWeight.toString()
categorySpinner.setSelection(abstractProduct!!.category - 1)
unitTypeSpinner.setSelection(abstractProduct!!.unit)
if (abstractProduct!!.barcode == "" || abstractProduct!!.barcode == " ") {
noBarcodeCheckBox.isChecked = true
}
}
saveButton.setOnClickListener {
@ -152,18 +154,26 @@ class AddAbstractProductActivity : AppCompatActivity() {
return@setOnClickListener
}
loadingDialog.show()
val currentGroup: Int
val currentGroupString = sharedPreferences.getString("currentGroup", "offline")!!
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")!!
val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
lateinit var response: Response
} else {
currentGroup = 0
}
abstractProduct = AbstractProduct(
if(abstractProduct == null) 0 else abstractProduct!!.id,
if (abstractProduct == null) 0 else abstractProduct!!.id,
if (noBarcodeCheckBox.isChecked) "" else barcode,
productName,
netWeight.toString().toDouble(),
@ -171,18 +181,17 @@ class AddAbstractProductActivity : AppCompatActivity() {
categorySpinner.selectedItemPosition + 1,
unitTypeSpinner.selectedItemPosition
)
val pictureFile = File(File(filesDir, "pictures"), "${abstractProduct!!.imageHash}.png")
if (action == "update") {
DAO.updateAbstractProduct(abstractProduct!!)
response = net.updateAbstractProduct(currentGroup, abstractProduct!!, pictureFile)
if (currentGroup > 0) net.updateAbstractProduct(currentGroup, abstractProduct!!, pictureFile, this::notifyUserAndExit)
} else if (action == "new" || action == "new_from_barcode") {
abstractProduct!!.id = DAO.addAbstractProduct(abstractProduct!!).toInt()
response = net.uploadAbstractProduct(currentGroup, abstractProduct!!, pictureFile);
if (currentGroup > 0)
net.uploadAbstractProduct(currentGroup, abstractProduct!!, pictureFile, this::notifyUserAndExit)
}
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
finish()
}
takePictureButton.setOnClickListener {
@ -195,10 +204,16 @@ class AddAbstractProductActivity : AppCompatActivity() {
}
}
fun notifyUserAndExit(response: Response) {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
loadingDialog.dismiss()
finish()
}
}
fun performRequest(barcode: String) {
barcodeText.setText(this.barcode)
val net = Net();
val result = net.requestProductFromOnlineDB(barcode)
var abstractProduct: AbstractProduct
@ -220,24 +235,25 @@ class AddAbstractProductActivity : AppCompatActivity() {
}.show()
}
thread {
if (result == "Not found 404") {
val net = Net();
net.requestProductFromOnlineDB(barcode, {response ->
if (response.code == 404) {
runOnUiThread {
Toast.makeText(this, getString(R.string.no_product_in_online_database), Toast.LENGTH_LONG)
.show()
productNameText.setText("")
netWeightText.setText("")
}
return@thread
return@requestProductFromOnlineDB
}
abstractProduct = Parser().parse(result)
abstractProduct = Parser().parse(response.body!!.string())
runOnUiThread {
productNameText.text = abstractProduct.name
netWeightText.text = abstractProduct.netWeight.toString()
unitTypeSpinner.setSelection(abstractProduct.unit)
}
}
})
}
private fun fillupUnitsSpinner() {
@ -257,7 +273,8 @@ class AddAbstractProductActivity : AppCompatActivity() {
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 }
@ -280,7 +297,7 @@ class AddAbstractProductActivity : AppCompatActivity() {
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val matrix = Matrix()
when(orientation){
when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F)
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F)

View File

@ -1,13 +1,13 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.app.ProgressDialog
import android.content.SharedPreferences
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import okhttp3.Response
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
@ -18,19 +18,19 @@ class AddCategoryActivity : Activity() {
private lateinit var DAO: CategoryDAO
private lateinit var sharedPreferences: SharedPreferences
private lateinit var loadingDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_category)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
DAO = CategoryDAO(dbHelper)
@ -48,25 +48,35 @@ class AddCategoryActivity : Activity() {
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "")!!
val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
val currentGroup: Int = if (sharedPreferences.getString(
"currentGroup",
"offline"
)!! == "offline"
) 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
findViewById<Button>(R.id.saveButton).setOnClickListener {
if (categoryNameTextEdit.text.toString() == "") {
Toast.makeText(this, getString(R.string.category_name_required), Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
loadingDialog.show()
if (category.id == 0) { // Inserting new category
val newCategory = Category(0, categoryNameTextEdit.text.toString())
newCategory.id = DAO.addCategory(newCategory).toInt()
if (currentGroup > 0) net.uploadCategory(currentGroup, newCategory)
if (currentGroup > 0) net.uploadCategory(currentGroup, newCategory, this::notifyUserAndExit)
} else { // Updating existing category
category.name = categoryNameTextEdit.text.toString()
DAO.updateCategory(category)
if (currentGroup > 0) net.updateCategory(currentGroup, category)
if (currentGroup > 0) net.updateCategory(currentGroup, category, this::notifyUserAndExit)
}
}
}
fun notifyUserAndExit(response: Response) {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
loadingDialog.dismiss()
finish()
}
}
}

View File

@ -1,6 +1,7 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
@ -13,8 +14,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions
@ -56,18 +55,19 @@ class AddProductActivity : AppCompatActivity() {
private lateinit var abstractProductDAO: AbstractProductDAO
private lateinit var sharedPreferences: SharedPreferences
private lateinit var loadingDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_add_product)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
productDAO = ProductDAO(dbHelper)
@ -177,23 +177,17 @@ class AddProductActivity : AppCompatActivity() {
return@setOnClickListener
}
val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
loadingDialog.show()
var response: Response? = null
val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
if (updatingExistentProduct) {
productDAO.updateProduct(product!!)
if (currentGroup > 0) response = net.updateProduct(currentGroup, product!!)
if (currentGroup > 0) net.updateProduct(currentGroup, product!!, this::notifyUserAndExit)
} else {
product!!.id = productDAO.insertNewProduct(product!!).toInt()
if (currentGroup > 0) response = net.uploadProduct(currentGroup, product!!)
if (currentGroup > 0) net.uploadProduct(currentGroup, product!!, this::notifyUserAndExit)
}
if (response != null) {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
finish()
}
update()
@ -207,6 +201,14 @@ class AddProductActivity : AppCompatActivity() {
}
fun notifyUserAndExit(response: Response) {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
loadingDialog.dismiss()
finish()
}
}
private val intentLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {

View File

@ -7,8 +7,6 @@ import android.os.Bundle
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.database.ProductDAO
@ -26,13 +24,7 @@ class ExpiryCalendarActivity : AppCompatActivity() {
setContentView(R.layout.fragment_expiry_dates)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)
productDAO = ProductDAO(dbHelper)

View File

@ -18,7 +18,7 @@ class FindBarcodelessAbstractProduct() : AppCompatActivity() {
binding = ActivityFindBarcodelessAbstractProductBinding.inflate(layoutInflater)
val ft = supportFragmentManager.beginTransaction()
val fragment = StorageFragment.newInstance("barcodeless", arrayOf("", " "))
val fragment = StorageFragment.newInstance("barcodeless", arrayOf("", " ", " "))
ft.replace(R.id.content, fragment)
ft.commit()

View File

@ -15,10 +15,7 @@ import java.io.File
class FullscreenActivity : Activity() {
private lateinit var binding: ActivityFullscreenBinding
private lateinit var fullscreenImageView: ImageView
private lateinit var fullscreenContentControls: LinearLayout
private val hideHandler = Handler(Looper.myLooper()!!)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@ -1,12 +1,12 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.ProgressDialog
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityGroupBinding
import org.foxarmy.barcodescannerforemployees.noInternetConnectionAvailableNotification
@ -20,13 +20,12 @@ class GroupActivity : AppCompatActivity() {
binding = ActivityGroupBinding.inflate(layoutInflater)
setContentView(binding.root)
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
binding.createGroupButton.setOnClickListener {
val groupName = binding.groupNameTextEdit.text.toString()
@ -36,10 +35,14 @@ class GroupActivity : AppCompatActivity() {
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
if (!net.serverIsAvailable(this)) {
net.serverIsAvailable(this, { isServerAvailable ->
if (!isServerAvailable) {
noInternetConnectionAvailableNotification(this)
} else {
val response = net.createGroup(groupName, groupPassword)
runOnUiThread {
loadingDialog.show()
}
net.createGroup(groupName, groupPassword, { response ->
val responseText = response.body!!.string()
if (response.code == 200) {
@ -47,13 +50,21 @@ class GroupActivity : AppCompatActivity() {
currentGroups!!.add(responseText)
sharedPreferences.edit().putStringSet("groups", currentGroups).apply()
sharedPreferences.edit().putString("currentGroup", responseText).apply()
runOnUiThread {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
loadingDialog.dismiss()
finish()
}
} else {
runOnUiThread {
loadingDialog.dismiss()
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show()
}
}
})
}
})
}
binding.joinGroupButton.setOnClickListener {
@ -65,18 +76,25 @@ class GroupActivity : AppCompatActivity() {
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
if (!net.serverIsAvailable(this)) {
net.serverIsAvailable(this) { isServerAvailable ->
if (!isServerAvailable) {
noInternetConnectionAvailableNotification(this)
} else {
val requestGroupIdResponse = net.getGroupId(groupName)
runOnUiThread {
loadingDialog.show()
}
net.getGroupId(groupName, { requestGroupIdResponse ->
var groupId: Int
try {
groupId = requestGroupIdResponse.toInt()
} catch (e: Exception) {
runOnUiThread {
loadingDialog.dismiss()
Toast.makeText(this, requestGroupIdResponse, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val response = net.joinGroup(groupId, groupPassword)
return@getGroupId
}
net.joinGroup(groupId, groupPassword, { response ->
val responseText = response.body!!.string()
if (response.code == 200) {
@ -84,13 +102,22 @@ class GroupActivity : AppCompatActivity() {
currentGroups!!.add(groupId.toString())
sharedPreferences.edit().putStringSet("groups", currentGroups).apply()
sharedPreferences.edit().putString("currentGroup", groupId.toString()).apply()
runOnUiThread {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
loadingDialog.dismiss()
finish()
}
} else {
runOnUiThread {
loadingDialog.dismiss()
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show()
}
}
})
})
}
}
}
}
}

View File

@ -1,17 +1,16 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.ProgressDialog
import android.content.Intent
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityLoginBinding
import org.foxarmy.barcodescannerforemployees.noInternetConnectionAvailableNotification
import org.foxarmy.barcodescannerforemployees.parseArray
import org.foxarmy.barcodescannerforemployees.parseIntArray
import org.json.JSONObject
class LoginActivity : AppCompatActivity() {
@ -24,15 +23,14 @@ class LoginActivity : AppCompatActivity() {
binding = ActivityLoginBinding.inflate(layoutInflater);
setContentView(binding.root)
val loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
fillUpLanguagesSpinner()
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
binding.loginButton.setOnClickListener {
val server = binding.serverTextEdit.text.toString()
@ -45,13 +43,19 @@ class LoginActivity : AppCompatActivity() {
net.language = sharedPreferences.getString("language", "en-US")!!
net.server = server
if (!net.serverIsAvailable(this)) {
net.serverIsAvailable(this, { isServerAvailable ->
if (!isServerAvailable) {
noInternetConnectionAvailableNotification(this)
} else {
val response = net.login(username, password)
runOnUiThread {
loadingDialog.show()
}
net.login(username, password, { response ->
val responseText = response.body!!.string()
if (response.code != 200) {
runOnUiThread {
Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show()
}
} else {
val json = JSONObject(responseText)
sharedPreferences.edit().putString("token", json["token"].toString()).apply()
@ -59,18 +63,25 @@ class LoginActivity : AppCompatActivity() {
sharedPreferences.edit().putInt("userId", json["id"].toString().toInt()).apply()
sharedPreferences.edit().putString("server", server).apply()
val r = net.getMyGroups().body!!.string()
val myGroups = parseArray(r).map { a -> a.toString()}
net.getMyGroups({ response ->
runOnUiThread {
loadingDialog.dismiss()
}
val r = response.body!!.string()
if (r == "" || r == "[]") {
goToActivity("GroupActivity")
}
val myGroups = parseIntArray(r).map { a -> a.toString() }
sharedPreferences.edit().putStringSet("groups", myGroups.toSet()).apply()
sharedPreferences.edit().putString("currentGroup", myGroups[0]).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
goToActivity("MainActivity")
})
}
})
}
})
}
binding.registerButton.setOnClickListener {
@ -85,24 +96,30 @@ class LoginActivity : AppCompatActivity() {
net.language = language
net.server = server
if (!net.serverIsAvailable(this)) {
net.serverIsAvailable(this, {isServerAvailable ->
if (!isServerAvailable) {
noInternetConnectionAvailableNotification(this)
} else {
val response = net.registerAccount(username, password);
net.registerAccount(username, password, {response ->
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()
net.login(username, password, {response ->
val token = JSONObject(response.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()
runOnUiThread {
loadingDialog.dismiss()
}
goToActivity("GroupActivity")
})
}
})
}
})
}
binding.offlineButton.setOnClickListener {
@ -117,9 +134,22 @@ class LoginActivity : AppCompatActivity() {
private fun fillUpLanguagesSpinner() {
val languages = resources.getStringArray(R.array.languages)
val arrayAdapter = ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, languages)
val arrayAdapter =
ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, languages)
arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
binding.languageSpinner.adapter = arrayAdapter
}
private fun goToActivity(activityName: String) {
runOnUiThread {
val intent = Intent(this, when(activityName) {
"MainActivity" -> MainActivity::class.java
"GroupActivity" -> GroupActivity::class.java
"LoginActivity" -> LoginActivity::class.java
else -> LoginActivity::class.java
})
startActivity(intent)
finish()
}
}
}

View File

@ -1,8 +1,10 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.app.ProgressDialog
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.Menu
@ -14,8 +16,6 @@ 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.*
@ -43,16 +43,36 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
private var actionBarDrawerToggle: ActionBarDrawerToggle? = null
private lateinit var ws: WebSocketClient
var selectionMode = false
private var selectedAmount = 0
private lateinit var sharedPreferences: SharedPreferences
private lateinit var loadingDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
sharedPreferences = getPreferences(this)
setSupportActionBar(binding.toolbar)
setupViewPager(binding.tabViewpager)
binding.tabTablayout.setupWithViewPager(binding.tabViewpager)
binding.tabTablayout.setOnDragListener { _, _ ->
selectionMode = false
selectedAmount = 0
true
}
drawerLayout = binding.drawerLayout
actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.nav_open, R.string.nav_close)
@ -62,14 +82,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
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 { _ ->
val expiryCalendarIntent = Intent(this, ExpiryCalendarActivity::class.java)
val extras = Bundle()
@ -77,7 +89,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
ContextCompat.startActivity(this, expiryCalendarIntent, extras)
}
binding.newElementFab.setOnClickListener { view ->
binding.newElementFab.setOnClickListener { _ ->
val currentPosition = binding.tabTablayout.selectedTabPosition
val fragment = adapter.getItem(currentPosition)
@ -112,26 +124,21 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val net = Net()
net.server = sharedPreferences.getString("server", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!
if ( currentGroup != "offline" && !net.serverIsAvailable(this)) {
net.serverIsAvailable(this, {isServerAvailable ->
if ( currentGroup != "offline" && !isServerAvailable) {
runOnUiThread {
noInternetConnectionAvailableNotification(this)
} else if (currentGroup != "offline" && net.serverIsAvailable(this)) {
}
} else if (currentGroup != "offline" && isServerAvailable) {
synchronize()
ws = WebSocketClient(this, sharedPreferences.getString("server", "")!!)
val token = sharedPreferences.getString("token", "")!!
ws.connect(token, currentGroup)
}
})
}
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()
@ -142,7 +149,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
val data = JSONObject(net.synchronize(currentGroup).body!!.string())
net.synchronize(currentGroup, {response ->
val data = JSONObject(response.body!!.string())
val remoteAbstractProducts = data["abstract_products"] as JSONArray
val remoteProducts = data["products"] as JSONArray
@ -161,7 +169,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
syncAbstractProducts(net, currentGroup, abstractProductDAO, remoteAbstractProducts, localAbstractProducts)
syncProducts(productDAO, remoteProducts, localProducts)
syncCategories(categoryDAO, remoteCategories, localCategories)
})
}
private fun syncCategories(
@ -304,12 +312,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val pictureFile =
File(picturesDir, "${remoteAbstractProduct["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}"
net.downloadImage(url, pictureFile)
net.downloadImage(url, pictureFile, {
val newAbstractProduct = AbstractProduct.createFromJSON(remoteAbstractProduct)
abstractProductDAO.addAbstractProduct(newAbstractProduct)
})
}
if (abstractProductInRemoteDB != null && abstractProductInLocalDB != null) {
@ -319,11 +326,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val pictureFile =
File(File(filesDir, "pictures"), "${abstractProductInRemoteDB["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}"
net.downloadImage(url, pictureFile)
net.downloadImage(url, pictureFile, {
val updatedData = AbstractProduct.createFromJSON(abstractProductInRemoteDB)
abstractProductDAO.updateAbstractProduct(updatedData)
})
}
}
}
@ -337,6 +344,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
adapter.addFragment(CategoriesFragment(), getString(R.string.categories_title))
viewpager.adapter = adapter
viewpager.offscreenPageLimit = 3
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -346,13 +354,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
private fun isOffline(): Boolean {
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
return sharedPreferences.getString("currentGroup", "") == "offline"
}
@ -380,6 +381,15 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
R.id.nav_settings -> {
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)
@ -495,4 +505,17 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val storageFragment = fragment as StorageFragment
storageFragment.filterByCategory(id)
}
fun addSelection() {
selectionMode = true
selectedAmount ++
}
fun removeSelection() {
selectedAmount --
if (selectedAmount <= 0) {
selectedAmount = 0
selectionMode = false
}
}
}

View File

@ -1,19 +1,27 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.PopupMenu
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityManageGroupBinding
import org.foxarmy.barcodescannerforemployees.parseArray
import org.foxarmy.barcodescannerforemployees.parseIntArray
import org.foxarmy.barcodescannerforemployees.views.GroupMemberView
class ManageGroupActivity : AppCompatActivity(){
class ManageGroupActivity : AppCompatActivity() {
private lateinit var binding: ActivityManageGroupBinding
private var isAdmin: Boolean = true
private var isAdmin: Boolean = false
private lateinit var sharedPreferences: SharedPreferences
private lateinit var net: Net
private var groupId: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -21,56 +29,150 @@ class ManageGroupActivity : AppCompatActivity(){
binding = ActivityManageGroupBinding.inflate(layoutInflater)
setContentView(binding.root)
val groupId = intent.extras!!.getInt("groupId")
groupId = intent.extras!!.getInt("groupId")
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
masterKeyAlias,
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val net = Net()
net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val users = parseArray(net.getUsersInGroup(groupId).body!!.string())
setUpMembers()
for (user in users) {
val groupMemberView = GroupMemberView(this, this as Context, net.getUsernameById(user).body!!.string(), user)
binding.renameButton.setOnClickListener {
val input = EditText(this)
AlertDialog.Builder(this)
.setTitle(getString(R.string.new_group_name))
.setView(input)
.setPositiveButton(getString(R.string.ok)) { _, _ ->
val newName = input.text.toString()
net.renameGroup(groupId, newName, { response ->
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
})
binding.groupsContent.addView(groupMemberView)
}
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
}.show()
}
isAdmin = amIAnAdminIn(groupId)
binding.leaveButton.setOnClickListener {
net.leaveGroup(groupId, { response ->
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
if (response.code == 200) {
val groups = sharedPreferences.getStringSet("groups", emptySet())!!
groups.remove(groupId.toString())
sharedPreferences.edit().putStringSet("groups", groups).apply()
sharedPreferences.edit().putString("currentGroup", "").apply()
if (groups.isEmpty()) {
runOnUiThread {
val intent = Intent(this, GroupActivity::class.java)
startActivity(intent)
finish()
}
}
sharedPreferences.edit().putString("currentGroup", groups.toList()[0]).apply()
runOnUiThread {
finish()
}
}
})
}
setUpAdminRelatedButtons()
}
private fun setUpMembers() {
net.getUsersInGroup(groupId, { response ->
val users = parseIntArray(response.body!!.string())
for (user in users) {
net.getUsernameById(user, { response ->
val groupMemberView = GroupMemberView(this, this as Context, response.body!!.string(), user)
groupMemberView.setOnLongClickListener { view ->
amIAnAdminIn(groupId, { amIAnAdmin ->
if (!amIAnAdmin) return@amIAnAdminIn
runOnUiThread {
val popupMenu = PopupMenu(this, groupMemberView)
popupMenu.inflate(R.menu.user_pop_menu)
popupMenu.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.kick -> {
setUpMembers()
false
}
R.id.transfer_ownership -> {
AlertDialog.Builder(this)
.setMessage(getString(R.string.transfer_ownership_confirmation))
.setPositiveButton(R.string.yes) { _, _ ->
net.transfer_ownership(
groupId,
(view as GroupMemberView).userId,
{ response ->
runOnUiThread {
Toast.makeText(
this,
response.body!!.string(),
Toast.LENGTH_SHORT
).show()
setUpAdminRelatedButtons()
}
})
}
.setNegativeButton(R.string.no) { _, _ -> }
.show()
true
}
else -> false
}
}
runOnUiThread {
popupMenu.show()
}
}
})
true
}
runOnUiThread {
binding.groupsContent.addView(groupMemberView)
}
})
}
})
}
fun amIAnAdminIn(groupId: Int, callback: (Boolean) -> Unit) {
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
net.getGroupAdminId(groupId, { response ->
val result = sharedPreferences.getInt("userId", 0) == response.body!!.string().toInt()
callback(result)
})
}
fun setUpAdminRelatedButtons() {
amIAnAdminIn(groupId, { isAdmin ->
runOnUiThread {
if (!isAdmin) {
binding.renameButton.visibility = View.GONE
}
}
fun amIAnAdminIn(groupId: Int): Boolean {
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val result = sharedPreferences.getInt("userId", 0) == net.getGroupAdminId(groupId).body!!.string().toInt()
return result
})
}
}

View File

@ -5,12 +5,10 @@ import android.content.Intent
import android.os.Bundle
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityMyGroupsBinding
import org.foxarmy.barcodescannerforemployees.parseArray
import org.foxarmy.barcodescannerforemployees.parseIntArray
import org.foxarmy.barcodescannerforemployees.views.GroupView
class MyGroupsActivity : AppCompatActivity(){
@ -22,13 +20,7 @@ class MyGroupsActivity : AppCompatActivity(){
binding = ActivityMyGroupsBinding.inflate(layoutInflater)
setContentView(binding.root)
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val net = Net()
@ -36,20 +28,28 @@ class MyGroupsActivity : AppCompatActivity(){
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val groups = parseArray(net.getMyGroups().body!!.string())
net.getMyGroups { response ->
val groups = parseIntArray(response.body!!.string())
val container = findViewById<LinearLayout>(R.id.groupsLayout)
for (group in groups) {
val groupView = GroupView(this, this as Context, net.getGroupName(group), group)
net.getGroupName(group, {response ->
val groupView = GroupView(this, this as Context, response, group)
runOnUiThread {
container.addView(groupView)
}
})
}
binding.newGroup.setOnClickListener {
runOnUiThread {
val intent = Intent(this, GroupActivity::class.java)
startActivity(intent)
}
}
}
}
}

View File

@ -4,8 +4,8 @@ import android.app.Activity
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.parseStringList
class NavigatorActivity : Activity() {
@ -14,27 +14,24 @@ class NavigatorActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
var intent = Intent(this, MainActivity::class.java)
if (!isInGroup() && !isOffline()) {
isInGroup { flag ->
if (!flag && !isOffline()) {
intent = Intent(this, GroupActivity::class.java)
}
if (!isAuthenticated() && !isOffline()) {
intent = Intent(this, LoginActivity::class.java);
}
runOnUiThread {
startActivity(intent);
finish();
}
}
}
private fun isOffline(): Boolean {
return sharedPreferences.getString("currentGroup", "")!! == "offline"
@ -44,7 +41,28 @@ class NavigatorActivity : Activity() {
return sharedPreferences.getString("token", "") != ""
}
private fun isInGroup(): Boolean {
return sharedPreferences.getStringSet("groups", emptySet())!!.isNotEmpty()
private fun isInGroup(callback: (Boolean) -> Unit) {
val groups = sharedPreferences.getStringSet("groups", emptySet())!!
if (groups.isEmpty()) {
if (sharedPreferences.getString("token", "")!! == "") callback(false)
val net = Net()
net.language = sharedPreferences.getString("language", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
net.getMyGroups { response ->
val responseBody = response.body!!.string()
if (responseBody == "" || responseBody == "[]") callback(false)
val groupsFromServer = parseStringList(responseBody)
if (groupsFromServer.isNotEmpty()) {
sharedPreferences.edit().putStringSet("groups", groupsFromServer.toSet()).apply()
sharedPreferences.edit().putString("currentGroup", groupsFromServer[0]).apply()
callback(true)
} else {
callback(false)
}
}
}
callback(true)
}
}

View File

@ -6,8 +6,6 @@ import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.databinding.ActivitySettingsBinding
@ -17,17 +15,12 @@ class SettingsActivity : AppCompatActivity() {
lateinit var myGroups: List<String>
lateinit var currentGroup: String
lateinit var groupsNames: MutableList<String>
lateinit var namesMap: MutableMap<Int, String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
myGroups = sharedPreferences.getStringSet("groups", emptySet())!!.toList()
currentGroup = sharedPreferences.getString("currentGroup", "offline")!!
@ -46,21 +39,27 @@ class SettingsActivity : AppCompatActivity() {
if (isOffline()) {
binding.currentGroupSetting.visibility = View.GONE
} else {
groupsNames = mutableListOf()
namesMap = mutableMapOf()
for (myGroup in myGroups) {
groupsNames.add(net.getGroupName(myGroup.toInt()))
}
net.getGroupName(myGroup.toInt(), { name ->
namesMap[myGroup.toInt()] = name
fillUpCurrentGroupSpinner()
})
}
}
binding.saveButton.setOnClickListener {
sharedPreferences.edit().putString("currentGroup", net.getGroupId(binding.currentGroupSpinner.selectedItem.toString())).apply()
sharedPreferences.edit().putInt("imageCompression", binding.imageCompressionFactorSeekBar.progress + 1).apply()
net.getGroupId(binding.currentGroupSpinner.selectedItem.toString(), { groupId ->
sharedPreferences.edit().putString("currentGroup", groupId).apply()
sharedPreferences.edit().putInt("imageCompression", binding.imageCompressionFactorSeekBar.progress + 1)
.apply()
runOnUiThread {
setResult(Activity.RESULT_OK)
finish()
}
})
}
binding.cancelButton.setOnClickListener {
setResult(Activity.RESULT_CANCELED)
@ -78,14 +77,27 @@ class SettingsActivity : AppCompatActivity() {
}
private fun fillUpCurrentGroupSpinner() {
runOnUiThread {
if (currentGroup == "offline") {
binding.currentGroupSetting.visibility = View.GONE
}
groupsNames = mutableListOf()
groupsNames.clear()
val sortedMap = namesMap.entries.sortedBy { it.key }.associate { it.toPair() }
for ((id, name) in sortedMap) {
groupsNames.add(name)
}
val arrayAdapter =
ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, groupsNames)
arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
binding.currentGroupSpinner.adapter = arrayAdapter
binding.currentGroupSpinner.setSelection(myGroups.indexOf(currentGroup))
if (myGroups.indexOf(currentGroup) < groupsNames.size) binding.currentGroupSpinner.setSelection(
myGroups.indexOf(
currentGroup
)
)
}
}
}

View File

@ -308,7 +308,7 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) {
}
"barcodeless" -> {
selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = ? OR ${AbstractProductContract.AbstractProductEntry.BARCODE} = ?"
selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = ? OR ${AbstractProductContract.AbstractProductEntry.BARCODE} = ? OR ${AbstractProductContract.AbstractProductEntry.BARCODE} = ?"
selectionArgs = filter
}
}

View File

@ -11,36 +11,31 @@ import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.AddCategoryActivity
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.getPreferences
import org.foxarmy.barcodescannerforemployees.views.CategoryView
import kotlin.concurrent.thread
class CategoriesFragment : Fragment() {
private lateinit var sharedPreferences: SharedPreferences
private lateinit var categoryDAO: CategoryDAO
private var updateInProgress = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
requireContext(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = getPreferences(requireContext())
prepareDatabaseConnection()
}
private fun prepareDatabaseConnection() {
val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "database")!!)
val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "offline")!!)
categoryDAO = CategoryDAO(dbHelper)
}
@ -58,6 +53,7 @@ class CategoriesFragment : Fragment() {
}
fun removeSelected() {
thread {
val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
var deleted = false
@ -70,22 +66,27 @@ class CategoriesFragment : Fragment() {
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
categoryDAO.eraseCategory(view.category.id, requireContext())
val response = net.deleteCategory(currentGroup, view.category.id)
net.deleteCategory(currentGroup, view.category.id, {response ->
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
})
deleted = true
}
}
if (!deleted) {
activity!!.runOnUiThread {
Toast.makeText(requireContext(), getString(R.string.nothing_to_delete), Toast.LENGTH_SHORT).show()
}
}
updateContent()
}
}
fun updateSelected() {
thread {
val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) {
if (view.isCategorySelected) {
@ -93,24 +94,37 @@ class CategoriesFragment : Fragment() {
val extras = Bundle()
extras.putParcelable("category", view.category)
addCategoryIntent.putExtras(extras)
activity!!.runOnUiThread {
ContextCompat.startActivity(context!!, addCategoryIntent, extras)
}
}
}
}
}
fun updateContent() {
thread {
if (updateInProgress) return@thread
updateInProgress = true
prepareDatabaseConnection()
val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
activity!!.runOnUiThread {
layout?.removeAllViews()
}
val categories = categoryDAO.getAllCategories()
for (category in categories) {
val categoryView = CategoryView(requireActivity(), requireContext(), category)
activity!!.runOnUiThread {
layout?.addView(categoryView)
}
}
updateInProgress = false
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

View File

@ -14,14 +14,13 @@ import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.gridlayout.widget.GridLayout
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.AddProductActivity
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.database.ProductDAO
import org.foxarmy.barcodescannerforemployees.databinding.FragmentShelfBinding
import org.foxarmy.barcodescannerforemployees.getPreferences
import org.foxarmy.barcodescannerforemployees.views.ProductView
import kotlin.concurrent.thread
@ -38,16 +37,9 @@ class ShelfFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
requireContext(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = getPreferences(requireContext())
prepareDatabaseConnection()
}
override fun onCreateView(
@ -122,11 +114,12 @@ class ShelfFragment : Fragment() {
}
fun updateContent() {
prepareDatabaseConnection()
thread {
if (updateInProgress) return@thread
updateInProgress = true
prepareDatabaseConnection()
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
activity!!.runOnUiThread {
grv?.removeAllViews()
}
@ -144,7 +137,6 @@ class ShelfFragment : Fragment() {
}
}
updateInProgress = false
}
}
@ -165,10 +157,11 @@ class ShelfFragment : Fragment() {
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
productDAO.eraseProduct(view.product.id)
val response = net.deleteProduct(currentGroup, view.product.id)
net.deleteProduct(currentGroup, view.product.id, {response ->
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
})
deleted = true
}

View File

@ -7,11 +7,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.AddAbstractProductActivity
@ -20,6 +19,7 @@ import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.databinding.FragmentStorageBinding
import org.foxarmy.barcodescannerforemployees.generateThumbnailForImage
import org.foxarmy.barcodescannerforemployees.getPreferences
import org.foxarmy.barcodescannerforemployees.views.AbstractProductView
import kotlin.concurrent.thread
@ -32,23 +32,18 @@ class StorageFragment : Fragment() {
private lateinit var sharedPreferences: SharedPreferences
private lateinit var abstractProductDAO: AbstractProductDAO
private var updateInProcess = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
requireContext(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
binding = FragmentStorageBinding.inflate(inflater)
sharedPreferences = getPreferences(requireContext())
prepareDatabaseConnection()
binding = FragmentStorageBinding.inflate(inflater)
fillUpSortBySpinner()
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
@ -107,10 +102,12 @@ class StorageFragment : Fragment() {
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
abstractProductDAO.eraseAbstractProduct(view.abstractProduct.id, requireContext())
val response = net.deleteAbstractProduct(currentGroup, view.abstractProduct.id)
net.deleteAbstractProduct(currentGroup, view.abstractProduct.id, {response ->
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
})
deleted = true
}
@ -141,10 +138,14 @@ class StorageFragment : Fragment() {
}
fun updateContent() {
prepareDatabaseConnection()
thread {
if (updateInProcess) return@thread
updateInProcess = true
prepareDatabaseConnection()
val grv = binding.contentGridLayout
activity!!.runOnUiThread{
activity!!.runOnUiThread {
grv.removeAllViews()
}
@ -160,26 +161,36 @@ class StorageFragment : Fragment() {
)
if (filterBy == "barcodeless") {
abstractProductView.setOnClickListener {
abstractProductView.also {
it.setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
abstractProductView.findViewById<TextView>(R.id.productNameView).setOnClickListener {
it.findViewById<TextView>(R.id.productNameView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<ConstraintLayout>(R.id.productLayout).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<TextView>(R.id.categoryView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<TextView>(R.id.unitView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<TextView>(R.id.productNetWeightView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
}
}
activity!!.runOnUiThread{
grv.addView(abstractProductView)
}
}
}.join()
updateInProcess = false
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
updateContent()
}
fun filterByCategory(id: Int) {
filterBy = "category"
@ -194,7 +205,7 @@ class StorageFragment : Fragment() {
val args = Bundle()
args.putString("filterBy", filterBy)
args.putStringArray("filter", filter)
fragment.setArguments(args)
fragment.arguments = args
return fragment
}

View File

@ -14,19 +14,15 @@ import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.*
import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity
import org.foxarmy.barcodescannerforemployees.activities.MainActivity
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.getActivity
import org.foxarmy.barcodescannerforemployees.getImageUri
import org.foxarmy.barcodescannerforemployees.getUnitNameById
import java.io.File
class AbstractProductView: LinearLayout {
class AbstractProductView : LinearLayout {
private var productLayout: ConstraintLayout? = null
private var productPicture: ImageView? = null
private var productNameField: TextView? = null
@ -42,16 +38,10 @@ class AbstractProductView: LinearLayout {
constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!!
val inflater:LayoutInflater = activity.layoutInflater
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
)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
categoryDAO = CategoryDAO(dbHelper)
@ -60,23 +50,17 @@ class AbstractProductView: LinearLayout {
constructor(activity: Activity, context: Context, abstractProduct: AbstractProduct) : super(context) {
this.abstractProduct = abstractProduct
this.activity = activity
val inflater:LayoutInflater = activity.layoutInflater
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.abstract_product_view, this)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
categoryDAO = CategoryDAO(dbHelper)
update()
}
fun init () {
private fun init() {
productLayout = findViewById(R.id.productLayout)
productPicture = findViewById(R.id.productPicture)
productNameField = findViewById(R.id.productNameView)
@ -84,30 +68,8 @@ class AbstractProductView: LinearLayout {
categoryField = findViewById(R.id.categoryView)
unitField = findViewById(R.id.unitView)
productPicture!!.setOnClickListener {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
}
productLayout!!.setOnClickListener {
}
setUpClicks()
unitField?.text = getUnitNameById(context, abstractProduct.unit)
productLayout!!.setOnLongClickListener {
isProductSelected = !isProductSelected
this.background = ContextCompat.getDrawable(context, if (isProductSelected) R.drawable.outline_selected else R.drawable.outline)
true
}
productNameField!!.setOnClickListener {
Toast.makeText(activity, productNameField!!.text, Toast.LENGTH_SHORT).show()
}
}
fun update() {
@ -117,10 +79,126 @@ class AbstractProductView: LinearLayout {
thumbnailsDir.mkdirs()
val imageUri = getImageUri(activity, File(thumbnailsDir, "${abstractProduct.imageHash}.webp"))
productPicture!!.setImageURI(imageUri)
// productPicture!!.rotation = 90f
productNameField!!.text = abstractProduct.name
netWeightField!!.text = abstractProduct.netWeight.toString()
categoryField!!.text = categoryDAO.getCategoryNameById(abstractProduct.category)
}
private fun setUpClicks() {
productLayout!!.setOnLongClickListener { onAnyLongClick() }
productNameField!!.setOnLongClickListener { onNameLongClick() }
productPicture!!.setOnLongClickListener { onImageLongClick() }
netWeightField!!.setOnLongClickListener { onAnyLongClick() }
categoryField!!.setOnLongClickListener { onAnyLongClick() }
unitField!!.setOnLongClickListener { onAnyLongClick() }
productLayout!!.setOnClickListener { onAnyClick() }
netWeightField!!.setOnClickListener { onAnyClick() }
categoryField!!.setOnClickListener { onAnyClick() }
unitField!!.setOnClickListener { onAnyClick() }
productPicture!!.setOnClickListener { onImageLongClick() }
productNameField!!.setOnClickListener { onNameLongClick() }
}
private fun onAnyClick(): Boolean {
if (activity !is MainActivity) return false
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
isProductSelected = !isProductSelected
if (isProductSelected) {
mainActivity.addSelection()
} else {
mainActivity.removeSelection()
}
activity.runOnUiThread {
this.background = ContextCompat.getDrawable(
context,
if (isProductSelected) R.drawable.outline_selected else R.drawable.outline
)
}
return true
} else {
return false
}
}
private fun onAnyLongClick(): Boolean {
if (activity !is MainActivity) return false
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
isProductSelected = !isProductSelected
if (isProductSelected) {
mainActivity.addSelection()
} else {
mainActivity.removeSelection()
}
activity.runOnUiThread {
this.background = ContextCompat.getDrawable(
context,
if (isProductSelected) R.drawable.outline_selected else R.drawable.outline
)
}
return true
} else {
mainActivity.addSelection()
isProductSelected = !isProductSelected
activity.runOnUiThread {
this.background = ContextCompat.getDrawable(
context,
if (isProductSelected) R.drawable.outline_selected else R.drawable.outline
)
}
return true
}
}
private fun onImageLongClick(): Boolean {
if (activity !is MainActivity) {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
return true
}
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
return onAnyLongClick()
} else {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
}
return true
}
private fun onNameLongClick(): Boolean {
if (activity !is MainActivity) {
activity.runOnUiThread {
Toast.makeText(activity, productNameField!!.text, Toast.LENGTH_SHORT).show()
}
return true
}
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
return onAnyLongClick()
} else {
activity.runOnUiThread {
Toast.makeText(activity, productNameField!!.text, Toast.LENGTH_SHORT).show()
}
}
return true
}
}

View File

@ -7,16 +7,16 @@ import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.MainActivity
import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.getPreferences
class CategoryView : LinearLayout {
var category: Category
val categoryName: TextView
val amountOfProducts: TextView
var isCategorySelected = false
@ -27,13 +27,7 @@ class CategoryView : LinearLayout {
constructor(activity: Activity, context: Context, category: Category) : super(context) {
this.category = category
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
@ -41,7 +35,7 @@ class CategoryView : LinearLayout {
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.category_view, this)
this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline)
updateStroke()
categoryName = findViewById(R.id.categoryNameTextView)
amountOfProducts = findViewById(R.id.amountOfProducts)
@ -49,13 +43,47 @@ class CategoryView : LinearLayout {
categoryName.text = category.name
amountOfProducts.text = abstractProductDAO.getAmountOfAbstractProductsInCategory(category.id).toString()
setOnLongClickListener {
isCategorySelected = !isCategorySelected
this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline)
true
setUpClicks()
}
setOnClickListener {
(activity as MainActivity).filterAbstractProductsByCategory(category.id)
private fun setUpClicks() {
categoryName.setOnClickListener { onCategoryClick() }
amountOfProducts.setOnClickListener { onCategoryClick() }
categoryName.setOnLongClickListener { onAnyLongClick() }
amountOfProducts.setOnLongClickListener { onAnyLongClick() }
}
private fun onCategoryClick(): Boolean {
if (context !is MainActivity) return false
val mainActivity = context as MainActivity
if (mainActivity.selectionMode) return onAnyLongClick()
else (context as MainActivity).filterAbstractProductsByCategory(category.id)
return true
}
private fun onAnyLongClick(): Boolean {
if (context !is MainActivity) return false
val mainActivity = context as MainActivity
isCategorySelected = !isCategorySelected
updateStroke()
if (mainActivity.selectionMode) {
if (isCategorySelected) mainActivity.addSelection()
else mainActivity.removeSelection()
} else {
mainActivity.addSelection()
}
return true
}
private fun updateStroke() {
(context as Activity).runOnUiThread {
this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline)
}
}
}

View File

@ -7,12 +7,11 @@ import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.ExpiryCalendarActivity
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.database.ProductDAO
import org.foxarmy.barcodescannerforemployees.getPreferences
import java.text.SimpleDateFormat
class ExpiryGroupView : LinearLayout {
@ -24,13 +23,7 @@ class ExpiryGroupView : LinearLayout {
constructor(activity: Activity, context: Context, representingDate: Long) : super(context) {
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
productDAO = ProductDAO(dbHelper)

View File

@ -10,7 +10,7 @@ import org.foxarmy.barcodescannerforemployees.R
class GroupMemberView : LinearLayout {
private var username: String
private var userId: Int
var userId: Int
constructor(activity: Activity, context: Context, username: String, userId: Int) : super(context) {

View File

@ -13,6 +13,7 @@ import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
@ -21,29 +22,26 @@ import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.math.MathUtils.clamp
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.*
import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity
import org.foxarmy.barcodescannerforemployees.calculateProductFreshness
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.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import org.foxarmy.barcodescannerforemployees.getActivity
import org.foxarmy.barcodescannerforemployees.getImageUri
import java.io.File
import java.text.DecimalFormat
import java.text.SimpleDateFormat
import java.util.*
import kotlin.concurrent.thread
class ProductView: LinearLayout {
class ProductView : LinearLayout {
var product: Product = Product()
var isProductSelected = false
private var activity: Activity
private lateinit var productLayout: ConstraintLayout
private lateinit var productImageView: ImageView
private lateinit var productNameTextView: TextView
private lateinit var productNetWeightTextView: TextView
@ -63,13 +61,7 @@ class ProductView: LinearLayout {
constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!!
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
@ -86,18 +78,13 @@ 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
)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
categoryDAO = CategoryDAO(dbHelper)
productLayout = findViewById(R.id.productLayout)
productImageView = findViewById(R.id.productPicture)
productNameTextView = findViewById(R.id.productNameView)
productNetWeightTextView = findViewById(R.id.productNetWeightView)
@ -106,45 +93,129 @@ class ProductView: LinearLayout {
productLifeSpan = findViewById(R.id.dateSpan)
productFreshnessTextView = findViewById(R.id.freshnessPercentTextView)
findViewById<ConstraintLayout>(R.id.productLayout).setOnLongClickListener {
isProductSelected = !isProductSelected
updateStroke()
true
}
productImageView.setOnClickListener {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
val abstractProduct = abstractProductDAO.findAbstractProductById(product.abstractProductId)
extras.putString("imagehash", abstractProduct!!.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
}
setUpClicks()
update()
}
@RequiresApi(Build.VERSION_CODES.O)
fun update () {
val linkedAbstractProduct: AbstractProduct = abstractProductDAO.findAbstractProductById(product.abstractProductId) ?: return
private fun setUpClicks() {
productLayout.setOnLongClickListener { onAnyLongClick() }
productImageView.setOnLongClickListener { onImageLongClick() }
productNameTextView.setOnLongClickListener { onNameLongClick() }
productNetWeightTextView.setOnLongClickListener { onAnyLongClick() }
productAmountTextView.setOnLongClickListener { onAnyLongClick() }
productCategoryView.setOnLongClickListener { onAnyLongClick() }
productLifeSpan.setOnLongClickListener { onAnyLongClick() }
productFreshnessTextView.setOnLongClickListener { onAnyLongClick() }
productLayout.setOnClickListener { onAnyClick() }
productImageView.setOnClickListener { onImageLongClick() }
productNameTextView.setOnClickListener { onNameLongClick() }
productNetWeightTextView.setOnClickListener { onAnyClick() }
productAmountTextView.setOnClickListener { onAnyClick() }
productCategoryView.setOnClickListener { onAnyClick() }
productLifeSpan.setOnClickListener { onAnyClick() }
productFreshnessTextView.setOnClickListener { onAnyClick() }
}
@RequiresApi(Build.VERSION_CODES.O)
private fun onAnyClick(): Boolean {
if (activity !is MainActivity) return false
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
isProductSelected = !isProductSelected
if (isProductSelected) mainActivity.addSelection()
else mainActivity.removeSelection()
updateStroke()
return true
} else {
return false
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun onAnyLongClick(): Boolean {
if (activity !is MainActivity) return false
val mainActivity = activity as MainActivity
isProductSelected = !isProductSelected
updateStroke()
if (mainActivity.selectionMode) {
if (isProductSelected) mainActivity.addSelection()
else mainActivity.removeSelection()
} else {
mainActivity.addSelection()
}
return true
}
private fun onNameLongClick(): Boolean {
if (activity !is MainActivity) {
Toast.makeText(activity, productNameTextView.text, Toast.LENGTH_SHORT).show()
return true
}
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) return onAnyLongClick()
else Toast.makeText(activity, productNameTextView.text, Toast.LENGTH_SHORT).show()
return true
}
private fun onImageLongClick(): Boolean {
val abstractProduct = abstractProductDAO.findAbstractProductById(product.abstractProductId)
if (activity !is MainActivity) {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct!!.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
return true
}
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
return onAnyLongClick()
} else {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct!!.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
}
return true
}
@RequiresApi(Build.VERSION_CODES.O)
fun update() {
val linkedAbstractProduct: AbstractProduct =
abstractProductDAO.findAbstractProductById(product.abstractProductId) ?: return
val thumbnailsDir = File(activity.cacheDir, "thumbnails")
thumbnailsDir.mkdirs()
val pictureFile = File(thumbnailsDir, "${linkedAbstractProduct.imageHash}.webp")
val imageUri = getImageUri(activity, pictureFile)
productImageView.setImageURI(imageUri)
// productImageView.rotation = 90f
productNameTextView.text = linkedAbstractProduct.name
productNetWeightTextView.text = linkedAbstractProduct.netWeight.toString()
productAmountTextView.text = product.amount.toString()
productCategoryView.text = categoryDAO.getCategoryNameById(linkedAbstractProduct.category)
productLifeSpan.text = "${SimpleDateFormat("dd.MM.yyyy").format(Date(product.dateOfProduction*1000))}-${SimpleDateFormat("dd.MM.yyyy").format(Date(product.dateOfExpiry*1000))}"
productLifeSpan.text = "${SimpleDateFormat("dd.MM.yyyy").format(Date(product.dateOfProduction * 1000))}-${
SimpleDateFormat("dd.MM.yyyy").format(Date(product.dateOfExpiry * 1000))
}"
productFreshnessTextView.text =
if (product.freshness == Double.NEGATIVE_INFINITY || product.freshness == Double.POSITIVE_INFINITY || product.freshness < 0) {
context.getString(R.string.expired)
} else {
"${DecimalFormat("#.#").format(product.freshness*100)}%"
"${DecimalFormat("#.#").format(product.freshness * 100)}%"
}
updateStroke()
@ -152,11 +223,11 @@ class ProductView: LinearLayout {
@RequiresApi(Build.VERSION_CODES.O)
fun updateStroke() {
activity.runOnUiThread {
if (isProductSelected) {
this.background = ContextCompat.getDrawable(context, R.drawable.outline_selected)
} else {
if (product.id != 0) {
thread {
this.outline = GradientDrawable()
backgroundColor = evaluateColor()
strokeColor = darkenColor(backgroundColor, 0.25) // (backgroundColor and 0xfefefe ) shr 1
@ -164,12 +235,12 @@ class ProductView: LinearLayout {
this.outline.setStroke(4, strokeColor)
this.outline.alpha = 84
this.background = outline
}
} else {
this.background = ContextCompat.getDrawable(context, R.drawable.outline)
}
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
fun evaluateColor(): Int {
@ -178,12 +249,14 @@ class ProductView: LinearLayout {
}
fun calculateFreshnessGradient(percentage: Double): Int {
val startColor = ContextCompat.getColor(context, if (percentage > 0.5) R.color.full_freshness else R.color.half_freshness)
val endColor = ContextCompat.getColor(context, if (percentage > 0.5) R.color.half_freshness else R.color.expired_freshness)
val startColor =
ContextCompat.getColor(context, if (percentage > 0.5) R.color.full_freshness else R.color.half_freshness)
val endColor =
ContextCompat.getColor(context, if (percentage > 0.5) R.color.half_freshness else R.color.expired_freshness)
val gradientPosition = 1 - if (percentage > 0.5) (percentage - 0.5) * 2 else percentage * 2
val red = clamp((startColor.red + gradientPosition * (endColor.red - startColor.red )).toInt(), 0, 255)
val red = clamp((startColor.red + gradientPosition * (endColor.red - startColor.red)).toInt(), 0, 255)
val green = clamp((startColor.green + gradientPosition * (endColor.green - startColor.green)).toInt(), 0, 255)
val blue = clamp((startColor.blue + gradientPosition * (endColor.blue - startColor.blue)).toInt(), 0, 255)
@ -194,7 +267,7 @@ class ProductView: LinearLayout {
if (greenHex.length == 1) greenHex = "0$greenHex"
var blueHex = Integer.toHexString(blue)
if (blueHex.length == 1 ) blueHex = "0$blueHex"
if (blueHex.length == 1) blueHex = "0$blueHex"
val colorString = "#$redHex$greenHex$blueHex"
@ -202,7 +275,7 @@ class ProductView: LinearLayout {
}
@RequiresApi(Build.VERSION_CODES.O)
fun darkenColor(color: Int, darkPercent: Double) : Int {
fun darkenColor(color: Int, darkPercent: Double): Int {
val c = Color.valueOf(color)
val red = c.red() * (1 - darkPercent)

View File

@ -8,7 +8,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/productLayout" android:outlineProvider="bounds"
android:background="#00FFFEFE" android:clickable="true">
android:background="#00FFFEFE" android:clickable="true" android:translationZ="0sp">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp" app:srcCompat="@android:drawable/ic_menu_gallery"

View File

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.ShelfFragment"
android:id="@+id/content">
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_height="match_parent">
</androidx.core.widget.NestedScrollView>
<androidx.core.widget.NestedScrollView
android:layout_height="0dp"
tools:context=".fragments.ShelfFragment"
android:id="@+id/content" android:layout_width="0dp" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -39,12 +39,13 @@
<androidx.viewpager.widget.ViewPager
android:id="@+id/tab_viewpager"
android:layout_width="0dp"
android:layout_height="777dp"
android:layout_height="0dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_gravity="bottom"
android:layout_marginTop="2dp"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="6dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="6dp"/>
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="6dp"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/new_element_fab"
android:layout_width="wrap_content"

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/storageLayout">
<fragment
android:id="@+id/nav_host_fragment_content_storage"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_main"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -26,7 +26,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent" app:layout_constraintTop_toBottomOf="@+id/spinner"
android:layout_marginTop="32dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" android:id="@+id/scrollView">
app:layout_constraintEnd_toEndOf="parent" android:id="@+id/scrollView"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/contentGridLayout" app:columnCount="2"

View File

@ -1,13 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/fragment_storage"
tools:context=".fragments.StorageFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:text="@string/sort_by"
@ -16,7 +12,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="@+id/scrollView2"/>
app:layout_constraintBottom_toTopOf="@+id/scrollView2"/>
<Spinner
android:layout_width="0dp"
android:layout_height="32dp" android:id="@+id/spinner"
@ -34,15 +30,16 @@
android:layout_marginEnd="10dp" app:layout_constraintTop_toTopOf="parent"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:id="@+id/scrollView2"
app:layout_constraintTop_toBottomOf="@+id/dropFiltersButton">
app:layout_constraintTop_toBottomOf="@+id/dropFiltersButton"
android:layout_marginTop="4dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/contentGridLayout" app:columnCount="2"
android:layout_height="match_parent" android:id="@+id/contentGridLayout" app:columnCount="2"
app:rowCount="100" android:nestedScrollingEnabled="true">
</androidx.gridlayout.widget.GridLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

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

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/kick" android:id="@+id/kick"/>
<item android:title="@string/transfer_ownership" android:id="@+id/transfer_ownership"/>
</menu>

View File

@ -118,6 +118,14 @@
<string name="ok">Ок</string>
<string name="logout">Выйти</string>
<string name="online_only_feature">Эта возможность доступна только с онлайн аккаунтом</string>
<string name="username_changed">Имя пользователя изменено. Пожалуйста, перезайдите</string>
<string name="password_changed">Пароль изменён. Пожалуйста, перезайдите</string>
<string name="new_group_name">Введите новое имя группы</string>
<string name="kick">Выгнать</string>
<string name="transfer_ownership">Передать владение группой</string>
<string name="transfer_ownership_confirmation">Вы увереных, что хотите передать владение группой этому пользователю?</string>
<string name="loading_please_wait">Загрузка, пожалуйста ждите</string>
<string name="loading">Загрузка</string>
<string-array name="languages">
<item>en-US</item>
<item>ru-RU</item>

View File

@ -116,6 +116,14 @@
<string name="ok">OK</string>
<string name="logout">Log out</string>
<string name="online_only_feature">This feature is online only</string>
<string name="username_changed">Your username has been changed. Please, relogin</string>
<string name="password_changed">You password was changed. Please, relogin</string>
<string name="new_group_name">Enter new group name</string>
<string name="kick">Kick</string>
<string name="transfer_ownership">Transfer ownership</string>
<string name="transfer_ownership_confirmation">Are you sure you want to transfer ownership on current group to that user?</string>
<string name="loading_please_wait">Loading, please wait</string>
<string name="loading">Loading</string>
<string-array name="languages">
<item>en-US</item>
<item>ru-RU</item>