16 Commits

51 changed files with 3294 additions and 785 deletions

View File

@@ -50,13 +50,16 @@ dependencies {
implementation(libs.androidx.activity)
implementation(libs.androidx.legacy.support.v4)
implementation(libs.androidx.fragment)
implementation(libs.androidx.material3.android)
testImplementation(libs.junit)
implementation(libs.volley)
androidTestImplementation(libs.androidx.junit)
implementation(libs.zxing.android.embedded)
implementation("com.google.zxing:core:3.4.1")
androidTestImplementation(libs.androidx.espresso.core)
implementation("com.google.android.material:material:1.3.0-alpha03")
implementation(libs.androidx.security.crypto)
implementation(libs.okhttp)
// Barcode scanning API
implementation (libs.barcode.scanning)

View File

@@ -48,6 +48,34 @@
android:name=".activities.FindBarcodelessAbstractProduct"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.MainActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.LoginActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.AccountSettingsActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.GroupActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.ManageGroupActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.MyGroupsActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.SettingsActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.FullscreenActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
@@ -61,7 +89,7 @@
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.google.firebase.components.activities.MainActivity.provider;com.google.firebase.components.activities.FullscreenActivity.provider;com.google.firebase.components.activities.AddAbstractProductActivity.provider;com.google.firebase.components.activities.AddProductActivity.provider;com.google.firebase.components.activities.ExpiryCalendarActivity.provider;com.google.firebase.components.activities.FindBarcodelessAbstractProduct.provider;com.google.firebase.components.activities.ExpiryCalendarGroupActivity.provider"
android:authorities="com.google.firebase.components.activities.MainActivity.provider;com.google.firebase.components.activities.FullscreenActivity.provider;com.google.firebase.components.activities.AddAbstractProductActivity.provider;com.google.firebase.components.activities.AddProductActivity.provider;com.google.firebase.components.activities.ExpiryCalendarActivity.provider;com.google.firebase.components.activities.FindBarcodelessAbstractProduct.provider;com.google.firebase.components.activities.ExpiryCalendarGroupActivity.provider;com.google.firebase.components.activities.AccountSettingsActivity.provider;com.google.firebase.components.activities.GroupActivity.provider;com.google.firebase.components.activities.SettingsActivity.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
@@ -70,7 +98,7 @@
</provider>
<activity
android:name=".activities.MainActivity"
android:name=".activities.NavigatorActivity"
android:exported="true"
android:theme="@style/Theme.BarcodeScannerForEmployees">
<intent-filter>

View File

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

View File

@@ -0,0 +1,633 @@
package org.foxarmy.barcodescannerforemployees
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlin.concurrent.thread
class Net {
var language = "en-US"
var server = "bsfe.foxarmy.org"
var token = ""
fun requestProductFromOnlineDB(barcode: String): String {
var response = ""
thread {
val url = "https://ean-online.ru/match.php"
val client = OkHttpClient()
val body = FormBody.Builder()
.add("barcode", barcode)
.build()
val request = Request.Builder()
.url(url)
.post(body)
.addHeader("referer", "https://ean-online.ru")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute().body!!.string()
}.join()
return if (response == "") {
"Not found 404"
} else {
response
}
}
fun registerAccount(username: String, password: String): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("username", username)
.add("password", password)
.build()
val request = Request.Builder()
.url("https://$server/api/user/register")
.post(body)
.addHeader("content-type", "application/x-www-form-urlencoded")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun login(username: String, password: String): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("username", username)
.add("password", password)
.build()
val requestLogin = Request.Builder()
.url("https://$server/api/user/login")
.post(body)
.addHeader("content-type", "application/x-www-form-urlencoded")
.addHeader("accept-language", language)
.build()
response = client.newCall(requestLogin).execute()
}.join()
return response
}
fun uploadAbstractProduct(groupId: Int, abstractProduct: AbstractProduct, imageFile: File): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = MultipartBody.Builder()
.setType("multipart/form-data".toMediaType())
.addFormDataPart("file", imageFile.name, imageFile.asRequestBody("image/png".toMediaTypeOrNull()))
.addFormDataPart("groupId", groupId.toString())
.addFormDataPart("localId", abstractProduct.id.toString())
.addFormDataPart("barcode", abstractProduct.barcode)
.addFormDataPart("name", abstractProduct.name)
.addFormDataPart("net_weight", abstractProduct.netWeight.toString())
.addFormDataPart("category", abstractProduct.category.toString())
.addFormDataPart("unit", abstractProduct.unit.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/abstractproduct/create")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun changeUsername(newUsername: String): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
body.add("username", newUsername)
val requestBody = body.build()
val request = Request.Builder()
.url("https://$server/api/user/changeUsername")
.post(requestBody)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun changePassword(newPassword: String): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
body.add("password", newPassword)
val requestBody = body.build()
val request = Request.Builder()
.url("https://$server/api/user/changePassword")
.post(requestBody)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun createGroup(name: String, password: String): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
val requestBody = body.build()
val request = Request.Builder()
.url("https://$server/api/group/create/$name")
.post(requestBody)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
changeGroupPassword(name, password)
return response
}
fun joinGroup(id: Int, password: String): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("password", password)
.build()
val request = Request.Builder()
.url("https://$server/api/group/join/$id")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun changeGroupPassword(name: String, password: String): Response {
lateinit var response: Response
thread {
val groupId = getGroupId(name);
val client = OkHttpClient()
val body = FormBody.Builder()
body.add("password", password)
val requestBody = body.build()
val request = Request.Builder()
.url("https://$server/api/group/password/$groupId")
.post(requestBody)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun getGroupId(name: String): String {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/group/byName/$name")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
val responseText = response.body!!.string()
return responseText
}
fun getGroupName(id: Int): String {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/group/byId/$id")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
val responseText = response.body!!.string()
return responseText
}
fun getUsersInGroup(groupId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/group/getUsers/$groupId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun getMyGroups(): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/user/myGroups")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun getUsernameById(userId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/user/byId/$userId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun getGroupAdminId(groupId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/group/adminId/$groupId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun updateAbstractProduct(groupId: Int, abstractProduct: AbstractProduct, imageFile: File): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = MultipartBody.Builder()
.setType("multipart/form-data".toMediaType())
.addFormDataPart("file", imageFile.name, imageFile.asRequestBody("image/png".toMediaTypeOrNull()))
.addFormDataPart("groupId", groupId.toString())
.addFormDataPart("localId", abstractProduct.id.toString())
.addFormDataPart("barcode", abstractProduct.barcode)
.addFormDataPart("name", abstractProduct.name)
.addFormDataPart("net_weight", abstractProduct.netWeight.toString())
.addFormDataPart("category", abstractProduct.category.toString())
.addFormDataPart("unit", abstractProduct.unit.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/abstractproduct/update")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun uploadCategory(groupId: Int, category: Category): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("localId", category.id.toString())
.add("categoryName", category.name)
.add("groupId", groupId.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/category/create")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun updateCategory(groupId: Int, category: Category): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("localId", category.id.toString())
.add("categoryName", category.name)
.add("groupId", groupId.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/category/update")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun uploadProduct(groupId: Int, product: Product): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("localId", product.id.toString())
.add("groupId", groupId.toString())
.add("abstract_product_id", product.abstractProductId.toString())
.add("amount", product.amount.toString())
.add("date_of_production", product.dateOfProduction.toString())
.add("expiry_date", product.dateOfExpiry.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/product/create")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun updateProduct(groupId: Int, product: Product): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val body = FormBody.Builder()
.add("localId", product.id.toString())
.add("groupId", groupId.toString())
.add("abstract_product_id", product.abstractProductId.toString())
.add("amount", product.amount.toString())
.add("date_of_production", product.dateOfProduction.toString())
.add("expiry_date", product.dateOfExpiry.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/product/update")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun synchronize(groupId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/user/synchronize/$groupId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun getProduct(groupId: Int, localId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/product/$groupId/$localId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun getAbstractProduct(groupId: Int, localId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/abstractproduct/$groupId/$localId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun downloadImage(url: String, file: File) {
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val fos = FileOutputStream(file)
response.body?.byteStream()?.use { inputStream ->
fos.use {
inputStream.copyTo(fos)
}
}
}
}.join()
}
fun deleteCategory(groupId: Int, localId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/category/$groupId/$localId")
.delete()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun deleteAbstractProduct(groupId: Int, localId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/abstractproduct/$groupId/$localId")
.delete()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
fun deleteProduct(groupId: Int, localId: Int): Response {
lateinit var response: Response
thread {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/product/$groupId/$localId")
.delete()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
}.join()
return response
}
}

View File

@@ -1,40 +0,0 @@
package org.foxarmy.barcodescannerforemployees
import android.content.Context
import android.widget.Toast
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
class Requester constructor(var siteName: String, var endpoint: String) {
var response = ""
fun request(context: Context, barcode: String) {
val url = "${siteName}/${endpoint}"
val volleyQueue = Volley.newRequestQueue(context)
val stringRequest = object: StringRequest(
Method.POST, url, { resp ->
run {
response =
if (resp == "") {
"Not found 404"
} else {
resp
}
}
},
{
Toast.makeText(context, "Cannot make request", Toast.LENGTH_LONG).show()
}
) {
override fun getHeaders(): Map<String, String> {
return mapOf("referer" to "$siteName/")
}
public override fun getParams(): MutableMap<String, String> {
return mutableMapOf("barcode" to barcode)
}
}
volleyQueue.add(stringRequest)
}
}

View File

@@ -6,17 +6,26 @@ import android.content.ContextWrapper
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import com.google.firebase.components.BuildConfig
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URLEncoder
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*
fun convertToUnixEpochTimestamp(dateString: String): Long {
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
format.timeZone = TimeZone.getTimeZone("UTC")
val date = format.parse(dateString)
return date!!.time / 1000
}
fun getImageUri(activity: Activity, imageFile: File): Uri? {
return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider", imageFile)
}
@@ -34,11 +43,18 @@ fun generateThumbnailForImage(context: Context, imageHash: String) {
val imageContent = imageFile.inputStream().readBytes()
var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size)
val matrix = Matrix();
matrix.postRotate(90f)
val scaled = Bitmap.createScaledBitmap(img, img.width/4, img.height/4, true)
val rotated = Bitmap.createBitmap(scaled, 0, 0, scaled.width, scaled.height, matrix, true)
rotated.compress(Bitmap.CompressFormat.WEBP_LOSSY, 25, FileOutputStream(thumbnailFile))
val exif = ExifInterface(imageFile.absoluteFile.toString())
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val matrix = Matrix()
when(orientation){
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F)
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F)
}
val rotated = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true)
val scaled = Bitmap.createScaledBitmap(rotated, rotated.width/4, rotated.height/4, true)
scaled.compress(Bitmap.CompressFormat.WEBP_LOSSY, 25, FileOutputStream(thumbnailFile))
}
@OptIn(ExperimentalStdlibApi::class)
@@ -60,8 +76,6 @@ fun removeSubstringsFromString(text: String, toRemove: Array<String>): String {
return result
}
fun String.utf8(): String = URLEncoder.encode(this, "UTF-8")
fun getActivity(context: Context?): Activity? {
if (context == null) {
return null
@@ -95,3 +109,32 @@ fun getUnitNameById (context: Context, id: Int): String {
else -> { "" }
}
}
fun parseArray(input: String): IntArray {
return input.trim('[', ']').split(",").map { it.trim().toInt() }.toIntArray()
}
fun calculateMd5Hash(file: File): String {
val digest = MessageDigest.getInstance("MD5")
val fis = FileInputStream(file)
val buffer = ByteArray(1024)
var bytesRead = fis.read(buffer)
while (bytesRead != -1) {
digest.update(buffer, 0, bytesRead)
bytesRead = fis.read(buffer)
}
fis.close()
return bytesToHex(digest.digest())
}
fun bytesToHex(bytes: ByteArray): String {
val hexString = StringBuilder()
for (byte in bytes) {
val hex = Integer.toHexString(0xff and byte.toInt())
if (hex.length == 1) {
hexString.append('0')
}
hexString.append(hex)
}
return hexString.toString()
}

View File

@@ -0,0 +1,48 @@
package org.foxarmy.barcodescannerforemployees.activities
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.databinding.ActivityAccountSettingsBinding
class AccountSettingsActivity : AppCompatActivity() {
private lateinit var binding: ActivityAccountSettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAccountSettingsBinding.inflate(layoutInflater)
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 net = Net()
net.server = sharedPreferences.getString("server", "bsfe.foxarmy.org")!!
net.language = sharedPreferences.getString("language", "")!!
net.token = sharedPreferences.getString("token", "")!!
binding.saveUsernameButton.setOnClickListener {
val response = net.changeUsername(binding.newUsernameTextEdit.text.toString())
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
binding.savePasswordButton.setOnClickListener {
val response = net.changePassword(binding.newPasswordTextEdit.text.toString())
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
}
}

View File

@@ -1,11 +1,14 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.content.ContentValues
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import android.os.Build
import android.os.Bundle
import android.provider.BaseColumns
import android.util.Log
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
@@ -14,12 +17,19 @@ 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
import okhttp3.Response
import org.foxarmy.barcodescannerforemployees.*
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 java.io.File
import java.io.FileOutputStream
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import kotlin.concurrent.thread
@@ -47,11 +57,24 @@ class AddAbstractProductActivity : AppCompatActivity() {
private var scanningBarcode = false
private lateinit var DAO: AbstractProductDAO
private lateinit var sharedPreferences: SharedPreferences
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
)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)
DAO = AbstractProductDAO(dbHelper)
picturesPath = File(filesDir, "pictures")
val thumbnailsDir = File(cacheDir, "thumbnails")
@@ -75,9 +98,7 @@ class AddAbstractProductActivity : AppCompatActivity() {
fillupUnitsSpinner()
noBarcodeCheckBox.setOnClickListener {
if (noBarcodeCheckBox.isChecked) {
barcodeText.setText("")
}
barcodeText.isEnabled = !noBarcodeCheckBox.isChecked
}
barcodeText.addTextChangedListener {
@@ -90,6 +111,7 @@ class AddAbstractProductActivity : AppCompatActivity() {
"update" -> {
abstractProduct = extras.get("abstractProduct") as AbstractProduct?
}
"new_from_barcode" -> {
abstractProduct = extras.get("abstractProduct") as AbstractProduct?
barcode = abstractProduct!!.barcode
@@ -129,26 +151,35 @@ class AddAbstractProductActivity : AppCompatActivity() {
Toast.makeText(this, getString(R.string.product_net_weight_request), Toast.LENGTH_SHORT).show()
}
val db = DBStorageController(this).writableDatabase
val values = ContentValues().apply {
put(AbstractProductContract.AbstractProductEntry.BARCODE, barcode)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME, productName)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT, netWeight.toString())
put(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME, pictureFile.nameWithoutExtension)
put(AbstractProductContract.AbstractProductEntry.CATEGORY, categorySpinner.selectedItemPosition)
put(AbstractProductContract.AbstractProductEntry.UNIT, unitTypeSpinner.selectedItemPosition)
}
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!
lateinit var response: Response
abstractProduct = AbstractProduct(
if(abstractProduct == null) 0 else abstractProduct!!.id,
if (noBarcodeCheckBox.isChecked) "" else barcode,
productName,
netWeight.toString().toDouble(),
pictureFile.nameWithoutExtension,
categorySpinner.selectedItemPosition + 1,
unitTypeSpinner.selectedItemPosition
)
val pictureFile = File(File(filesDir, "pictures"), "${abstractProduct!!.imageHash}.png")
if (action == "update") {
db.update(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
values,
"${BaseColumns._ID} = ?",
arrayOf(abstractProduct!!.id.toString())
)
} else if (action == "new" || action == "new_from_barcode"){
db.insert(AbstractProductContract.AbstractProductEntry.TABLE_NAME, null, values)
DAO.updateAbstractProduct(abstractProduct!!)
response = net.updateAbstractProduct(currentGroup.toInt(), abstractProduct!!, pictureFile)
} else if (action == "new" || action == "new_from_barcode") {
abstractProduct!!.id = DAO.addAbstractProduct(abstractProduct!!).toInt()
response = net.uploadAbstractProduct(currentGroup.toInt(), abstractProduct!!, pictureFile);
}
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
finish()
}
@@ -165,15 +196,13 @@ class AddAbstractProductActivity : AppCompatActivity() {
fun performRequest(barcode: String) {
barcodeText.setText(this.barcode)
val requester = Requester("https://ean-online.ru", "match.php")
requester.request(this, barcode)
val net = Net();
val result = net.requestProductFromOnlineDB(barcode)
Log.d("QWERTYUIOP", "Result of request: $result")
var abstractProduct: AbstractProduct
if (DBStorageController(this).findAbstractProductByBarcode(
DBStorageController(this).readableDatabase,
this.barcode
) != null
) {
if (DAO.findAbstractProductByBarcode(this.barcode) != null) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.abstract_product_already_exists))
.setPositiveButton(getString(R.string.quit)) { _: DialogInterface, _: Int ->
@@ -182,10 +211,8 @@ class AddAbstractProductActivity : AppCompatActivity() {
.setNegativeButton(getString(R.string.edit_existing)) { _: DialogInterface, _: Int ->
val addProductIntent = Intent(this, AddAbstractProductActivity::class.java)
val extras = Bundle()
val existingAbstractProduct = DBStorageController(this).findAbstractProductByBarcode(
DBStorageController(this).readableDatabase,
this.barcode
)
val existingAbstractProduct = DAO.findAbstractProductByBarcode(this.barcode)
extras.putParcelable("abstractProduct", existingAbstractProduct)
addProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addProductIntent, extras)
@@ -194,19 +221,17 @@ class AddAbstractProductActivity : AppCompatActivity() {
}
thread {
// Я сам в ахуях какой это костыль, пока хз как фиксить, потом придумаю :))
while (requester.response == "") {
}
if (requester.response == "Not found 404") {
if (result == "Not found 404") {
runOnUiThread {
Toast.makeText(this, getString(R.string.no_product_in_online_database), Toast.LENGTH_LONG)
.show()
productNameText.setText("")
netWeightText.setText("")
}
return@thread
}
abstractProduct = Parser().parse(requester.response)
requester.response = ""
abstractProduct = Parser().parse(result)
runOnUiThread {
productNameText.text = abstractProduct.name
netWeightText.text = abstractProduct.netWeight.toString()
@@ -224,40 +249,20 @@ class AddAbstractProductActivity : AppCompatActivity() {
getString(R.string.pieces)
)
val arrayAdapter =
ArrayAdapter<String>(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, units)
ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, units)
arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
unitTypeSpinner.adapter = arrayAdapter
}
fun fillupCategorySpinner() {
val db = DBStorageController(this).readableDatabase
val categories = mutableListOf("")
val categoriesDAO = CategoryDAO(DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!))
val projection = arrayOf(
CategoriesContract.CategoryEntry.CATEGORY_NAME
)
val cursor = db.query(
CategoriesContract.CategoryEntry.TABLE_NAME,
projection,
null,
null,
null,
null,
BaseColumns._ID + " ASC"
)
with(cursor) {
while (moveToNext()) {
categories.add(getString(getColumnIndexOrThrow(CategoriesContract.CategoryEntry.CATEGORY_NAME)))
}
}
val categories = categoriesDAO.getAllCategories().map { category -> category.name }
val arrayAdapter =
ArrayAdapter<String>(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, categories)
ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, categories)
arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
categorySpinner.adapter = arrayAdapter
}
@@ -267,12 +272,35 @@ class AddAbstractProductActivity : AppCompatActivity() {
if (success) {
//Move picture to a proper directory according to its calculated hash
val tempfile = File(filesDir, "image.png")
val imageContent = tempfile.inputStream().readBytes()
val imageHash = imageContent.toString(Charsets.UTF_8).md5()
val imageHash = calculateMd5Hash(tempfile)
// val imageContent = tempfile.inputStream().readBytes()
// val imageHash = imageContent.toString(Charsets.UTF_8).md5()
pictureFile = File(picturesPath, "$imageHash.png")
Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
tempfile.delete()
val imageContent = pictureFile.inputStream().readBytes()
var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size)
val exif = ExifInterface(pictureFile.absoluteFile.toString())
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val matrix = Matrix()
when(orientation){
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F)
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F)
}
val rotated = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true)
val compressionFactor = sharedPreferences.getInt("compression", 1)
rotated.compress(
Bitmap.CompressFormat.WEBP_LOSSY,
100 * (15 / (16 - compressionFactor)),
FileOutputStream(pictureFile)
)
generateThumbnailForImage(this, imageHash)
imageView.setImageURI(getImageUri(this, pictureFile))
@@ -303,7 +331,8 @@ class AddAbstractProductActivity : AppCompatActivity() {
getPicture()
}
} else {
Toast.makeText(this, getString(R.string.camera_permission_for_picture_request), Toast.LENGTH_LONG).show()
Toast.makeText(this, getString(R.string.camera_permission_for_picture_request), Toast.LENGTH_LONG)
.show()
}
}

View File

@@ -1,23 +1,40 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.content.ContentValues
import android.content.SharedPreferences
import android.os.Bundle
import android.provider.BaseColumns
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import org.foxarmy.barcodescannerforemployees.CategoriesContract
import org.foxarmy.barcodescannerforemployees.DBStorageController
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
class AddCategoryActivity : Activity() {
private lateinit var DAO: CategoryDAO
private lateinit var sharedPreferences: SharedPreferences
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
)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
DAO = CategoryDAO(dbHelper)
val extras = intent.extras
val category = extras!!.get("category") as Category?
@@ -25,25 +42,28 @@ class AddCategoryActivity : Activity() {
categoryNameTextEdit.setText(category!!.name)
findViewById<Button>(R.id.saveButton).setOnClickListener {
val db = DBStorageController(this).writableDatabase
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!.toInt()
findViewById<Button>(R.id.saveButton).setOnClickListener {
if (categoryNameTextEdit.text.toString() == "") {
Toast.makeText(this, getString(R.string.category_name_required), Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (category.id == 0) { // Inserting new category
val values = ContentValues().apply {
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, categoryNameTextEdit.text.toString())
}
db.insert(CategoriesContract.CategoryEntry.TABLE_NAME, null, values)
val newCategory = Category(0, categoryNameTextEdit.text.toString())
newCategory.id = DAO.addCategory(newCategory).toInt()
net.uploadCategory(currentGroup, newCategory)
} else { // Updating existing category
val values = ContentValues().apply {
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, categoryNameTextEdit.text.toString())
}
db.update(CategoriesContract.CategoryEntry.TABLE_NAME, values, "${BaseColumns._ID} = ?", arrayOf(category.id.toString()))
category.name = categoryNameTextEdit.text.toString()
DAO.updateCategory(category)
net.updateCategory(currentGroup, category)
}
finish()

View File

@@ -2,6 +2,7 @@ package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.view.View
@@ -12,11 +13,17 @@ 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
import org.foxarmy.barcodescannerforemployees.DBStorageController
import okhttp3.Response
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.database.ProductDAO
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import org.foxarmy.barcodescannerforemployees.views.AbstractProductView
@@ -45,11 +52,28 @@ class AddProductActivity : AppCompatActivity() {
private var product: Product? = null
private var abstractProduct: AbstractProduct? = null
private lateinit var productDAO: ProductDAO
private lateinit var abstractProductDAO: AbstractProductDAO
private lateinit var sharedPreferences: SharedPreferences
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
)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
productDAO = ProductDAO(dbHelper)
abstractProductDAO = AbstractProductDAO(dbHelper)
scanButton = findViewById(R.id.scanButton)
noBarcodeButton = findViewById(R.id.noBarcodeButton)
abstractProductView = findViewById(R.id.abstractProductView)
@@ -70,7 +94,7 @@ class AddProductActivity : AppCompatActivity() {
product = extras!!.get("product") as Product?
if (product != null) {
abstractProduct = DBStorageController(this).findAbstractProductById(DBStorageController(this).readableDatabase, product!!.abstractProductId)
abstractProduct = abstractProductDAO.findAbstractProductById(product!!.abstractProductId)
abstractProductView.abstractProduct = abstractProduct!!
expiryDateRadioButton.isSelected = true
shelfLifeRadioButton.isSelected = false
@@ -116,6 +140,12 @@ class AddProductActivity : AppCompatActivity() {
product!!.amount = text.toInt()
}
val net = Net()
net.language = sharedPreferences.getString("language", "en-US")!!
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
saveProductButton.setOnClickListener {
if (abstractProduct == null) {
Toast.makeText(this, getString(R.string.abstract_product_request), Toast.LENGTH_SHORT).show()
@@ -147,12 +177,22 @@ class AddProductActivity : AppCompatActivity() {
return@setOnClickListener
}
val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "")!! == "") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
var response: Response? = null
if (updatingExistentProduct) {
DBStorageController(this).updateProduct(DBStorageController(this).writableDatabase, product!!)
productDAO.updateProduct(product!!)
if (currentGroup > 0) response = net.updateProduct(currentGroup, product!!)
} else {
DBStorageController(this).insertNewProduct(DBStorageController(this).writableDatabase, product!!)
product!!.id = productDAO.insertNewProduct(product!!).toInt()
if (currentGroup > 0) response = net.uploadProduct(currentGroup, product!!)
}
if (response != null) {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
finish()
}
update()
@@ -245,7 +285,7 @@ class AddProductActivity : AppCompatActivity() {
Toast.makeText(this, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
} else {
val scannedBarcode = result.contents
abstractProduct = DBStorageController(this).findAbstractProductByBarcode(DBStorageController(this).readableDatabase, scannedBarcode)
abstractProduct = abstractProductDAO.findAbstractProductByBarcode(scannedBarcode)
displayAbstractProduct(abstractProduct, scannedBarcode)
if (abstractProduct != null) {

View File

@@ -2,23 +2,41 @@ package org.foxarmy.barcodescannerforemployees.activities
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import org.foxarmy.barcodescannerforemployees.DBStorageController
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
import org.foxarmy.barcodescannerforemployees.databinding.ActivityExpiryCalendarBinding
import org.foxarmy.barcodescannerforemployees.views.ExpiryGroupView
class ExpiryCalendarActivity : AppCompatActivity() {
private lateinit var binding: ActivityExpiryCalendarBinding
private lateinit var productDAO: ProductDAO
private lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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
)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)
productDAO = ProductDAO(dbHelper)
fillUp()
}
@@ -31,7 +49,7 @@ class ExpiryCalendarActivity : AppCompatActivity() {
}
private fun fillUp() {
val dates = DBStorageController(this).findAllExpiryDates(DBStorageController(this).readableDatabase)
val dates = productDAO.findAllExpiryDates()
val container = findViewById<LinearLayout>(R.id.datesLinearLayout)

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

@@ -0,0 +1,81 @@
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.databinding.ActivityGroupBinding
class GroupActivity : AppCompatActivity() {
private lateinit var binding: ActivityGroupBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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
)
binding.createGroupButton.setOnClickListener {
val groupName = binding.groupNameTextEdit.text.toString()
val groupPassword = binding.groupPasswordTextEdit.text.toString()
// group is set to "successful"
val n = Net()
n.language = sharedPreferences.getString("language", "en-US")!!
n.server = sharedPreferences.getString("server", "")!!
n.token = sharedPreferences.getString("token", "")!!
val response = n.createGroup(groupName, groupPassword)
val responseText = response.body!!.string()
if (response.code == 200) {
val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf())
currentGroups!!.add(responseText)
sharedPreferences.edit().putStringSet("groups", currentGroups).apply()
sharedPreferences.edit().putString("currentGroup", responseText).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else {
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show()
}
}
binding.joinGroupButton.setOnClickListener {
val groupName = binding.groupNameTextEdit.text.toString()
val groupPassword = binding.groupPasswordTextEdit.text.toString()
val n = Net()
n.language = sharedPreferences.getString("language", "en-US")!!
n.server = sharedPreferences.getString("server", "")!!
n.token = sharedPreferences.getString("token", "")!!
val groupId = n.getGroupId(groupName).toInt()
val response = n.joinGroup(groupId, groupPassword)
val responseText = response.body!!.string()
if (response.code == 200) {
val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf())
currentGroups!!.add(groupId.toString())
sharedPreferences.edit().putStringSet("groups", currentGroups).apply()
sharedPreferences.edit().putString("currentGroup", groupId.toString()).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else {
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show()
}
}
}
}

View File

@@ -0,0 +1,106 @@
package org.foxarmy.barcodescannerforemployees.activities
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.parseArray
import org.json.JSONObject
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater);
setContentView(binding.root)
fillUpLanguagesSpinner()
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
binding.loginButton.setOnClickListener {
val server = binding.serverTextEdit.text.toString()
val username = binding.usernameTextEdit.text.toString()
val password = binding.passwordTextEdit.text.toString()
val language = resources.getStringArray(R.array.languages)[binding.languageSpinner.selectedItemPosition]
sharedPreferences.edit().putString("language", language).apply()
val n = Net()
n.language = sharedPreferences.getString("language", "en-US")!!
n.server = server
val response = n.login(username, password)
val responseText = response.body!!.string()
if (response.code != 200) {
Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show()
} else {
val json = JSONObject(responseText)
sharedPreferences.edit().putString("token", json["token"].toString()).apply()
n.token = json["token"].toString()
sharedPreferences.edit().putInt("userId", json["id"].toString().toInt()).apply()
sharedPreferences.edit().putString("server", server).apply()
val r = n.getMyGroups().body!!.string()
val myGroups = parseArray(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()
}
}
binding.registerButton.setOnClickListener {
val server = binding.serverTextEdit.text.toString()
val username = binding.usernameTextEdit.text.toString()
val password = binding.passwordTextEdit.text.toString()
val language = resources.getStringArray(R.array.languages)[binding.languageSpinner.selectedItemPosition]
sharedPreferences.edit().putString("language", language).apply()
sharedPreferences.edit().putString("server", server).apply()
val n = Net()
n.language = language
n.server = server
val response = n.registerAccount(username, password);
val responseText = response.body!!.string()
if (response.code != 200) {
Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show();
} else {
sharedPreferences.edit().putInt("userId", responseText.toInt()).apply()
val token = JSONObject(n.login(username, password).body!!.string())["token"].toString()
sharedPreferences.edit().putString("token", token).apply()
sharedPreferences.edit().putString("server", server).apply()
val intent = Intent(this, GroupActivity::class.java)
startActivity(intent)
finish()
}
}
}
private fun fillUpLanguagesSpinner() {
val languages = resources.getStringArray(R.array.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
}
}

View File

@@ -1,26 +1,47 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import androidx.viewpager.widget.ViewPager
import com.google.android.material.navigation.NavigationView
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.ViewPagerAdapter
import org.foxarmy.barcodescannerforemployees.convertToUnixEpochTimestamp
import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.database.ProductDAO
import org.foxarmy.barcodescannerforemployees.databinding.ActivityMainBinding
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import org.foxarmy.barcodescannerforemployees.fragments.CategoriesFragment
import org.foxarmy.barcodescannerforemployees.fragments.ShelfFragment
import org.foxarmy.barcodescannerforemployees.fragments.StorageFragment
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
class MainActivity : AppCompatActivity() {
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding
lateinit var adapter: ViewPagerAdapter
var drawerLayout: DrawerLayout? = null
var actionBarDrawerToggle: ActionBarDrawerToggle? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -32,6 +53,16 @@ class MainActivity : AppCompatActivity() {
setupViewPager(binding.tabViewpager)
binding.tabTablayout.setupWithViewPager(binding.tabViewpager)
drawerLayout = binding.drawerLayout
actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.nav_open, R.string.nav_close)
drawerLayout!!.addDrawerListener(actionBarDrawerToggle!!)
actionBarDrawerToggle!!.syncState()
binding.navView.setNavigationItemSelectedListener(this)
// to make the Navigation drawer icon always appear on the action bar
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
binding.expiryCalendarFab.setOnClickListener { _ ->
val expiryCalendarIntent = Intent(this, ExpiryCalendarActivity::class.java)
val extras = Bundle()
@@ -69,6 +100,269 @@ class MainActivity : AppCompatActivity() {
extras.putParcelable("product", null)
addProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addProductIntent, extras)
}
}
}
synchronize()
}
private fun synchronize() {
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") return
val net = Net()
net.language = sharedPreferences.getString("language", "en-US")!!
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
val data = JSONObject(net.synchronize(currentGroup).body!!.string())
val remoteAbstractProducts = data["abstract_products"] as JSONArray
val remoteProducts = data["products"] as JSONArray
val remoteCategories = data["categories"] as JSONArray
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "")!!)
val abstractProductDAO = AbstractProductDAO(dbHelper)
val productDAO = ProductDAO(dbHelper)
val categoryDAO = CategoryDAO(dbHelper)
val localAbstractProducts = abstractProductDAO.getSortedListOfAbstractProducts(0, "", arrayOf(""))
val localProducts = productDAO.getSortedListOfProducts(0, "", "")
val localCategories = categoryDAO.getAllCategories()
syncAbstractProducts(net, currentGroup, abstractProductDAO, remoteAbstractProducts, localAbstractProducts)
syncProducts(productDAO, remoteProducts, localProducts)
syncCategories(categoryDAO, remoteCategories, localCategories)
}
private fun syncCategories(
categoryDAO: CategoryDAO,
remoteCategories: JSONArray,
localCategories: List<Category>
) {
for (localCategory in localCategories) {
var found = false
for (i in 0 until remoteCategories.length()) {
val remoteProduct = remoteCategories.getJSONObject(i)
if (remoteProduct["local_id"] == localCategory.id) {
found = true
break
}
}
if (!found) categoryDAO.eraseCategory(localCategory.id, this)
}
for (i in 0 until remoteCategories.length()) {
var categoryInRemoteDB: JSONObject? = null
var categoryInLocalDB: Category? = null
val remoteCategory = remoteCategories.getJSONObject(i)
for (localCategory in localCategories) {
if (remoteCategory["local_id"] == localCategory.id) {
categoryInRemoteDB = remoteCategory
categoryInLocalDB = localCategory
break
}
}
if (categoryInLocalDB == null && remoteCategory != null) {
var newCategory: Category
with(remoteCategory) {
newCategory = Category(
this["local_id"].toString().toInt(),
this["name"].toString()
)
}
categoryDAO.addCategory(newCategory)
}
if (categoryInRemoteDB != null && categoryInLocalDB != null) {
if (categoryInRemoteDB["hash"] != categoryInLocalDB.calculateHash()) {
var updatedData: Category
with(categoryInRemoteDB) {
updatedData = Category(
this["local_id"].toString().toInt(),
this["name"].toString()
)
}
categoryDAO.updateCategory(updatedData)
}
}
}
}
private fun syncProducts(
productDAO: ProductDAO,
remoteProducts: JSONArray,
localProducts: List<Product>
) {
for (localProduct in localProducts) {
var found = false
for (i in 0 until remoteProducts.length()) {
val remoteProduct = remoteProducts.getJSONObject(i)
if (remoteProduct["local_id"] == localProduct.id) {
found = true
break
}
}
if (!found) productDAO.eraseProduct(localProduct.id)
}
for (i in 0 until remoteProducts.length()) {
var productInRemoteDB: JSONObject? = null
var productInLocalDB: Product? = null
val remoteProduct = remoteProducts.getJSONObject(i)
for (localProduct in localProducts) {
if (remoteProduct["local_id"] == localProduct.id) {
productInRemoteDB = remoteProduct
productInLocalDB = localProduct
break
}
}
if (productInLocalDB == null && remoteProduct != null) {
var newProduct: Product
with(remoteProduct) {
newProduct = Product(
this["local_id"].toString().toInt(),
this["abstract_product_id"].toString().toInt(),
this["amount"].toString().toInt(),
convertToUnixEpochTimestamp(this["date_of_production"].toString()),
convertToUnixEpochTimestamp(this["expiry_date"].toString())
)
}
productDAO.insertNewProduct(newProduct)
}
if (productInRemoteDB != null && productInLocalDB != null) {
if (productInRemoteDB["hash"] != productInLocalDB.calculateHash()) {
var updatedData: Product
with(productInRemoteDB) {
updatedData = Product(
this["local_id"].toString().toInt(),
this["abstract_product_id"].toString().toInt(),
this["amount"].toString().toInt(),
convertToUnixEpochTimestamp(this["date_of_production"].toString()),
convertToUnixEpochTimestamp(this["expiry_date"].toString())
)
}
productDAO.updateProduct(updatedData)
}
}
}
}
private fun syncAbstractProducts(
net: Net,
currentGroup: Int,
abstractProductDAO: AbstractProductDAO,
remoteAbstractProducts: JSONArray,
localAbstractProducts: List<AbstractProduct>
) {
for (localAbstractProduct in localAbstractProducts) {
var found = false
for (i in 0 until remoteAbstractProducts.length()) {
val remoteAbstractProduct = remoteAbstractProducts.getJSONObject(i)
if (remoteAbstractProduct["local_id"] == localAbstractProduct.id) {
found = true
break
}
}
if (!found) abstractProductDAO.eraseAbstractProduct(localAbstractProduct.id, this)
}
for (i in 0 until remoteAbstractProducts.length()) {
var abstractProductInRemoteDB: JSONObject? = null
var abstractProductInLocalDB: AbstractProduct? = null
val remoteAbstractProduct = remoteAbstractProducts.getJSONObject(i)
for (localAbstractProduct in localAbstractProducts) {
if (remoteAbstractProduct["local_id"] == localAbstractProduct.id) {
abstractProductInRemoteDB = remoteAbstractProduct
abstractProductInLocalDB = localAbstractProduct
break
}
}
if (abstractProductInLocalDB == null && remoteAbstractProduct != null) {
val localId = remoteAbstractProduct["local_id"].toString().toInt()
val picturesDir = File(filesDir, "pictures")
picturesDir.mkdirs()
val pictureFile =
File(picturesDir, "${remoteAbstractProduct["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}"
net.downloadImage(url, pictureFile)
var newAbstractProduct: AbstractProduct
with(remoteAbstractProduct) {
newAbstractProduct = AbstractProduct(
this["local_id"].toString().toInt(),
this["barcode"].toString(),
this["name"].toString(),
this["net_weight"].toString().toDouble(),
this["image_filename"].toString(),
this["category"].toString().toInt(),
this["unit"].toString().toInt()
)
}
abstractProductDAO.addAbstractProduct(newAbstractProduct)
}
if (abstractProductInRemoteDB != null && abstractProductInLocalDB != null) {
if (abstractProductInRemoteDB["hash"] != abstractProductInLocalDB.calculateHash()) {
val localId = abstractProductInRemoteDB["local_id"].toString().toInt()
val pictureFile =
File(File(filesDir, "pictures"), "${abstractProductInRemoteDB["image_filename"]}.png")
val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}"
net.downloadImage(url, pictureFile)
var updatedData: AbstractProduct
with(abstractProductInRemoteDB) {
updatedData = AbstractProduct(
this["local_id"].toString().toInt(),
this["barcode"].toString(),
this["name"].toString(),
this["net_weight"].toString().toDouble(),
this["image_filename"].toString(),
this["category"].toString().toInt(),
this["unit"].toString().toInt()
)
}
abstractProductDAO.updateAbstractProduct(updatedData)
}
}
}
@@ -81,84 +375,129 @@ class MainActivity : AppCompatActivity() {
adapter.addFragment(ShelfFragment(), getString(R.string.shelf_title))
adapter.addFragment(CategoriesFragment(), getString(R.string.categories_title))
//TODO: settings fragments
// setting adapter to view pager.
viewpager.adapter = adapter
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val currentPosition = binding.tabTablayout.selectedTabPosition
val fragment = adapter.getItem(currentPosition)
return when (item.itemId) {
R.id.action_settings -> {
true
override fun onNavigationItemSelected(item: MenuItem): Boolean {
lateinit var intent: Intent
when (item.itemId) {
R.id.nav_account -> {
intent = Intent(this, AccountSettingsActivity::class.java)
settingsActivityLauncher.launch(intent)
return true
}
R.id.action_delete -> {
when (fragment::class.simpleName.toString()) {
"StorageFragment" -> {
AlertDialog.Builder(this)
.setMessage(getString(R.string.deleting_abstract_product_warning))
.setPositiveButton(getString(R.string.yes)) { _: DialogInterface, _: Int ->
val storageFragment = fragment as StorageFragment
storageFragment.removeSelected()
}
.setNegativeButton(getString(R.string.no)) { _: DialogInterface, _: Int ->
}.show()
}
"CategoriesFragment" -> {
AlertDialog.Builder(this)
.setMessage(getString(R.string.deleting_category_warning))
.setPositiveButton(getString(R.string.yes)) { _: DialogInterface, _: Int ->
val categoriesFragment = fragment as CategoriesFragment
categoriesFragment.removeSelected()
}
.setNegativeButton(getString(R.string.no)) { _: DialogInterface, _: Int ->
}.show()
}
"ShelfFragment" -> {
val shelfFragment = fragment as ShelfFragment
shelfFragment.removeSelected()
}
}
true
R.id.nav_groups -> {
intent = Intent(this, MyGroupsActivity::class.java)
}
R.id.action_update -> {
when (fragment::class.simpleName.toString()) {
"StorageFragment" -> {
val storageFragment = fragment as StorageFragment
storageFragment.updateSelected()
}
"CategoriesFragment" -> {
val categoriesFragment = fragment as CategoriesFragment
categoriesFragment.updateSelected()
}
"ShelfFragment" -> {
val shelfFragment = fragment as ShelfFragment
shelfFragment.updateSelected()
}
}
true
R.id.nav_settings -> {
intent = Intent(this, SettingsActivity::class.java)
}
else -> super.onOptionsItemSelected(item)
}
startActivity(intent)
return true
}
private val settingsActivityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
synchronize()
updateAll()
}/* else if (result.resultCode == Activity.RESULT_CANCELED) {
}*/
}
fun updateAll() {
val storageFragment: StorageFragment = adapter.getItem(0) as StorageFragment
val shelfFragment: ShelfFragment = adapter.getItem(1) as ShelfFragment
val categoriesFragment: CategoriesFragment = adapter.getItem(2) as CategoriesFragment
storageFragment.updateContent()
shelfFragment.updateContent()
categoriesFragment.updateContent()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (actionBarDrawerToggle!!.onOptionsItemSelected(item)) {
return true
} else {
val currentPosition = binding.tabTablayout.selectedTabPosition
val fragment = adapter.getItem(currentPosition)
return when (item.itemId) {
R.id.action_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
settingsActivityLauncher.launch(intent)
true
}
R.id.action_delete -> {
when (fragment::class.simpleName.toString()) {
"StorageFragment" -> {
AlertDialog.Builder(this)
.setMessage(getString(R.string.deleting_abstract_product_warning))
.setPositiveButton(getString(R.string.yes)) { _: DialogInterface, _: Int ->
val storageFragment = fragment as StorageFragment
storageFragment.removeSelected()
}
.setNegativeButton(getString(R.string.no)) { _: DialogInterface, _: Int ->
}.show()
}
"CategoriesFragment" -> {
AlertDialog.Builder(this)
.setMessage(getString(R.string.deleting_category_warning))
.setPositiveButton(getString(R.string.yes)) { _: DialogInterface, _: Int ->
val categoriesFragment = fragment as CategoriesFragment
categoriesFragment.removeSelected()
}
.setNegativeButton(getString(R.string.no)) { _: DialogInterface, _: Int ->
}.show()
}
"ShelfFragment" -> {
val shelfFragment = fragment as ShelfFragment
shelfFragment.removeSelected()
}
}
true
}
R.id.action_update -> {
when (fragment::class.simpleName.toString()) {
"StorageFragment" -> {
val storageFragment = fragment as StorageFragment
storageFragment.updateSelected()
}
"CategoriesFragment" -> {
val categoriesFragment = fragment as CategoriesFragment
categoriesFragment.updateSelected()
}
"ShelfFragment" -> {
val shelfFragment = fragment as ShelfFragment
shelfFragment.updateSelected()
}
}
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
fun filterAbstractProductsByCategory(id: Int) {

View File

@@ -0,0 +1,76 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.content.Context
import android.os.Bundle
import android.view.View
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.ActivityManageGroupBinding
import org.foxarmy.barcodescannerforemployees.parseArray
import org.foxarmy.barcodescannerforemployees.views.GroupMemberView
class ManageGroupActivity : AppCompatActivity(){
private lateinit var binding: ActivityManageGroupBinding
private var isAdmin: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityManageGroupBinding.inflate(layoutInflater)
setContentView(binding.root)
val groupId = intent.extras!!.getInt("groupId")
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
val sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
masterKeyAlias,
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 users = parseArray(net.getUsersInGroup(groupId).body!!.string())
for (user in users) {
val groupMemberView = GroupMemberView(this, this as Context, net.getUsernameById(user).body!!.string(), user)
binding.groupsContent.addView(groupMemberView)
}
isAdmin = amIAnAdminIn(groupId)
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

@@ -0,0 +1,55 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.content.Context
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.views.GroupView
class MyGroupsActivity : AppCompatActivity(){
private lateinit var binding: ActivityMyGroupsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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 net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val groups = parseArray(net.getMyGroups().body!!.string())
val container = findViewById<LinearLayout>(R.id.groupsLayout)
for (group in groups) {
val groupView = GroupView(this, this as Context, net.getGroupName(group), group)
container.addView(groupView)
}
binding.newGroup.setOnClickListener {
val intent = Intent(this, GroupActivity::class.java)
startActivity(intent)
}
}
}

View File

@@ -0,0 +1,47 @@
package org.foxarmy.barcodescannerforemployees.activities
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
class NavigatorActivity : Activity() {
private lateinit var sharedPreferences: SharedPreferences
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
)
var intent = Intent(this, MainActivity::class.java)
if (!isInGroup()) {
intent = Intent(this, GroupActivity::class.java)
}
if (!isAuthenticated()) {
intent = Intent(this, LoginActivity::class.java);
}
startActivity(intent);
finish();
}
private fun isAuthenticated(): Boolean {
return sharedPreferences.getString("token", "") != ""
}
private fun isInGroup(): Boolean {
return sharedPreferences.getStringSet("groups", emptySet())!!.isNotEmpty()
}
}

View File

@@ -0,0 +1,83 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.content.SharedPreferences
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
class SettingsActivity : AppCompatActivity() {
lateinit var binding: ActivitySettingsBinding
lateinit var sharedPreferences: SharedPreferences
lateinit var myGroups: List<String>
lateinit var currentGroup: String
lateinit var groupsNames: MutableList<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
)
myGroups = sharedPreferences.getStringSet("groups", emptySet())!!.toList()
currentGroup = sharedPreferences.getString("currentGroup", "offline")!!
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.language = sharedPreferences.getString("language", "")!!
net.server = sharedPreferences.getString("server", "")!!
groupsNames = mutableListOf()
for (myGroup in myGroups) {
groupsNames.add(net.getGroupName(myGroup.toInt()))
}
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
setUpImageCompressionSeekBar()
fillUpCurrentGroupSpinner()
binding.saveButton.setOnClickListener {
sharedPreferences.edit().putString("currentGroup", net.getGroupId(binding.currentGroupSpinner.selectedItem.toString())).apply()
sharedPreferences.edit().putInt("imageCompression", binding.imageCompressionFactorSeekBar.progress + 1).apply()
setResult(Activity.RESULT_OK)
finish()
}
binding.cancelButton.setOnClickListener {
setResult(Activity.RESULT_CANCELED)
finish()
}
}
private fun setUpImageCompressionSeekBar() {
val compressionFactor = sharedPreferences.getInt("imageCompression", 1)
binding.imageCompressionFactorSeekBar.progress = compressionFactor - 1
}
private fun fillUpCurrentGroupSpinner() {
if (currentGroup == "offline") {
binding.currentGroupSetting.visibility = View.GONE
}
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))
}
}

View File

@@ -0,0 +1,348 @@
package org.foxarmy.barcodescannerforemployees.database
import android.content.ContentValues
import android.content.Context
import android.database.DatabaseUtils
import android.provider.BaseColumns
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import java.io.File
class AbstractProductDAO(private val dbHelper: DBStorageController) {
fun getAllAbstractProductInCategory(id: Int): List<AbstractProduct> {
val db = dbHelper.readableDatabase
val result = mutableListOf<AbstractProduct>()
val projection = arrayOf(
BaseColumns._ID,
AbstractProductContract.AbstractProductEntry.BARCODE,
AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.UNIT
)
val selection = "${AbstractProductContract.AbstractProductEntry.CATEGORY} = ?"
val selectionArgs = arrayOf(id.toString())
val cursor = db.query(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
null
)
with(cursor) {
while (moveToNext()) {
val abstractProductId = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID))
val abstractProductBarcode =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.BARCODE))
val abstractProductName =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val abstractProductNetWeight =
getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val abstractProductImageHash =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val abstractProductUnit =
getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
val abstractProduct = AbstractProduct(
abstractProductId,
abstractProductBarcode,
abstractProductName,
abstractProductNetWeight,
abstractProductImageHash,
category = id,
abstractProductUnit
)
result.add(abstractProduct)
}
}
return result
}
fun getAmountOfAbstractProductsInCategory(id: Int): Int {
val db = dbHelper.readableDatabase
return DatabaseUtils.longForQuery(
db,
"SELECT COUNT(*) FROM ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} WHERE ${AbstractProductContract.AbstractProductEntry.CATEGORY} = ?",
arrayOf(id.toString())
).toInt()
}
fun eraseAbstractProduct(id: Int, context: Context) {
val db = dbHelper.writableDatabase
val projectionForAbstractProduct = arrayOf(
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME
)
val selectionForAbstractProduct = "${BaseColumns._ID} = ?"
val selectionArgsForAbstractProduct = arrayOf(id.toString())
val cursorForAbstractProduct = db.query(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
projectionForAbstractProduct,
selectionForAbstractProduct,
selectionArgsForAbstractProduct,
null,
null,
null
)
var imageHash: String = ""
with(cursorForAbstractProduct) {
while (moveToNext()) {
val productImageHash =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
imageHash = productImageHash
}
}
val picturesDir = File(context.filesDir, "pictures")
val thumbnailsDir = File(context.cacheDir, "thumbnails")
File(picturesDir, "$imageHash.png").delete()
File(thumbnailsDir, "$imageHash.webp").delete()
val projectionForProducts = arrayOf(BaseColumns._ID)
val selectionForProducts = "${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID} = ?"
val selectionArgsForProducts = arrayOf(id.toString())
val cursorForProducts = db.query(
ProductContract.ProductEntry.TABLE_NAME,
projectionForProducts,
selectionForProducts,
selectionArgsForProducts,
null,
null,
null
)
with(cursorForProducts) {
while (moveToNext()) {
ProductDAO(dbHelper).eraseProduct(getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID)))
}
}
db.delete(AbstractProductContract.AbstractProductEntry.TABLE_NAME, BaseColumns._ID + "=" + id, null)
}
fun findAbstractProductByBarcode(barcode: String): AbstractProduct? {
val db = dbHelper.readableDatabase
var abstractProduct: AbstractProduct? = null
val projection = arrayOf(
BaseColumns._ID,
AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.CATEGORY,
AbstractProductContract.AbstractProductEntry.UNIT
)
val selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = ?"
val selectionArgs = arrayOf(barcode)
val cursor = db.query(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
null
)
with(cursor) {
while (moveToNext()) {
val id = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID))
val productName =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val imageFilename =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val netWeight =
getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val category = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.CATEGORY))
val unit = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
abstractProduct = AbstractProduct(id, barcode, productName, netWeight, imageFilename, category, unit)
}
}
return abstractProduct
}
fun findAbstractProductById(id: Int): AbstractProduct? {
val db = dbHelper.readableDatabase
var abstractProduct: AbstractProduct? = null
val projection = arrayOf(
BaseColumns._ID,
AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.CATEGORY,
AbstractProductContract.AbstractProductEntry.BARCODE,
AbstractProductContract.AbstractProductEntry.UNIT
)
val selection = "${BaseColumns._ID} = ?"
val selectionArgs = arrayOf(id.toString())
val cursor = db.query(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
null
)
with(cursor) {
while (moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID))
val barcode = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.BARCODE))
val name = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val netWeight =
getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val imageHash =
getString((getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME)))
val category = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.CATEGORY))
val unit = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
abstractProduct = AbstractProduct(productId, barcode, name, netWeight, imageHash, category, unit)
}
}
return abstractProduct
}
fun addAbstractProduct(abstractProduct: AbstractProduct): Long {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
if (abstractProduct.id > 0) put(BaseColumns._ID, abstractProduct.id)
put(AbstractProductContract.AbstractProductEntry.BARCODE, abstractProduct.barcode)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME, abstractProduct.name)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT, abstractProduct.netWeight.toString())
put(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME, abstractProduct.imageHash)
put(AbstractProductContract.AbstractProductEntry.CATEGORY, abstractProduct.category)
put(AbstractProductContract.AbstractProductEntry.UNIT, abstractProduct.unit)
}
val id = db.insert(AbstractProductContract.AbstractProductEntry.TABLE_NAME, null, values)
return id
}
fun updateAbstractProduct(abstractProduct: AbstractProduct) {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
put(BaseColumns._ID, abstractProduct.id)
put(AbstractProductContract.AbstractProductEntry.BARCODE, abstractProduct.barcode)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME, abstractProduct.name)
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT, abstractProduct.netWeight.toString())
put(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME, abstractProduct.imageHash)
put(AbstractProductContract.AbstractProductEntry.CATEGORY, abstractProduct.category)
put(AbstractProductContract.AbstractProductEntry.UNIT, abstractProduct.unit)
}
db.update(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
values,
"${BaseColumns._ID} = ?",
arrayOf(abstractProduct.id.toString())
)
}
fun getSortedListOfAbstractProducts(
selectedSort: Int,
filterBy: String,
filter: Array<String>
): List<AbstractProduct> {
val db = dbHelper.readableDatabase
val abstractProducts = mutableListOf<AbstractProduct>()
val projection = arrayOf(
BaseColumns._ID,
AbstractProductContract.AbstractProductEntry.BARCODE,
AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.CATEGORY,
AbstractProductContract.AbstractProductEntry.UNIT
)
var orderBy: String = ""
when (selectedSort) {
0 -> {
orderBy = "${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} ASC"
}
1 -> {
orderBy = "${AbstractProductContract.AbstractProductEntry.CATEGORY} ASC"
}
}
var selection = ""
var selectionArgs: Array<String>? = null
when (filterBy) {
"category" -> {
selection = "${AbstractProductContract.AbstractProductEntry.CATEGORY} = ?"
selectionArgs = filter
}
"barcodeless" -> {
selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = ? OR ${AbstractProductContract.AbstractProductEntry.BARCODE} = ?"
selectionArgs = filter
}
}
val cursor = db.query(
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
orderBy
)
with(cursor) {
while (moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID))
val barcode = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.BARCODE))
val productName =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val netWeight =
getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val productImageHash =
getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val category = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.CATEGORY))
val unit = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
val product =
AbstractProduct(productId, barcode, productName, netWeight, productImageHash, category, unit)
abstractProducts.add(product)
}
}
return abstractProducts
}
}

View File

@@ -0,0 +1,114 @@
package org.foxarmy.barcodescannerforemployees.database
import android.content.ContentValues
import android.content.Context
import android.provider.BaseColumns
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
class CategoryDAO(private val dbHelper: DBStorageController) {
fun eraseCategory(id: Int, context: Context) {
val abstractProductDAO = AbstractProductDAO(dbHelper)
val db = dbHelper.writableDatabase
val productsInCategory = abstractProductDAO.getAllAbstractProductInCategory(id)
for (product in productsInCategory.iterator()) {
abstractProductDAO.eraseAbstractProduct(product.id, context)
}
db.delete(CategoriesContract.CategoryEntry.TABLE_NAME, BaseColumns._ID + "=" + id, null)
}
fun getCategoryNameById(id: Int): String {
val db = dbHelper.readableDatabase
var result = ""
val projection = arrayOf(CategoriesContract.CategoryEntry.CATEGORY_NAME)
val selection = "${BaseColumns._ID} = ?"
val selectionArgs = arrayOf(id.toString())
val cursor = db.query(
CategoriesContract.CategoryEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
null
)
with(cursor) {
while (moveToNext()) {
result = getString(getColumnIndexOrThrow(CategoriesContract.CategoryEntry.CATEGORY_NAME))
}
}
return result
}
fun getAllCategories(): List<Category> {
val db = dbHelper.readableDatabase
val categories = mutableListOf<Category>()
val projection = arrayOf(
BaseColumns._ID,
CategoriesContract.CategoryEntry.CATEGORY_NAME
)
val cursor = db.query(
CategoriesContract.CategoryEntry.TABLE_NAME,
projection,
null,
null,
null,
null,
BaseColumns._ID + " ASC"
)
with(cursor) {
while (moveToNext()) {
val category = Category(
getInt(getColumnIndexOrThrow(BaseColumns._ID)),
getString(getColumnIndexOrThrow(CategoriesContract.CategoryEntry.CATEGORY_NAME))
)
categories.add(category)
}
}
return categories
}
fun addCategory(category: Category): Long {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
if (category.id > 0) put(BaseColumns._ID, category.id)
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, category.name)
}
val id = db.insert(CategoriesContract.CategoryEntry.TABLE_NAME, null, values)
return id
}
fun updateCategory(category: Category) {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, category.name)
}
db.update(
CategoriesContract.CategoryEntry.TABLE_NAME,
values,
"${BaseColumns._ID} = ?",
arrayOf(category.id.toString())
)
}
}

View File

@@ -0,0 +1,82 @@
package org.foxarmy.barcodescannerforemployees.database
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.provider.BaseColumns
object AbstractProductContract {
object AbstractProductEntry : BaseColumns {
const val TABLE_NAME = "abstract_products"
const val BARCODE = "barcode"
const val PRODUCT_NAME = "name"
const val PRODUCT_NET_WEIGHT = "net_weight"
const val IMAGE_FILENAME = "image_filename"
const val CATEGORY = "category"
const val UNIT = "unit"
}
}
object CategoriesContract {
object CategoryEntry : BaseColumns {
const val TABLE_NAME = "categories"
const val CATEGORY_NAME = "category_name"
}
}
object ProductContract {
object ProductEntry : BaseColumns {
const val TABLE_NAME = "products"
const val ABSTRACT_PRODUCT_ID = "abstract_product_id"
const val AMOUNT = "amount"
const val DATE_OF_PRODUCTION = "date_of_production"
const val EXPIRY_DATE = "expiry_date"
}
}
const val SQL_CREATE_ABSTRACT_PRODUCTS_TABLE =
"CREATE TABLE ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} (" +
"${BaseColumns._ID} INTEGER PRIMARY KEY," +
"${AbstractProductContract.AbstractProductEntry.BARCODE} TEXT," +
"${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} TEXT," +
"${AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT} REAL," +
"${AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME} TEXT," +
"${AbstractProductContract.AbstractProductEntry.CATEGORY} INTEGER," +
"${AbstractProductContract.AbstractProductEntry.UNIT} INTEGER)"
const val SQL_CREATE_PRODUCTS_TABLE =
"CREATE TABLE ${ProductContract.ProductEntry.TABLE_NAME} (" +
"${BaseColumns._ID} INTEGER PRIMARY KEY," +
"${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID} INTEGER," +
"${ProductContract.ProductEntry.AMOUNT} INTEGER," +
"${ProductContract.ProductEntry.DATE_OF_PRODUCTION} INTEGER," +
"${ProductContract.ProductEntry.EXPIRY_DATE} INTGER)"
const val SQL_CREATE_CATEGORIES_TABLE =
"CREATE TABLE ${CategoriesContract.CategoryEntry.TABLE_NAME} (" +
"${BaseColumns._ID} INTEGER PRIMARY KEY," +
"${CategoriesContract.CategoryEntry.CATEGORY_NAME} TEXT)"
class DBStorageController(context: Context, dbname: String) :
SQLiteOpenHelper(context, dbname, null, DATABASE_VERSION) {
init {
DATABASE_NAME = "$dbname.db"
}
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(SQL_CREATE_ABSTRACT_PRODUCTS_TABLE)
db.execSQL(SQL_CREATE_PRODUCTS_TABLE)
db.execSQL(SQL_CREATE_CATEGORIES_TABLE)
}
override fun onUpgrade(db: SQLiteDatabase?, oldV: Int, newV: Int) {
TODO("Not yet implemented")
}
companion object {
const val DATABASE_VERSION = 1
var DATABASE_NAME = "database.db"
}
}

View File

@@ -0,0 +1,172 @@
package org.foxarmy.barcodescannerforemployees.database
import android.content.ContentValues
import android.provider.BaseColumns
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
class ProductDAO (private val dbHelper: DBStorageController) {
fun eraseProduct(id: Int) {
val db = dbHelper.writableDatabase
db.delete(ProductContract.ProductEntry.TABLE_NAME, BaseColumns._ID + "=" + id, null)
}
fun insertNewProduct(product: Product) : Long{
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
if (product.id > 0) put(BaseColumns._ID, product.id)
put(ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID, product.abstractProductId)
put(ProductContract.ProductEntry.AMOUNT, product.amount)
put(ProductContract.ProductEntry.DATE_OF_PRODUCTION, product.dateOfProduction)
put(ProductContract.ProductEntry.EXPIRY_DATE, product.dateOfExpiry)
}
val id = db.insert(ProductContract.ProductEntry.TABLE_NAME, null, values)
return id
}
fun findAmountOfProductsWithExpiryDate(date: Long): Int {
val db = dbHelper.readableDatabase
var amount = 0
val projection = arrayOf(
ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID,
ProductContract.ProductEntry.AMOUNT,
ProductContract.ProductEntry.DATE_OF_PRODUCTION,
)
val selection = "${ProductContract.ProductEntry.EXPIRY_DATE} = ?"
val selectionArgs = arrayOf(date.toString())
val cursor =
db.query(ProductContract.ProductEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, null)
with(cursor) {
while (moveToNext()) {
amount++
}
}
return amount
}
fun findAllExpiryDates(): Set<Long> {
val db = dbHelper.readableDatabase
val dates: MutableSet<Long> = mutableSetOf<Long>()
val projection = arrayOf(
ProductContract.ProductEntry.EXPIRY_DATE
)
val orderBy = "${ProductContract.ProductEntry.EXPIRY_DATE} ASC"
val cursor = db.query(ProductContract.ProductEntry.TABLE_NAME, projection, null, null, null, null, orderBy)
with(cursor) {
while (moveToNext()) {
dates.add(getLong(getColumnIndexOrThrow(ProductContract.ProductEntry.EXPIRY_DATE)))
}
}
return dates
}
fun updateProduct(product: Product) {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
put(BaseColumns._ID, product.id)
put(ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID, product.abstractProductId)
put(ProductContract.ProductEntry.AMOUNT, product.amount)
put(ProductContract.ProductEntry.DATE_OF_PRODUCTION, product.dateOfProduction)
put(ProductContract.ProductEntry.EXPIRY_DATE, product.dateOfExpiry)
}
db.update(
ProductContract.ProductEntry.TABLE_NAME,
values,
"${BaseColumns._ID} = ?",
arrayOf(product.id.toString())
)
}
fun getSortedListOfProducts(selectedSort: Int, filterBy: String, filter: String): List<Product> {
val db = dbHelper.readableDatabase
val products = mutableListOf<Product>()
val projection = arrayOf(
BaseColumns._ID,
ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID,
ProductContract.ProductEntry.AMOUNT,
ProductContract.ProductEntry.DATE_OF_PRODUCTION,
ProductContract.ProductEntry.EXPIRY_DATE,
)
var orderBy: String = ""
when (selectedSort) {
0 -> {
orderBy =
"(SELECT ${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} FROM ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} WHERE ${BaseColumns._ID} = ${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID}) ASC"
}
1 -> {
orderBy =
"(SELECT ${AbstractProductContract.AbstractProductEntry.CATEGORY} FROM ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} WHERE ${BaseColumns._ID} = ${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID}) ASC"
}
3 -> {
orderBy = "${ProductContract.ProductEntry.DATE_OF_PRODUCTION} ASC"
}
4 -> {
orderBy = "${ProductContract.ProductEntry.EXPIRY_DATE} ASC"
}
}
var selection: String? = null
var selectionArgs: Array<String>? = null
when (filterBy) {
"expiryDate" -> {
selection = "${ProductContract.ProductEntry.EXPIRY_DATE} = ?"
selectionArgs = arrayOf(filter)
}
"" -> {}
}
val cursor = db.query(ProductContract.ProductEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy)
with(cursor) {
while (moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID))
val abstractProductId =
getInt(getColumnIndexOrThrow(ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID))
val amount = getInt(getColumnIndexOrThrow(ProductContract.ProductEntry.AMOUNT))
val dateOfProduction =
getLong(getColumnIndexOrThrow(ProductContract.ProductEntry.DATE_OF_PRODUCTION))
val dateOfExpiry = getLong(getColumnIndexOrThrow(ProductContract.ProductEntry.EXPIRY_DATE))
val product = Product(productId, abstractProductId, amount, dateOfProduction, dateOfExpiry)
products.add(product)
}
}
products.sortWith(compareByDescending { it.freshness })
return products
}
}

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.provider.BaseColumns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -11,14 +11,39 @@ import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.CategoriesContract
import org.foxarmy.barcodescannerforemployees.DBStorageController
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.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.views.CategoryView
class CategoriesFragment : Fragment() {
private lateinit var sharedPreferences: SharedPreferences
private lateinit var categoryDAO: CategoryDAO
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
)
prepareDatabaseConnection()
}
private fun prepareDatabaseConnection() {
val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "database")!!)
categoryDAO = CategoryDAO(dbHelper)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -35,11 +60,21 @@ class CategoriesFragment : Fragment() {
fun removeSelected() {
val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
val db = DBStorageController(requireContext())
var deleted = false
for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) {
if (view.isCategorySelected) {
db.eraseCategory(db.writableDatabase, view.category.id, requireContext())
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
categoryDAO.eraseCategory(view.category.id, requireContext())
val response = net.deleteCategory(currentGroup, view.category.id)
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
deleted = true
}
}
@@ -64,28 +99,15 @@ class CategoriesFragment : Fragment() {
}
fun updateContent() {
prepareDatabaseConnection()
val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
layout?.removeAllViews()
val db = DBStorageController(requireContext()).readableDatabase
val categories = categoryDAO.getAllCategories()
val projection = arrayOf(
BaseColumns._ID,
CategoriesContract.CategoryEntry.CATEGORY_NAME
)
val cursor = db.query(CategoriesContract.CategoryEntry.TABLE_NAME, projection, null, null, null, null, null)
with (cursor) {
while(moveToNext()) {
val categoryId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val categoryName = getString(getColumnIndexOrThrow(CategoriesContract.CategoryEntry.CATEGORY_NAME))
val category = Category(categoryId, categoryName)
val categoryView = CategoryView(requireActivity(), requireContext(), category)
layout?.addView(categoryView)
}
for (category in categories) {
val categoryView = CategoryView(requireActivity(), requireContext(), category)
layout?.addView(categoryView)
}
}

View File

@@ -1,9 +1,8 @@
package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.provider.BaseColumns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -15,13 +14,14 @@ import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.gridlayout.widget.GridLayout
import org.foxarmy.barcodescannerforemployees.AbstractProductContract
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.ProductContract
import 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.dataclasses.Product
import org.foxarmy.barcodescannerforemployees.views.ProductView
import kotlin.concurrent.thread
@@ -32,6 +32,24 @@ class ShelfFragment : Fragment() {
private var filterBy = ""
private var filter = ""
private lateinit var productDAO: ProductDAO
private lateinit var sharedPreferences: SharedPreferences
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
)
prepareDatabaseConnection()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -61,6 +79,11 @@ class ShelfFragment : Fragment() {
return binding.root
}
private fun prepareDatabaseConnection() {
val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "database")!!)
productDAO = ProductDAO(dbHelper)
}
fun updateSelected() {
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
@@ -99,6 +122,7 @@ class ShelfFragment : Fragment() {
}
fun updateContent() {
prepareDatabaseConnection()
thread {
if (updateInProgress) return@thread
updateInProgress = true
@@ -107,88 +131,7 @@ class ShelfFragment : Fragment() {
grv?.removeAllViews()
}
val db = DBStorageController(requireContext()).readableDatabase
val projection = arrayOf(
BaseColumns._ID,
ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID,
ProductContract.ProductEntry.AMOUNT,
ProductContract.ProductEntry.DATE_OF_PRODUCTION,
ProductContract.ProductEntry.EXPIRY_DATE,
)
var orderBy: String = ""
when (binding.spinner.selectedItemPosition) {
0 -> {
orderBy =
"(SELECT ${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} FROM ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} WHERE ${BaseColumns._ID} = ${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID}) ASC"
}
1 -> {
orderBy =
"(SELECT ${AbstractProductContract.AbstractProductEntry.CATEGORY} FROM ${AbstractProductContract.AbstractProductEntry.TABLE_NAME} WHERE ${BaseColumns._ID} = ${ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID}) ASC"
}
// Wow, I wrote this on first try but unfortunately SQLite can't do that :(
// I'll leave it here as a memory.
// "Freshness" -> {
// orderBy =
// "(SELECT ( (julianday(${ProductContract.ProductEntry.EXPIRY_DATE}) - julianday('now', 'localtime')) / (julianday(${ProductContract.ProductEntry.EXPIRY_DATE}) - julianday(${ProductContract.ProductEntry.DATE_OF_PRODUCTION})) )) ASC"
// }
3 -> {
orderBy = "${ProductContract.ProductEntry.DATE_OF_PRODUCTION} ASC"
}
4 -> {
orderBy = "${ProductContract.ProductEntry.EXPIRY_DATE} ASC"
}
}
var selection: String? = null
var selectionArgs: Array<String>? = null
when (filterBy) {
"expiryDate" -> {
selection = "${ProductContract.ProductEntry.EXPIRY_DATE} = ?"
selectionArgs = arrayOf(filter)
}
"" -> {}
}
val cursor = db.query(ProductContract.ProductEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy)
val products = mutableListOf<Product>()
with(cursor) {
while (moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val abstractProductId =
getInt(getColumnIndexOrThrow(ProductContract.ProductEntry.ABSTRACT_PRODUCT_ID))
val amount = getInt(getColumnIndexOrThrow(ProductContract.ProductEntry.AMOUNT))
val dateOfProduction =
getLong(getColumnIndexOrThrow(ProductContract.ProductEntry.DATE_OF_PRODUCTION))
val dateOfExpiry = getLong(getColumnIndexOrThrow(ProductContract.ProductEntry.EXPIRY_DATE))
val product = Product(productId, abstractProductId, amount, dateOfProduction, dateOfExpiry)
if (binding.spinner.selectedItemPosition == 2) { //freshness
products.add(product)
} else {
val productView = ProductView(
requireActivity(),
requireContext(),
product
)
activity!!.runOnUiThread {
grv?.addView(productView)
}
}
}
}
products.sortWith(compareByDescending { it.freshness })
val products = productDAO.getSortedListOfProducts(binding.spinner.selectedItemPosition, filterBy, filter)
for (product in products.iterator()) {
val productView = ProductView(
@@ -201,6 +144,7 @@ class ShelfFragment : Fragment() {
}
}
updateInProgress = false
}
}
@@ -208,14 +152,24 @@ class ShelfFragment : Fragment() {
thread {
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
val db = DBStorageController(requireContext())
var deleted = false
for (view: ProductView in grv?.children!!.iterator() as Iterator<ProductView>) {
activity!!.runOnUiThread {
view.findViewById<ImageView>(R.id.productPicture).setImageURI(null)
}
if (view.isProductSelected) {
db.eraseProduct(db.writableDatabase, view.product.id)
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
productDAO.eraseProduct(view.product.id)
val response = net.deleteProduct(currentGroup, view.product.id)
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
deleted = true
}
}

View File

@@ -1,8 +1,8 @@
package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.provider.BaseColumns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -10,13 +10,15 @@ import android.widget.*
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.AbstractProductContract
import org.foxarmy.barcodescannerforemployees.DBStorageController
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
import org.foxarmy.barcodescannerforemployees.activities.FindBarcodelessAbstractProduct
import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.databinding.FragmentStorageBinding
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.generateThumbnailForImage
import org.foxarmy.barcodescannerforemployees.views.AbstractProductView
import kotlin.concurrent.thread
@@ -26,10 +28,25 @@ class StorageFragment : Fragment() {
private lateinit var binding: FragmentStorageBinding
private var filterBy = ""
private lateinit var filter: Array<String>
private lateinit var sharedPreferences: SharedPreferences
private lateinit var abstractProductDAO: AbstractProductDAO
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
)
prepareDatabaseConnection()
binding = FragmentStorageBinding.inflate(inflater)
fillUpSortBySpinner()
@@ -55,6 +72,11 @@ class StorageFragment : Fragment() {
return binding.root
}
private fun prepareDatabaseConnection() {
val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "database")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
}
override fun onResume() {
super.onResume()
@@ -72,14 +94,24 @@ class StorageFragment : Fragment() {
thread {
val grv = binding.contentGridLayout
val db = DBStorageController(requireContext())
var deleted = false
for (view: AbstractProductView in grv.children.iterator() as Iterator<AbstractProductView>) {
activity!!.runOnUiThread {
view.findViewById<ImageView>(R.id.productPicture).setImageURI(null)
}
if (view.isProductSelected) {
db.eraseAbstractProduct(db.writableDatabase, view.abstractProduct.id, requireContext())
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt()
abstractProductDAO.eraseAbstractProduct(view.abstractProduct.id, requireContext())
val response = net.deleteAbstractProduct(currentGroup, view.abstractProduct.id)
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
deleted = true
}
}
@@ -109,84 +141,36 @@ class StorageFragment : Fragment() {
}
fun updateContent() {
prepareDatabaseConnection()
thread {
val grv = binding.contentGridLayout
activity!!.runOnUiThread{
grv.removeAllViews()
}
val db = DBStorageController(requireContext()).readableDatabase
val projection = arrayOf(
BaseColumns._ID,
AbstractProductContract.AbstractProductEntry.BARCODE,
AbstractProductContract.AbstractProductEntry.PRODUCT_NAME,
AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT,
AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME,
AbstractProductContract.AbstractProductEntry.CATEGORY,
AbstractProductContract.AbstractProductEntry.UNIT
)
val abstractProducts = abstractProductDAO.getSortedListOfAbstractProducts(binding.spinner.selectedItemPosition, filterBy, filter)
var orderBy: String = ""
when(binding.spinner.selectedItemPosition) {
0 -> {
orderBy = "${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} ASC"
}
1 -> {
orderBy = "${AbstractProductContract.AbstractProductEntry.CATEGORY} ASC"
}
}
for (abstractProduct in abstractProducts) {
generateThumbnailForImage(context!!, abstractProduct.imageHash)
var selection = ""
var selectionArgs: Array<String>? = null
val abstractProductView = AbstractProductView(
requireActivity(),
requireContext(),
abstractProduct
)
when (filterBy) {
"category" -> {
selection = "${AbstractProductContract.AbstractProductEntry.CATEGORY} = ?"
selectionArgs = filter
}
"barcodeless" -> {
selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = '' "
selectionArgs = null
}
}
val cursor = db.query(AbstractProductContract.AbstractProductEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy)
with (cursor) {
while(moveToNext()) {
val productId = getInt(getColumnIndexOrThrow(BaseColumns._ID))
val barcode = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.BARCODE))
val productName = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME))
val netWeight = getDouble(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT))
val productImageHash = getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME))
val category = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.CATEGORY))
val unit = getInt(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.UNIT))
val product = AbstractProduct(productId, barcode, productName, netWeight, productImageHash, category, unit)
generateThumbnailForImage(context!!, productImageHash)
val abstractProduct = AbstractProductView(
requireActivity(),
requireContext(),
product
)
if (filterBy == "barcodeless") {
abstractProduct.setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProduct.abstractProduct)
}
abstractProduct.findViewById<TextView>(R.id.productNameView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProduct.abstractProduct)
}
if (filterBy == "barcodeless") {
abstractProductView.setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
activity!!.runOnUiThread{
grv.addView(abstractProduct)
abstractProductView.findViewById<TextView>(R.id.productNameView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
}
activity!!.runOnUiThread{
grv.addView(abstractProductView)
}
}
}.join()
}
@@ -202,6 +186,7 @@ class StorageFragment : Fragment() {
filter = arrayOf("$id")
updateContent()
}
companion object {
fun newInstance(filterBy: String, filter: Array<String>):StorageFragment = StorageFragment().apply {

View File

@@ -3,6 +3,7 @@ package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.AttributeSet
import android.view.LayoutInflater
@@ -13,9 +14,16 @@ import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity
import org.foxarmy.barcodescannerforemployees.*
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity
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 {
@@ -27,12 +35,26 @@ class AbstractProductView: LinearLayout {
private var unitField: TextView? = null
var abstractProduct: AbstractProduct = AbstractProduct()
var isProductSelected = false
private lateinit var activity: Activity
private var activity: Activity
private var categoryDAO: CategoryDAO
private var sharedPreferences: SharedPreferences
constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!!
val inflater:LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.abstract_product_view, this)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
categoryDAO = CategoryDAO(dbHelper)
}
constructor(activity: Activity, context: Context, abstractProduct: AbstractProduct) : super(context) {
@@ -41,6 +63,16 @@ class AbstractProductView: LinearLayout {
val inflater:LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.abstract_product_view, this)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
categoryDAO = CategoryDAO(dbHelper)
update()
}
@@ -89,6 +121,6 @@ class AbstractProductView: LinearLayout {
productNameField!!.text = abstractProduct.name
netWeightField!!.text = abstractProduct.netWeight.toString()
categoryField!!.text = DBStorageController(context).getCategoryNameById(DBStorageController(context).readableDatabase, abstractProduct.category)
categoryField!!.text = categoryDAO.getCategoryNameById(abstractProduct.category)
}
}

View File

@@ -2,13 +2,17 @@ package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import org.foxarmy.barcodescannerforemployees.DBStorageController
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
class CategoryView : LinearLayout {
@@ -17,9 +21,23 @@ class CategoryView : LinearLayout {
val amountOfProducts: TextView
var isCategorySelected = false
private var abstractProductDAO: AbstractProductDAO
private var sharedPreferences: SharedPreferences
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
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.category_view, this)
@@ -29,7 +47,7 @@ class CategoryView : LinearLayout {
amountOfProducts = findViewById(R.id.amountOfProducts)
categoryName.text = category.name
amountOfProducts.text = DBStorageController(context).getAmountOfAbstractProductsInCategory(DBStorageController(context).readableDatabase, category.id).toString()
amountOfProducts.text = abstractProductDAO.getAmountOfAbstractProductsInCategory(category.id).toString()
setOnLongClickListener {
isCategorySelected = !isCategorySelected

View File

@@ -2,21 +2,39 @@ package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import org.foxarmy.barcodescannerforemployees.DBStorageController
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 java.text.SimpleDateFormat
class ExpiryGroupView : LinearLayout {
private var representingDate: Long? = null
private var sharedPreferences: SharedPreferences
private var productDAO: ProductDAO
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
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
productDAO = ProductDAO(dbHelper)
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.expiry_group_view, this)
@@ -24,14 +42,13 @@ class ExpiryGroupView : LinearLayout {
this.background = ContextCompat.getDrawable(context,R.drawable.outline)
var amount = DBStorageController(context).findAmountOfProductsWithExpiryDate(DBStorageController(context).readableDatabase, representingDate)
val amount = productDAO.findAmountOfProductsWithExpiryDate(representingDate)
findViewById<TextView>(R.id.representingDateTextView).text = SimpleDateFormat("dd.MM.yyyy").format(representingDate * 1000)
findViewById<TextView>(R.id.amountTextView).text = amount.toString()
setOnClickListener {
(activity as ExpiryCalendarActivity).displayDate(representingDate)
// (activity as MainActivity).filterAbstractProductsByCategory(category.id)
}
}
}

View File

@@ -0,0 +1,25 @@
package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.TextView
import org.foxarmy.barcodescannerforemployees.R
class GroupMemberView : LinearLayout {
private var username: String
private var userId: Int
constructor(activity: Activity, context: Context, username: String, userId: Int) : super(context) {
this.username = username
this.userId = userId
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.group_member_view, this)
findViewById<TextView>(R.id.userNameTextView).text = username
}
}

View File

@@ -0,0 +1,37 @@
package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.ManageGroupActivity
class GroupView : LinearLayout {
private var name: String
private var id: Int
constructor(activity: Activity, context: Context, name: String, id: Int) : super(context) {
this.name = name
this.id = id
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.group_view, this)
findViewById<TextView>(R.id.nameTextView).text = name
setOnClickListener {
val intent = Intent(activity, ManageGroupActivity::class.java)
val extras = Bundle()
extras.putInt("groupId", id)
intent.putExtras(extras)
ContextCompat.startActivity(context, intent, extras)
}
}
}

View File

@@ -3,6 +3,7 @@ package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Build
@@ -20,10 +21,18 @@ import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.math.MathUtils.clamp
import org.foxarmy.barcodescannerforemployees.*
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity
import org.foxarmy.barcodescannerforemployees.calculateProductFreshness
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
@@ -33,7 +42,7 @@ import kotlin.concurrent.thread
class ProductView: LinearLayout {
var product: Product = Product()
var isProductSelected = false
private lateinit var activity: Activity
private var activity: Activity
private lateinit var productImageView: ImageView
private lateinit var productNameTextView: TextView
@@ -47,8 +56,25 @@ class ProductView: LinearLayout {
private var strokeColor: Int = 0x000000
private lateinit var outline: GradientDrawable
private var categoryDAO: CategoryDAO
private var abstractProductDAO: AbstractProductDAO
private var sharedPreferences: SharedPreferences
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
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
categoryDAO = CategoryDAO(dbHelper)
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.product_view, this)
}
@@ -60,6 +86,18 @@ class ProductView: LinearLayout {
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.product_view, this)
sharedPreferences = EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
categoryDAO = CategoryDAO(dbHelper)
productImageView = findViewById(R.id.productPicture)
productNameTextView = findViewById(R.id.productNameView)
productNetWeightTextView = findViewById(R.id.productNetWeightView)
@@ -77,7 +115,7 @@ class ProductView: LinearLayout {
productImageView.setOnClickListener {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
val abstractProduct = DBStorageController(context).findAbstractProductById(DBStorageController(context).readableDatabase, product.abstractProductId)
val abstractProduct = abstractProductDAO.findAbstractProductById(product.abstractProductId)
extras.putString("imagehash", abstractProduct!!.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
@@ -88,11 +126,7 @@ class ProductView: LinearLayout {
@RequiresApi(Build.VERSION_CODES.O)
fun update () {
val linkedAbstractProduct: AbstractProduct? = DBStorageController(activity).findAbstractProductById(DBStorageController(activity).readableDatabase, product.abstractProductId)
if(linkedAbstractProduct == null) {
return
}
val linkedAbstractProduct: AbstractProduct = abstractProductDAO.findAbstractProductById(product.abstractProductId) ?: return
val thumbnailsDir = File(activity.cacheDir, "thumbnails")
thumbnailsDir.mkdirs()
@@ -104,7 +138,7 @@ class ProductView: LinearLayout {
productNameTextView.text = linkedAbstractProduct.name
productNetWeightTextView.text = linkedAbstractProduct.netWeight.toString()
productAmountTextView.text = product.amount.toString()
productCategoryView.text = DBStorageController(activity).getCategoryNameById(DBStorageController(activity).readableDatabase, linkedAbstractProduct.category)
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))}"
productFreshnessTextView.text =
if (product.freshness == Double.NEGATIVE_INFINITY || product.freshness == Double.POSITIVE_INFINITY || product.freshness < 0) {

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:width="4dp" android:height="4dp" />
<solid android:color="#FFFFFF" />
</shape>

View File

@@ -0,0 +1,35 @@
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
android:id="@+id/newPasswordTextEdit"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.497" android:hint="@string/new_password"
app:layout_constraintTop_toBottomOf="@+id/saveUsernameButton" android:layout_marginTop="64dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text"
android:ems="10"
android:id="@+id/newUsernameTextEdit"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
android:hint="@string/new_name" app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="48dp"/>
<Button
android:text="@string/saveButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/saveUsernameButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/newUsernameTextEdit" android:layout_marginTop="16dp"/>
<Button
android:text="@string/saveButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/savePasswordButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@+id/newPasswordTextEdit"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,38 @@
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text"
android:ems="10"
android:id="@+id/groupNameTextEdit"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:hint="@string/group_name" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="210dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
android:id="@+id/groupPasswordTextEdit"
android:layout_marginTop="27dp"
app:layout_constraintTop_toBottomOf="@+id/groupNameTextEdit" android:hint="@string/group_password"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"/>
<Button
android:text="@string/create_group"
android:layout_width="91dp"
android:layout_height="wrap_content" android:id="@+id/createGroupButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/groupPasswordTextEdit"
android:layout_marginTop="24dp" app:layout_constraintEnd_toStartOf="@+id/joinGroupButton"
android:layout_marginEnd="12dp"/>
<Button
android:text="@string/join_group"
android:layout_width="91dp"
android:layout_height="wrap_content" android:id="@+id/joinGroupButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/groupPasswordTextEdit"
android:layout_marginTop="24dp" android:layout_marginStart="12dp"
app:layout_constraintStart_toEndOf="@+id/createGroupButton"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,62 @@
<?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"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@string/register"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/registerButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/loginButton" app:layout_constraintHorizontal_bias="0.15"
app:layout_constraintBottom_toTopOf="@+id/offlineButton" android:layout_marginBottom="64dp"/>
<Button
android:text="@string/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/loginButton"
app:layout_constraintStart_toEndOf="@+id/registerButton" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="2dp"
app:layout_constraintHorizontal_bias="0.1" app:layout_constraintBottom_toTopOf="@+id/offlineButton"
android:layout_marginBottom="64dp"/>
<Button
android:text="@string/offline"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/offlineButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="250dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="50dp"
android:inputType="text"
android:ems="10"
android:id="@+id/usernameTextEdit"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:hint="@string/username" app:layout_constraintBottom_toTopOf="@+id/passwordTextEdit"
android:layout_marginBottom="16dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="50dp"
android:inputType="textPassword"
android:ems="10"
android:id="@+id/passwordTextEdit"
android:hint="@string/password" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/languageSpinner" android:layout_marginBottom="24dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text"
android:ems="10"
android:id="@+id/serverTextEdit"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/usernameTextEdit" android:layout_marginBottom="16dp"
android:hint="@string/server" android:text="bsfe.foxarmy.org"/>
<Spinner
android:layout_width="409dp"
android:layout_height="32dp" android:id="@+id/languageSpinner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/registerButton" android:layout_marginBottom="24dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,61 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".activities.MainActivity">
tools:context=".activities.MainActivity"
android:fitsSystemWindows="true" android:id="@+id/drawer_layout">
<androidx.constraintlayout.widget.ConstraintLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/storageLayout">
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.appbar.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
android:layout_height="match_parent" android:id="@+id/storageLayout">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="#FFF"
app:tabIndicatorHeight="3dp"
app:tabMode="fixed" />
<com.google.android.material.appbar.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:id="@+id/appBarLayout">
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
<!-- <include layout="@layout/content_storage"/>-->
<androidx.viewpager.widget.ViewPager
android:id="@+id/tab_viewpager"
android:layout_width="match_parent"
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="#FFF"
app:tabIndicatorHeight="3dp"
app:tabMode="fixed"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/tab_viewpager"
android:layout_width="0dp"
android:layout_height="777dp"
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"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/new_element_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
app:srcCompat="@android:drawable/ic_input_add"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp" android:layout_marginBottom="16dp"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true" app:srcCompat="@android:drawable/ic_menu_my_calendar"
android:id="@+id/expiryCalendarFab" android:layout_gravity="bottom|end"
app:layout_constraintBottom_toTopOf="@+id/new_element_fab" android:layout_marginBottom="16dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
<!-- <include layout="@layout/content_storage"/>-->
<com.google.android.material.navigation.NavigationView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
android:layout_gravity="start"
app:menu="@menu/drawer_menu" android:id="@+id/nav_view"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/new_element_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_input_add"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true" app:srcCompat="@android:drawable/ic_menu_my_calendar"
android:id="@+id/expiryCalendarFab" android:layout_gravity="bottom|end"
tools:layout_editor_absoluteY="742dp" tools:layout_editor_absoluteX="337dp"
android:layout_marginBottom="84dp" android:layout_marginRight="16dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -0,0 +1,37 @@
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@string/leave_group"
android:layout_width="91dp"
android:layout_height="wrap_content" android:id="@+id/leaveButton"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="12dp"
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="48dp"
app:layout_constraintEnd_toStartOf="@+id/renameButton" android:layout_marginEnd="12dp"/>
<Button
android:text="@string/rename_group"
android:layout_width="91dp"
android:layout_height="wrap_content" android:id="@+id/renameButton"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="48dp"
app:layout_constraintStart_toEndOf="@+id/leaveButton" app:layout_constraintEnd_toStartOf="@+id/button3"
android:visibility="visible"/>
<Button
android:text="Button"
android:layout_width="91dp"
android:layout_height="wrap_content" android:id="@+id/button3"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="48dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="12dp"
app:layout_constraintStart_toEndOf="@+id/renameButton" android:visibility="gone"
android:layout_marginStart="12dp"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@+id/leaveButton"
android:layout_marginTop="48dp" android:id="@+id/membersScrollView">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="vertical" android:id="@+id/groupsContent"/>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,19 @@
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@string/join_or_create_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/newGroup"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="24dp"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/groupsContent"
app:layout_constraintTop_toBottomOf="@+id/newGroup" android:layout_marginTop="16dp">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="vertical" android:id="@+id/groupsLayout"/>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,58 @@
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" android:id="@+id/imageCompressionSetting"
android:layout_marginTop="16dp">
<TextView
android:text="@string/image_compress_factor"
android:layout_width="187dp"
android:layout_height="30dp" android:id="@+id/imageCompressionFactorTextView" android:textSize="20sp"
android:textAlignment="center"
/>
<SeekBar
style="@style/Widget.AppCompat.SeekBar.Discrete"
android:layout_width="219dp"
android:layout_height="wrap_content"
android:max="15"
android:progress="0"
android:id="@+id/imageCompressionFactorSeekBar"
android:tickMark="@drawable/seekbar_tick"
android:tickMarkTint="@color/seekbar_tick_color"
android:layout_marginTop="6dp" android:indeterminateOnly="false" android:splitTrack="true"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/currentGroupSetting"
app:layout_constraintTop_toBottomOf="@+id/imageCompressionSetting">
<TextView
android:text="@string/current_group"
android:layout_width="81dp"
android:layout_height="wrap_content" android:id="@+id/currentGroupTextView" android:layout_weight="1"
android:textSize="20sp" android:textAlignment="center"/>
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/currentGroupSpinner" android:layout_weight="1"
android:layout_marginTop="-1dp"/>
</LinearLayout>
<Button
android:text="@string/saveButton"
android:layout_width="88dp"
android:layout_height="wrap_content" android:id="@+id/saveButton"
app:layout_constraintTop_toBottomOf="@+id/currentGroupSetting"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/cancelButton"
android:layout_marginTop="32dp"/>
<Button
android:text="@string/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/cancelButton"
app:layout_constraintTop_toBottomOf="@+id/currentGroupSetting" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/saveButton" android:layout_marginTop="32dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="User"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/userNameTextView" android:layout_weight="1"
android:textSize="30sp"/>
</LinearLayout>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="Group"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/nameTextView" android:textSize="30sp"/>
</LinearLayout>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/nav_account"
android:title="@string/my_account" />
<item
android:id="@+id/nav_groups"
android:title="@string/my_groups" />
<item
android:id="@+id/nav_settings"
android:title="@string/settings"/>
</menu>

View File

@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.foxarmy.barcodescannerforemployees.activities.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:title="@string/settings"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/action_delete" android:title="@string/delete_menu"/>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">СканнерШтрихкодовДляРаботников</string>
<string name="action_settings">Настройки</string>
<string name="settings">Настройки</string>
<string name="first_fragment_label">Добавить продукт</string>
<string name="second_fragment_label">Продукты</string>
<string name="scan_label">Сканировать</string>
@@ -87,6 +87,35 @@
ошибка сканирования или введите данные вручную
</string>
<string name="abstract_product_request">Пожалуйста, отсканируйте штрихкод, чтобы добавить продукт</string>
<string name="no_barcode">No barcode present</string>
<string name="no_barcode_button">No barcode</string>
<string name="no_barcode">Нет штрихкода</string>
<string name="no_barcode_button">Без штрихкода</string>
<string name="register">Зарегистрироваться</string>
<string name="login">Войти</string>
<string name="offline">Оффлайн</string>
<string name="username">Имя пользователя</string>
<string name="password">Пароль</string>
<string name="username_already_exists">Имя пользователя занято</string>
<string name="wrong_password">Неправильный пароль</string>
<string name="server">Сервер</string>
<string name="my_account">Мой аккаунт</string>
<string name="my_groups">Мои группы</string>
<string name="nav_open">Открыть навигатор</string>
<string name="nav_close">Закрыть навигатор</string>
<string name="new_name">Новое имя</string>
<string name="new_password">Новый пароль</string>
<string name="group_name">Имя группы</string>
<string name="group_password">Пароль группы</string>
<string name="create_group">Создать группу</string>
<string name="join_group">Присоединиться к группе</string>
<string name="leave">Выйти</string>
<string name="leave_group">Покинуть группу</string>
<string name="rename_group">Переименовать группу</string>
<string name="join_or_create_group">Присоединиться или создать группу</string>
<string name="image_compress_factor">Степень сжатия изображения</string>
<string name="current_group">Текущая группа</string>
<string name="cancel">Отмена</string>
<string-array name="languages">
<item>en-US</item>
<item>ru-RU</item>
</string-array>
</resources>

View File

@@ -14,4 +14,5 @@
<color name="full_freshness">#5AFF30</color>
<color name="half_freshness">#FFF200</color>
<color name="expired_freshness">#ff0000</color>
<color name="seekbar_tick_color">#FFFFFF</color>
</resources>

View File

@@ -1,6 +1,6 @@
<resources>
<string name="app_name">BarcodeScannerForEmployees</string>
<string name="action_settings">Settings</string>
<string name="settings">Settings</string>
<string name="first_fragment_label">Add new product</string>
<string name="second_fragment_label">Products</string>
<string name="netWeight">Net weight</string>
@@ -87,4 +87,33 @@
<string name="abstract_product_request">Please, scan a barcode in order to add product</string>
<string name="no_barcode">No barcode present</string>
<string name="no_barcode_button">No barcode</string>
<string name="register">Register</string>
<string name="login">Login</string>
<string name="offline">Offline</string>
<string name="username">Username</string>
<string name="password">Password</string>
<string name="username_already_exists">Such account already exists</string>
<string name="wrong_password">Wrong password</string>
<string name="server">Server</string>
<string name="my_account">My account</string>
<string name="my_groups">My groups</string>
<string name="nav_open">navigation open</string>
<string name="nav_close">Navigation close</string>
<string name="new_name">New name</string>
<string name="new_password">New password</string>
<string name="group_name">Group name</string>
<string name="group_password">Group password</string>
<string name="create_group">Create group</string>
<string name="join_group">Join group</string>
<string name="leave">Leave</string>
<string name="leave_group">Leave group</string>
<string name="rename_group">Rename group</string>
<string name="join_or_create_group">Join or create a group</string>
<string name="image_compress_factor">Image compression factor</string>
<string name="current_group">Current group</string>
<string name="cancel">Cancel</string>
<string-array name="languages">
<item>en-US</item>
<item>ru-RU</item>
</string-array>
</resources>

View File

@@ -17,15 +17,19 @@ gridlayout = "1.0.0"
activity = "1.9.2"
legacySupportV4 = "1.0.0"
fragment = "1.8.4"
okhttp = "4.10.0"
playServicesCodeScanner = "16.1.0"
securityCrypto = "1.0.0"
volley = "1.2.1"
zxingAndroidEmbedded = "4.3.0"
material3Android = "1.3.0"
[libraries]
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraView" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraView" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraView" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" }
barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
@@ -40,9 +44,11 @@ androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", vers
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" }
androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
play-services-code-scanner = { module = "com.google.android.gms:play-services-code-scanner", version.ref = "playServicesCodeScanner" }
volley = { module = "com.android.volley:volley", version.ref = "volley" }
zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }