43 Commits

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

Кволити - не кволити,
Хуй меня уволите
2024-11-20 15:42:40 +03:00
a860e4d59f added functionality to group rename & leave buttons, added ability to transfer ownership of a group 2024-11-18 14:20:34 +03:00
37e52b3f2f Fixed category filter 2024-11-17 22:37:54 +03:00
f18d7ad1e0 performance fixes 2024-11-17 19:26:25 +03:00
1d7050c687 some ui improvements 2024-11-17 17:56:30 +03:00
153506fc83 fixed some layout overflows 2024-11-16 15:40:08 +03:00
abe2a850d6 logging out on user data changes 2024-11-15 11:36:44 +03:00
58a7ea7357 added ability to log out 2024-11-14 13:38:33 +03:00
2ebcfff51a fixed using network in offline mode 2024-11-14 13:32:56 +03:00
2676d8083e fixed net weight parsing 2024-11-14 13:11:13 +03:00
e5772bcad3 now handling unexpected ws close 2024-11-14 04:02:36 +03:00
988272070d fixed websockets, synchronization works properly, and a bunch of bugfixes 2024-11-14 03:53:28 +03:00
fbf630090c real-time sync with websockets (untested) 2024-11-13 18:51:46 +03:00
8c3845a07e added ability to use application offline 2024-11-12 18:17:01 +03:00
d08a79e981 handling no internet situations 2024-11-12 17:57:45 +03:00
ec7062602d do not erase barcode on 'no barcode present' checkbox 2024-11-11 17:06:40 +03:00
c6def13eb4 translation 2024-11-11 17:01:57 +03:00
767e1ec818 Fixed abstract product image rotation, added image compression 2024-11-11 17:00:21 +03:00
3a296e7a26 added settings activity, group swapping, working on image scale factor 2024-11-11 16:05:04 +03:00
617dcf2e7d items are now fully synchronized 2024-11-11 03:11:52 +03:00
7cc0467967 info about deleting an item is sent to the server now 2024-11-11 03:05:06 +03:00
cd299477d4 synchronizing 2024-11-10 12:43:23 +03:00
94d309c491 updating abstract products are now uploaded 2024-11-06 04:21:12 +03:00
3e5150754a first working upload: abstract product 2024-11-06 03:29:06 +03:00
8adbe65aa0 refactored for work with multiple databases. Didn't test yet 2024-11-05 04:13:15 +03:00
4041e63a4b a lot of groups-related stuff, still WIP 2024-11-04 04:49:55 +03:00
f636511983 done groups 2024-10-31 12:36:27 +03:00
7c33fe526c minor improvements 2024-10-31 08:31:47 +03:00
2feb2c1b5e working on user settings 2024-10-31 04:03:33 +03:00
a0d96da9e4 working on network and UI 2024-10-29 03:28:26 +03:00
3d4f86085e clear input fields if product's not found 2024-10-23 17:12:23 +03:00
6e497323c0 ability to add and link barcodeless products 2024-10-23 02:21:35 +03:00
6e32545b9d cleanup 2024-10-22 04:16:12 +03:00
62cc4db615 Huge load of shit, hope I'll be able to do it normally 2024-10-22 04:01:34 +03:00
60aa1973b1 fixed parser behaviour on null net weight 2024-10-20 17:14:32 +03:00
9b9946b280 cleanup 2024-10-20 17:08:03 +03:00
4f1845dd0e updated date input 2024-10-20 17:07:25 +03:00
79ab5c83c0 fixed persisted scan state on scan cancelling 2024-10-20 16:02:47 +03:00
e05f223075 translated some text 2024-10-20 12:48:57 +03:00
3a0d8cbf7f added things my wife asked 2024-10-20 00:54:31 +03:00
54693ff15d remade scaling, fixed updating 2024-10-19 23:50:01 +03:00
6ce23c2081 handling non-existend abstract products 2024-10-19 23:09:50 +03:00
79 changed files with 4792 additions and 1029 deletions

View File

@@ -50,13 +50,16 @@ dependencies {
implementation(libs.androidx.activity) implementation(libs.androidx.activity)
implementation(libs.androidx.legacy.support.v4) implementation(libs.androidx.legacy.support.v4)
implementation(libs.androidx.fragment) implementation(libs.androidx.fragment)
implementation(libs.androidx.material3.android)
testImplementation(libs.junit) testImplementation(libs.junit)
implementation(libs.volley) implementation(libs.volley)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
implementation(libs.zxing.android.embedded) implementation(libs.zxing.android.embedded)
implementation("com.google.zxing:core:3.4.1") implementation("com.google.zxing:core:3.4.1")
androidTestImplementation(libs.androidx.espresso.core) 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 // Barcode scanning API
implementation (libs.barcode.scanning) implementation (libs.barcode.scanning)

View File

@@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -36,6 +37,46 @@
android:name=".activities.AddAbstractProductActivity" android:name=".activities.AddAbstractProductActivity"
android:exported="false" android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/> android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.ExpiryCalendarActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
android:name=".activities.ExpiryCalendarGroupActivity"
android:exported="false"
android:theme="@style/Theme.BarcodeScannerForEmployees"/>
<activity
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 <activity
android:name=".activities.FullscreenActivity" android:name=".activities.FullscreenActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
@@ -49,7 +90,7 @@
<provider <provider
android:name="androidx.core.content.FileProvider" 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" 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:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data
@@ -58,7 +99,7 @@
</provider> </provider>
<activity <activity
android:name=".activities.MainActivity" android:name=".activities.NavigatorActivity"
android:exported="true" android:exported="true"
android:theme="@style/Theme.BarcodeScannerForEmployees"> android:theme="@style/Theme.BarcodeScannerForEmployees">
<intent-filter> <intent-filter>

View File

@@ -1,283 +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 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,14 @@
package org.foxarmy.barcodescannerforemployees
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
class LoadingDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState)
}
}

View File

@@ -0,0 +1,662 @@
package org.foxarmy.barcodescannerforemployees
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
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 java.util.concurrent.TimeUnit
class Net {
var language = "en-US"
var server = "bsfe.foxarmy.org"
var token = ""
fun serverIsAvailable(context: Context, callback: (Boolean) -> Unit) {
if (!isInternetConnectionAvailable(context)) {
callback(false)
}
CoroutineScope(Dispatchers.IO).launch {
var flag = false
val client = OkHttpClient();
val request = Request.Builder()
.url("https://$server/status")
.get()
.build();
try {
val response = client.newCall(request).execute();
flag = response.code == 200;
} catch (e: Exception) {
flag = false;
}
callback(flag)
}
}
fun requestProductFromOnlineDB(barcode: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val url = "https://ean-online.ru/match.php"
val client = OkHttpClient()
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()
callback(response)
}
}
fun registerAccount(username: String, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun login(username: String, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun uploadAbstractProduct(
groupId: Int,
abstractProduct: AbstractProduct,
imageFile: File,
callback: (Response) -> Unit,
) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun changeUsername(newUsername: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun changePassword(newPassword: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun createGroup(name: String, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
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()
val response = client.newCall(request).execute()
changeGroupPassword(name, password, { responseFromPassword ->
callback(response)
})
}
}
fun joinGroup(id: Int, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun changeGroupPassword(name: String, password: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
getGroupId(name, { groupId ->
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()
callback(response)
});
}
}
fun getGroupId(name: String, callback: (String) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response.body!!.string())
}
}
fun getGroupName(id: Int, callback: (String) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response.body!!.string())
}
}
fun getUsersInGroup(groupId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/group/getUsers/$groupId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
callback(response)
}
}
fun getMyGroups(callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun getUsernameById(userId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun getGroupAdminId(groupId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/group/adminId/$groupId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
callback(response)
}
}
fun updateAbstractProduct(
groupId: Int,
abstractProduct: AbstractProduct,
imageFile: File,
callback: (Response) -> Unit,
) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun uploadCategory(groupId: Int, category: Category, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun updateCategory(groupId: Int, category: Category, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun uploadProduct(groupId: Int, product: Product, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun updateProduct(groupId: Int, product: Product, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun synchronize(groupId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun downloadImage(url: String, file: File, callback: () -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.MINUTES)
.build()
val request = Request.Builder()
.url(url)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val fos = FileOutputStream(file)
response.body?.byteStream()?.use { inputStream ->
fos.use {
inputStream.copyTo(fos)
}
}
callback()
}
}
fun deleteCategory(groupId: Int, localId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun deleteAbstractProduct(groupId: Int, localId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun deleteProduct(groupId: Int, localId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
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()
callback(response)
}
}
fun renameGroup(groupId: Int, newName: String, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val client = OkHttpClient()
val body = FormBody.Builder()
.add("name", newName)
.build()
val request = Request.Builder()
.url("https://$server/api/group/rename/$groupId")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
callback(response)
}
}
fun transfer_ownership(groupId: Int, userId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val client = OkHttpClient()
val body = FormBody.Builder()
.add("userId", userId.toString())
.build()
val request = Request.Builder()
.url("https://$server/api/group/transferOwnership/$groupId")
.post(body)
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
callback(response)
}
}
fun leaveGroup(groupId: Int, callback: (Response) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
lateinit var response: Response
val client = OkHttpClient()
val request = Request.Builder()
.url("https://$server/api/group/leave/$groupId")
.get()
.addHeader("Authorization", "Bearer $token")
.addHeader("accept-language", language)
.build()
response = client.newCall(request).execute()
callback(response)
}
}
}

View File

@@ -13,7 +13,6 @@ class Parser constructor() {
val found = foundByRegex.groupValues[0] val found = foundByRegex.groupValues[0]
text = text.replace(found, "") text = text.replace(found, "")
netWeight = stripNetWeight(found) netWeight = stripNetWeight(found)
// }
return Triple(text, netWeight, found) return Triple(text, netWeight, found)
} }
} }
@@ -40,7 +39,9 @@ class Parser constructor() {
"мл" -> { 3 } "мл" -> { 3 }
"шт" -> { 4 } "шт" -> { 4 }
else -> { -1 } else -> {
4
}
} }
return AbstractProduct(0, "", name, netWeight, "", 0, unitNumber) return AbstractProduct(0, "", name, netWeight, "", 0, unitNumber)

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

@@ -3,22 +3,44 @@ package org.foxarmy.barcodescannerforemployees
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.graphics.scale import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.google.firebase.components.BuildConfig import com.google.firebase.components.BuildConfig
import org.foxarmy.barcodescannerforemployees.activities.LoginActivity
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.net.URLEncoder
import java.security.MessageDigest import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.system.exitProcess
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? { fun getImageUri(activity: Activity, imageFile: File): Uri? {
return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider", imageFile) return FileProvider.getUriForFile(
activity,
BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider",
imageFile
)
} }
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
@@ -33,8 +55,19 @@ fun generateThumbnailForImage(context: Context, imageHash: String) {
val imageFile = File(picturesDir, "$imageHash.png") val imageFile = File(picturesDir, "$imageHash.png")
val imageContent = imageFile.inputStream().readBytes() val imageContent = imageFile.inputStream().readBytes()
var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size) var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size)
img = img.scale(img.width/4,img.height/4)
img.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) @OptIn(ExperimentalStdlibApi::class)
@@ -45,7 +78,10 @@ fun String.md5(): String {
} }
fun stripNetWeight(netWeight: String): Double { fun stripNetWeight(netWeight: String): Double {
return removeSubstringsFromString(netWeight, arrayOf("Л", "л", "мл", "Мл", "г", "Г", "кг", "Кг", "шт", "Шт", ",", " ", ".")).toDouble() return removeSubstringsFromString(
netWeight,
arrayOf("Л", "л", "мл", "Мл", "г", "Г", "кг", "Кг", "шт", "Шт", ",", " ", ".")
).toDouble()
} }
fun removeSubstringsFromString(text: String, toRemove: Array<String>): String { fun removeSubstringsFromString(text: String, toRemove: Array<String>): String {
@@ -56,8 +92,6 @@ fun removeSubstringsFromString(text: String, toRemove: Array<String>): String {
return result return result
} }
fun String.utf8(): String = URLEncoder.encode(this, "UTF-8")
fun getActivity(context: Context?): Activity? { fun getActivity(context: Context?): Activity? {
if (context == null) { if (context == null) {
return null return null
@@ -84,10 +118,100 @@ fun calculateProductFreshness(dateOfProduction: Long, dateOfExpiry: Long): Doubl
fun getUnitNameById(context: Context, id: Int): String { fun getUnitNameById(context: Context, id: Int): String {
return when (id) { return when (id) {
0 -> { context.getString(R.string.kilogram) } 0 -> { context.getString(R.string.kilogram) }
1 -> { context.getString(R.string.gram) } 1 -> { context.getString(R.string.gram) }
2 -> { context.getString(R.string.liter) } 2 -> { context.getString(R.string.liter) }
3 -> { context.getString(R.string.milliliter) } 3 -> { context.getString(R.string.milliliter) }
4 -> { context.getString(R.string.pieces) } 4 -> { context.getString(R.string.pieces) }
else -> { "" } else -> { "" }
} }
} }
fun parseIntArray(input: String): IntArray {
return input.trim('[', ']').split(",").map { it.trim().toInt() }.toIntArray()
}
fun parseStringList(input: String): List<String> {
return input.trim('[', ']').split(",").map { it.trim() }.toList()
}
fun calculateMd5Hash(file: File): String {
val digest = MessageDigest.getInstance("MD5")
val fis = FileInputStream(file)
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()
}
fun isInternetConnectionAvailable(context: Context): Boolean {
if (context.getSystemService(Context.CONNECTIVITY_SERVICE) == null) return false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (connectivityManager != null) {
val capabilities =
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
if (capabilities != null) {
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return true
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return true
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
return true
}
}
}
return false
}
fun noInternetConnectionAvailableNotification(context: Context) {
(context as Activity).runOnUiThread {
AlertDialog.Builder(context)
.setMessage(context.getString(R.string.no_internet_connection))
.setCancelable(false)
.setPositiveButton(R.string.quit) { _, _ ->
exitProcess(0)
}
.setNeutralButton(R.string.logout) { _, _ ->
val sharedPreferences = getPreferences(context)
sharedPreferences.edit().putString("token", "").apply()
sharedPreferences.edit().putString("server", "").apply()
sharedPreferences.edit().putStringSet("groups", emptySet()).apply()
sharedPreferences.edit().putString("currentGroup", "").apply()
val intent = Intent(context, LoginActivity::class.java)
context.startActivity(intent)
context.finish()
}.show()
}
}
fun getPreferences(context: Context): SharedPreferences {
return EncryptedSharedPreferences.create(
"sensitive",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}

View File

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

View File

@@ -0,0 +1,66 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
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 sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val net = Net()
net.server = sharedPreferences.getString("server", "bsfe.foxarmy.org")!!
net.language = sharedPreferences.getString("language", "")!!
net.token = sharedPreferences.getString("token", "")!!
binding.saveUsernameButton.setOnClickListener {
net.changeUsername(binding.newUsernameTextEdit.text.toString(), { response ->
if (response.code == 200) {
runOnUiThread {
Toast.makeText(this, getString(R.string.username_changed), Toast.LENGTH_LONG).show()
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
}
} else {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
}
})
}
binding.savePasswordButton.setOnClickListener {
net.changePassword(binding.newPasswordTextEdit.text.toString(), { response ->
if (response.code == 200) {
runOnUiThread {
Toast.makeText(this, getString(R.string.password_changed), Toast.LENGTH_LONG).show()
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
}
} else {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
}
}
})
}
}
}

View File

@@ -1,14 +1,15 @@
package org.foxarmy.barcodescannerforemployees.activities package org.foxarmy.barcodescannerforemployees.activities
//import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions import android.app.ProgressDialog
//import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import android.content.ContentValues
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent 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.Build
import android.os.Bundle import android.os.Bundle
import android.provider.BaseColumns
import android.util.Log import android.util.Log
import android.widget.* import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -20,12 +21,16 @@ import androidx.core.widget.addTextChangedListener
import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanIntentResult import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions import com.journeyapps.barcodescanner.ScanOptions
import okhttp3.Response
import org.foxarmy.barcodescannerforemployees.* 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 org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import kotlin.concurrent.thread
class AddAbstractProductActivity : AppCompatActivity() { class AddAbstractProductActivity : AppCompatActivity() {
private lateinit var imageView: ImageView private lateinit var imageView: ImageView
@@ -35,6 +40,7 @@ class AddAbstractProductActivity : AppCompatActivity() {
private lateinit var scanButton: Button private lateinit var scanButton: Button
private lateinit var barcodeText: EditText private lateinit var barcodeText: EditText
private lateinit var noBarcodeCheckBox: CheckBox
private lateinit var productNameText: TextView private lateinit var productNameText: TextView
private lateinit var netWeightText: TextView private lateinit var netWeightText: TextView
private lateinit var unitTypeSpinner: Spinner private lateinit var unitTypeSpinner: Spinner
@@ -45,19 +51,29 @@ class AddAbstractProductActivity : AppCompatActivity() {
private lateinit var pictureFile: File private lateinit var pictureFile: File
private lateinit var picturesPath: File private lateinit var picturesPath: File
private var barcode: String = "" private var barcode: String = ""
private var action: String = "new"
private var scanningBarcode = false private var scanningBarcode = false
private lateinit var DAO: AbstractProductDAO
private lateinit var sharedPreferences: SharedPreferences
private lateinit var loadingDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_add_abstract_product) setContentView(R.layout.fragment_add_abstract_product)
val extras = intent.extras loadingDialog = ProgressDialog(this)
abstractProduct = extras!!.get("abstractProduct") as AbstractProduct? loadingDialog.setMessage(getString(R.string.loading_please_wait))
if (abstractProduct != null) { loadingDialog.setCancelable(false)
barcode = abstractProduct!!.barcode loadingDialog.setTitle(getString(R.string.loading))
}
sharedPreferences = getPreferences(this)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
DAO = AbstractProductDAO(dbHelper)
picturesPath = File(filesDir, "pictures") picturesPath = File(filesDir, "pictures")
val thumbnailsDir = File(cacheDir, "thumbnails") val thumbnailsDir = File(cacheDir, "thumbnails")
@@ -70,6 +86,7 @@ class AddAbstractProductActivity : AppCompatActivity() {
scanButton = findViewById(R.id.scan_button) scanButton = findViewById(R.id.scan_button)
barcodeText = findViewById(R.id.barcodeTextEdit) barcodeText = findViewById(R.id.barcodeTextEdit)
noBarcodeCheckBox = findViewById(R.id.noBarcodeCheckBox)
productNameText = findViewById(R.id.productName) productNameText = findViewById(R.id.productName)
netWeightText = findViewById(R.id.netWeight) netWeightText = findViewById(R.id.netWeight)
unitTypeSpinner = findViewById(R.id.unitTypeSpinner) unitTypeSpinner = findViewById(R.id.unitTypeSpinner)
@@ -79,35 +96,51 @@ class AddAbstractProductActivity : AppCompatActivity() {
fillupCategorySpinner() fillupCategorySpinner()
fillupUnitsSpinner() fillupUnitsSpinner()
noBarcodeCheckBox.setOnClickListener {
barcodeText.isEnabled = !noBarcodeCheckBox.isChecked
}
barcodeText.addTextChangedListener { barcodeText.addTextChangedListener {
this.barcode = barcodeText.text.toString() this.barcode = barcodeText.text.toString()
} }
if (abstractProduct?.name == "" && abstractProduct?.barcode != "") { val extras = intent.extras
performRequest(abstractProduct?.barcode!!) action = extras!!.get("action") as String
when (action) {
"update" -> {
abstractProduct = extras.get("abstractProduct") as AbstractProduct
} }
if (abstractProduct != null) { "new_from_barcode" -> {
abstractProduct = extras.get("abstractProduct") as AbstractProduct
barcode = abstractProduct!!.barcode
performRequest(abstractProduct!!.barcode)
}
}
if (abstractProduct != null && action == "update") {
val imageThumbnailUri = getImageUri(this, File(thumbnailsDir, "${abstractProduct!!.imageHash}.webp")) val imageThumbnailUri = getImageUri(this, File(thumbnailsDir, "${abstractProduct!!.imageHash}.webp"))
pictureFile = File(picturesPath, "${abstractProduct!!.imageHash}.png]") pictureFile = File(picturesPath, "${abstractProduct!!.imageHash}.png")
imageView.setImageURI(imageThumbnailUri) imageView.setImageURI(imageThumbnailUri)
imageView.rotation = 90f
barcodeText.setText(abstractProduct!!.barcode) barcodeText.setText(abstractProduct!!.barcode)
productNameText.text = abstractProduct!!.name productNameText.text = abstractProduct!!.name
netWeightText.text = abstractProduct!!.netWeight.toString() netWeightText.text = abstractProduct!!.netWeight.toString()
categorySpinner.setSelection(abstractProduct!!.category) categorySpinner.setSelection(abstractProduct!!.category - 1)
unitTypeSpinner.setSelection(abstractProduct!!.unit) unitTypeSpinner.setSelection(abstractProduct!!.unit)
if (abstractProduct!!.barcode == "" || abstractProduct!!.barcode == " ") {
noBarcodeCheckBox.isChecked = true
}
} }
saveButton.setOnClickListener { saveButton.setOnClickListener {
val productName = productNameText.text.toString() val productName = productNameText.text.toString()
val netWeight = netWeightText.text val netWeight = netWeightText.text
if (abstractProduct == null && (!this::pictureFile.isInitialized || !pictureFile.exists())) { if (action != "update" && (!this::pictureFile.isInitialized || !pictureFile.exists())) {
Toast.makeText(this, getString(R.string.product_picture_request), Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.product_picture_request), Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
} }
if (barcode == "") { if (barcode == "" && !noBarcodeCheckBox.isChecked) {
Toast.makeText(this, getString(R.string.product_barcode_request), Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.product_barcode_request), Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
} }
@@ -118,30 +151,47 @@ class AddAbstractProductActivity : AppCompatActivity() {
} }
if (netWeight.toString() == "" || netWeight.toString().toDoubleOrNull() == null) { if (netWeight.toString() == "" || netWeight.toString().toDoubleOrNull() == null) {
Toast.makeText(this, getString(R.string.product_net_weight_request), Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.product_net_weight_request), Toast.LENGTH_SHORT).show()
return@setOnClickListener
} }
val db = DBStorageController(this).writableDatabase loadingDialog.show()
val values = ContentValues().apply {
put(AbstractProductContract.AbstractProductEntry.BARCODE, barcode) val currentGroup: Int
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME, productName) val currentGroupString = sharedPreferences.getString("currentGroup", "offline")!!
put(AbstractProductContract.AbstractProductEntry.PRODUCT_NET_WEIGHT, netWeight.toString())
put(AbstractProductContract.AbstractProductEntry.IMAGE_FILENAME, pictureFile.nameWithoutExtension) val net = Net()
put(AbstractProductContract.AbstractProductEntry.CATEGORY, categorySpinner.selectedItemPosition)
put(AbstractProductContract.AbstractProductEntry.UNIT, unitTypeSpinner.selectedItemPosition) if (currentGroupString != "offline") {
} currentGroup = currentGroupString.toInt()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
if (abstractProduct == null) {
db.insert(AbstractProductContract.AbstractProductEntry.TABLE_NAME, null, values)
} else { } else {
db.update( currentGroup = 0
AbstractProductContract.AbstractProductEntry.TABLE_NAME,
values,
"${BaseColumns._ID} = ?",
arrayOf(abstractProduct!!.id.toString())
)
} }
finish() 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") {
DAO.updateAbstractProduct(abstractProduct!!)
if (currentGroup > 0) net.updateAbstractProduct(currentGroup, abstractProduct!!, pictureFile, this::notifyUserAndExit)
} else if (action == "new" || action == "new_from_barcode") {
abstractProduct!!.id = DAO.addAbstractProduct(abstractProduct!!).toInt()
if (currentGroup > 0)
net.uploadAbstractProduct(currentGroup, abstractProduct!!, pictureFile, this::notifyUserAndExit)
}
} }
takePictureButton.setOnClickListener { takePictureButton.setOnClickListener {
@@ -154,29 +204,30 @@ class AddAbstractProductActivity : AppCompatActivity() {
} }
} }
fun performRequest(barcode: String) { fun notifyUserAndExit(response: Response) {
barcodeText.setText(this.barcode) runOnUiThread {
val requester = Requester("https://ean-online.ru", "match.php") Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
requester.request(this, barcode) loadingDialog.dismiss()
var abstractProduct: AbstractProduct
if (DBStorageController(this).findAbstractProductByBarcode(
DBStorageController(this).readableDatabase,
this.barcode
) != null
) {
AlertDialog.Builder(this)
.setMessage("You've got an abstract product with such barcode in your database")
.setPositiveButton("Quit") { _: DialogInterface, _: Int ->
finish() finish()
} }
.setNegativeButton("Edit existing") { _: DialogInterface, _: Int -> }
fun performRequest(barcode: String) {
barcodeText.setText(this.barcode)
var abstractProduct: AbstractProduct
if (DAO.findAbstractProductByBarcode(this.barcode) != null) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.abstract_product_already_exists))
.setPositiveButton(getString(R.string.quit)) { _: DialogInterface, _: Int ->
finish()
}
.setNegativeButton(getString(R.string.edit_existing)) { _: DialogInterface, _: Int ->
val addProductIntent = Intent(this, AddAbstractProductActivity::class.java) val addProductIntent = Intent(this, AddAbstractProductActivity::class.java)
val extras = Bundle() val extras = Bundle()
val existingAbstractProduct = DBStorageController(this).findAbstractProductByBarcode( val existingAbstractProduct = DAO.findAbstractProductByBarcode(this.barcode)
DBStorageController(this).readableDatabase,
this.barcode
)
extras.putParcelable("abstractProduct", existingAbstractProduct) extras.putParcelable("abstractProduct", existingAbstractProduct)
addProductIntent.putExtras(extras) addProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addProductIntent, extras) ContextCompat.startActivity(this, addProductIntent, extras)
@@ -184,26 +235,25 @@ class AddAbstractProductActivity : AppCompatActivity() {
}.show() }.show()
} }
thread { val net = Net();
// Я сам в ахуях какой это костыль, пока хз как фиксить, потом придумаю :)) net.requestProductFromOnlineDB(barcode, {response ->
while (requester.response == "") { if (response.code == 404) {
}
if (requester.response == "Not found 404") {
runOnUiThread { runOnUiThread {
Toast.makeText(this, "Product not found. Please, try again or type manually", Toast.LENGTH_LONG) Toast.makeText(this, getString(R.string.no_product_in_online_database), Toast.LENGTH_LONG)
.show() .show()
productNameText.setText("")
netWeightText.setText("")
} }
return@thread return@requestProductFromOnlineDB
} }
abstractProduct = Parser().parse(requester.response) abstractProduct = Parser().parse(response.body!!.string())
requester.response = ""
runOnUiThread { runOnUiThread {
productNameText.text = abstractProduct.name productNameText.text = abstractProduct.name
netWeightText.text = abstractProduct.netWeight.toString() netWeightText.text = abstractProduct.netWeight.toString()
unitTypeSpinner.setSelection(abstractProduct.unit) unitTypeSpinner.setSelection(abstractProduct.unit)
} }
} })
} }
private fun fillupUnitsSpinner() { private fun fillupUnitsSpinner() {
@@ -215,40 +265,21 @@ class AddAbstractProductActivity : AppCompatActivity() {
getString(R.string.pieces) getString(R.string.pieces)
) )
val arrayAdapter = 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) arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
unitTypeSpinner.adapter = arrayAdapter unitTypeSpinner.adapter = arrayAdapter
} }
fun fillupCategorySpinner() { fun fillupCategorySpinner() {
val db = DBStorageController(this).readableDatabase
val categories = mutableListOf("") val categoriesDAO =
CategoryDAO(DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!))
val projection = arrayOf( val categories = categoriesDAO.getAllCategories().map { category -> category.name }
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 arrayAdapter = 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) arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
categorySpinner.adapter = arrayAdapter categorySpinner.adapter = arrayAdapter
} }
@@ -258,12 +289,34 @@ class AddAbstractProductActivity : AppCompatActivity() {
if (success) { if (success) {
//Move picture to a proper directory according to its calculated hash //Move picture to a proper directory according to its calculated hash
val tempfile = File(filesDir, "image.png") val tempfile = File(filesDir, "image.png")
val imageContent = tempfile.inputStream().readBytes() val imageContent = tempfile.inputStream().readBytes()
val imageHash = imageContent.toString(Charsets.UTF_8).md5() var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size)
val exif = ExifInterface(tempfile.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(tempfile)
)
val imageHash = calculateMd5Hash(tempfile)
pictureFile = File(picturesPath, "$imageHash.png") pictureFile = File(picturesPath, "$imageHash.png")
Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING) Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
tempfile.delete() tempfile.delete()
generateThumbnailForImage(this, imageHash) generateThumbnailForImage(this, imageHash)
imageView.setImageURI(getImageUri(this, pictureFile)) imageView.setImageURI(getImageUri(this, pictureFile))
@@ -294,7 +347,8 @@ class AddAbstractProductActivity : AppCompatActivity() {
getPicture() getPicture()
} }
} else { } 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()
} }
} }
@@ -303,11 +357,11 @@ class AddAbstractProductActivity : AppCompatActivity() {
if (result.contents == null) { if (result.contents == null) {
Toast.makeText(this, getString(R.string.cancelled), Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
} else { } else {
scanningBarcode = false
val scannedBarcode = result.contents val scannedBarcode = result.contents
barcodeText.setText(scannedBarcode) barcodeText.setText(scannedBarcode)
performRequest(scannedBarcode) performRequest(scannedBarcode)
} }
scanningBarcode = false
} }
} }

View File

@@ -1,22 +1,40 @@
package org.foxarmy.barcodescannerforemployees.activities package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity import android.app.Activity
import android.content.ContentValues import android.app.ProgressDialog
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.provider.BaseColumns
import android.widget.Button import android.widget.Button
import android.widget.EditText import android.widget.EditText
import org.foxarmy.barcodescannerforemployees.CategoriesContract import android.widget.Toast
import org.foxarmy.barcodescannerforemployees.dataclasses.Category import okhttp3.Response
import org.foxarmy.barcodescannerforemployees.DBStorageController import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R 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() { class AddCategoryActivity : Activity() {
private lateinit var DAO: CategoryDAO
private lateinit var sharedPreferences: SharedPreferences
private lateinit var loadingDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_category) setContentView(R.layout.activity_add_category)
loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
DAO = CategoryDAO(dbHelper)
val extras = intent.extras val extras = intent.extras
val category = extras!!.get("category") as Category? val category = extras!!.get("category") as Category?
@@ -24,23 +42,41 @@ class AddCategoryActivity : Activity() {
categoryNameTextEdit.setText(category!!.name) categoryNameTextEdit.setText(category!!.name)
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "")!!
val currentGroup: Int = if (sharedPreferences.getString(
"currentGroup",
"offline"
)!! == "offline"
) 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
findViewById<Button>(R.id.saveButton).setOnClickListener { findViewById<Button>(R.id.saveButton).setOnClickListener {
val db = DBStorageController(this).writableDatabase if (categoryNameTextEdit.text.toString() == "") {
Toast.makeText(this, getString(R.string.category_name_required), Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
loadingDialog.show()
if (category.id == 0) { // Inserting new category if (category.id == 0) { // Inserting new category
val values = ContentValues().apply { val newCategory = Category(0, categoryNameTextEdit.text.toString())
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, categoryNameTextEdit.text.toString()) newCategory.id = DAO.addCategory(newCategory).toInt()
} if (currentGroup > 0) net.uploadCategory(currentGroup, newCategory, this::notifyUserAndExit)
db.insert(CategoriesContract.CategoryEntry.TABLE_NAME, null, values)
} else { // Updating existing category } else { // Updating existing category
val values = ContentValues().apply { category.name = categoryNameTextEdit.text.toString()
put(CategoriesContract.CategoryEntry.CATEGORY_NAME, categoryNameTextEdit.text.toString()) DAO.updateCategory(category)
if (currentGroup > 0) net.updateCategory(currentGroup, category, this::notifyUserAndExit)
} }
db.update(CategoriesContract.CategoryEntry.TABLE_NAME, values, "${BaseColumns._ID} = ?", arrayOf(category.id.toString()))
} }
}
fun notifyUserAndExit(response: Response) {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
loadingDialog.dismiss()
finish() finish()
} }
} }
}

View File

@@ -1,19 +1,28 @@
package org.foxarmy.barcodescannerforemployees.activities package org.foxarmy.barcodescannerforemployees.activities
import android.app.DatePickerDialog import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.* import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanIntentResult import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions 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.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.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Product import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import org.foxarmy.barcodescannerforemployees.views.AbstractProductView import org.foxarmy.barcodescannerforemployees.views.AbstractProductView
@@ -23,19 +32,18 @@ import java.util.*
class AddProductActivity : AppCompatActivity() { class AddProductActivity : AppCompatActivity() {
private lateinit var scanButton: Button private lateinit var scanButton: Button
private lateinit var noBarcodeButton: Button
private lateinit var abstractProductView: AbstractProductView private lateinit var abstractProductView: AbstractProductView
private lateinit var expiryDateRadioButton: RadioButton private lateinit var expiryDateRadioButton: RadioButton
private lateinit var shelfLifeRadioButton: RadioButton private lateinit var shelfLifeRadioButton: RadioButton
private lateinit var expiryDateTextView: TextView private lateinit var expiryDatePicker: DatePicker
private lateinit var expiryDateSelectButton: Button
private lateinit var shelfLifeTextView: TextView
private lateinit var shelfLifeTextEdit: EditText private lateinit var shelfLifeTextEdit: EditText
private lateinit var amountTextEdit: EditText private lateinit var amountTextEdit: EditText
private lateinit var dateOfProductionSelectButton: Button private lateinit var dateOfProductionDatePicker: DatePicker
private lateinit var saveProductButton: Button private lateinit var saveProductButton: Button
private var expiryDateOverShelfLife: Boolean? = null private var expiryDateOverShelfLife: Boolean? = null
@@ -43,32 +51,50 @@ class AddProductActivity : AppCompatActivity() {
private var product: Product? = null private var product: Product? = null
private var abstractProduct: AbstractProduct? = null private var abstractProduct: AbstractProduct? = null
private lateinit var productDAO: ProductDAO
private lateinit var abstractProductDAO: AbstractProductDAO
private lateinit var sharedPreferences: SharedPreferences
private lateinit var loadingDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_add_product) setContentView(R.layout.fragment_add_product)
loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!)
productDAO = ProductDAO(dbHelper)
abstractProductDAO = AbstractProductDAO(dbHelper)
scanButton = findViewById(R.id.scanButton) scanButton = findViewById(R.id.scanButton)
noBarcodeButton = findViewById(R.id.noBarcodeButton)
abstractProductView = findViewById(R.id.abstractProductView) abstractProductView = findViewById(R.id.abstractProductView)
expiryDateRadioButton = findViewById(R.id.expiryDateRadio) expiryDateRadioButton = findViewById(R.id.expiryDateRadio)
shelfLifeRadioButton = findViewById(R.id.shelfLifeRadio) shelfLifeRadioButton = findViewById(R.id.shelfLifeRadio)
expiryDateTextView = findViewById(R.id.expiryDateTextView) expiryDatePicker = findViewById(R.id.expiryDatePicker)
expiryDateSelectButton = findViewById(R.id.selectExpiryDateButton)
shelfLifeTextView = findViewById(R.id.shelfLife)
shelfLifeTextEdit = findViewById(R.id.shelfLifeTextEdit) shelfLifeTextEdit = findViewById(R.id.shelfLifeTextEdit)
amountTextEdit = findViewById(R.id.amountTextEdit) amountTextEdit = findViewById(R.id.amountTextEdit)
amountTextEdit.setText("1")
dateOfProductionSelectButton = findViewById(R.id.selectDateOfProductionButton) dateOfProductionDatePicker = findViewById(R.id.dateOfProductionDatePicker)
saveProductButton = findViewById(R.id.saveProductButton) saveProductButton = findViewById(R.id.saveProductButton)
val extras = intent.extras val extras = intent.extras
product = extras!!.get("product") as Product? product = extras!!.get("product") as Product?
if (product != null) { if (product != null) {
abstractProduct = DBStorageController(this).findAbstractProductById(DBStorageController(this).readableDatabase, product!!.abstractProductId) abstractProduct = abstractProductDAO.findAbstractProductById(product!!.abstractProductId)
abstractProductView.abstractProduct = abstractProduct!! abstractProductView.abstractProduct = abstractProduct!!
expiryDateRadioButton.isSelected = true expiryDateRadioButton.isSelected = true
shelfLifeRadioButton.isSelected = false shelfLifeRadioButton.isSelected = false
@@ -78,13 +104,16 @@ class AddProductActivity : AppCompatActivity() {
update() update()
} else { } else {
product = Product(0, 0, 0, 0, 0) product = Product(0, 0, 0, 0, 0)
abstractProduct = AbstractProduct(0, "", "", 0.0, "", 0, 0)
} }
scanButton.setOnClickListener { scanButton.setOnClickListener {
requestPermissionLauncher.launch(android.Manifest.permission.CAMERA) requestPermissionLauncher.launch(android.Manifest.permission.CAMERA)
} }
noBarcodeButton.setOnClickListener {
intentLauncher.launch(Intent(this, FindBarcodelessAbstractProduct::class.java))
}
expiryDateRadioButton.setOnClickListener { expiryDateRadioButton.setOnClickListener {
expiryDateOverShelfLife = true expiryDateOverShelfLife = true
update() update()
@@ -95,32 +124,14 @@ class AddProductActivity : AppCompatActivity() {
update() update()
} }
dateOfProductionSelectButton.setOnClickListener { dateOfProductionDatePicker.setOnDateChangedListener { _, year, monthOfYear, dayOfMonth ->
val c = Calendar.getInstance() product!!.dateOfProduction = SimpleDateFormat("dd.MM.yyyy").parse("$dayOfMonth.$monthOfYear.$year")!!.time / 1000
val year = c.get(Calendar.YEAR)
val month = c.get(Calendar.MONTH)
val day = c.get(Calendar.DAY_OF_MONTH)
val dpd = DatePickerDialog(this, { _, y, m, d ->
product!!.dateOfProduction = SimpleDateFormat("dd.MM.yyyy").parse("$d.${m+1}.$y")!!.time / 1000
update() update()
}, year, month, day)
dpd.show()
} }
expiryDateSelectButton.setOnClickListener { expiryDatePicker.setOnDateChangedListener { _, year, monthOfYear, dayOfMonth ->
val c = Calendar.getInstance() product!!.dateOfExpiry = SimpleDateFormat("dd.MM.yyyy").parse("$dayOfMonth.$monthOfYear.$year")!!.time / 1000
val year = c.get(Calendar.YEAR)
val month = c.get(Calendar.MONTH)
val day = c.get(Calendar.DAY_OF_MONTH)
val dpd = DatePickerDialog(this, { _, y, m, d ->
product!!.dateOfExpiry = SimpleDateFormat("dd.MM.yyyy").parse("$d.${m+1}.$y")!!.time / 1000
update() update()
}, year, month, day)
dpd.show()
} }
amountTextEdit.addTextChangedListener { amountTextEdit.addTextChangedListener {
@@ -129,7 +140,18 @@ class AddProductActivity : AppCompatActivity() {
product!!.amount = text.toInt() 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 { saveProductButton.setOnClickListener {
if (abstractProduct == null) {
Toast.makeText(this, getString(R.string.abstract_product_request), Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (expiryDateOverShelfLife == null) { if (expiryDateOverShelfLife == null) {
Toast.makeText(this, getString(R.string.shell_life_or_expiry_date_request), Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.shell_life_or_expiry_date_request), Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
@@ -155,35 +177,58 @@ class AddProductActivity : AppCompatActivity() {
return@setOnClickListener return@setOnClickListener
} }
loadingDialog.show()
val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt()
if (updatingExistentProduct) { if (updatingExistentProduct) {
DBStorageController(this).updateProduct(DBStorageController(this).writableDatabase, product!!) productDAO.updateProduct(product!!)
if (currentGroup > 0) net.updateProduct(currentGroup, product!!, this::notifyUserAndExit)
} else { } else {
DBStorageController(this).insertNewProduct(DBStorageController(this).writableDatabase, product!!) product!!.id = productDAO.insertNewProduct(product!!).toInt()
if (currentGroup > 0) net.uploadProduct(currentGroup, product!!, this::notifyUserAndExit)
}
}
update()
val today = SimpleDateFormat("dd.MM.yyyy").format(Calendar.getInstance().time).split(".")
dateOfProductionDatePicker.updateDate(today[2].toInt(), today[1].toInt() - 1, today[0].toInt())
expiryDatePicker.updateDate(today[2].toInt(), today[1].toInt() - 1, today[0].toInt())
product!!.dateOfProduction = SimpleDateFormat("dd.MM.yyyy").parse("${today[0]}.${today[1]}.${today[2]}")!!.time / 1000
} }
fun notifyUserAndExit(response: Response) {
runOnUiThread {
Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show()
loadingDialog.dismiss()
finish() finish()
} }
} }
private val intentLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val selectedAbstractProduct = (result.data?.extras!!.getParcelable("abstractProduct") as AbstractProduct?)!!
product!!.abstractProductId = selectedAbstractProduct.id
abstractProduct = selectedAbstractProduct
displayAbstractProduct(selectedAbstractProduct, "")
}
}
private fun update () { private fun update () {
if (expiryDateOverShelfLife == true) { if (expiryDateOverShelfLife == true) {
expiryDateTextView.visibility = View.VISIBLE expiryDatePicker.visibility = View.VISIBLE
expiryDateSelectButton.visibility = View.VISIBLE
shelfLifeTextEdit.visibility = View.INVISIBLE shelfLifeTextEdit.visibility = View.INVISIBLE
shelfLifeTextView.visibility = View.INVISIBLE
} else if (expiryDateOverShelfLife == false){ } else if (expiryDateOverShelfLife == false){
expiryDateTextView.visibility = View.INVISIBLE expiryDatePicker.visibility = View.INVISIBLE
expiryDateSelectButton.visibility = View.INVISIBLE
shelfLifeTextEdit.visibility = View.VISIBLE shelfLifeTextEdit.visibility = View.VISIBLE
shelfLifeTextView.visibility = View.VISIBLE
} }
val dateOfProductionParsed = SimpleDateFormat("dd.MM.yyyy").format(Date(product!!.dateOfProduction * 1000))
findViewById<TextView>(R.id.dateOfProductionTextView).text = "${getString(R.string.date_of_production)}: $dateOfProductionParsed"
val expiryDateParsed = SimpleDateFormat("dd.MM.yyyy").format(Date(product!!.dateOfExpiry * 1000))
expiryDateTextView.text = "${getString(R.string.expiry_date)}: $expiryDateParsed"
if (amountTextEdit.text.toString() == "") { if (amountTextEdit.text.toString() == "") {
amountTextEdit.setText(product!!.amount.toString()) amountTextEdit.setText(product!!.amount.toString())
@@ -206,10 +251,26 @@ class AddProductActivity : AppCompatActivity() {
product!!.dateOfExpiry = c.timeInMillis / 1000 product!!.dateOfExpiry = c.timeInMillis / 1000
} }
private fun displayAbstractProduct(abstractProduct: AbstractProduct) { private fun displayAbstractProduct(abstractProduct: AbstractProduct?, scannedBarcode: String) {
if (abstractProduct == null) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.abstract_product_does_not_exist))
.setPositiveButton(R.string.yes) { _, _ ->
val addAbstractProductIntent = Intent(this, AddAbstractProductActivity::class.java)
val extras = Bundle()
extras.putParcelable("abstractProduct", AbstractProduct(0, scannedBarcode, "", 0.0, "", 0, 0))
extras.putString("action", "new_from_barcode")
addAbstractProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addAbstractProductIntent, extras)
}
.setNegativeButton(R.string.no) {_, _ ->
}.show()
} else {
abstractProductView.abstractProduct = abstractProduct abstractProductView.abstractProduct = abstractProduct
abstractProductView.update() abstractProductView.update()
} }
}
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
val requestPermissionLauncher = val requestPermissionLauncher =
@@ -229,12 +290,15 @@ class AddProductActivity : AppCompatActivity() {
Toast.makeText(this, getString(R.string.cancelled), Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
} else { } else {
val scannedBarcode = result.contents val scannedBarcode = result.contents
abstractProduct = DBStorageController(this).findAbstractProductByBarcode(DBStorageController(this).readableDatabase, scannedBarcode) abstractProduct = abstractProductDAO.findAbstractProductByBarcode(scannedBarcode)
displayAbstractProduct(abstractProduct!!)
displayAbstractProduct(abstractProduct, scannedBarcode)
if (abstractProduct != null) {
product?.abstractProductId = abstractProduct!!.id product?.abstractProductId = abstractProduct!!.id
} }
} }
} }
}
private fun prepareBarcodeScanner() { private fun prepareBarcodeScanner() {
val options = ScanOptions() val options = ScanOptions()

View File

@@ -0,0 +1,53 @@
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.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 = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)
productDAO = ProductDAO(dbHelper)
fillUp()
}
fun displayDate(date:Long) {
val expiryCalendarGroupActivityIntent = Intent(this, ExpiryCalendarGroupActivity::class.java)
val extras = Bundle()
extras.putLong("date", date)
expiryCalendarGroupActivityIntent.putExtras(extras)
ContextCompat.startActivity(this, expiryCalendarGroupActivityIntent, extras)
}
private fun fillUp() {
val dates = productDAO.findAllExpiryDates()
val container = findViewById<LinearLayout>(R.id.datesLinearLayout)
dates.forEach { date ->
val newDate = ExpiryGroupView(this, this as Context, date)
container.addView(newDate)
}
}
}

View File

@@ -0,0 +1,25 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityExpiryCalendarGroupBinding
import org.foxarmy.barcodescannerforemployees.fragments.ShelfFragment
class ExpiryCalendarGroupActivity : AppCompatActivity() {
private lateinit var binding: ActivityExpiryCalendarGroupBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityExpiryCalendarGroupBinding.inflate(layoutInflater)
val date = intent.extras!!.getLong("date")
val ft = supportFragmentManager.beginTransaction()
ft.replace(R.id.content, ShelfFragment.newInstance(date))
ft.commit()
setContentView(binding.root)
}
}

View File

@@ -0,0 +1,34 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityFindBarcodelessAbstractProductBinding
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.fragments.StorageFragment
class FindBarcodelessAbstractProduct() : AppCompatActivity() {
private lateinit var binding: ActivityFindBarcodelessAbstractProductBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityFindBarcodelessAbstractProductBinding.inflate(layoutInflater)
val ft = supportFragmentManager.beginTransaction()
val fragment = StorageFragment.newInstance("barcodeless", arrayOf("", " ", " "))
ft.replace(R.id.content, fragment)
ft.commit()
setContentView(binding.root)
}
fun selected(abstractProduct: AbstractProduct) {
val data = Intent()
data.putExtra("abstractProduct", abstractProduct)
setResult(Activity.RESULT_OK, data)
finish()
}
}

View File

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

View File

@@ -0,0 +1,123 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.ProgressDialog
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityGroupBinding
import org.foxarmy.barcodescannerforemployees.noInternetConnectionAvailableNotification
class GroupActivity : AppCompatActivity() {
private lateinit var binding: ActivityGroupBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityGroupBinding.inflate(layoutInflater)
setContentView(binding.root)
val loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
binding.createGroupButton.setOnClickListener {
val groupName = binding.groupNameTextEdit.text.toString()
val groupPassword = binding.groupPasswordTextEdit.text.toString()
val net = Net()
net.language = sharedPreferences.getString("language", "en-US")!!
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
net.serverIsAvailable(this, { isServerAvailable ->
if (!isServerAvailable) {
noInternetConnectionAvailableNotification(this)
} else {
runOnUiThread {
loadingDialog.show()
}
net.createGroup(groupName, groupPassword, { response ->
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()
runOnUiThread {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
loadingDialog.dismiss()
finish()
}
} else {
runOnUiThread {
loadingDialog.dismiss()
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show()
}
}
})
}
})
}
binding.joinGroupButton.setOnClickListener {
val groupName = binding.groupNameTextEdit.text.toString()
val groupPassword = binding.groupPasswordTextEdit.text.toString()
val net = Net()
net.language = sharedPreferences.getString("language", "en-US")!!
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
net.serverIsAvailable(this) { isServerAvailable ->
if (!isServerAvailable) {
noInternetConnectionAvailableNotification(this)
} else {
runOnUiThread {
loadingDialog.show()
}
net.getGroupId(groupName, { requestGroupIdResponse ->
var groupId: Int
try {
groupId = requestGroupIdResponse.toInt()
} catch (e: Exception) {
runOnUiThread {
loadingDialog.dismiss()
Toast.makeText(this, requestGroupIdResponse, Toast.LENGTH_SHORT).show()
}
return@getGroupId
}
net.joinGroup(groupId, groupPassword, { response ->
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()
runOnUiThread {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
loadingDialog.dismiss()
finish()
}
} else {
runOnUiThread {
loadingDialog.dismiss()
Toast.makeText(this, responseText, Toast.LENGTH_LONG).show()
}
}
})
})
}
}
}
}
}

View File

@@ -0,0 +1,155 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.ProgressDialog
import android.content.Intent
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityLoginBinding
import org.foxarmy.barcodescannerforemployees.noInternetConnectionAvailableNotification
import org.foxarmy.barcodescannerforemployees.parseIntArray
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)
val loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
fillUpLanguagesSpinner()
val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
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 net = Net()
net.language = sharedPreferences.getString("language", "en-US")!!
net.server = server
net.serverIsAvailable(this, { isServerAvailable ->
if (!isServerAvailable) {
noInternetConnectionAvailableNotification(this)
} else {
runOnUiThread {
loadingDialog.show()
}
net.login(username, password, { response ->
val responseText = response.body!!.string()
if (response.code != 200) {
runOnUiThread {
Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show()
}
} else {
val json = JSONObject(responseText)
sharedPreferences.edit().putString("token", json["token"].toString()).apply()
net.token = json["token"].toString()
sharedPreferences.edit().putInt("userId", json["id"].toString().toInt()).apply()
sharedPreferences.edit().putString("server", server).apply()
net.getMyGroups({ response ->
runOnUiThread {
loadingDialog.dismiss()
}
val r = response.body!!.string()
if (r == "" || r == "[]") {
goToActivity("GroupActivity")
}
val myGroups = parseIntArray(r).map { a -> a.toString() }
sharedPreferences.edit().putStringSet("groups", myGroups.toSet()).apply()
sharedPreferences.edit().putString("currentGroup", myGroups[0]).apply()
goToActivity("MainActivity")
})
}
})
}
})
}
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 net = Net()
net.language = language
net.server = server
net.serverIsAvailable(this, {isServerAvailable ->
if (!isServerAvailable) {
noInternetConnectionAvailableNotification(this)
} else {
net.registerAccount(username, password, {response ->
val responseText = response.body!!.string()
if (response.code != 200) {
Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show();
} else {
sharedPreferences.edit().putInt("userId", responseText.toInt()).apply()
net.login(username, password, {response ->
val token = JSONObject(response.body!!.string())["token"].toString()
sharedPreferences.edit().putString("token", token).apply()
sharedPreferences.edit().putString("server", server).apply()
runOnUiThread {
loadingDialog.dismiss()
}
goToActivity("GroupActivity")
})
}
})
}
})
}
binding.offlineButton.setOnClickListener {
sharedPreferences.edit().putString("currentGroup", "offline").apply()
sharedPreferences.edit().putStringSet("groups", setOf("offline")).apply()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
}
private fun fillUpLanguagesSpinner() {
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
}
private fun goToActivity(activityName: String) {
runOnUiThread {
val intent = Intent(this, when(activityName) {
"MainActivity" -> MainActivity::class.java
"GroupActivity" -> GroupActivity::class.java
"LoginActivity" -> LoginActivity::class.java
else -> LoginActivity::class.java
})
startActivity(intent)
finish()
}
}
}

View File

@@ -1,26 +1,54 @@
package org.foxarmy.barcodescannerforemployees.activities package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.app.ProgressDialog
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import org.foxarmy.barcodescannerforemployees.R import com.google.android.material.navigation.NavigationView
import org.foxarmy.barcodescannerforemployees.ViewPagerAdapter 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.database.ProductDAO
import org.foxarmy.barcodescannerforemployees.databinding.ActivityMainBinding import org.foxarmy.barcodescannerforemployees.databinding.ActivityMainBinding
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Category import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import org.foxarmy.barcodescannerforemployees.fragments.CategoriesFragment import org.foxarmy.barcodescannerforemployees.fragments.CategoriesFragment
import org.foxarmy.barcodescannerforemployees.fragments.ShelfFragment import org.foxarmy.barcodescannerforemployees.fragments.ShelfFragment
import org.foxarmy.barcodescannerforemployees.fragments.StorageFragment 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 private lateinit var binding: ActivityMainBinding
lateinit var adapter: ViewPagerAdapter private lateinit var adapter: ViewPagerAdapter
private var drawerLayout: DrawerLayout? = null
private var actionBarDrawerToggle: ActionBarDrawerToggle? = null
private lateinit var ws: WebSocketClient
var selectionMode = false
private var selectedAmount = 0
private lateinit var sharedPreferences: SharedPreferences
private lateinit var loadingDialog: ProgressDialog
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -28,11 +56,40 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
loadingDialog = ProgressDialog(this)
loadingDialog.setMessage(getString(R.string.loading_please_wait))
loadingDialog.setCancelable(false)
loadingDialog.setTitle(getString(R.string.loading))
sharedPreferences = getPreferences(this)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
setupViewPager(binding.tabViewpager) setupViewPager(binding.tabViewpager)
binding.tabTablayout.setupWithViewPager(binding.tabViewpager) binding.tabTablayout.setupWithViewPager(binding.tabViewpager)
binding.newElementFab.setOnClickListener { view -> binding.tabTablayout.setOnDragListener { _, _ ->
selectionMode = false
selectedAmount = 0
true
}
drawerLayout = binding.drawerLayout
actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.nav_open, R.string.nav_close)
drawerLayout!!.addDrawerListener(actionBarDrawerToggle!!)
actionBarDrawerToggle!!.syncState()
binding.navView.setNavigationItemSelectedListener(this)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
binding.expiryCalendarFab.setOnClickListener { _ ->
val expiryCalendarIntent = Intent(this, ExpiryCalendarActivity::class.java)
val extras = Bundle()
expiryCalendarIntent.putExtras(extras)
ContextCompat.startActivity(this, expiryCalendarIntent, extras)
}
binding.newElementFab.setOnClickListener { _ ->
val currentPosition = binding.tabTablayout.selectedTabPosition val currentPosition = binding.tabTablayout.selectedTabPosition
val fragment = adapter.getItem(currentPosition) val fragment = adapter.getItem(currentPosition)
@@ -40,9 +97,8 @@ class MainActivity : AppCompatActivity() {
"StorageFragment" -> { "StorageFragment" -> {
val addAbstractProductIntent = Intent(this, AddAbstractProductActivity::class.java) val addAbstractProductIntent = Intent(this, AddAbstractProductActivity::class.java)
val extras = Bundle() val extras = Bundle()
// I reuse the same stuff for editing and adding new product.
// if abstractProduct == null, it means that we need to create new object
extras.putParcelable("abstractProduct", null) extras.putParcelable("abstractProduct", null)
extras.putString("action", "new")
addAbstractProductIntent.putExtras(extras) addAbstractProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addAbstractProductIntent, extras) ContextCompat.startActivity(this, addAbstractProductIntent, extras)
} }
@@ -50,7 +106,7 @@ class MainActivity : AppCompatActivity() {
"CategoriesFragment" -> { "CategoriesFragment" -> {
val addCategoryIntent = Intent(this, AddCategoryActivity::class.java) val addCategoryIntent = Intent(this, AddCategoryActivity::class.java)
val extras = Bundle() val extras = Bundle()
extras.putParcelable("category", Category(0, "New category")) extras.putParcelable("category", Category(0, ""))
addCategoryIntent.putExtras(extras) addCategoryIntent.putExtras(extras)
ContextCompat.startActivity(this, addCategoryIntent, extras) ContextCompat.startActivity(this, addCategoryIntent, extras)
} }
@@ -61,6 +117,220 @@ class MainActivity : AppCompatActivity() {
extras.putParcelable("product", null) extras.putParcelable("product", null)
addProductIntent.putExtras(extras) addProductIntent.putExtras(extras)
ContextCompat.startActivity(this, addProductIntent, extras) ContextCompat.startActivity(this, addProductIntent, extras)
}
}
}
val net = Net()
net.server = sharedPreferences.getString("server", "")!!
val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!
net.serverIsAvailable(this, {isServerAvailable ->
if ( currentGroup != "offline" && !isServerAvailable) {
runOnUiThread {
noInternetConnectionAvailableNotification(this)
}
} else if (currentGroup != "offline" && isServerAvailable) {
synchronize()
ws = WebSocketClient(this, sharedPreferences.getString("server", "")!!)
val token = sharedPreferences.getString("token", "")!!
ws.connect(token, currentGroup)
}
})
}
private fun synchronize() {
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()
net.synchronize(currentGroup, {response ->
val data = JSONObject(response.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()) {
val updatedData = Category.createFromJSON(categoryInRemoteDB)
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) {
val newProduct = Product.createFromJSON(remoteProduct)
productDAO.insertNewProduct(newProduct)
}
if (productInRemoteDB != null && productInLocalDB != null) {
if (productInRemoteDB["hash"] != productInLocalDB.calculateHash()) {
val updatedData = Product.createFromJSON(productInRemoteDB)
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, {
val newAbstractProduct = AbstractProduct.createFromJSON(remoteAbstractProduct)
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, {
val updatedData = AbstractProduct.createFromJSON(abstractProductInRemoteDB)
abstractProductDAO.updateAbstractProduct(updatedData)
})
} }
} }
} }
@@ -69,33 +339,104 @@ class MainActivity : AppCompatActivity() {
private fun setupViewPager(viewpager: ViewPager) { private fun setupViewPager(viewpager: ViewPager) {
adapter = ViewPagerAdapter(supportFragmentManager) adapter = ViewPagerAdapter(supportFragmentManager)
adapter.addFragment(CategoriesFragment(), getString(R.string.categories_title)) adapter.addFragment(StorageFragment.newInstance("", arrayOf("")), getString(R.string.storage_title))
adapter.addFragment(StorageFragment(), getString(R.string.storage_title))
adapter.addFragment(ShelfFragment(), getString(R.string.shelf_title)) adapter.addFragment(ShelfFragment(), getString(R.string.shelf_title))
//TODO: settings fragments adapter.addFragment(CategoriesFragment(), getString(R.string.categories_title))
// setting adapter to view pager. viewpager.adapter = adapter
viewpager.setAdapter(adapter) viewpager.offscreenPageLimit = 3
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu) menuInflater.inflate(R.menu.menu_main, menu)
return true return true
} }
private fun isOffline(): Boolean {
return sharedPreferences.getString("currentGroup", "") == "offline"
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
lateinit var intent: Intent
when (item.itemId) {
R.id.nav_account -> {
if (isOffline()) {
Toast.makeText(this, getString(R.string.online_only_feature), Toast.LENGTH_SHORT).show()
return false
}
intent = Intent(this, AccountSettingsActivity::class.java)
settingsActivityLauncher.launch(intent)
return true
}
R.id.nav_groups -> {
if (isOffline()) {
Toast.makeText(this, getString(R.string.online_only_feature), Toast.LENGTH_SHORT).show()
return false
}
intent = Intent(this, MyGroupsActivity::class.java)
}
R.id.nav_settings -> {
intent = Intent(this, SettingsActivity::class.java)
}
R.id.nav_logout -> {
sharedPreferences.edit().putString("currentGroup", "").apply()
sharedPreferences.edit().putStringSet("groups", emptySet()).apply()
sharedPreferences.edit().putString("token", "").apply()
sharedPreferences.edit().putString("server", "").apply()
intent = Intent(this, LoginActivity::class.java)
}
}
startActivity(intent)
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() {
runOnUiThread {
try {
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()
} catch (e:Exception) {
Log.e("BSFE/MainActivity", e.message!!)
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (actionBarDrawerToggle!!.onOptionsItemSelected(item)) {
return true
} else {
val currentPosition = binding.tabTablayout.selectedTabPosition val currentPosition = binding.tabTablayout.selectedTabPosition
val fragment = adapter.getItem(currentPosition) val fragment = adapter.getItem(currentPosition)
return when (item.itemId) { return when (item.itemId) {
R.id.action_settings -> { R.id.action_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
settingsActivityLauncher.launch(intent)
true true
} }
R.id.action_delete -> { R.id.action_delete -> {
when (fragment::class.simpleName.toString()) { when (fragment::class.simpleName.toString()) {
"StorageFragment" -> { "StorageFragment" -> {
AlertDialog.Builder(this) AlertDialog.Builder(this)
@@ -120,6 +461,7 @@ class MainActivity : AppCompatActivity() {
}.show() }.show()
} }
"ShelfFragment" -> { "ShelfFragment" -> {
val shelfFragment = fragment as ShelfFragment val shelfFragment = fragment as ShelfFragment
shelfFragment.removeSelected() shelfFragment.removeSelected()
@@ -151,4 +493,29 @@ class MainActivity : AppCompatActivity() {
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
}
fun filterAbstractProductsByCategory(id: Int) {
binding.tabViewpager.setCurrentItem(0, true)
val currentPosition = binding.tabTablayout.selectedTabPosition
val fragment = adapter.getItem(currentPosition)
val storageFragment = fragment as StorageFragment
storageFragment.filterByCategory(id)
}
fun addSelection() {
selectionMode = true
selectedAmount ++
}
fun removeSelection() {
selectedAmount --
if (selectedAmount <= 0) {
selectedAmount = 0
selectionMode = false
}
}
} }

View File

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

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 org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.ActivityMyGroupsBinding
import org.foxarmy.barcodescannerforemployees.parseIntArray
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 = org.foxarmy.barcodescannerforemployees.getPreferences(this)
val net = Net()
net.token = sharedPreferences.getString("token", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.language = sharedPreferences.getString("language", "en-US")!!
net.getMyGroups { response ->
val groups = parseIntArray(response.body!!.string())
val container = findViewById<LinearLayout>(R.id.groupsLayout)
for (group in groups) {
net.getGroupName(group, {response ->
val groupView = GroupView(this, this as Context, response, group)
runOnUiThread {
container.addView(groupView)
}
})
}
binding.newGroup.setOnClickListener {
runOnUiThread {
val intent = Intent(this, GroupActivity::class.java)
startActivity(intent)
}
}
}
}
}

View File

@@ -0,0 +1,68 @@
package org.foxarmy.barcodescannerforemployees.activities
import android.app.Activity
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.parseStringList
class NavigatorActivity : Activity() {
private lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
var intent = Intent(this, MainActivity::class.java)
isInGroup { flag ->
if (!flag && !isOffline()) {
intent = Intent(this, GroupActivity::class.java)
}
if (!isAuthenticated() && !isOffline()) {
intent = Intent(this, LoginActivity::class.java);
}
runOnUiThread {
startActivity(intent);
finish();
}
}
}
private fun isOffline(): Boolean {
return sharedPreferences.getString("currentGroup", "")!! == "offline"
}
private fun isAuthenticated(): Boolean {
return sharedPreferences.getString("token", "") != ""
}
private fun isInGroup(callback: (Boolean) -> Unit) {
val groups = sharedPreferences.getStringSet("groups", emptySet())!!
if (groups.isEmpty()) {
if (sharedPreferences.getString("token", "")!! == "") callback(false)
val net = Net()
net.language = sharedPreferences.getString("language", "")!!
net.server = sharedPreferences.getString("server", "")!!
net.token = sharedPreferences.getString("token", "")!!
net.getMyGroups { response ->
val responseBody = response.body!!.string()
if (responseBody == "" || responseBody == "[]") callback(false)
val groupsFromServer = parseStringList(responseBody)
if (groupsFromServer.isNotEmpty()) {
sharedPreferences.edit().putStringSet("groups", groupsFromServer.toSet()).apply()
sharedPreferences.edit().putString("currentGroup", groupsFromServer[0]).apply()
callback(true)
} else {
callback(false)
}
}
}
callback(true)
}
}

View File

@@ -0,0 +1,103 @@
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 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>
lateinit var namesMap: MutableMap<Int, String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this)
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", "")!!
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
setUpImageCompressionSeekBar()
if (isOffline()) {
binding.currentGroupSetting.visibility = View.GONE
} else {
namesMap = mutableMapOf()
for (myGroup in myGroups) {
net.getGroupName(myGroup.toInt(), { name ->
namesMap[myGroup.toInt()] = name
fillUpCurrentGroupSpinner()
})
}
}
binding.saveButton.setOnClickListener {
net.getGroupId(binding.currentGroupSpinner.selectedItem.toString(), { groupId ->
sharedPreferences.edit().putString("currentGroup", groupId).apply()
sharedPreferences.edit().putInt("imageCompression", binding.imageCompressionFactorSeekBar.progress + 1)
.apply()
runOnUiThread {
setResult(Activity.RESULT_OK)
finish()
}
})
}
binding.cancelButton.setOnClickListener {
setResult(Activity.RESULT_CANCELED)
finish()
}
}
private fun isOffline(): Boolean {
return sharedPreferences.getString("currentGroup", "offline") == "offline"
}
private fun setUpImageCompressionSeekBar() {
val compressionFactor = sharedPreferences.getInt("imageCompression", 1)
binding.imageCompressionFactorSeekBar.progress = compressionFactor - 1
}
private fun fillUpCurrentGroupSpinner() {
runOnUiThread {
if (currentGroup == "offline") {
binding.currentGroupSetting.visibility = View.GONE
}
groupsNames = mutableListOf()
groupsNames.clear()
val sortedMap = namesMap.entries.sortedBy { it.key }.associate { it.toPair() }
for ((id, name) in sortedMap) {
groupsNames.add(name)
}
val arrayAdapter =
ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, groupsNames)
arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item)
binding.currentGroupSpinner.adapter = arrayAdapter
if (myGroups.indexOf(currentGroup) < groupsNames.size) 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(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} = ? 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,8 @@ package org.foxarmy.barcodescannerforemployees.dataclasses
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import org.foxarmy.barcodescannerforemployees.md5
import org.json.JSONObject
class AbstractProduct() : Parcelable { class AbstractProduct() : Parcelable {
var id: Int = 0 var id: Int = 0
@@ -32,6 +34,10 @@ class AbstractProduct() : Parcelable {
unit = parcel.readInt() 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) { override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id) parcel.writeInt(id)
parcel.writeString(barcode) parcel.writeString(barcode)
@@ -54,5 +60,17 @@ class AbstractProduct() : Parcelable {
override fun newArray(size: Int): Array<AbstractProduct?> { override fun newArray(size: Int): Array<AbstractProduct?> {
return arrayOfNulls(size) return arrayOfNulls(size)
} }
fun createFromJSON(json: JSONObject): AbstractProduct {
return AbstractProduct(
json["local_id"].toString().toInt(),
json["barcode"].toString(),
json["name"].toString(),
json["net_weight"].toString().toDouble(),
json["image_filename"].toString(),
json["category"].toString().toInt(),
json["unit"].toString().toInt()
)
}
} }
} }

View File

@@ -2,6 +2,8 @@ package org.foxarmy.barcodescannerforemployees.dataclasses
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import org.foxarmy.barcodescannerforemployees.md5
import org.json.JSONObject
class Category() : Parcelable { class Category() : Parcelable {
var id = 0 var id = 0
@@ -27,6 +29,10 @@ class Category() : Parcelable {
return 0 return 0
} }
fun calculateHash(): String {
return "$id:$name".md5()
}
companion object CREATOR : Parcelable.Creator<Category> { companion object CREATOR : Parcelable.Creator<Category> {
override fun createFromParcel(parcel: Parcel): Category { override fun createFromParcel(parcel: Parcel): Category {
return Category(parcel) return Category(parcel)
@@ -35,5 +41,12 @@ class Category() : Parcelable {
override fun newArray(size: Int): Array<Category?> { override fun newArray(size: Int): Array<Category?> {
return arrayOfNulls(size) return arrayOfNulls(size)
} }
fun createFromJSON(json: JSONObject): Category {
return Category(
json["local_id"].toString().toInt(),
json["name"].toString()
)
}
} }
} }

View File

@@ -5,6 +5,9 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import org.foxarmy.barcodescannerforemployees.calculateProductFreshness import org.foxarmy.barcodescannerforemployees.calculateProductFreshness
import org.foxarmy.barcodescannerforemployees.convertToUnixEpochTimestamp
import org.foxarmy.barcodescannerforemployees.md5
import org.json.JSONObject
class Product() : Parcelable { class Product() : Parcelable {
var id = 0 var id = 0
@@ -46,6 +49,10 @@ class Product() : Parcelable {
return 0 return 0
} }
fun calculateHash(): String {
return "$id:$abstractProductId:$amount:$dateOfProduction:$dateOfExpiry".md5()
}
companion object CREATOR : Parcelable.Creator<Product> { companion object CREATOR : Parcelable.Creator<Product> {
override fun createFromParcel(parcel: Parcel): Product { override fun createFromParcel(parcel: Parcel): Product {
return Product(parcel) return Product(parcel)
@@ -54,5 +61,16 @@ class Product() : Parcelable {
override fun newArray(size: Int): Array<Product?> { override fun newArray(size: Int): Array<Product?> {
return arrayOfNulls(size) return arrayOfNulls(size)
} }
@RequiresApi(Build.VERSION_CODES.O)
fun createFromJSON(json: JSONObject): Product {
return Product(
json["local_id"].toString().toInt(),
json["abstract_product_id"].toString().toInt(),
json["amount"].toString().toInt(),
convertToUnixEpochTimestamp(json["date_of_production"].toString()),
convertToUnixEpochTimestamp(json["expiry_date"].toString())
)
}
} }
} }

View File

@@ -5,7 +5,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.FragmentAddAbstractProductBinding import org.foxarmy.barcodescannerforemployees.databinding.FragmentAddAbstractProductBinding
class AddAbstractProductFragment : Fragment() { class AddAbstractProductFragment : Fragment() {
@@ -16,6 +15,6 @@ class AddAbstractProductFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentAddAbstractProductBinding.inflate(layoutInflater) binding = FragmentAddAbstractProductBinding.inflate(layoutInflater)
return inflater.inflate(R.layout.fragment_add_abstract_product, container, false) return binding.root
} }
} }

View File

@@ -5,7 +5,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.databinding.FragmentAddProductBinding import org.foxarmy.barcodescannerforemployees.databinding.FragmentAddProductBinding
class AddProductFragment : Fragment() { class AddProductFragment : Fragment() {
@@ -16,6 +15,6 @@ class AddProductFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentAddProductBinding.inflate(layoutInflater) binding = FragmentAddProductBinding.inflate(layoutInflater)
return inflater.inflate(R.layout.fragment_add_product, container, false) return binding.root
} }
} }

View File

@@ -1,8 +1,8 @@
package org.foxarmy.barcodescannerforemployees.fragments package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.provider.BaseColumns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -11,14 +11,34 @@ import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.CategoriesContract import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.AddCategoryActivity 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.getPreferences
import org.foxarmy.barcodescannerforemployees.views.CategoryView import org.foxarmy.barcodescannerforemployees.views.CategoryView
import kotlin.concurrent.thread
class CategoriesFragment : Fragment() { class CategoriesFragment : Fragment() {
private lateinit var sharedPreferences: SharedPreferences
private lateinit var categoryDAO: CategoryDAO
private var updateInProgress = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = getPreferences(requireContext())
prepareDatabaseConnection()
}
private fun prepareDatabaseConnection() {
val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "offline")!!)
categoryDAO = CategoryDAO(dbHelper)
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@@ -33,24 +53,40 @@ class CategoriesFragment : Fragment() {
} }
fun removeSelected() { fun removeSelected() {
thread {
val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
val db = DBStorageController(requireContext())
var deleted = false var deleted = false
for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) { for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) {
if (view.isCategorySelected) { 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())
net.deleteCategory(currentGroup, view.category.id, {response ->
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
})
deleted = true deleted = true
} }
} }
if (!deleted) { if (!deleted) {
activity!!.runOnUiThread {
Toast.makeText(requireContext(), getString(R.string.nothing_to_delete), Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), getString(R.string.nothing_to_delete), Toast.LENGTH_SHORT).show()
} }
}
updateContent() updateContent()
} }
}
fun updateSelected() { fun updateSelected() {
thread {
val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) { for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) {
if (view.isCategorySelected) { if (view.isCategorySelected) {
@@ -58,35 +94,35 @@ class CategoriesFragment : Fragment() {
val extras = Bundle() val extras = Bundle()
extras.putParcelable("category", view.category) extras.putParcelable("category", view.category)
addCategoryIntent.putExtras(extras) addCategoryIntent.putExtras(extras)
activity!!.runOnUiThread {
ContextCompat.startActivity(context!!, addCategoryIntent, extras) ContextCompat.startActivity(context!!, addCategoryIntent, extras)
} }
} }
} }
}
}
fun updateContent() { fun updateContent() {
thread {
if (updateInProgress) return@thread
updateInProgress = true
prepareDatabaseConnection()
val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout)
activity!!.runOnUiThread {
layout?.removeAllViews() 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)
for (category in categories) {
val categoryView = CategoryView(requireActivity(), requireContext(), category) val categoryView = CategoryView(requireActivity(), requireContext(), category)
activity!!.runOnUiThread {
layout?.addView(categoryView) layout?.addView(categoryView)
} }
} }
updateInProgress = false
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -0,0 +1,21 @@
package org.foxarmy.barcodescannerforemployees.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.databinding.FragmentExpiryDatesBinding
class ExpiryDatesFragment : Fragment() {
private lateinit var binding: FragmentExpiryDatesBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentExpiryDatesBinding.inflate(layoutInflater)
return binding.root
}
}

View File

@@ -1,8 +1,8 @@
package org.foxarmy.barcodescannerforemployees.fragments package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.provider.BaseColumns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -14,13 +14,13 @@ import androidx.core.content.ContextCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.gridlayout.widget.GridLayout import androidx.gridlayout.widget.GridLayout
import org.foxarmy.barcodescannerforemployees.AbstractProductContract import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.ProductContract
import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.AddProductActivity 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.databinding.FragmentShelfBinding
import org.foxarmy.barcodescannerforemployees.dataclasses.Product import org.foxarmy.barcodescannerforemployees.getPreferences
import org.foxarmy.barcodescannerforemployees.views.ProductView import org.foxarmy.barcodescannerforemployees.views.ProductView
import kotlin.concurrent.thread import kotlin.concurrent.thread
@@ -28,6 +28,19 @@ class ShelfFragment : Fragment() {
private lateinit var binding: FragmentShelfBinding private lateinit var binding: FragmentShelfBinding
private var updateInProgress = false private var updateInProgress = false
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 = getPreferences(requireContext())
prepareDatabaseConnection()
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@@ -37,6 +50,12 @@ class ShelfFragment : Fragment() {
fillUpSortBySpinner() fillUpSortBySpinner()
val filterByDate = arguments?.getLong("filterByExpiryDate") as Long?
if (filterByDate != null) {
filter = filterByDate.toString()
filterBy = "expiryDate"
}
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) { override fun onNothingSelected(parent: AdapterView<*>?) {
updateContent() updateContent()
@@ -52,6 +71,11 @@ class ShelfFragment : Fragment() {
return binding.root return binding.root
} }
private fun prepareDatabaseConnection() {
val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "database")!!)
productDAO = ProductDAO(dbHelper)
}
fun updateSelected() { fun updateSelected() {
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout) val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
@@ -93,80 +117,14 @@ class ShelfFragment : Fragment() {
thread { thread {
if (updateInProgress) return@thread if (updateInProgress) return@thread
updateInProgress = true updateInProgress = true
prepareDatabaseConnection()
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout) val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
activity!!.runOnUiThread { activity!!.runOnUiThread {
grv?.removeAllViews() grv?.removeAllViews()
} }
val db = DBStorageController(requireContext()).readableDatabase val products = productDAO.getSortedListOfProducts(binding.spinner.selectedItemPosition, filterBy, filter)
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"
}
}
val cursor = db.query(ProductContract.ProductEntry.TABLE_NAME, projection, null, null, 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 })
for (product in products.iterator()) { for (product in products.iterator()) {
val productView = ProductView( val productView = ProductView(
@@ -186,14 +144,25 @@ class ShelfFragment : Fragment() {
thread { thread {
val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout) val grv = view?.findViewById<GridLayout>(R.id.contentGridLayout)
val db = DBStorageController(requireContext())
var deleted = false var deleted = false
for (view: ProductView in grv?.children!!.iterator() as Iterator<ProductView>) { for (view: ProductView in grv?.children!!.iterator() as Iterator<ProductView>) {
activity!!.runOnUiThread { activity!!.runOnUiThread {
view.findViewById<ImageView>(R.id.productPicture).setImageURI(null) view.findViewById<ImageView>(R.id.productPicture).setImageURI(null)
} }
if (view.isProductSelected) { 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)
net.deleteProduct(currentGroup, view.product.id, {response ->
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
})
deleted = true deleted = true
} }
} }
@@ -206,4 +175,16 @@ class ShelfFragment : Fragment() {
updateContent() updateContent()
} }
} }
companion object {
fun newInstance(date: Long):ShelfFragment = ShelfFragment().apply {
val fragment = ShelfFragment()
val args = Bundle()
args.putLong("filterByExpiryDate", date)
fragment.setArguments(args)
return fragment
}
}
} }

View File

@@ -1,31 +1,38 @@
package org.foxarmy.barcodescannerforemployees.fragments package org.foxarmy.barcodescannerforemployees.fragments
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.provider.BaseColumns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.*
import android.widget.ArrayAdapter import androidx.constraintlayout.widget.ConstraintLayout
import android.widget.ImageView
import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.foxarmy.barcodescannerforemployees.AbstractProductContract import org.foxarmy.barcodescannerforemployees.Net
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.AddAbstractProductActivity 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.databinding.FragmentStorageBinding
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.generateThumbnailForImage import org.foxarmy.barcodescannerforemployees.generateThumbnailForImage
import org.foxarmy.barcodescannerforemployees.getPreferences
import org.foxarmy.barcodescannerforemployees.views.AbstractProductView import org.foxarmy.barcodescannerforemployees.views.AbstractProductView
import kotlin.concurrent.thread import kotlin.concurrent.thread
class StorageFragment : Fragment() { class StorageFragment : Fragment() {
private lateinit var binding: FragmentStorageBinding private lateinit var binding: FragmentStorageBinding
private var filterBy = ""
private lateinit var filter: Array<String>
private lateinit var sharedPreferences: SharedPreferences
private lateinit var abstractProductDAO: AbstractProductDAO
private var updateInProcess = false
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@@ -33,6 +40,10 @@ class StorageFragment : Fragment() {
): View { ): View {
binding = FragmentStorageBinding.inflate(inflater) binding = FragmentStorageBinding.inflate(inflater)
sharedPreferences = getPreferences(requireContext())
prepareDatabaseConnection()
fillUpSortBySpinner() fillUpSortBySpinner()
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
@@ -45,9 +56,22 @@ class StorageFragment : Fragment() {
} }
} }
binding.dropFiltersButton.setOnClickListener {
filterBy = ""
updateContent()
}
filterBy = arguments?.getString("filterBy")!!
filter = arguments?.getStringArray("filter")!!
return binding.root return binding.root
} }
private fun prepareDatabaseConnection() {
val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "database")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@@ -65,14 +89,26 @@ class StorageFragment : Fragment() {
thread { thread {
val grv = binding.contentGridLayout val grv = binding.contentGridLayout
val db = DBStorageController(requireContext())
var deleted = false var deleted = false
for (view: AbstractProductView in grv.children.iterator() as Iterator<AbstractProductView>) { for (view: AbstractProductView in grv.children.iterator() as Iterator<AbstractProductView>) {
activity!!.runOnUiThread { activity!!.runOnUiThread {
view.findViewById<ImageView>(R.id.productPicture).setImageURI(null) view.findViewById<ImageView>(R.id.productPicture).setImageURI(null)
} }
if (view.isProductSelected) { 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())
net.deleteAbstractProduct(currentGroup, view.abstractProduct.id, {response ->
activity!!.runOnUiThread{
Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show()
}
})
deleted = true deleted = true
} }
} }
@@ -94,6 +130,7 @@ class StorageFragment : Fragment() {
val addProductIntent = Intent(requireContext(), AddAbstractProductActivity::class.java) val addProductIntent = Intent(requireContext(), AddAbstractProductActivity::class.java)
val extras = Bundle() val extras = Bundle()
extras.putParcelable("abstractProduct", view.abstractProduct) extras.putParcelable("abstractProduct", view.abstractProduct)
extras.putString("action", "update")
addProductIntent.putExtras(extras) addProductIntent.putExtras(extras)
ContextCompat.startActivity(requireContext(), addProductIntent, extras) ContextCompat.startActivity(requireContext(), addProductIntent, extras)
} }
@@ -102,65 +139,75 @@ class StorageFragment : Fragment() {
fun updateContent() { fun updateContent() {
thread { thread {
if (updateInProcess) return@thread
updateInProcess = true
prepareDatabaseConnection()
val grv = binding.contentGridLayout val grv = binding.contentGridLayout
activity!!.runOnUiThread { activity!!.runOnUiThread {
grv.removeAllViews() grv.removeAllViews()
} }
val db = DBStorageController(requireContext()).readableDatabase val abstractProducts = abstractProductDAO.getSortedListOfAbstractProducts(binding.spinner.selectedItemPosition, filterBy, filter)
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 = "" for (abstractProduct in abstractProducts) {
when(binding.spinner.selectedItemPosition) { generateThumbnailForImage(context!!, abstractProduct.imageHash)
0 -> {
orderBy = "${AbstractProductContract.AbstractProductEntry.PRODUCT_NAME} ASC"
}
1 -> {
orderBy = "${AbstractProductContract.AbstractProductEntry.CATEGORY} ASC"
}
}
val cursor = db.query(AbstractProductContract.AbstractProductEntry.TABLE_NAME, projection, null, null, null, null, orderBy) val abstractProductView = AbstractProductView(
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(), requireActivity(),
requireContext(), requireContext(),
product abstractProduct
) )
if (filterBy == "barcodeless") {
abstractProductView.also {
it.setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<TextView>(R.id.productNameView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<ConstraintLayout>(R.id.productLayout).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<TextView>(R.id.categoryView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<TextView>(R.id.unitView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
it.findViewById<TextView>(R.id.productNetWeightView).setOnClickListener {
(activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct)
}
}
}
activity!!.runOnUiThread{ activity!!.runOnUiThread{
grv.addView(abstractProduct) grv.addView(abstractProductView)
} }
} }
updateInProcess = false
} }
}.join()
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fun filterByCategory(id: Int) {
filterBy = "category"
filter = arrayOf("$id")
updateContent() updateContent()
} }
companion object {
fun newInstance(filterBy: String, filter: Array<String>):StorageFragment = StorageFragment().apply {
val fragment = StorageFragment()
val args = Bundle()
args.putString("filterBy", filterBy)
args.putStringArray("filter", filter)
fragment.arguments = args
return fragment
}
}
} }

View File

@@ -3,17 +3,22 @@ package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import org.foxarmy.barcodescannerforemployees.* import org.foxarmy.barcodescannerforemployees.*
import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity
import org.foxarmy.barcodescannerforemployees.activities.MainActivity
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import java.io.File import java.io.File
@@ -26,12 +31,20 @@ class AbstractProductView: LinearLayout {
private var unitField: TextView? = null private var unitField: TextView? = null
var abstractProduct: AbstractProduct = AbstractProduct() var abstractProduct: AbstractProduct = AbstractProduct()
var isProductSelected = false 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) { constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!! activity = getActivity(context)!!
val inflater: LayoutInflater = activity.layoutInflater val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.abstract_product_view, this) inflater.inflate(R.layout.abstract_product_view, this)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
categoryDAO = CategoryDAO(dbHelper)
} }
constructor(activity: Activity, context: Context, abstractProduct: AbstractProduct) : super(context) { constructor(activity: Activity, context: Context, abstractProduct: AbstractProduct) : super(context) {
@@ -40,10 +53,14 @@ class AbstractProductView: LinearLayout {
val inflater: LayoutInflater = activity.layoutInflater val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.abstract_product_view, this) inflater.inflate(R.layout.abstract_product_view, this)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
categoryDAO = CategoryDAO(dbHelper)
update() update()
} }
fun init () { private fun init() {
productLayout = findViewById(R.id.productLayout) productLayout = findViewById(R.id.productLayout)
productPicture = findViewById(R.id.productPicture) productPicture = findViewById(R.id.productPicture)
productNameField = findViewById(R.id.productNameView) productNameField = findViewById(R.id.productNameView)
@@ -51,26 +68,8 @@ class AbstractProductView: LinearLayout {
categoryField = findViewById(R.id.categoryView) categoryField = findViewById(R.id.categoryView)
unitField = findViewById(R.id.unitView) unitField = findViewById(R.id.unitView)
setUpClicks()
productPicture!!.setOnClickListener {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
}
productLayout!!.setOnClickListener {
}
unitField?.text = getUnitNameById(context, abstractProduct.unit) unitField?.text = getUnitNameById(context, abstractProduct.unit)
productLayout!!.setOnLongClickListener {
isProductSelected = !isProductSelected
this.background = ContextCompat.getDrawable(context, if (isProductSelected) R.drawable.outline_selected else R.drawable.outline)
true
}
} }
fun update() { fun update() {
@@ -80,10 +79,126 @@ class AbstractProductView: LinearLayout {
thumbnailsDir.mkdirs() thumbnailsDir.mkdirs()
val imageUri = getImageUri(activity, File(thumbnailsDir, "${abstractProduct.imageHash}.webp")) val imageUri = getImageUri(activity, File(thumbnailsDir, "${abstractProduct.imageHash}.webp"))
productPicture!!.setImageURI(imageUri) productPicture!!.setImageURI(imageUri)
productPicture!!.rotation = 90f
productNameField!!.text = abstractProduct.name productNameField!!.text = abstractProduct.name
netWeightField!!.text = abstractProduct.netWeight.toString() netWeightField!!.text = abstractProduct.netWeight.toString()
categoryField!!.text = DBStorageController(context).getCategoryNameById(DBStorageController(context).readableDatabase, abstractProduct.category) categoryField!!.text = categoryDAO.getCategoryNameById(abstractProduct.category)
}
private fun setUpClicks() {
productLayout!!.setOnLongClickListener { onAnyLongClick() }
productNameField!!.setOnLongClickListener { onNameLongClick() }
productPicture!!.setOnLongClickListener { onImageLongClick() }
netWeightField!!.setOnLongClickListener { onAnyLongClick() }
categoryField!!.setOnLongClickListener { onAnyLongClick() }
unitField!!.setOnLongClickListener { onAnyLongClick() }
productLayout!!.setOnClickListener { onAnyClick() }
netWeightField!!.setOnClickListener { onAnyClick() }
categoryField!!.setOnClickListener { onAnyClick() }
unitField!!.setOnClickListener { onAnyClick() }
productPicture!!.setOnClickListener { onImageLongClick() }
productNameField!!.setOnClickListener { onNameLongClick() }
}
private fun onAnyClick(): Boolean {
if (activity !is MainActivity) return false
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
isProductSelected = !isProductSelected
if (isProductSelected) {
mainActivity.addSelection()
} else {
mainActivity.removeSelection()
}
activity.runOnUiThread {
this.background = ContextCompat.getDrawable(
context,
if (isProductSelected) R.drawable.outline_selected else R.drawable.outline
)
}
return true
} else {
return false
}
}
private fun onAnyLongClick(): Boolean {
if (activity !is MainActivity) return false
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
isProductSelected = !isProductSelected
if (isProductSelected) {
mainActivity.addSelection()
} else {
mainActivity.removeSelection()
}
activity.runOnUiThread {
this.background = ContextCompat.getDrawable(
context,
if (isProductSelected) R.drawable.outline_selected else R.drawable.outline
)
}
return true
} else {
mainActivity.addSelection()
isProductSelected = !isProductSelected
activity.runOnUiThread {
this.background = ContextCompat.getDrawable(
context,
if (isProductSelected) R.drawable.outline_selected else R.drawable.outline
)
}
return true
}
}
private fun onImageLongClick(): Boolean {
if (activity !is MainActivity) {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
return true
}
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
return onAnyLongClick()
} else {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
}
return true
}
private fun onNameLongClick(): Boolean {
if (activity !is MainActivity) {
activity.runOnUiThread {
Toast.makeText(activity, productNameField!!.text, Toast.LENGTH_SHORT).show()
}
return true
}
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
return onAnyLongClick()
} else {
activity.runOnUiThread {
Toast.makeText(activity, productNameField!!.text, Toast.LENGTH_SHORT).show()
}
}
return true
} }
} }

View File

@@ -2,38 +2,88 @@ package org.foxarmy.barcodescannerforemployees.views
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.DBStorageController
import org.foxarmy.barcodescannerforemployees.R import org.foxarmy.barcodescannerforemployees.R
import org.foxarmy.barcodescannerforemployees.activities.MainActivity
import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.dataclasses.Category
import org.foxarmy.barcodescannerforemployees.getPreferences
class CategoryView : LinearLayout { class CategoryView : LinearLayout {
var category: Category var category: Category
val categoryName: TextView val categoryName: TextView
val amountOfProducts: TextView val amountOfProducts: TextView
var isCategorySelected = false var isCategorySelected = false
private var abstractProductDAO: AbstractProductDAO
private var sharedPreferences: SharedPreferences
constructor(activity: Activity, context: Context, category: Category) : super(context) { constructor(activity: Activity, context: Context, category: Category) : super(context) {
this.category = category this.category = category
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
val inflater: LayoutInflater = activity.layoutInflater val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.category_view, this) inflater.inflate(R.layout.category_view, this)
this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline) updateStroke()
categoryName = findViewById(R.id.categoryNameTextView) categoryName = findViewById(R.id.categoryNameTextView)
amountOfProducts = findViewById(R.id.amountOfProducts) amountOfProducts = findViewById(R.id.amountOfProducts)
categoryName.text = category.name categoryName.text = category.name
amountOfProducts.text = DBStorageController(context).getAmountOfAbstractProductsInCategory(DBStorageController(context).readableDatabase, category.id).toString() amountOfProducts.text = abstractProductDAO.getAmountOfAbstractProductsInCategory(category.id).toString()
setUpClicks()
}
private fun setUpClicks() {
categoryName.setOnClickListener { onCategoryClick() }
amountOfProducts.setOnClickListener { onCategoryClick() }
categoryName.setOnLongClickListener { onAnyLongClick() }
amountOfProducts.setOnLongClickListener { onAnyLongClick() }
}
private fun onCategoryClick(): Boolean {
if (context !is MainActivity) return false
val mainActivity = context as MainActivity
if (mainActivity.selectionMode) return onAnyLongClick()
else (context as MainActivity).filterAbstractProductsByCategory(category.id)
return true
}
private fun onAnyLongClick(): Boolean {
if (context !is MainActivity) return false
val mainActivity = context as MainActivity
setOnLongClickListener {
isCategorySelected = !isCategorySelected isCategorySelected = !isCategorySelected
updateStroke()
if (mainActivity.selectionMode) {
if (isCategorySelected) mainActivity.addSelection()
else mainActivity.removeSelection()
} else {
mainActivity.addSelection()
}
return true
}
private fun updateStroke() {
(context as Activity).runOnUiThread {
this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline) this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline)
true
} }
} }
} }

View File

@@ -0,0 +1,47 @@
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.R
import org.foxarmy.barcodescannerforemployees.activities.ExpiryCalendarActivity
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.database.ProductDAO
import org.foxarmy.barcodescannerforemployees.getPreferences
import java.text.SimpleDateFormat
class ExpiryGroupView : LinearLayout {
private var representingDate: Long? = null
private var sharedPreferences: SharedPreferences
private var productDAO: ProductDAO
constructor(activity: Activity, context: Context, representingDate: Long) : super(context) {
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!)
productDAO = ProductDAO(dbHelper)
val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.expiry_group_view, this)
this.representingDate = representingDate
this.background = ContextCompat.getDrawable(context,R.drawable.outline)
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)
}
}
}

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
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.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.os.Build import android.os.Build
@@ -12,6 +13,7 @@ import android.view.LayoutInflater
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -22,6 +24,10 @@ import androidx.core.graphics.red
import androidx.core.math.MathUtils.clamp import androidx.core.math.MathUtils.clamp
import org.foxarmy.barcodescannerforemployees.* import org.foxarmy.barcodescannerforemployees.*
import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity
import org.foxarmy.barcodescannerforemployees.activities.MainActivity
import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO
import org.foxarmy.barcodescannerforemployees.database.CategoryDAO
import org.foxarmy.barcodescannerforemployees.database.DBStorageController
import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct
import org.foxarmy.barcodescannerforemployees.dataclasses.Product import org.foxarmy.barcodescannerforemployees.dataclasses.Product
import java.io.File import java.io.File
@@ -33,8 +39,9 @@ import kotlin.concurrent.thread
class ProductView : LinearLayout { class ProductView : LinearLayout {
var product: Product = Product() var product: Product = Product()
var isProductSelected = false var isProductSelected = false
private lateinit var activity: Activity private var activity: Activity
private lateinit var productLayout: ConstraintLayout
private lateinit var productImageView: ImageView private lateinit var productImageView: ImageView
private lateinit var productNameTextView: TextView private lateinit var productNameTextView: TextView
private lateinit var productNetWeightTextView: TextView private lateinit var productNetWeightTextView: TextView
@@ -47,8 +54,19 @@ class ProductView: LinearLayout {
private var strokeColor: Int = 0x000000 private var strokeColor: Int = 0x000000
private lateinit var outline: GradientDrawable 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) { constructor(context: Context, a: AttributeSet) : super(context, a) {
activity = getActivity(context)!! activity = getActivity(context)!!
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
categoryDAO = CategoryDAO(dbHelper)
val inflater: LayoutInflater = activity.layoutInflater val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.product_view, this) inflater.inflate(R.layout.product_view, this)
} }
@@ -60,6 +78,13 @@ class ProductView: LinearLayout {
val inflater: LayoutInflater = activity.layoutInflater val inflater: LayoutInflater = activity.layoutInflater
inflater.inflate(R.layout.product_view, this) inflater.inflate(R.layout.product_view, this)
sharedPreferences = getPreferences(context)
val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!)
abstractProductDAO = AbstractProductDAO(dbHelper)
categoryDAO = CategoryDAO(dbHelper)
productLayout = findViewById(R.id.productLayout)
productImageView = findViewById(R.id.productPicture) productImageView = findViewById(R.id.productPicture)
productNameTextView = findViewById(R.id.productNameView) productNameTextView = findViewById(R.id.productNameView)
productNetWeightTextView = findViewById(R.id.productNetWeightView) productNetWeightTextView = findViewById(R.id.productNetWeightView)
@@ -68,40 +93,124 @@ class ProductView: LinearLayout {
productLifeSpan = findViewById(R.id.dateSpan) productLifeSpan = findViewById(R.id.dateSpan)
productFreshnessTextView = findViewById(R.id.freshnessPercentTextView) productFreshnessTextView = findViewById(R.id.freshnessPercentTextView)
findViewById<ConstraintLayout>(R.id.productLayout).setOnLongClickListener { setUpClicks()
isProductSelected = !isProductSelected
updateStroke()
true
}
productImageView.setOnClickListener {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
val abstractProduct = DBStorageController(context).findAbstractProductById(DBStorageController(context).readableDatabase, product.abstractProductId)
extras.putString("imagehash", abstractProduct!!.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
}
update() update()
} }
@RequiresApi(Build.VERSION_CODES.O)
private fun setUpClicks() {
productLayout.setOnLongClickListener { onAnyLongClick() }
productImageView.setOnLongClickListener { onImageLongClick() }
productNameTextView.setOnLongClickListener { onNameLongClick() }
productNetWeightTextView.setOnLongClickListener { onAnyLongClick() }
productAmountTextView.setOnLongClickListener { onAnyLongClick() }
productCategoryView.setOnLongClickListener { onAnyLongClick() }
productLifeSpan.setOnLongClickListener { onAnyLongClick() }
productFreshnessTextView.setOnLongClickListener { onAnyLongClick() }
productLayout.setOnClickListener { onAnyClick() }
productImageView.setOnClickListener { onImageLongClick() }
productNameTextView.setOnClickListener { onNameLongClick() }
productNetWeightTextView.setOnClickListener { onAnyClick() }
productAmountTextView.setOnClickListener { onAnyClick() }
productCategoryView.setOnClickListener { onAnyClick() }
productLifeSpan.setOnClickListener { onAnyClick() }
productFreshnessTextView.setOnClickListener { onAnyClick() }
}
@RequiresApi(Build.VERSION_CODES.O)
private fun onAnyClick(): Boolean {
if (activity !is MainActivity) return false
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
isProductSelected = !isProductSelected
if (isProductSelected) mainActivity.addSelection()
else mainActivity.removeSelection()
updateStroke()
return true
} else {
return false
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun onAnyLongClick(): Boolean {
if (activity !is MainActivity) return false
val mainActivity = activity as MainActivity
isProductSelected = !isProductSelected
updateStroke()
if (mainActivity.selectionMode) {
if (isProductSelected) mainActivity.addSelection()
else mainActivity.removeSelection()
} else {
mainActivity.addSelection()
}
return true
}
private fun onNameLongClick(): Boolean {
if (activity !is MainActivity) {
Toast.makeText(activity, productNameTextView.text, Toast.LENGTH_SHORT).show()
return true
}
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) return onAnyLongClick()
else Toast.makeText(activity, productNameTextView.text, Toast.LENGTH_SHORT).show()
return true
}
private fun onImageLongClick(): Boolean {
val abstractProduct = abstractProductDAO.findAbstractProductById(product.abstractProductId)
if (activity !is MainActivity) {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct!!.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
return true
}
val mainActivity = activity as MainActivity
if (mainActivity.selectionMode) {
return onAnyLongClick()
} else {
val fullscreenIntent = Intent(activity, FullscreenActivity::class.java)
val extras = Bundle()
extras.putString("imagehash", abstractProduct!!.imageHash)
fullscreenIntent.putExtras(extras)
startActivity(context, fullscreenIntent, extras)
}
return true
}
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun update() { fun update() {
val linkedAbstractProduct: AbstractProduct = DBStorageController(activity).findAbstractProductById(DBStorageController(activity).readableDatabase, product.abstractProductId)!! val linkedAbstractProduct: AbstractProduct =
abstractProductDAO.findAbstractProductById(product.abstractProductId) ?: return
val thumbnailsDir = File(activity.cacheDir, "thumbnails") val thumbnailsDir = File(activity.cacheDir, "thumbnails")
thumbnailsDir.mkdirs() thumbnailsDir.mkdirs()
val pictureFile = File(thumbnailsDir, "${linkedAbstractProduct.imageHash}.webp") val pictureFile = File(thumbnailsDir, "${linkedAbstractProduct.imageHash}.webp")
val imageUri = getImageUri(activity, pictureFile) val imageUri = getImageUri(activity, pictureFile)
productImageView.setImageURI(imageUri) productImageView.setImageURI(imageUri)
productImageView.rotation = 90f
productNameTextView.text = linkedAbstractProduct.name productNameTextView.text = linkedAbstractProduct.name
productNetWeightTextView.text = linkedAbstractProduct.netWeight.toString() productNetWeightTextView.text = linkedAbstractProduct.netWeight.toString()
productAmountTextView.text = product.amount.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))}" productLifeSpan.text = "${SimpleDateFormat("dd.MM.yyyy").format(Date(product.dateOfProduction * 1000))}-${
SimpleDateFormat("dd.MM.yyyy").format(Date(product.dateOfExpiry * 1000))
}"
productFreshnessTextView.text = productFreshnessTextView.text =
if (product.freshness == Double.NEGATIVE_INFINITY || product.freshness == Double.POSITIVE_INFINITY || product.freshness < 0) { if (product.freshness == Double.NEGATIVE_INFINITY || product.freshness == Double.POSITIVE_INFINITY || product.freshness < 0) {
context.getString(R.string.expired) context.getString(R.string.expired)
@@ -114,11 +223,11 @@ class ProductView: LinearLayout {
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun updateStroke() { fun updateStroke() {
activity.runOnUiThread {
if (isProductSelected) { if (isProductSelected) {
this.background = ContextCompat.getDrawable(context, R.drawable.outline_selected) this.background = ContextCompat.getDrawable(context, R.drawable.outline_selected)
} else { } else {
if (product.id != 0) { if (product.id != 0) {
thread {
this.outline = GradientDrawable() this.outline = GradientDrawable()
backgroundColor = evaluateColor() backgroundColor = evaluateColor()
strokeColor = darkenColor(backgroundColor, 0.25) // (backgroundColor and 0xfefefe ) shr 1 strokeColor = darkenColor(backgroundColor, 0.25) // (backgroundColor and 0xfefefe ) shr 1
@@ -126,12 +235,12 @@ class ProductView: LinearLayout {
this.outline.setStroke(4, strokeColor) this.outline.setStroke(4, strokeColor)
this.outline.alpha = 84 this.outline.alpha = 84
this.background = outline this.background = outline
}
} else { } else {
this.background = ContextCompat.getDrawable(context, R.drawable.outline) this.background = ContextCompat.getDrawable(context, R.drawable.outline)
} }
} }
} }
}
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun evaluateColor(): Int { fun evaluateColor(): Int {
@@ -140,8 +249,10 @@ class ProductView: LinearLayout {
} }
fun calculateFreshnessGradient(percentage: Double): Int { fun calculateFreshnessGradient(percentage: Double): Int {
val startColor = ContextCompat.getColor(context, if (percentage > 0.5) R.color.full_freshness else R.color.half_freshness) val startColor =
val endColor = ContextCompat.getColor(context, if (percentage > 0.5) R.color.half_freshness else R.color.expired_freshness) ContextCompat.getColor(context, if (percentage > 0.5) R.color.full_freshness else R.color.half_freshness)
val endColor =
ContextCompat.getColor(context, if (percentage > 0.5) R.color.half_freshness else R.color.expired_freshness)
val gradientPosition = 1 - if (percentage > 0.5) (percentage - 0.5) * 2 else percentage * 2 val gradientPosition = 1 - if (percentage > 0.5) (percentage - 0.5) * 2 else percentage * 2

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

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

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

@@ -6,5 +6,5 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.AddAbstractProductFragment"> tools:context=".fragments.AddAbstractProductFragment">
<include layout="@layout/content_add_abstract_product" android:id="@+id/include_content"/> <include layout="@layout/fragment_add_abstract_product" android:id="@+id/include_content"/>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -4,19 +4,18 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
tools:context=".activities.AddCategoryActivity"> tools:context=".activities.AddCategoryActivity" android:layout_gravity="center">
<EditText <EditText
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="text" android:inputType="text"
android:text="@string/sample_category"
android:ems="10" android:ems="10"
android:id="@+id/newCategoryName" app:layout_constraintStart_toStartOf="parent" android:id="@+id/newCategoryName" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"/> android:layout_marginTop="20dp" android:hint="@string/sample_category"/>
<Button <Button
android:text="@string/saveButton" android:text="@string/saveButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android" 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.AddProductFragment"> tools:context=".fragments.AddProductFragment">
<include layout="@layout/content_add_product" android:id="@+id/include_content"/> <include layout="@layout/fragment_add_product" android:id="@+id/include_content"/>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.ExpiryDatesFragment">
<include layout="@layout/fragment_expiry_dates" android:id="@+id/include_content"/>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.ShelfFragment"
android:id="@+id/content">
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,15 @@
<?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">
<androidx.core.widget.NestedScrollView
android:layout_height="0dp"
tools:context=".fragments.ShelfFragment"
android:id="@+id/content" android:layout_width="0dp" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -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,23 +1,26 @@
<?xml version="1.0" encoding="utf-8"?> <?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:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/storageLayout"> android:layout_height="match_parent" android:id="@+id/storageLayout">
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
> android:id="@+id/appBarLayout">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
@@ -33,22 +36,40 @@
app:tabMode="fixed"/> app:tabMode="fixed"/>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<!-- <include layout="@layout/content_storage"/>-->
<androidx.viewpager.widget.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/tab_viewpager" android:id="@+id/tab_viewpager"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_marginTop="5dp" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_gravity="bottom"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> android:layout_marginTop="2dp"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="6dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="6dp"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/new_element_fab" android:id="@+id/new_element_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin" app:srcCompat="@android:drawable/ic_input_add"
android:layout_marginBottom="16dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@android:drawable/ic_input_add"/> 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>
</androidx.coordinatorlayout.widget.CoordinatorLayout> <!-- <include layout="@layout/content_storage"/>-->
<com.google.android.material.navigation.NavigationView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@menu/drawer_menu" android:id="@+id/nav_view"/>
</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

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/addAbstractProductLayout">
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" app:navGraph="@navigation/nav_graph_add_abstract_product"
app:defaultNavHost="true" android:id="@+id/fragmentContainerView"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/addProductLayout">
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" app:navGraph="@navigation/nav_graph_add_product"
app:defaultNavHost="true" android:id="@+id/fragmentContainerView"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@@ -0,0 +1,17 @@
<?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="TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/representingDateTextView" android:layout_weight="1"
android:textSize="45sp"/>
<TextView
android:text="TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/amountTextView" android:layout_weight="1"
android:textSize="45sp" android:textAlignment="textEnd"/>
</LinearLayout>

View File

@@ -17,7 +17,7 @@
android:text="@string/scan_label" android:text="@string/scan_label"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/categoryTextView" app:layout_constraintTop_toBottomOf="@+id/categoryTextView"
android:layout_marginTop="15dp" android:layout_marginStart="5dp"/> android:layout_marginTop="40dp" android:layout_marginStart="5dp"/>
<ImageView <ImageView
android:src="@android:drawable/ic_menu_camera" android:src="@android:drawable/ic_menu_camera"
android:layout_width="0dp" android:layout_width="0dp"
@@ -61,35 +61,42 @@
android:inputType="text" android:inputType="text"
android:ems="10" android:ems="10"
android:id="@+id/barcodeTextEdit" app:layout_constraintTop_toBottomOf="@+id/imageView" android:id="@+id/barcodeTextEdit" app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="8dp" android:hint="Barcode" android:textColorHint="#737373" android:layout_marginTop="8dp" android:hint="@string/barcode" android:textColorHint="#737373"
android:layout_marginStart="5dp" android:layout_marginEnd="5dp"/> android:layout_marginStart="5dp"/>
<CheckBox
android:text="@string/no_barcode"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/noBarcodeCheckBox"
app:layout_constraintStart_toEndOf="@+id/barcodeTextEdit"
app:layout_constraintTop_toBottomOf="@+id/imageView" android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView <TextView
android:text="@string/category" android:text="@string/category"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/categoryTextView" android:layout_height="wrap_content" android:id="@+id/categoryTextView"
app:layout_constraintTop_toBottomOf="@+id/netWeight" app:layout_constraintTop_toBottomOf="@+id/netWeight"
android:layout_marginTop="20dp" app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="30dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"/> android:layout_marginStart="8dp"/>
<Spinner <Spinner
android:layout_width="match_parent" android:layout_width="140dp"
android:layout_height="wrap_content" android:id="@+id/categorySpinner" android:layout_height="50dp" android:id="@+id/categorySpinner"
app:layout_constraintStart_toEndOf="@+id/categoryTextView" app:layout_constraintStart_toEndOf="@+id/categoryTextView"
app:layout_constraintTop_toBottomOf="@+id/netWeight" android:layout_marginStart="8dp" app:layout_constraintTop_toBottomOf="@+id/netWeight" android:layout_marginStart="8dp"
android:layout_marginTop="18dp"/> android:layout_marginTop="18dp" android:outlineProvider="bounds"/>
<Button <Button
android:text="@string/saveButton" android:text="@string/saveButton"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="50dp" android:id="@+id/saveButton" android:layout_height="50dp" android:id="@+id/saveButton"
app:layout_constraintTop_toBottomOf="@+id/categoryTextView" app:layout_constraintTop_toBottomOf="@+id/categoryTextView"
android:layout_marginTop="15dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="40dp" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="5dp"/> android:layout_marginEnd="5dp"/>
<Button <Button
android:text="@string/takePicture" android:text="@string/takePicture"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="55dp" android:id="@+id/takePictureButton" android:layout_height="55dp" android:id="@+id/takePictureButton"
app:layout_constraintTop_toBottomOf="@+id/categoryTextView" app:layout_constraintTop_toBottomOf="@+id/categoryTextView"
android:layout_marginTop="15dp" android:layout_marginTop="40dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -21,83 +21,77 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/scanButton" android:layout_weight="1" android:layout_height="wrap_content" android:id="@+id/scanButton" android:layout_weight="1"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/abstractProductView" android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/abstractProductView" android:layout_marginTop="16dp"/> android:layout_marginStart="32dp"/>
<Button
android:text="@string/no_barcode_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/noBarcodeButton"
app:layout_constraintTop_toBottomOf="@+id/abstractProductView"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"/>
<TextView <TextView
android:text="@string/date_of_production" android:text="@string/date_of_production"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/dateOfProductionTextView" android:layout_height="wrap_content" android:id="@+id/dateOfProductionTextView"
app:layout_constraintTop_toBottomOf="@+id/scanButton" app:layout_constraintTop_toBottomOf="@+id/scanButton"
android:layout_marginTop="16dp" app:layout_constraintStart_toStartOf="parent"/> android:layout_marginTop="48dp" app:layout_constraintStart_toStartOf="parent"
<Button
android:text="@string/select"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/selectDateOfProductionButton"
app:layout_constraintTop_toBottomOf="@+id/scanButton"
app:layout_constraintStart_toEndOf="@+id/dateOfProductionTextView"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
<RadioGroup <RadioGroup
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@+id/selectDateOfProductionButton" android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="16dp" app:layout_constraintEnd_toEndOf="parent"
android:orientation="horizontal" android:id="@+id/radioGroup"> android:orientation="horizontal" android:id="@+id/radioGroup"
app:layout_constraintTop_toBottomOf="@+id/dateOfProductionDatePicker" android:layout_marginTop="16dp">
<RadioButton <RadioButton
android:text="@string/expiry_date" android:text="@string/expiry_date"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/expiryDateRadio"/> android:layout_height="match_parent" android:id="@+id/expiryDateRadio"/>
<RadioButton <RadioButton
android:text="@string/shelf_life" android:text="@string/shelf_life"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/shelfLifeRadio"/> android:layout_height="wrap_content" android:id="@+id/shelfLifeRadio"/>
</RadioGroup> </RadioGroup>
<TextView <DatePicker
android:text="Expiry date: " android:layout_width="247dp"
android:layout_width="wrap_content" android:layout_height="100dp" android:id="@+id/expiryDatePicker"
android:layout_height="wrap_content" android:id="@+id/expiryDateTextView" android:datePickerMode="spinner"
app:layout_constraintTop_toBottomOf="@+id/radioGroup" app:layout_constraintStart_toStartOf="parent" android:calendarViewShown="false"
android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:visibility="invisible"/> app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
<Button app:layout_constraintHorizontal_bias="0.505" android:layout_marginTop="12dp"
android:text="Select" app:layout_constraintTop_toBottomOf="@+id/radioGroup" android:visibility="gone"/>
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/selectExpiryDateButton"
app:layout_constraintTop_toBottomOf="@+id/radioGroup"
app:layout_constraintStart_toEndOf="@+id/expiryDateTextView" android:layout_marginStart="16dp"
android:visibility="invisible"/>
<TextView
android:text="Shelf life:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/shelfLife"
app:layout_constraintTop_toBottomOf="@+id/radioGroup"
android:layout_marginTop="16dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp" android:visibility="invisible"/>
<EditText <EditText
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="numberDecimal" android:inputType="numberDecimal"
android:ems="10" android:ems="10"
android:id="@+id/shelfLifeTextEdit" app:layout_constraintTop_toBottomOf="@+id/radioGroup" android:id="@+id/shelfLifeTextEdit" app:layout_constraintTop_toBottomOf="@+id/radioGroup"
app:layout_constraintStart_toEndOf="@+id/expiryDateTextView" android:layout_marginStart="16dp" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent"
android:visibility="invisible"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/amountText"
app:layout_constraintTop_toBottomOf="@+id/shelfLifeTextEdit"
app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="16dp" app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="16dp"
android:text="@string/amount"/> android:hint="@string/shelf_life"/>
<EditText <EditText
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="numberSigned" android:inputType="numberSigned"
android:ems="10" android:ems="10"
android:id="@+id/amountTextEdit" android:id="@+id/amountTextEdit"
app:layout_constraintTop_toBottomOf="@+id/selectExpiryDateButton" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/amountText"/> android:hint="@string/amount" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/expiryDatePicker" android:layout_marginTop="12dp"/>
<Button <Button
android:text="@string/saveButton" android:text="@string/saveButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/saveProductButton" android:layout_height="wrap_content" android:id="@+id/saveProductButton"
app:layout_constraintTop_toBottomOf="@+id/amountTextEdit" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/amountTextEdit" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="16dp"/> app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="16dp"/>
<DatePicker
android:layout_width="247dp"
android:layout_height="100dp" android:id="@+id/dateOfProductionDatePicker"
android:datePickerMode="spinner"
android:calendarViewShown="false"
app:layout_constraintTop_toBottomOf="@+id/dateOfProductionTextView"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp" app:layout_constraintHorizontal_bias="0.505"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent" android:id="@+id/layout">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollView">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/datesLinearLayout">
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.core.widget.NestedScrollView>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -25,7 +26,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" app:layout_constraintTop_toBottomOf="@+id/spinner" android:layout_height="match_parent" app:layout_constraintTop_toBottomOf="@+id/spinner"
android:layout_marginTop="32dp" app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="32dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" android:id="@+id/scrollView"> app:layout_constraintEnd_toEndOf="parent" android:id="@+id/scrollView"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.gridlayout.widget.GridLayout <androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/contentGridLayout" app:columnCount="2" android:layout_height="wrap_content" android:id="@+id/contentGridLayout" app:columnCount="2"

View File

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

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,16 @@
<?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"/>
<item android:title="@string/logout" android:id="@+id/nav_logout"/>
</menu>

View File

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

View File

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

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph_add_abstract_product"
app:startDestination="@id/addAbstractProductFragment">
<fragment android:id="@+id/addAbstractProductFragment"
android:name="org.foxarmy.barcodescannerforemployees.fragments.AddAbstractProductFragment"
android:label="activity_add_abstract_product" tools:layout="@layout/fragment_add_abstract_product"/>
</navigation>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph_add_product"
app:startDestination="@id/addProductFragment">
<fragment android:id="@+id/addProductFragment"
android:name="org.foxarmy.barcodescannerforemployees.fragments.AddProductFragment"
android:label="activity_add_product" tools:layout="@layout/fragment_add_product"/>
</navigation>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">СканнерШтрихкодовДляРаботников</string> <string name="app_name">СканнерШтрихкодовДляРаботников</string>
<string name="action_settings">Настройки</string> <string name="settings">Настройки</string>
<string name="first_fragment_label">Добавить продукт</string> <string name="first_fragment_label">Добавить продукт</string>
<string name="second_fragment_label">Продукты</string> <string name="second_fragment_label">Продукты</string>
<string name="scan_label">Сканировать</string> <string name="scan_label">Сканировать</string>
@@ -77,4 +77,57 @@
<item>Имя</item> <item>Имя</item>
<item>Категория</item> <item>Категория</item>
</string-array> </string-array>
<string name="abstract_product_does_not_exist">Абстрактный продукт с таким штрихкодом не существует. Хотите его добавить?. </string>
<string name="drop_filters">Убрать фильтры</string>
<string name="category_name_required">Требуется название категории</string>
<string name="abstract_product_already_exists">Такой абстрактный продукт в ваше базе данных уже есть</string>
<string name="quit">Выйти</string>
<string name="edit_existing">Редактировать существующий</string>
<string name="no_product_in_online_database">Продукт не найден в онлайн базе данных. Попробуйте снова, если это
ошибка сканирования или введите данные вручную
</string>
<string name="abstract_product_request">Пожалуйста, отсканируйте штрихкод, чтобы добавить продукт</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 name="no_internet_connection">Не могу установить соединение с сервером. Возможно, вы отключены от сети?</string>
<string name="ok">Ок</string>
<string name="logout">Выйти</string>
<string name="online_only_feature">Эта возможность доступна только с онлайн аккаунтом</string>
<string name="username_changed">Имя пользователя изменено. Пожалуйста, перезайдите</string>
<string name="password_changed">Пароль изменён. Пожалуйста, перезайдите</string>
<string name="new_group_name">Введите новое имя группы</string>
<string name="kick">Выгнать</string>
<string name="transfer_ownership">Передать владение группой</string>
<string name="transfer_ownership_confirmation">Вы увереных, что хотите передать владение группой этому пользователю?</string>
<string name="loading_please_wait">Загрузка, пожалуйста ждите</string>
<string name="loading">Загрузка</string>
<string-array name="languages">
<item>en-US</item>
<item>ru-RU</item>
</string-array>
</resources> </resources>

View File

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

View File

@@ -1,6 +1,6 @@
<resources> <resources>
<string name="app_name">BarcodeScannerForEmployees</string> <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="first_fragment_label">Add new product</string>
<string name="second_fragment_label">Products</string> <string name="second_fragment_label">Products</string>
<string name="netWeight">Net weight</string> <string name="netWeight">Net weight</string>
@@ -75,4 +75,57 @@
<string name="expired">Expired</string> <string name="expired">Expired</string>
<string name="barcode">Barcode</string> <string name="barcode">Barcode</string>
<string name="scan_label">Scan</string> <string name="scan_label">Scan</string>
<string name="abstract_product_does_not_exist">Abstract product with such barcode does not exist. Do you want to add one?</string>
<string name="drop_filters">Drop filters</string>
<string name="category_name_required">Category name required</string>
<string name="abstract_product_already_exists">You\'ve got an abstract product with such barcode in your database.</string>
<string name="quit">Quit</string>
<string name="edit_existing">Edit existing</string>
<string name="no_product_in_online_database">Product not found. Please, try again if you beleive barcode scanned
wrongly or type manually
</string>
<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 name="no_internet_connection">Cannot reach the server. Maybe, you lost internet connection?</string>
<string name="ok">OK</string>
<string name="logout">Log out</string>
<string name="online_only_feature">This feature is online only</string>
<string name="username_changed">Your username has been changed. Please, relogin</string>
<string name="password_changed">You password was changed. Please, relogin</string>
<string name="new_group_name">Enter new group name</string>
<string name="kick">Kick</string>
<string name="transfer_ownership">Transfer ownership</string>
<string name="transfer_ownership_confirmation">Are you sure you want to transfer ownership on current group to that user?</string>
<string name="loading_please_wait">Loading, please wait</string>
<string name="loading">Loading</string>
<string-array name="languages">
<item>en-US</item>
<item>ru-RU</item>
</string-array>
</resources> </resources>

View File

@@ -17,15 +17,19 @@ gridlayout = "1.0.0"
activity = "1.9.2" activity = "1.9.2"
legacySupportV4 = "1.0.0" legacySupportV4 = "1.0.0"
fragment = "1.8.4" fragment = "1.8.4"
okhttp = "4.10.0"
playServicesCodeScanner = "16.1.0" playServicesCodeScanner = "16.1.0"
securityCrypto = "1.0.0"
volley = "1.2.1" volley = "1.2.1"
zxingAndroidEmbedded = "4.3.0" zxingAndroidEmbedded = "4.3.0"
material3Android = "1.3.0"
[libraries] [libraries]
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraView" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraView" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", 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-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraView" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 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" } barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } 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-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" } androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" }
androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" } 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" } 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" } volley = { module = "com.android.volley:volley", version.ref = "volley" }
zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }