Compare commits
	
		
			16 Commits
		
	
	
		
			alpha-0.0.
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| edd9c666ac | |||
| 6bbf436f45 | |||
| a860e4d59f | |||
| 37e52b3f2f | |||
| f18d7ad1e0 | |||
| 1d7050c687 | |||
| 153506fc83 | |||
| abe2a850d6 | |||
| 58a7ea7357 | |||
| 2ebcfff51a | |||
| 2676d8083e | |||
| e5772bcad3 | |||
| 988272070d | |||
| fbf630090c | |||
| 8c3845a07e | |||
| d08a79e981 | 
| @@ -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" | ||||||
|   | |||||||
| @@ -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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,5 +1,9 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees | package org.foxarmy.barcodescannerforemployees | ||||||
|  |  | ||||||
|  | import android.content.Context | ||||||
|  | import kotlinx.coroutines.CoroutineScope | ||||||
|  | import kotlinx.coroutines.Dispatchers | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import okhttp3.* | import okhttp3.* | ||||||
| import okhttp3.MediaType.Companion.toMediaType | import okhttp3.MediaType.Companion.toMediaType | ||||||
| import okhttp3.MediaType.Companion.toMediaTypeOrNull | import okhttp3.MediaType.Companion.toMediaTypeOrNull | ||||||
| @@ -10,16 +14,38 @@ import org.foxarmy.barcodescannerforemployees.dataclasses.Product | |||||||
| import java.io.File | import java.io.File | ||||||
| import java.io.FileOutputStream | import java.io.FileOutputStream | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
| import kotlin.concurrent.thread | import java.util.concurrent.TimeUnit | ||||||
|  |  | ||||||
| class Net { | class Net { | ||||||
|     var language = "en-US" |     var language = "en-US" | ||||||
|     var server = "bsfe.foxarmy.org" |     var server = "bsfe.foxarmy.org" | ||||||
|     var token = "" |     var token = "" | ||||||
|  |  | ||||||
|     fun requestProductFromOnlineDB(barcode: String): String { |     fun serverIsAvailable(context: Context, callback: (Boolean) -> Unit) { | ||||||
|         var response = "" |         if (!isInternetConnectionAvailable(context)) { | ||||||
|         thread { |             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 url = "https://ean-online.ru/match.php" | ||||||
|  |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
| @@ -35,19 +61,15 @@ class Net { | |||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute().body!!.string() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |  | ||||||
|         return if (response == "") { |  | ||||||
|             "Not found 404" |  | ||||||
|         } else { |  | ||||||
|             response |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun registerAccount(username: String, password: String): Response { |     fun registerAccount(username: String, password: String, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|         thread { |             lateinit var response: Response | ||||||
|  |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -63,14 +85,13 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun login(username: String, password: String): Response { |     fun login(username: String, password: String, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|         thread { |             lateinit var response: Response | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -84,15 +105,19 @@ class Net { | |||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|             response = client.newCall(requestLogin).execute() |             response = client.newCall(requestLogin).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun uploadAbstractProduct(groupId: Int, abstractProduct: AbstractProduct, imageFile: File): Response { |     fun uploadAbstractProduct( | ||||||
|         lateinit var response: Response |         groupId: Int, | ||||||
|  |         abstractProduct: AbstractProduct, | ||||||
|  |         imageFile: File, | ||||||
|  |         callback: (Response) -> Unit, | ||||||
|  |     ) { | ||||||
|  |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = MultipartBody.Builder() |             val body = MultipartBody.Builder() | ||||||
| @@ -115,16 +140,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun changeUsername(newUsername: String): Response { |     fun changeUsername(newUsername: String, callback: (Response) -> Unit) { | ||||||
|  |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         lateinit var response: Response |  | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -139,16 +162,14 @@ class Net { | |||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun changePassword(newPassword: String): Response { |     fun changePassword(newPassword: String, callback: (Response) -> Unit) { | ||||||
|  |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         lateinit var response: Response |  | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -163,15 +184,12 @@ class Net { | |||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun createGroup(name: String, password: String): Response { |     fun createGroup(name: String, password: String, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -184,19 +202,19 @@ class Net { | |||||||
|                 .addHeader("Authorization", "Bearer $token") |                 .addHeader("Authorization", "Bearer $token") | ||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|             response = client.newCall(request).execute() |             val response = client.newCall(request).execute() | ||||||
|         }.join() |  | ||||||
|  |  | ||||||
|  |             changeGroupPassword(name, password, { responseFromPassword -> | ||||||
|  |                 callback(response) | ||||||
|  |             }) | ||||||
|  |  | ||||||
|         changeGroupPassword(name, password) |         } | ||||||
|  |  | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun joinGroup(id: Int, password: String): Response { |     fun joinGroup(id: Int, password: String, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -210,41 +228,38 @@ class Net { | |||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun changeGroupPassword(name: String, password: String): Response { |     fun changeGroupPassword(name: String, password: String, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |             getGroupId(name, { groupId -> | ||||||
|  |                 val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val groupId = getGroupId(name); |                 val body = FormBody.Builder() | ||||||
|  |                 body.add("password", password) | ||||||
|  |  | ||||||
|             val client = OkHttpClient() |                 val requestBody = body.build() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |                 val request = Request.Builder() | ||||||
|             body.add("password", password) |                     .url("https://$server/api/group/password/$groupId") | ||||||
|  |                     .post(requestBody) | ||||||
|             val requestBody = body.build() |                     .addHeader("Authorization", "Bearer $token") | ||||||
|  |                     .addHeader("accept-language", language) | ||||||
|             val request = Request.Builder() |                     .build() | ||||||
|                 .url("https://$server/api/group/password/$groupId") |                 response = client.newCall(request).execute() | ||||||
|                 .post(requestBody) |                 callback(response) | ||||||
|                 .addHeader("Authorization", "Bearer $token") |             }); | ||||||
|                 .addHeader("accept-language", language) |         } | ||||||
|                 .build() |  | ||||||
|             response = client.newCall(request).execute() |  | ||||||
|         }.join() |  | ||||||
|  |  | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getGroupId(name: String): String { |     fun getGroupId(name: String, callback: (String) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -254,17 +269,14 @@ class Net { | |||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response.body!!.string()) | ||||||
|  |         } | ||||||
|         val responseText = response.body!!.string() |  | ||||||
|  |  | ||||||
|         return responseText |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getGroupName(id: Int): String { |     fun getGroupName(id: Int, callback: (String) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -274,17 +286,14 @@ class Net { | |||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response.body!!.string()) | ||||||
|  |         } | ||||||
|         val responseText = response.body!!.string() |  | ||||||
|  |  | ||||||
|         return responseText |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getUsersInGroup(groupId: Int): Response { |     fun getUsersInGroup(groupId: Int, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -295,15 +304,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getMyGroups(): Response { |     fun getMyGroups(callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -314,15 +322,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getUsernameById(userId: Int): Response { |     fun getUsernameById(userId: Int, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -333,15 +340,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getGroupAdminId(groupId: Int): Response { |     fun getGroupAdminId(groupId: Int, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -352,15 +358,19 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun updateAbstractProduct(groupId: Int, abstractProduct: AbstractProduct, imageFile: File): Response { |     fun updateAbstractProduct( | ||||||
|         lateinit var response: Response |         groupId: Int, | ||||||
|  |         abstractProduct: AbstractProduct, | ||||||
|  |         imageFile: File, | ||||||
|  |         callback: (Response) -> Unit, | ||||||
|  |     ) { | ||||||
|  |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = MultipartBody.Builder() |             val body = MultipartBody.Builder() | ||||||
| @@ -383,15 +393,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun uploadCategory(groupId: Int, category: Category): Response { |     fun uploadCategory(groupId: Int, category: Category, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -408,15 +417,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun updateCategory(groupId: Int, category: Category): Response { |     fun updateCategory(groupId: Int, category: Category, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -433,15 +441,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun uploadProduct(groupId: Int, product: Product): Response { |     fun uploadProduct(groupId: Int, product: Product, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -461,15 +468,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun updateProduct(groupId: Int, product: Product): Response { |     fun updateProduct(groupId: Int, product: Product, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val body = FormBody.Builder() |             val body = FormBody.Builder() | ||||||
| @@ -489,15 +495,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun synchronize(groupId: Int): Response { |     fun synchronize(groupId: Int, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -508,76 +513,41 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getProduct(groupId: Int, localId: Int): Response { |     fun downloadImage(url: String, file: File, callback: () -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             val client = OkHttpClient.Builder() | ||||||
|         thread { |                 .connectTimeout(30, TimeUnit.SECONDS) | ||||||
|             val client = OkHttpClient() |                 .readTimeout(10, TimeUnit.MINUTES) | ||||||
|  |  | ||||||
|             val request = Request.Builder() |  | ||||||
|                 .url("https://$server/api/product/$groupId/$localId") |  | ||||||
|                 .get() |  | ||||||
|                 .addHeader("Authorization", "Bearer $token") |  | ||||||
|                 .addHeader("accept-language", language) |  | ||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |  | ||||||
|         }.join() |  | ||||||
|  |  | ||||||
|         return response |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun getAbstractProduct(groupId: Int, localId: Int): Response { |  | ||||||
|         lateinit var response: Response |  | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |  | ||||||
|  |  | ||||||
|             val request = Request.Builder() |  | ||||||
|                 .url("https://$server/api/abstractproduct/$groupId/$localId") |  | ||||||
|                 .get() |  | ||||||
|                 .addHeader("Authorization", "Bearer $token") |  | ||||||
|                 .addHeader("accept-language", language) |  | ||||||
|                 .build() |  | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |  | ||||||
|         }.join() |  | ||||||
|  |  | ||||||
|         return response |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun downloadImage(url: String, file: File) { |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
|                 .url(url) |                 .url(url) | ||||||
|                 .addHeader("Authorization", "Bearer $token") |                 .addHeader("Authorization", "Bearer $token") | ||||||
|                 .addHeader("accept-language", language) |                 .addHeader("accept-language", language) | ||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             client.newCall(request).execute().use { response -> |             val response = client.newCall(request).execute() | ||||||
|                 if (!response.isSuccessful) throw IOException("Unexpected code $response") |             if (!response.isSuccessful) throw IOException("Unexpected code $response") | ||||||
|  |  | ||||||
|                 val fos = FileOutputStream(file) |             val fos = FileOutputStream(file) | ||||||
|  |  | ||||||
|                 response.body?.byteStream()?.use { inputStream -> |             response.body?.byteStream()?.use { inputStream -> | ||||||
|                     fos.use { |                 fos.use { | ||||||
|                         inputStream.copyTo(fos) |                     inputStream.copyTo(fos) | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }.join() |             callback() | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun deleteCategory(groupId: Int, localId: Int): Response { |     fun deleteCategory(groupId: Int, localId: Int, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -588,15 +558,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun deleteAbstractProduct(groupId: Int, localId: Int): Response { |     fun deleteAbstractProduct(groupId: Int, localId: Int, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -607,15 +576,14 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|         return response |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun deleteProduct(groupId: Int, localId: Int): Response { |     fun deleteProduct(groupId: Int, localId: Int, callback: (Response) -> Unit) { | ||||||
|         lateinit var response: Response |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|         thread { |  | ||||||
|             val client = OkHttpClient() |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|             val request = Request.Builder() |             val request = Request.Builder() | ||||||
| @@ -626,8 +594,69 @@ class Net { | |||||||
|                 .build() |                 .build() | ||||||
|  |  | ||||||
|             response = client.newCall(request).execute() |             response = client.newCall(request).execute() | ||||||
|         }.join() |             callback(response) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|         return response |     fun renameGroup(groupId: Int, newName: String, callback: (Response) -> Unit) { | ||||||
|  |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|  |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|  |             val body = FormBody.Builder() | ||||||
|  |                 .add("name", newName) | ||||||
|  |                 .build() | ||||||
|  |  | ||||||
|  |             val request = Request.Builder() | ||||||
|  |                 .url("https://$server/api/group/rename/$groupId") | ||||||
|  |                 .post(body) | ||||||
|  |                 .addHeader("Authorization", "Bearer $token") | ||||||
|  |                 .addHeader("accept-language", language) | ||||||
|  |                 .build() | ||||||
|  |  | ||||||
|  |             response = client.newCall(request).execute() | ||||||
|  |             callback(response) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun transfer_ownership(groupId: Int, userId: Int, callback: (Response) -> Unit) { | ||||||
|  |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|  |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|  |             val body = FormBody.Builder() | ||||||
|  |                 .add("userId", userId.toString()) | ||||||
|  |                 .build() | ||||||
|  |  | ||||||
|  |             val request = Request.Builder() | ||||||
|  |                 .url("https://$server/api/group/transferOwnership/$groupId") | ||||||
|  |                 .post(body) | ||||||
|  |                 .addHeader("Authorization", "Bearer $token") | ||||||
|  |                 .addHeader("accept-language", language) | ||||||
|  |                 .build() | ||||||
|  |  | ||||||
|  |             response = client.newCall(request).execute() | ||||||
|  |             callback(response) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun leaveGroup(groupId: Int, callback: (Response) -> Unit) { | ||||||
|  |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|  |             lateinit var response: Response | ||||||
|  |  | ||||||
|  |             val client = OkHttpClient() | ||||||
|  |  | ||||||
|  |             val request = Request.Builder() | ||||||
|  |                 .url("https://$server/api/group/leave/$groupId") | ||||||
|  |                 .get() | ||||||
|  |                 .addHeader("Authorization", "Bearer $token") | ||||||
|  |                 .addHeader("accept-language", language) | ||||||
|  |                 .build() | ||||||
|  |  | ||||||
|  |             response = client.newCall(request).execute() | ||||||
|  |             callback(response) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -14,8 +14,6 @@ class Parser constructor() { | |||||||
|                 text = text.replace(found, "") |                 text = text.replace(found, "") | ||||||
|                 netWeight = stripNetWeight(found) |                 netWeight = stripNetWeight(found) | ||||||
|                 return Triple(text, netWeight, found) |                 return Triple(text, netWeight, found) | ||||||
|             } else { |  | ||||||
|                 return Triple(text, 0.0, "") |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return Triple("", 0.0, "") |         return Triple("", 0.0, "") | ||||||
| @@ -48,4 +46,4 @@ class Parser constructor() { | |||||||
|  |  | ||||||
|         return AbstractProduct(0, "", name, netWeight, "", 0, unitNumber) |         return AbstractProduct(0, "", name, netWeight, "", 0, unitNumber) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,21 +3,30 @@ 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.graphics.Matrix | ||||||
| import android.media.ExifInterface | import android.media.ExifInterface | ||||||
|  | import android.net.ConnectivityManager | ||||||
|  | import android.net.NetworkCapabilities | ||||||
| import android.net.Uri | import android.net.Uri | ||||||
| import android.os.Build | import android.os.Build | ||||||
| import androidx.annotation.RequiresApi | import androidx.annotation.RequiresApi | ||||||
|  | import androidx.appcompat.app.AlertDialog | ||||||
| import androidx.core.content.FileProvider | import androidx.core.content.FileProvider | ||||||
|  | import androidx.security.crypto.EncryptedSharedPreferences | ||||||
|  | import androidx.security.crypto.MasterKeys | ||||||
| import com.google.firebase.components.BuildConfig | import com.google.firebase.components.BuildConfig | ||||||
|  | import org.foxarmy.barcodescannerforemployees.activities.LoginActivity | ||||||
| import java.io.File | import java.io.File | ||||||
| import java.io.FileInputStream | import java.io.FileInputStream | ||||||
| import java.io.FileOutputStream | import java.io.FileOutputStream | ||||||
| import java.security.MessageDigest | import java.security.MessageDigest | ||||||
| import java.text.SimpleDateFormat | import java.text.SimpleDateFormat | ||||||
| import java.util.* | import java.util.* | ||||||
|  | import kotlin.system.exitProcess | ||||||
|  |  | ||||||
| fun convertToUnixEpochTimestamp(dateString: String): Long { | fun convertToUnixEpochTimestamp(dateString: String): Long { | ||||||
|     val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") |     val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") | ||||||
| @@ -27,7 +36,11 @@ fun convertToUnixEpochTimestamp(dateString: String): Long { | |||||||
| } | } | ||||||
|  |  | ||||||
| fun getImageUri(activity: Activity, imageFile: File): Uri? { | fun getImageUri(activity: Activity, imageFile: File): Uri? { | ||||||
|     return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider", imageFile) |     return FileProvider.getUriForFile( | ||||||
|  |         activity, | ||||||
|  |         BuildConfig.APPLICATION_ID + "." + activity.localClassName + ".provider", | ||||||
|  |         imageFile | ||||||
|  |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
| @RequiresApi(Build.VERSION_CODES.R) | @RequiresApi(Build.VERSION_CODES.R) | ||||||
| @@ -47,13 +60,13 @@ fun generateThumbnailForImage(context: Context, imageHash: String) { | |||||||
|     val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) |     val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) | ||||||
|     val matrix = Matrix() |     val matrix = Matrix() | ||||||
|  |  | ||||||
|     when(orientation){ |     when (orientation) { | ||||||
|         ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F) |         ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F) | ||||||
|         ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F) |         ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F) | ||||||
|         ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F) |         ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F) | ||||||
|     } |     } | ||||||
|     val rotated = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true) |     val rotated = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true) | ||||||
|     val scaled = Bitmap.createScaledBitmap(rotated, rotated.width/4, rotated.height/4, true) |     val scaled = Bitmap.createScaledBitmap(rotated, rotated.width / 4, rotated.height / 4, true) | ||||||
|     scaled.compress(Bitmap.CompressFormat.WEBP_LOSSY, 25, FileOutputStream(thumbnailFile)) |     scaled.compress(Bitmap.CompressFormat.WEBP_LOSSY, 25, FileOutputStream(thumbnailFile)) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -64,8 +77,11 @@ fun String.md5(): String { | |||||||
|     return digest.toHexString() |     return digest.toHexString() | ||||||
| } | } | ||||||
|  |  | ||||||
| fun stripNetWeight (netWeight: String): Double { | fun stripNetWeight(netWeight: String): Double { | ||||||
|     return removeSubstringsFromString(netWeight, arrayOf("Л", "л", "мл", "Мл", "г", "Г", "кг", "Кг", "шт", "Шт", ",", " ", ".")).toDouble() |     return removeSubstringsFromString( | ||||||
|  |         netWeight, | ||||||
|  |         arrayOf("Л", "л", "мл", "Мл", "г", "Г", "кг", "Кг", "шт", "Шт", ",", " ", ".") | ||||||
|  |     ).toDouble() | ||||||
| } | } | ||||||
|  |  | ||||||
| fun removeSubstringsFromString(text: String, toRemove: Array<String>): String { | fun removeSubstringsFromString(text: String, toRemove: Array<String>): String { | ||||||
| @@ -99,21 +115,30 @@ fun calculateProductFreshness(dateOfProduction: Long, dateOfExpiry: Long): Doubl | |||||||
|     return lifeSpanLeft / productLifeSpan.toDouble() |     return lifeSpanLeft / productLifeSpan.toDouble() | ||||||
| } | } | ||||||
|  |  | ||||||
| fun getUnitNameById (context: Context, id: Int): String { | fun getUnitNameById(context: Context, id: Int): String { | ||||||
|     return when(id) { |     return when (id) { | ||||||
|         0 -> { context.getString(R.string.kilogram) } |         0 -> { context.getString(R.string.kilogram) } | ||||||
|  |  | ||||||
|         1 -> { context.getString(R.string.gram) } |         1 -> { context.getString(R.string.gram) } | ||||||
|  |  | ||||||
|         2 -> { context.getString(R.string.liter) } |         2 -> { context.getString(R.string.liter) } | ||||||
|  |  | ||||||
|         3 -> { context.getString(R.string.milliliter) } |         3 -> { context.getString(R.string.milliliter) } | ||||||
|  |  | ||||||
|         4 -> { context.getString(R.string.pieces) } |         4 -> { context.getString(R.string.pieces) } | ||||||
|  |  | ||||||
|         else -> { "" } |         else -> { "" } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fun parseArray(input: String): IntArray { | fun parseIntArray(input: String): IntArray { | ||||||
|     return input.trim('[', ']').split(",").map { it.trim().toInt() }.toIntArray() |     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 { | fun calculateMd5Hash(file: File): String { | ||||||
|     val digest = MessageDigest.getInstance("MD5") |     val digest = MessageDigest.getInstance("MD5") | ||||||
|     val fis = FileInputStream(file) |     val fis = FileInputStream(file) | ||||||
| @@ -137,4 +162,56 @@ fun bytesToHex(bytes: ByteArray): String { | |||||||
|         hexString.append(hex) |         hexString.append(hex) | ||||||
|     } |     } | ||||||
|     return hexString.toString() |     return hexString.toString() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun isInternetConnectionAvailable(context: Context): Boolean { | ||||||
|  |     if (context.getSystemService(Context.CONNECTIVITY_SERVICE) == null) return false | ||||||
|  |     val connectivityManager = | ||||||
|  |         context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | ||||||
|  |     if (connectivityManager != null) { | ||||||
|  |         val capabilities = | ||||||
|  |             connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) | ||||||
|  |         if (capabilities != null) { | ||||||
|  |             if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { | ||||||
|  |                 return true | ||||||
|  |             } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { | ||||||
|  |                 return true | ||||||
|  |             } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { | ||||||
|  |                 return true | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun noInternetConnectionAvailableNotification(context: Context) { | ||||||
|  |     (context as Activity).runOnUiThread { | ||||||
|  |         AlertDialog.Builder(context) | ||||||
|  |             .setMessage(context.getString(R.string.no_internet_connection)) | ||||||
|  |             .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 | ||||||
|  |     ) | ||||||
| } | } | ||||||
| @@ -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") | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,11 +1,11 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees.activities | package org.foxarmy.barcodescannerforemployees.activities | ||||||
|  |  | ||||||
|  | import android.content.Intent | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.widget.Toast | import android.widget.Toast | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
|  | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.databinding.ActivityAccountSettingsBinding | import org.foxarmy.barcodescannerforemployees.databinding.ActivityAccountSettingsBinding | ||||||
|  |  | ||||||
| class AccountSettingsActivity : AppCompatActivity() { | class AccountSettingsActivity : AppCompatActivity() { | ||||||
| @@ -19,15 +19,7 @@ class AccountSettingsActivity : AppCompatActivity() { | |||||||
|  |  | ||||||
|         setContentView(binding.root) |         setContentView(binding.root) | ||||||
|  |  | ||||||
|         val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) |         val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|  |  | ||||||
|         val sharedPreferences = EncryptedSharedPreferences.create( |  | ||||||
|             "sensitive", |  | ||||||
|             masterKeyAlias, |  | ||||||
|             applicationContext, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val net = Net() |         val net = Net() | ||||||
|  |  | ||||||
| @@ -36,13 +28,39 @@ class AccountSettingsActivity : AppCompatActivity() { | |||||||
|         net.token = sharedPreferences.getString("token", "")!! |         net.token = sharedPreferences.getString("token", "")!! | ||||||
|  |  | ||||||
|         binding.saveUsernameButton.setOnClickListener { |         binding.saveUsernameButton.setOnClickListener { | ||||||
|             val response = net.changeUsername(binding.newUsernameTextEdit.text.toString()) |             net.changeUsername(binding.newUsernameTextEdit.text.toString(), { response -> | ||||||
|             Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show() |                 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 { |         binding.savePasswordButton.setOnClickListener { | ||||||
|             val response = net.changePassword(binding.newPasswordTextEdit.text.toString()) |             net.changePassword(binding.newPasswordTextEdit.text.toString(), { response -> | ||||||
|             Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show() |                 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() | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees.activities | package org.foxarmy.barcodescannerforemployees.activities | ||||||
|  |  | ||||||
|  | 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.content.SharedPreferences | ||||||
| @@ -17,8 +18,6 @@ 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.core.widget.addTextChangedListener | import androidx.core.widget.addTextChangedListener | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| 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 | ||||||
| @@ -32,7 +31,6 @@ import java.io.File | |||||||
| import java.io.FileOutputStream | 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 | ||||||
| @@ -60,20 +58,21 @@ class AddAbstractProductActivity : AppCompatActivity() { | |||||||
|     private lateinit var DAO: AbstractProductDAO |     private lateinit var DAO: AbstractProductDAO | ||||||
|     private lateinit var sharedPreferences: SharedPreferences |     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) | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         loadingDialog = ProgressDialog(this) | ||||||
|             "sensitive", |         loadingDialog.setMessage(getString(R.string.loading_please_wait)) | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |         loadingDialog.setCancelable(false) | ||||||
|             applicationContext, |         loadingDialog.setTitle(getString(R.string.loading)) | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!) |         sharedPreferences = getPreferences(this) | ||||||
|  |  | ||||||
|  |         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!) | ||||||
|         DAO = AbstractProductDAO(dbHelper) |         DAO = AbstractProductDAO(dbHelper) | ||||||
|  |  | ||||||
|         picturesPath = File(filesDir, "pictures") |         picturesPath = File(filesDir, "pictures") | ||||||
| @@ -109,25 +108,28 @@ class AddAbstractProductActivity : AppCompatActivity() { | |||||||
|         action = extras!!.get("action") as String |         action = extras!!.get("action") as String | ||||||
|         when (action) { |         when (action) { | ||||||
|             "update" -> { |             "update" -> { | ||||||
|                 abstractProduct = extras.get("abstractProduct") as AbstractProduct? |                 abstractProduct = extras.get("abstractProduct") as AbstractProduct | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             "new_from_barcode" -> { |             "new_from_barcode" -> { | ||||||
|                 abstractProduct = extras.get("abstractProduct") as AbstractProduct? |                 abstractProduct = extras.get("abstractProduct") as AbstractProduct | ||||||
|                 barcode = abstractProduct!!.barcode |                 barcode = abstractProduct!!.barcode | ||||||
|                 performRequest(abstractProduct!!.barcode) |                 performRequest(abstractProduct!!.barcode) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (abstractProduct != null) { |         if (abstractProduct != null && action == "update") { | ||||||
|             val imageThumbnailUri = getImageUri(this, File(thumbnailsDir, "${abstractProduct!!.imageHash}.webp")) |             val imageThumbnailUri = getImageUri(this, File(thumbnailsDir, "${abstractProduct!!.imageHash}.webp")) | ||||||
|             pictureFile = File(picturesPath, "${abstractProduct!!.imageHash}.png]") |             pictureFile = File(picturesPath, "${abstractProduct!!.imageHash}.png") | ||||||
|             imageView.setImageURI(imageThumbnailUri) |             imageView.setImageURI(imageThumbnailUri) | ||||||
|             barcodeText.setText(abstractProduct!!.barcode) |             barcodeText.setText(abstractProduct!!.barcode) | ||||||
|             productNameText.text = abstractProduct!!.name |             productNameText.text = abstractProduct!!.name | ||||||
|             netWeightText.text = abstractProduct!!.netWeight.toString() |             netWeightText.text = abstractProduct!!.netWeight.toString() | ||||||
|             categorySpinner.setSelection(abstractProduct!!.category) |             categorySpinner.setSelection(abstractProduct!!.category - 1) | ||||||
|             unitTypeSpinner.setSelection(abstractProduct!!.unit) |             unitTypeSpinner.setSelection(abstractProduct!!.unit) | ||||||
|  |             if (abstractProduct!!.barcode == "" || abstractProduct!!.barcode == "             ") { | ||||||
|  |                 noBarcodeCheckBox.isChecked = true | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         saveButton.setOnClickListener { |         saveButton.setOnClickListener { | ||||||
| @@ -149,20 +151,29 @@ 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 | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             loadingDialog.show() | ||||||
|  |  | ||||||
|  |             val currentGroup: Int | ||||||
|  |             val currentGroupString = sharedPreferences.getString("currentGroup", "offline")!! | ||||||
|  |  | ||||||
|             val net = Net() |             val net = Net() | ||||||
|  |  | ||||||
|             net.token = sharedPreferences.getString("token", "")!! |             if (currentGroupString != "offline") { | ||||||
|             net.server = sharedPreferences.getString("server", "")!! |                 currentGroup = currentGroupString.toInt() | ||||||
|             net.language = sharedPreferences.getString("language", "en-US")!! |  | ||||||
|  |  | ||||||
|             val currentGroup = sharedPreferences.getString("currentGroup", "offline")!! |                 net.token = sharedPreferences.getString("token", "")!! | ||||||
|  |                 net.server = sharedPreferences.getString("server", "")!! | ||||||
|  |                 net.language = sharedPreferences.getString("language", "en-US")!! | ||||||
|  |  | ||||||
|             lateinit var response: Response |             } else { | ||||||
|  |                 currentGroup = 0 | ||||||
|  |             } | ||||||
|  |  | ||||||
|             abstractProduct = AbstractProduct( |             abstractProduct = AbstractProduct( | ||||||
|                 if(abstractProduct == null) 0 else abstractProduct!!.id, |                 if (abstractProduct == null) 0 else abstractProduct!!.id, | ||||||
|                 if (noBarcodeCheckBox.isChecked) "" else barcode, |                 if (noBarcodeCheckBox.isChecked) "" else barcode, | ||||||
|                 productName, |                 productName, | ||||||
|                 netWeight.toString().toDouble(), |                 netWeight.toString().toDouble(), | ||||||
| @@ -170,18 +181,17 @@ class AddAbstractProductActivity : AppCompatActivity() { | |||||||
|                 categorySpinner.selectedItemPosition + 1, |                 categorySpinner.selectedItemPosition + 1, | ||||||
|                 unitTypeSpinner.selectedItemPosition |                 unitTypeSpinner.selectedItemPosition | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|             val pictureFile = File(File(filesDir, "pictures"), "${abstractProduct!!.imageHash}.png") |             val pictureFile = File(File(filesDir, "pictures"), "${abstractProduct!!.imageHash}.png") | ||||||
|  |  | ||||||
|             if (action == "update") { |             if (action == "update") { | ||||||
|                 DAO.updateAbstractProduct(abstractProduct!!) |                 DAO.updateAbstractProduct(abstractProduct!!) | ||||||
|                 response = net.updateAbstractProduct(currentGroup.toInt(), abstractProduct!!, pictureFile) |                 if (currentGroup > 0) net.updateAbstractProduct(currentGroup, abstractProduct!!, pictureFile, this::notifyUserAndExit) | ||||||
|             } else if (action == "new" || action == "new_from_barcode") { |             } else if (action == "new" || action == "new_from_barcode") { | ||||||
|                 abstractProduct!!.id = DAO.addAbstractProduct(abstractProduct!!).toInt() |                 abstractProduct!!.id = DAO.addAbstractProduct(abstractProduct!!).toInt() | ||||||
|                 response = net.uploadAbstractProduct(currentGroup.toInt(), abstractProduct!!, pictureFile); |                 if (currentGroup > 0) | ||||||
|  |                     net.uploadAbstractProduct(currentGroup, abstractProduct!!, pictureFile, this::notifyUserAndExit) | ||||||
|             } |             } | ||||||
|             Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show() |  | ||||||
|  |  | ||||||
|             finish() |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         takePictureButton.setOnClickListener { |         takePictureButton.setOnClickListener { | ||||||
| @@ -194,11 +204,16 @@ class AddAbstractProductActivity : AppCompatActivity() { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun notifyUserAndExit(response: Response) { | ||||||
|  |         runOnUiThread { | ||||||
|  |             Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show() | ||||||
|  |             loadingDialog.dismiss() | ||||||
|  |             finish() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun performRequest(barcode: String) { |     fun performRequest(barcode: String) { | ||||||
|         barcodeText.setText(this.barcode) |         barcodeText.setText(this.barcode) | ||||||
|         val net = Net(); |  | ||||||
|         val result = net.requestProductFromOnlineDB(barcode) |  | ||||||
|         Log.d("QWERTYUIOP", "Result of request: $result") |  | ||||||
|  |  | ||||||
|         var abstractProduct: AbstractProduct |         var abstractProduct: AbstractProduct | ||||||
|  |  | ||||||
| @@ -220,24 +235,25 @@ class AddAbstractProductActivity : AppCompatActivity() { | |||||||
|                 }.show() |                 }.show() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         thread { |         val net = Net(); | ||||||
|             if (result == "Not found 404") { |         net.requestProductFromOnlineDB(barcode, {response -> | ||||||
|  |             if (response.code == 404) { | ||||||
|                 runOnUiThread { |                 runOnUiThread { | ||||||
|                     Toast.makeText(this, getString(R.string.no_product_in_online_database), Toast.LENGTH_LONG) |                     Toast.makeText(this, getString(R.string.no_product_in_online_database), Toast.LENGTH_LONG) | ||||||
|                         .show() |                         .show() | ||||||
|                     productNameText.setText("") |                     productNameText.setText("") | ||||||
|                     netWeightText.setText("") |                     netWeightText.setText("") | ||||||
|                 } |                 } | ||||||
|                 return@thread |                 return@requestProductFromOnlineDB | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             abstractProduct = Parser().parse(result) |             abstractProduct = Parser().parse(response.body!!.string()) | ||||||
|             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() { | ||||||
| @@ -257,7 +273,8 @@ class AddAbstractProductActivity : AppCompatActivity() { | |||||||
|  |  | ||||||
|     fun fillupCategorySpinner() { |     fun fillupCategorySpinner() { | ||||||
|  |  | ||||||
|         val categoriesDAO = CategoryDAO(DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)) |         val categoriesDAO = | ||||||
|  |             CategoryDAO(DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!)) | ||||||
|  |  | ||||||
|         val categories = categoriesDAO.getAllCategories().map { category -> category.name } |         val categories = categoriesDAO.getAllCategories().map { category -> category.name } | ||||||
|  |  | ||||||
| @@ -272,22 +289,15 @@ class AddAbstractProductActivity : AppCompatActivity() { | |||||||
|         if (success) { |         if (success) { | ||||||
|             //Move picture to a proper directory according to its calculated hash |             //Move picture to a proper directory according to its calculated hash | ||||||
|             val tempfile = File(filesDir, "image.png") |             val tempfile = File(filesDir, "image.png") | ||||||
|             val imageHash = calculateMd5Hash(tempfile) |  | ||||||
| //            val imageContent = tempfile.inputStream().readBytes() |  | ||||||
| //            val imageHash = imageContent.toString(Charsets.UTF_8).md5() |  | ||||||
|  |  | ||||||
|             pictureFile = File(picturesPath, "$imageHash.png") |             val imageContent = tempfile.inputStream().readBytes() | ||||||
|             Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING) |  | ||||||
|             tempfile.delete() |  | ||||||
|  |  | ||||||
|             val imageContent = pictureFile.inputStream().readBytes() |  | ||||||
|             var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size) |             var img = BitmapFactory.decodeByteArray(imageContent, 0, imageContent.size) | ||||||
|  |  | ||||||
|             val exif = ExifInterface(pictureFile.absoluteFile.toString()) |             val exif = ExifInterface(tempfile.absoluteFile.toString()) | ||||||
|             val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) |             val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) | ||||||
|             val matrix = Matrix() |             val matrix = Matrix() | ||||||
|  |  | ||||||
|             when(orientation){ |             when (orientation) { | ||||||
|                 ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F) |                 ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F) | ||||||
|                 ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F) |                 ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F) | ||||||
|                 ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F) |                 ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F) | ||||||
| @@ -298,9 +308,15 @@ class AddAbstractProductActivity : AppCompatActivity() { | |||||||
|             rotated.compress( |             rotated.compress( | ||||||
|                 Bitmap.CompressFormat.WEBP_LOSSY, |                 Bitmap.CompressFormat.WEBP_LOSSY, | ||||||
|                 100 * (15 / (16 - compressionFactor)), |                 100 * (15 / (16 - compressionFactor)), | ||||||
|                 FileOutputStream(pictureFile) |                 FileOutputStream(tempfile) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |             val imageHash = calculateMd5Hash(tempfile) | ||||||
|  |  | ||||||
|  |             pictureFile = File(picturesPath, "$imageHash.png") | ||||||
|  |             Files.move(tempfile.toPath(), pictureFile.toPath(), StandardCopyOption.REPLACE_EXISTING) | ||||||
|  |             tempfile.delete() | ||||||
|  |  | ||||||
|             generateThumbnailForImage(this, imageHash) |             generateThumbnailForImage(this, imageHash) | ||||||
|  |  | ||||||
|             imageView.setImageURI(getImageUri(this, pictureFile)) |             imageView.setImageURI(getImageUri(this, pictureFile)) | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees.activities | package org.foxarmy.barcodescannerforemployees.activities | ||||||
|  |  | ||||||
| import android.app.Activity | import android.app.Activity | ||||||
|  | import android.app.ProgressDialog | ||||||
| import android.content.SharedPreferences | import android.content.SharedPreferences | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.widget.Button | import android.widget.Button | ||||||
| import android.widget.EditText | import android.widget.EditText | ||||||
| import android.widget.Toast | import android.widget.Toast | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences | import okhttp3.Response | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.database.CategoryDAO | import org.foxarmy.barcodescannerforemployees.database.CategoryDAO | ||||||
| @@ -18,19 +18,19 @@ class AddCategoryActivity : Activity() { | |||||||
|  |  | ||||||
|     private lateinit var DAO: CategoryDAO |     private lateinit var DAO: CategoryDAO | ||||||
|     private lateinit var sharedPreferences: SharedPreferences |     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) | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         loadingDialog = ProgressDialog(this) | ||||||
|             "sensitive", |         loadingDialog.setMessage(getString(R.string.loading_please_wait)) | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |         loadingDialog.setCancelable(false) | ||||||
|             applicationContext, |         loadingDialog.setTitle(getString(R.string.loading)) | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |         sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!) |         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!) | ||||||
|         DAO = CategoryDAO(dbHelper) |         DAO = CategoryDAO(dbHelper) | ||||||
| @@ -48,25 +48,35 @@ class AddCategoryActivity : Activity() { | |||||||
|         net.server = sharedPreferences.getString("server", "")!! |         net.server = sharedPreferences.getString("server", "")!! | ||||||
|         net.language = sharedPreferences.getString("language", "")!! |         net.language = sharedPreferences.getString("language", "")!! | ||||||
|  |  | ||||||
|         val currentGroup = sharedPreferences.getString("currentGroup", "offline")!!.toInt() |         val currentGroup: Int = if (sharedPreferences.getString( | ||||||
|  |                 "currentGroup", | ||||||
|  |                 "offline" | ||||||
|  |             )!! == "offline" | ||||||
|  |         ) 0 else sharedPreferences.getString("currentGroup", "")!!.toInt() | ||||||
|  |  | ||||||
|         findViewById<Button>(R.id.saveButton).setOnClickListener { |         findViewById<Button>(R.id.saveButton).setOnClickListener { | ||||||
|             if (categoryNameTextEdit.text.toString() == "") { |             if (categoryNameTextEdit.text.toString() == "") { | ||||||
|                 Toast.makeText(this, getString(R.string.category_name_required), Toast.LENGTH_SHORT).show() |                 Toast.makeText(this, getString(R.string.category_name_required), Toast.LENGTH_SHORT).show() | ||||||
|                 return@setOnClickListener |                 return@setOnClickListener | ||||||
|             } |             } | ||||||
|  |             loadingDialog.show() | ||||||
|  |  | ||||||
|             if (category.id == 0) { // Inserting new category |             if (category.id == 0) { // Inserting new category | ||||||
|                 val newCategory = Category(0, categoryNameTextEdit.text.toString()) |                 val newCategory = Category(0, categoryNameTextEdit.text.toString()) | ||||||
|                 newCategory.id = DAO.addCategory(newCategory).toInt() |                 newCategory.id = DAO.addCategory(newCategory).toInt() | ||||||
|                 net.uploadCategory(currentGroup, newCategory) |                 if (currentGroup > 0) net.uploadCategory(currentGroup, newCategory, this::notifyUserAndExit) | ||||||
|             } else { // Updating existing category |             } else { // Updating existing category | ||||||
|                 category.name = categoryNameTextEdit.text.toString() |                 category.name = categoryNameTextEdit.text.toString() | ||||||
|                 DAO.updateCategory(category) |                 DAO.updateCategory(category) | ||||||
|                 net.updateCategory(currentGroup, category) |                 if (currentGroup > 0) net.updateCategory(currentGroup, category, this::notifyUserAndExit) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             finish() |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     fun notifyUserAndExit(response: Response) { | ||||||
|  |         runOnUiThread { | ||||||
|  |             Toast.makeText(this, response.body!!.string(), Toast.LENGTH_SHORT).show() | ||||||
|  |         } | ||||||
|  |         loadingDialog.dismiss() | ||||||
|  |         finish() | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees.activities | package org.foxarmy.barcodescannerforemployees.activities | ||||||
|  |  | ||||||
| import android.app.Activity | import android.app.Activity | ||||||
|  | import android.app.ProgressDialog | ||||||
| import android.content.Intent | import android.content.Intent | ||||||
| import android.content.SharedPreferences | import android.content.SharedPreferences | ||||||
| import android.os.Build | import android.os.Build | ||||||
| @@ -13,8 +14,6 @@ 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.core.widget.addTextChangedListener | import androidx.core.widget.addTextChangedListener | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| 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 | ||||||
| @@ -56,18 +55,19 @@ class AddProductActivity : AppCompatActivity() { | |||||||
|     private lateinit var abstractProductDAO: AbstractProductDAO |     private lateinit var abstractProductDAO: AbstractProductDAO | ||||||
|     private lateinit var sharedPreferences: SharedPreferences |     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) | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         loadingDialog = ProgressDialog(this) | ||||||
|             "sensitive", |         loadingDialog.setMessage(getString(R.string.loading_please_wait)) | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |         loadingDialog.setCancelable(false) | ||||||
|             applicationContext, |         loadingDialog.setTitle(getString(R.string.loading)) | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |         sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!) |         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "offline")!!) | ||||||
|         productDAO = ProductDAO(dbHelper) |         productDAO = ProductDAO(dbHelper) | ||||||
| @@ -177,31 +177,36 @@ class AddProductActivity : AppCompatActivity() { | |||||||
|                 return@setOnClickListener |                 return@setOnClickListener | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "")!! == "") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt() |             loadingDialog.show() | ||||||
|  |  | ||||||
|             var response: Response? = null |             val currentGroup: Int = if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") 0 else sharedPreferences.getString("currentGroup", "")!!.toInt() | ||||||
|  |  | ||||||
|             if (updatingExistentProduct) { |             if (updatingExistentProduct) { | ||||||
|                 productDAO.updateProduct(product!!) |                 productDAO.updateProduct(product!!) | ||||||
|                 if (currentGroup > 0) response = net.updateProduct(currentGroup, product!!) |                 if (currentGroup > 0) net.updateProduct(currentGroup, product!!, this::notifyUserAndExit) | ||||||
|             } else { |             } else { | ||||||
|                 product!!.id = productDAO.insertNewProduct(product!!).toInt() |                 product!!.id = productDAO.insertNewProduct(product!!).toInt() | ||||||
|                 if (currentGroup > 0) response = net.uploadProduct(currentGroup, product!!) |                 if (currentGroup > 0) net.uploadProduct(currentGroup, product!!, this::notifyUserAndExit) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (response != null) { |  | ||||||
|                 Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show() |  | ||||||
|             }    |  | ||||||
|  |  | ||||||
|             finish() |  | ||||||
|         } |         } | ||||||
|         update() |         update() | ||||||
|  |  | ||||||
|         val today = SimpleDateFormat("dd.MM.yyyy").format(Calendar.getInstance().time).split(".") |         val today = SimpleDateFormat("dd.MM.yyyy").format(Calendar.getInstance().time).split(".") | ||||||
|  |  | ||||||
|  |  | ||||||
|         dateOfProductionDatePicker.updateDate(today[2].toInt(), today[1].toInt(), today[0].toInt()) |         dateOfProductionDatePicker.updateDate(today[2].toInt(), today[1].toInt() - 1, today[0].toInt()) | ||||||
|         expiryDatePicker.updateDate(today[2].toInt(), today[1].toInt(), today[0].toInt()) |         expiryDatePicker.updateDate(today[2].toInt(), today[1].toInt() - 1, today[0].toInt()) | ||||||
|  |         product!!.dateOfProduction = SimpleDateFormat("dd.MM.yyyy").parse("${today[0]}.${today[1]}.${today[2]}")!!.time / 1000 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun notifyUserAndExit(response: Response) { | ||||||
|  |         runOnUiThread { | ||||||
|  |             Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show() | ||||||
|  |             loadingDialog.dismiss() | ||||||
|  |             finish() | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private val intentLauncher = |     private val intentLauncher = | ||||||
|   | |||||||
| @@ -7,8 +7,6 @@ import android.os.Bundle | |||||||
| import android.widget.LinearLayout | import android.widget.LinearLayout | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | import org.foxarmy.barcodescannerforemployees.database.DBStorageController | ||||||
| import org.foxarmy.barcodescannerforemployees.database.ProductDAO | import org.foxarmy.barcodescannerforemployees.database.ProductDAO | ||||||
| @@ -26,13 +24,7 @@ class ExpiryCalendarActivity : AppCompatActivity() { | |||||||
|  |  | ||||||
|         setContentView(R.layout.fragment_expiry_dates) |         setContentView(R.layout.fragment_expiry_dates) | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             applicationContext, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!) |         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "database")!!) | ||||||
|         productDAO = ProductDAO(dbHelper) |         productDAO = ProductDAO(dbHelper) | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ class FindBarcodelessAbstractProduct() : AppCompatActivity() { | |||||||
|         binding = ActivityFindBarcodelessAbstractProductBinding.inflate(layoutInflater) |         binding = ActivityFindBarcodelessAbstractProductBinding.inflate(layoutInflater) | ||||||
|  |  | ||||||
|         val ft = supportFragmentManager.beginTransaction() |         val ft = supportFragmentManager.beginTransaction() | ||||||
|         val fragment = StorageFragment.newInstance("barcodeless", arrayOf("", "             ")) |         val fragment = StorageFragment.newInstance("barcodeless", arrayOf("", "             ", " ")) | ||||||
|         ft.replace(R.id.content, fragment) |         ft.replace(R.id.content, fragment) | ||||||
|         ft.commit() |         ft.commit() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -1,13 +1,14 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees.activities | package org.foxarmy.barcodescannerforemployees.activities | ||||||
|  |  | ||||||
|  | import android.app.ProgressDialog | ||||||
| import android.content.Intent | import android.content.Intent | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.widget.Toast | import android.widget.Toast | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
|  | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.databinding.ActivityGroupBinding | import org.foxarmy.barcodescannerforemployees.databinding.ActivityGroupBinding | ||||||
|  | import org.foxarmy.barcodescannerforemployees.noInternetConnectionAvailableNotification | ||||||
|  |  | ||||||
| class GroupActivity : AppCompatActivity() { | class GroupActivity : AppCompatActivity() { | ||||||
|  |  | ||||||
| @@ -19,62 +20,103 @@ class GroupActivity : AppCompatActivity() { | |||||||
|         binding = ActivityGroupBinding.inflate(layoutInflater) |         binding = ActivityGroupBinding.inflate(layoutInflater) | ||||||
|         setContentView(binding.root) |         setContentView(binding.root) | ||||||
|  |  | ||||||
|         val sharedPreferences = EncryptedSharedPreferences.create( |         val loadingDialog = ProgressDialog(this) | ||||||
|             "sensitive", |         loadingDialog.setMessage(getString(R.string.loading_please_wait)) | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |         loadingDialog.setCancelable(false) | ||||||
|             applicationContext, |         loadingDialog.setTitle(getString(R.string.loading)) | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |         val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         binding.createGroupButton.setOnClickListener { |         binding.createGroupButton.setOnClickListener { | ||||||
|             val groupName = binding.groupNameTextEdit.text.toString() |             val groupName = binding.groupNameTextEdit.text.toString() | ||||||
|             val groupPassword = binding.groupPasswordTextEdit.text.toString() |             val groupPassword = binding.groupPasswordTextEdit.text.toString() | ||||||
|             // group is set to "successful" |             val net = Net() | ||||||
|             val n = Net() |             net.language = sharedPreferences.getString("language", "en-US")!! | ||||||
|             n.language = sharedPreferences.getString("language", "en-US")!! |             net.server = sharedPreferences.getString("server", "")!! | ||||||
|             n.server = sharedPreferences.getString("server", "")!! |             net.token = sharedPreferences.getString("token", "")!! | ||||||
|             n.token = sharedPreferences.getString("token", "")!! |  | ||||||
|  |  | ||||||
|             val response = n.createGroup(groupName, groupPassword) |             net.serverIsAvailable(this, { isServerAvailable -> | ||||||
|             val responseText = response.body!!.string() |                 if (!isServerAvailable) { | ||||||
|  |                     noInternetConnectionAvailableNotification(this) | ||||||
|  |                 } else { | ||||||
|  |                     runOnUiThread { | ||||||
|  |                         loadingDialog.show() | ||||||
|  |                     } | ||||||
|  |                     net.createGroup(groupName, groupPassword, { response -> | ||||||
|  |                         val responseText = response.body!!.string() | ||||||
|  |  | ||||||
|             if (response.code == 200) { |                         if (response.code == 200) { | ||||||
|                 val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf()) |                             val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf()) | ||||||
|                 currentGroups!!.add(responseText) |                             currentGroups!!.add(responseText) | ||||||
|                 sharedPreferences.edit().putStringSet("groups", currentGroups).apply() |                             sharedPreferences.edit().putStringSet("groups", currentGroups).apply() | ||||||
|                 sharedPreferences.edit().putString("currentGroup", responseText).apply() |                             sharedPreferences.edit().putString("currentGroup", responseText).apply() | ||||||
|                 val intent = Intent(this, MainActivity::class.java) |                             runOnUiThread { | ||||||
|                 startActivity(intent) |                                 val intent = Intent(this, MainActivity::class.java) | ||||||
|                 finish() |                                 startActivity(intent) | ||||||
|             } else { |                                 loadingDialog.dismiss() | ||||||
|                 Toast.makeText(this, responseText, Toast.LENGTH_LONG).show() |                                 finish() | ||||||
|             } |                             } | ||||||
|  |                         } else { | ||||||
|  |                             runOnUiThread { | ||||||
|  |                                 loadingDialog.dismiss() | ||||||
|  |                                 Toast.makeText(this, responseText, Toast.LENGTH_LONG).show() | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         binding.joinGroupButton.setOnClickListener { |         binding.joinGroupButton.setOnClickListener { | ||||||
|             val groupName = binding.groupNameTextEdit.text.toString() |             val groupName = binding.groupNameTextEdit.text.toString() | ||||||
|             val groupPassword = binding.groupPasswordTextEdit.text.toString() |             val groupPassword = binding.groupPasswordTextEdit.text.toString() | ||||||
|  |  | ||||||
|             val n = Net() |             val net = Net() | ||||||
|             n.language = sharedPreferences.getString("language", "en-US")!! |             net.language = sharedPreferences.getString("language", "en-US")!! | ||||||
|             n.server = sharedPreferences.getString("server", "")!! |             net.server = sharedPreferences.getString("server", "")!! | ||||||
|             n.token = sharedPreferences.getString("token", "")!! |             net.token = sharedPreferences.getString("token", "")!! | ||||||
|  |  | ||||||
|             val groupId = n.getGroupId(groupName).toInt() |             net.serverIsAvailable(this) { isServerAvailable -> | ||||||
|             val response = n.joinGroup(groupId, groupPassword) |                 if (!isServerAvailable) { | ||||||
|             val responseText = response.body!!.string() |                     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) { |                             if (response.code == 200) { | ||||||
|                 val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf()) |                                 val currentGroups = sharedPreferences.getStringSet("groups", mutableSetOf()) | ||||||
|                 currentGroups!!.add(groupId.toString()) |                                 currentGroups!!.add(groupId.toString()) | ||||||
|                 sharedPreferences.edit().putStringSet("groups", currentGroups).apply() |                                 sharedPreferences.edit().putStringSet("groups", currentGroups).apply() | ||||||
|                 sharedPreferences.edit().putString("currentGroup", groupId.toString()).apply() |                                 sharedPreferences.edit().putString("currentGroup", groupId.toString()).apply() | ||||||
|                 val intent = Intent(this, MainActivity::class.java) |                                 runOnUiThread { | ||||||
|                 startActivity(intent) |                                     val intent = Intent(this, MainActivity::class.java) | ||||||
|                 finish() |                                     startActivity(intent) | ||||||
|             } else { |                                     loadingDialog.dismiss() | ||||||
|                 Toast.makeText(this, responseText, Toast.LENGTH_LONG).show() |                                     finish() | ||||||
|  |                                 } | ||||||
|  |                             } else { | ||||||
|  |                                 runOnUiThread { | ||||||
|  |                                     loadingDialog.dismiss() | ||||||
|  |                                     Toast.makeText(this, responseText, Toast.LENGTH_LONG).show() | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         }) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,16 +1,16 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees.activities | package org.foxarmy.barcodescannerforemployees.activities | ||||||
|  |  | ||||||
|  | import android.app.ProgressDialog | ||||||
| import android.content.Intent | import android.content.Intent | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.widget.ArrayAdapter | import android.widget.ArrayAdapter | ||||||
| import android.widget.Toast | import android.widget.Toast | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.databinding.ActivityLoginBinding | import org.foxarmy.barcodescannerforemployees.databinding.ActivityLoginBinding | ||||||
| import org.foxarmy.barcodescannerforemployees.parseArray | import org.foxarmy.barcodescannerforemployees.noInternetConnectionAvailableNotification | ||||||
|  | import org.foxarmy.barcodescannerforemployees.parseIntArray | ||||||
| import org.json.JSONObject | import org.json.JSONObject | ||||||
|  |  | ||||||
| class LoginActivity : AppCompatActivity() { | class LoginActivity : AppCompatActivity() { | ||||||
| @@ -23,15 +23,14 @@ class LoginActivity : AppCompatActivity() { | |||||||
|         binding = ActivityLoginBinding.inflate(layoutInflater); |         binding = ActivityLoginBinding.inflate(layoutInflater); | ||||||
|         setContentView(binding.root) |         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() |         fillUpLanguagesSpinner() | ||||||
|  |  | ||||||
|         val sharedPreferences = EncryptedSharedPreferences.create( |         val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             applicationContext, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         binding.loginButton.setOnClickListener { |         binding.loginButton.setOnClickListener { | ||||||
|             val server = binding.serverTextEdit.text.toString() |             val server = binding.serverTextEdit.text.toString() | ||||||
| @@ -40,32 +39,49 @@ class LoginActivity : AppCompatActivity() { | |||||||
|             val language = resources.getStringArray(R.array.languages)[binding.languageSpinner.selectedItemPosition] |             val language = resources.getStringArray(R.array.languages)[binding.languageSpinner.selectedItemPosition] | ||||||
|             sharedPreferences.edit().putString("language", language).apply() |             sharedPreferences.edit().putString("language", language).apply() | ||||||
|  |  | ||||||
|             val n = Net() |             val net = Net() | ||||||
|             n.language = sharedPreferences.getString("language", "en-US")!! |             net.language = sharedPreferences.getString("language", "en-US")!! | ||||||
|             n.server = server |             net.server = server | ||||||
|  |  | ||||||
|             val response = n.login(username, password) |             net.serverIsAvailable(this, { isServerAvailable -> | ||||||
|             val responseText = response.body!!.string() |                 if (!isServerAvailable) { | ||||||
|             if (response.code != 200) { |                     noInternetConnectionAvailableNotification(this) | ||||||
|                 Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show() |                 } else { | ||||||
|             } else { |                     runOnUiThread { | ||||||
|                 val json = JSONObject(responseText) |                         loadingDialog.show() | ||||||
|                 sharedPreferences.edit().putString("token", json["token"].toString()).apply() |                     } | ||||||
|                 n.token = json["token"].toString() |                     net.login(username, password, { response -> | ||||||
|                 sharedPreferences.edit().putInt("userId", json["id"].toString().toInt()).apply() |                         val responseText = response.body!!.string() | ||||||
|                 sharedPreferences.edit().putString("server", server).apply() |                         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() } | ||||||
|  |  | ||||||
|                 val r = n.getMyGroups().body!!.string() |                                 sharedPreferences.edit().putStringSet("groups", myGroups.toSet()).apply() | ||||||
|                 val myGroups = parseArray(r).map { a -> a.toString()} |                                 sharedPreferences.edit().putString("currentGroup", myGroups[0]).apply() | ||||||
|  |  | ||||||
|                 sharedPreferences.edit().putStringSet("groups", myGroups.toSet()).apply() |                                 goToActivity("MainActivity") | ||||||
|                 sharedPreferences.edit().putString("currentGroup", myGroups[0]).apply() |                             }) | ||||||
|  |                         } | ||||||
|                 val intent = Intent(this, MainActivity::class.java) |                     }) | ||||||
|                 startActivity(intent) |                 } | ||||||
|                 finish() |             }) | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         binding.registerButton.setOnClickListener { |         binding.registerButton.setOnClickListener { | ||||||
| @@ -76,31 +92,64 @@ class LoginActivity : AppCompatActivity() { | |||||||
|             sharedPreferences.edit().putString("language", language).apply() |             sharedPreferences.edit().putString("language", language).apply() | ||||||
|             sharedPreferences.edit().putString("server", server).apply() |             sharedPreferences.edit().putString("server", server).apply() | ||||||
|  |  | ||||||
|             val n = Net() |             val net = Net() | ||||||
|             n.language = language |             net.language = language | ||||||
|             n.server = server |             net.server = server | ||||||
|             val response = n.registerAccount(username, password); |  | ||||||
|             val responseText = response.body!!.string() |  | ||||||
|             if (response.code != 200) { |  | ||||||
|                 Toast.makeText(this, responseText, Toast.LENGTH_SHORT).show(); |  | ||||||
|             } else { |  | ||||||
|                 sharedPreferences.edit().putInt("userId", responseText.toInt()).apply() |  | ||||||
|                 val token = JSONObject(n.login(username, password).body!!.string())["token"].toString() |  | ||||||
|                 sharedPreferences.edit().putString("token", token).apply() |  | ||||||
|  |  | ||||||
|                 sharedPreferences.edit().putString("server", server).apply() |             net.serverIsAvailable(this, {isServerAvailable -> | ||||||
|                 val intent = Intent(this, GroupActivity::class.java) |                 if (!isServerAvailable) { | ||||||
|                 startActivity(intent) |                     noInternetConnectionAvailableNotification(this) | ||||||
|                 finish() |                 } 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() { |     private fun fillUpLanguagesSpinner() { | ||||||
|         val languages = resources.getStringArray(R.array.languages) |         val languages = resources.getStringArray(R.array.languages) | ||||||
|         val arrayAdapter = ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, languages) |         val arrayAdapter = | ||||||
|  |             ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, languages) | ||||||
|         arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item) |         arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item) | ||||||
|         binding.languageSpinner.adapter = arrayAdapter |         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() | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -1,25 +1,24 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees.activities | package org.foxarmy.barcodescannerforemployees.activities | ||||||
|  |  | ||||||
| import android.app.Activity | 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.activity.result.contract.ActivityResultContracts | ||||||
| import androidx.appcompat.app.ActionBarDrawerToggle | 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.drawerlayout.widget.DrawerLayout | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import androidx.viewpager.widget.ViewPager | import androidx.viewpager.widget.ViewPager | ||||||
| import com.google.android.material.navigation.NavigationView | import com.google.android.material.navigation.NavigationView | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.* | ||||||
| import org.foxarmy.barcodescannerforemployees.R |  | ||||||
| import org.foxarmy.barcodescannerforemployees.ViewPagerAdapter |  | ||||||
| import org.foxarmy.barcodescannerforemployees.convertToUnixEpochTimestamp |  | ||||||
| import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO | import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO | ||||||
| import org.foxarmy.barcodescannerforemployees.database.CategoryDAO | import org.foxarmy.barcodescannerforemployees.database.CategoryDAO | ||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | import org.foxarmy.barcodescannerforemployees.database.DBStorageController | ||||||
| @@ -39,9 +38,17 @@ import java.io.File | |||||||
| class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { | class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { | ||||||
|  |  | ||||||
|     private lateinit var binding: ActivityMainBinding |     private lateinit var binding: ActivityMainBinding | ||||||
|     lateinit var adapter: ViewPagerAdapter |     private lateinit var adapter: ViewPagerAdapter | ||||||
|     var drawerLayout: DrawerLayout? = null |     private var drawerLayout: DrawerLayout? = null | ||||||
|     var actionBarDrawerToggle: ActionBarDrawerToggle? = null |     private var actionBarDrawerToggle: ActionBarDrawerToggle? = null | ||||||
|  |     private lateinit var ws: WebSocketClient | ||||||
|  |  | ||||||
|  |     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) | ||||||
| @@ -49,10 +56,23 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|         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.tabTablayout.setOnDragListener { _, _ -> | ||||||
|  |             selectionMode = false | ||||||
|  |             selectedAmount = 0 | ||||||
|  |             true | ||||||
|  |         } | ||||||
|  |  | ||||||
|         drawerLayout = binding.drawerLayout |         drawerLayout = binding.drawerLayout | ||||||
|         actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.nav_open, R.string.nav_close) |         actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.nav_open, R.string.nav_close) | ||||||
|  |  | ||||||
| @@ -60,7 +80,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|         actionBarDrawerToggle!!.syncState() |         actionBarDrawerToggle!!.syncState() | ||||||
|         binding.navView.setNavigationItemSelectedListener(this) |         binding.navView.setNavigationItemSelectedListener(this) | ||||||
|  |  | ||||||
|         // to make the Navigation drawer icon always appear on the action bar |  | ||||||
|         supportActionBar!!.setDisplayHomeAsUpEnabled(true) |         supportActionBar!!.setDisplayHomeAsUpEnabled(true) | ||||||
|  |  | ||||||
|         binding.expiryCalendarFab.setOnClickListener { _ -> |         binding.expiryCalendarFab.setOnClickListener { _ -> | ||||||
| @@ -70,7 +89,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|             ContextCompat.startActivity(this, expiryCalendarIntent, extras) |             ContextCompat.startActivity(this, expiryCalendarIntent, extras) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         binding.newElementFab.setOnClickListener { view -> |         binding.newElementFab.setOnClickListener { _ -> | ||||||
|             val currentPosition = binding.tabTablayout.selectedTabPosition |             val currentPosition = binding.tabTablayout.selectedTabPosition | ||||||
|             val fragment = adapter.getItem(currentPosition) |             val fragment = adapter.getItem(currentPosition) | ||||||
|  |  | ||||||
| @@ -78,8 +97,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|                 "StorageFragment" -> { |                 "StorageFragment" -> { | ||||||
|                     val addAbstractProductIntent = Intent(this, AddAbstractProductActivity::class.java) |                     val addAbstractProductIntent = Intent(this, AddAbstractProductActivity::class.java) | ||||||
|                     val extras = Bundle() |                     val extras = Bundle() | ||||||
|                     // I reuse the same stuff for editing and adding new product. |  | ||||||
|                     // if abstractProduct == null, it means that we need to create new object |  | ||||||
|                     extras.putParcelable("abstractProduct", null) |                     extras.putParcelable("abstractProduct", null) | ||||||
|                     extras.putString("action", "new") |                     extras.putString("action", "new") | ||||||
|                     addAbstractProductIntent.putExtras(extras) |                     addAbstractProductIntent.putExtras(extras) | ||||||
| @@ -104,19 +121,24 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         synchronize() |         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() { |     private fun synchronize() { | ||||||
|  |  | ||||||
|         val sharedPreferences = EncryptedSharedPreferences.create( |  | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             applicationContext, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") return |         if (sharedPreferences.getString("currentGroup", "offline")!! == "offline") return | ||||||
|  |  | ||||||
|         val net = Net() |         val net = Net() | ||||||
| @@ -127,26 +149,27 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|  |  | ||||||
|         val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt() |         val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt() | ||||||
|  |  | ||||||
|         val data = JSONObject(net.synchronize(currentGroup).body!!.string()) |         net.synchronize(currentGroup, {response -> | ||||||
|  |             val data = JSONObject(response.body!!.string()) | ||||||
|  |  | ||||||
|         val remoteAbstractProducts = data["abstract_products"] as JSONArray |             val remoteAbstractProducts = data["abstract_products"] as JSONArray | ||||||
|         val remoteProducts = data["products"] as JSONArray |             val remoteProducts = data["products"] as JSONArray | ||||||
|         val remoteCategories = data["categories"] as JSONArray |             val remoteCategories = data["categories"] as JSONArray | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "")!!) |             val dbHelper = DBStorageController(this, sharedPreferences.getString("currentGroup", "")!!) | ||||||
|  |  | ||||||
|         val abstractProductDAO = AbstractProductDAO(dbHelper) |             val abstractProductDAO = AbstractProductDAO(dbHelper) | ||||||
|         val productDAO = ProductDAO(dbHelper) |             val productDAO = ProductDAO(dbHelper) | ||||||
|         val categoryDAO = CategoryDAO(dbHelper) |             val categoryDAO = CategoryDAO(dbHelper) | ||||||
|  |  | ||||||
|         val localAbstractProducts = abstractProductDAO.getSortedListOfAbstractProducts(0, "", arrayOf("")) |             val localAbstractProducts = abstractProductDAO.getSortedListOfAbstractProducts(0, "", arrayOf("")) | ||||||
|         val localProducts = productDAO.getSortedListOfProducts(0, "", "") |             val localProducts = productDAO.getSortedListOfProducts(0, "", "") | ||||||
|         val localCategories = categoryDAO.getAllCategories() |             val localCategories = categoryDAO.getAllCategories() | ||||||
|  |  | ||||||
|         syncAbstractProducts(net, currentGroup, abstractProductDAO, remoteAbstractProducts, localAbstractProducts) |  | ||||||
|         syncProducts(productDAO, remoteProducts, localProducts) |  | ||||||
|         syncCategories(categoryDAO, remoteCategories, localCategories) |  | ||||||
|  |  | ||||||
|  |             syncAbstractProducts(net, currentGroup, abstractProductDAO, remoteAbstractProducts, localAbstractProducts) | ||||||
|  |             syncProducts(productDAO, remoteProducts, localProducts) | ||||||
|  |             syncCategories(categoryDAO, remoteCategories, localCategories) | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun syncCategories( |     private fun syncCategories( | ||||||
| @@ -194,15 +217,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|  |  | ||||||
|             if (categoryInRemoteDB != null && categoryInLocalDB != null) { |             if (categoryInRemoteDB != null && categoryInLocalDB != null) { | ||||||
|                 if (categoryInRemoteDB["hash"] != categoryInLocalDB.calculateHash()) { |                 if (categoryInRemoteDB["hash"] != categoryInLocalDB.calculateHash()) { | ||||||
|  |                     val updatedData = Category.createFromJSON(categoryInRemoteDB) | ||||||
|                     var updatedData: Category |  | ||||||
|  |  | ||||||
|                     with(categoryInRemoteDB) { |  | ||||||
|                         updatedData = Category( |  | ||||||
|                             this["local_id"].toString().toInt(), |  | ||||||
|                             this["name"].toString() |  | ||||||
|                         ) |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     categoryDAO.updateCategory(updatedData) |                     categoryDAO.updateCategory(updatedData) | ||||||
|                 } |                 } | ||||||
| @@ -241,35 +256,14 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (productInLocalDB == null && remoteProduct != null) { |             if (productInLocalDB == null && remoteProduct != null) { | ||||||
|                 var newProduct: Product |                 val newProduct = Product.createFromJSON(remoteProduct) | ||||||
|  |  | ||||||
|                 with(remoteProduct) { |  | ||||||
|                     newProduct = Product( |  | ||||||
|                         this["local_id"].toString().toInt(), |  | ||||||
|                         this["abstract_product_id"].toString().toInt(), |  | ||||||
|                         this["amount"].toString().toInt(), |  | ||||||
|                         convertToUnixEpochTimestamp(this["date_of_production"].toString()), |  | ||||||
|                         convertToUnixEpochTimestamp(this["expiry_date"].toString()) |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 productDAO.insertNewProduct(newProduct) |                 productDAO.insertNewProduct(newProduct) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (productInRemoteDB != null && productInLocalDB != null) { |             if (productInRemoteDB != null && productInLocalDB != null) { | ||||||
|                 if (productInRemoteDB["hash"] != productInLocalDB.calculateHash()) { |                 if (productInRemoteDB["hash"] != productInLocalDB.calculateHash()) { | ||||||
|  |                     val updatedData = Product.createFromJSON(productInRemoteDB) | ||||||
|                     var updatedData: Product |  | ||||||
|  |  | ||||||
|                     with(productInRemoteDB) { |  | ||||||
|                         updatedData = Product( |  | ||||||
|                             this["local_id"].toString().toInt(), |  | ||||||
|                             this["abstract_product_id"].toString().toInt(), |  | ||||||
|                             this["amount"].toString().toInt(), |  | ||||||
|                             convertToUnixEpochTimestamp(this["date_of_production"].toString()), |  | ||||||
|                             convertToUnixEpochTimestamp(this["expiry_date"].toString()) |  | ||||||
|                         ) |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     productDAO.updateProduct(updatedData) |                     productDAO.updateProduct(updatedData) | ||||||
|                 } |                 } | ||||||
| @@ -318,24 +312,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|                 val pictureFile = |                 val pictureFile = | ||||||
|                     File(picturesDir, "${remoteAbstractProduct["image_filename"]}.png") |                     File(picturesDir, "${remoteAbstractProduct["image_filename"]}.png") | ||||||
|                 val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}" |                 val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}" | ||||||
|                 net.downloadImage(url, pictureFile) |                 net.downloadImage(url, pictureFile, { | ||||||
|  |                     val newAbstractProduct = AbstractProduct.createFromJSON(remoteAbstractProduct) | ||||||
|                 var newAbstractProduct: AbstractProduct |  | ||||||
|  |  | ||||||
|                 with(remoteAbstractProduct) { |  | ||||||
|                     newAbstractProduct = AbstractProduct( |  | ||||||
|                         this["local_id"].toString().toInt(), |  | ||||||
|                         this["barcode"].toString(), |  | ||||||
|                         this["name"].toString(), |  | ||||||
|                         this["net_weight"].toString().toDouble(), |  | ||||||
|                         this["image_filename"].toString(), |  | ||||||
|                         this["category"].toString().toInt(), |  | ||||||
|                         this["unit"].toString().toInt() |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 abstractProductDAO.addAbstractProduct(newAbstractProduct) |  | ||||||
|  |  | ||||||
|  |                     abstractProductDAO.addAbstractProduct(newAbstractProduct) | ||||||
|  |                 }) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (abstractProductInRemoteDB != null && abstractProductInLocalDB != null) { |             if (abstractProductInRemoteDB != null && abstractProductInLocalDB != null) { | ||||||
| @@ -345,24 +326,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|                     val pictureFile = |                     val pictureFile = | ||||||
|                         File(File(filesDir, "pictures"), "${abstractProductInRemoteDB["image_filename"]}.png") |                         File(File(filesDir, "pictures"), "${abstractProductInRemoteDB["image_filename"]}.png") | ||||||
|                     val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}" |                     val url = "https://${net.server}/api/abstractproduct/getImage/${currentGroup}/${localId}" | ||||||
|                     net.downloadImage(url, pictureFile) |                     net.downloadImage(url, pictureFile, { | ||||||
|  |                         val updatedData = AbstractProduct.createFromJSON(abstractProductInRemoteDB) | ||||||
|  |  | ||||||
|                     var updatedData: AbstractProduct |                         abstractProductDAO.updateAbstractProduct(updatedData) | ||||||
|  |                     }) | ||||||
|                     with(abstractProductInRemoteDB) { |  | ||||||
|                         updatedData = AbstractProduct( |  | ||||||
|                             this["local_id"].toString().toInt(), |  | ||||||
|                             this["barcode"].toString(), |  | ||||||
|                             this["name"].toString(), |  | ||||||
|                             this["net_weight"].toString().toDouble(), |  | ||||||
|                             this["image_filename"].toString(), |  | ||||||
|                             this["category"].toString().toInt(), |  | ||||||
|                             this["unit"].toString().toInt() |  | ||||||
|                         ) |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|                     abstractProductDAO.updateAbstractProduct(updatedData) |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -376,6 +344,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|         adapter.addFragment(CategoriesFragment(), getString(R.string.categories_title)) |         adapter.addFragment(CategoriesFragment(), getString(R.string.categories_title)) | ||||||
|  |  | ||||||
|         viewpager.adapter = adapter |         viewpager.adapter = adapter | ||||||
|  |         viewpager.offscreenPageLimit = 3 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun onCreateOptionsMenu(menu: Menu): Boolean { |     override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||||||
| @@ -384,22 +353,43 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|         return true |         return true | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun isOffline(): Boolean { | ||||||
|  |         return sharedPreferences.getString("currentGroup", "") == "offline" | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override fun onNavigationItemSelected(item: MenuItem): Boolean { |     override fun onNavigationItemSelected(item: MenuItem): Boolean { | ||||||
|         lateinit var intent: Intent |         lateinit var intent: Intent | ||||||
|         when (item.itemId) { |         when (item.itemId) { | ||||||
|             R.id.nav_account -> { |             R.id.nav_account -> { | ||||||
|  |                 if (isOffline()) { | ||||||
|  |                     Toast.makeText(this, getString(R.string.online_only_feature), Toast.LENGTH_SHORT).show() | ||||||
|  |                     return false | ||||||
|  |                 } | ||||||
|                 intent = Intent(this, AccountSettingsActivity::class.java) |                 intent = Intent(this, AccountSettingsActivity::class.java) | ||||||
|                 settingsActivityLauncher.launch(intent) |                 settingsActivityLauncher.launch(intent) | ||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             R.id.nav_groups -> { |             R.id.nav_groups -> { | ||||||
|  |                 if (isOffline()) { | ||||||
|  |                     Toast.makeText(this, getString(R.string.online_only_feature), Toast.LENGTH_SHORT).show() | ||||||
|  |                     return false | ||||||
|  |                 } | ||||||
|                 intent = Intent(this, MyGroupsActivity::class.java) |                 intent = Intent(this, MyGroupsActivity::class.java) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             R.id.nav_settings -> { |             R.id.nav_settings -> { | ||||||
|                 intent = Intent(this, SettingsActivity::class.java) |                 intent = Intent(this, SettingsActivity::class.java) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             R.id.nav_logout -> { | ||||||
|  |                 sharedPreferences.edit().putString("currentGroup", "").apply() | ||||||
|  |                 sharedPreferences.edit().putStringSet("groups", emptySet()).apply() | ||||||
|  |                 sharedPreferences.edit().putString("token", "").apply() | ||||||
|  |                 sharedPreferences.edit().putString("server", "").apply() | ||||||
|  |  | ||||||
|  |                 intent = Intent(this, LoginActivity::class.java) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         startActivity(intent) |         startActivity(intent) | ||||||
|  |  | ||||||
| @@ -416,13 +406,19 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun updateAll() { |     fun updateAll() { | ||||||
|         val storageFragment: StorageFragment = adapter.getItem(0) as StorageFragment |         runOnUiThread { | ||||||
|         val shelfFragment: ShelfFragment = adapter.getItem(1) as ShelfFragment |             try { | ||||||
|         val categoriesFragment: CategoriesFragment = adapter.getItem(2) as CategoriesFragment |                 val storageFragment: StorageFragment = adapter.getItem(0) as StorageFragment | ||||||
|  |                 val shelfFragment: ShelfFragment = adapter.getItem(1) as ShelfFragment | ||||||
|  |                 val categoriesFragment: CategoriesFragment = adapter.getItem(2) as CategoriesFragment | ||||||
|  |  | ||||||
|         storageFragment.updateContent() |                 storageFragment.updateContent() | ||||||
|         shelfFragment.updateContent() |                 shelfFragment.updateContent() | ||||||
|         categoriesFragment.updateContent() |                 categoriesFragment.updateContent() | ||||||
|  |             } catch (e:Exception) { | ||||||
|  |                 Log.e("BSFE/MainActivity", e.message!!) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { |     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||||
| @@ -509,4 +505,17 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte | |||||||
|         val storageFragment = fragment as StorageFragment |         val storageFragment = fragment as StorageFragment | ||||||
|         storageFragment.filterByCategory(id) |         storageFragment.filterByCategory(id) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun addSelection() { | ||||||
|  |         selectionMode = true | ||||||
|  |         selectedAmount ++ | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun removeSelection() { | ||||||
|  |         selectedAmount -- | ||||||
|  |         if (selectedAmount <= 0) { | ||||||
|  |             selectedAmount = 0 | ||||||
|  |             selectionMode = false | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -1,19 +1,27 @@ | |||||||
| package org.foxarmy.barcodescannerforemployees.activities | package org.foxarmy.barcodescannerforemployees.activities | ||||||
|  |  | ||||||
| import android.content.Context | import android.content.Context | ||||||
|  | import android.content.Intent | ||||||
|  | import android.content.SharedPreferences | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.view.View | import android.view.View | ||||||
|  | import android.widget.EditText | ||||||
|  | import android.widget.PopupMenu | ||||||
|  | import android.widget.Toast | ||||||
|  | import androidx.appcompat.app.AlertDialog | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
|  | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.databinding.ActivityManageGroupBinding | import org.foxarmy.barcodescannerforemployees.databinding.ActivityManageGroupBinding | ||||||
| import org.foxarmy.barcodescannerforemployees.parseArray | import org.foxarmy.barcodescannerforemployees.parseIntArray | ||||||
| import org.foxarmy.barcodescannerforemployees.views.GroupMemberView | import org.foxarmy.barcodescannerforemployees.views.GroupMemberView | ||||||
|  |  | ||||||
| class ManageGroupActivity : AppCompatActivity(){ | class ManageGroupActivity : AppCompatActivity() { | ||||||
|     private lateinit var binding: ActivityManageGroupBinding |     private lateinit var binding: ActivityManageGroupBinding | ||||||
|     private var isAdmin: Boolean = true |     private var isAdmin: Boolean = false | ||||||
|  |     private lateinit var sharedPreferences: SharedPreferences | ||||||
|  |     private lateinit var net: Net | ||||||
|  |     private var groupId: Int = 0 | ||||||
|  |  | ||||||
|     override fun onCreate(savedInstanceState: Bundle?) { |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|         super.onCreate(savedInstanceState) |         super.onCreate(savedInstanceState) | ||||||
| @@ -21,56 +29,150 @@ class ManageGroupActivity : AppCompatActivity(){ | |||||||
|         binding = ActivityManageGroupBinding.inflate(layoutInflater) |         binding = ActivityManageGroupBinding.inflate(layoutInflater) | ||||||
|         setContentView(binding.root) |         setContentView(binding.root) | ||||||
|  |  | ||||||
|         val groupId = intent.extras!!.getInt("groupId") |         groupId = intent.extras!!.getInt("groupId") | ||||||
|  |  | ||||||
|         val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) |         sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|  |  | ||||||
|         val sharedPreferences = EncryptedSharedPreferences.create( |         net = Net() | ||||||
|             "sensitive", |  | ||||||
|             masterKeyAlias, |  | ||||||
|             applicationContext, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val net = Net() |  | ||||||
|  |  | ||||||
|         net.token = sharedPreferences.getString("token", "")!! |         net.token = sharedPreferences.getString("token", "")!! | ||||||
|         net.server = sharedPreferences.getString("server", "")!! |         net.server = sharedPreferences.getString("server", "")!! | ||||||
|         net.language = sharedPreferences.getString("language", "en-US")!! |         net.language = sharedPreferences.getString("language", "en-US")!! | ||||||
|  |  | ||||||
|  |  | ||||||
|         val users = parseArray(net.getUsersInGroup(groupId).body!!.string()) |         setUpMembers() | ||||||
|  |  | ||||||
|         for (user in users) { |         binding.renameButton.setOnClickListener { | ||||||
|             val groupMemberView = GroupMemberView(this, this as Context, net.getUsernameById(user).body!!.string(), user) |             val input = EditText(this) | ||||||
|  |             AlertDialog.Builder(this) | ||||||
|  |                 .setTitle(getString(R.string.new_group_name)) | ||||||
|  |                 .setView(input) | ||||||
|  |                 .setPositiveButton(getString(R.string.ok)) { _, _ -> | ||||||
|  |                     val newName = input.text.toString() | ||||||
|  |                     net.renameGroup(groupId, newName, { response -> | ||||||
|  |                         runOnUiThread { | ||||||
|  |                             Toast.makeText(this, response.body!!.string(), Toast.LENGTH_LONG).show() | ||||||
|  |                         } | ||||||
|  |                     }) | ||||||
|  |  | ||||||
|             binding.groupsContent.addView(groupMemberView) |                 } | ||||||
|  |                 .setNegativeButton(getString(R.string.cancel)) { _, _ -> | ||||||
|  |  | ||||||
|  |                 }.show() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         isAdmin = amIAnAdminIn(groupId) |         binding.leaveButton.setOnClickListener { | ||||||
|  |             net.leaveGroup(groupId, { response -> | ||||||
|  |                 runOnUiThread { | ||||||
|  |                     Toast.makeText(this, response.body!!.string(), Toast.LENGTH_SHORT).show() | ||||||
|  |                 } | ||||||
|  |                 if (response.code == 200) { | ||||||
|  |                     val groups = sharedPreferences.getStringSet("groups", emptySet())!! | ||||||
|  |                     groups.remove(groupId.toString()) | ||||||
|  |                     sharedPreferences.edit().putStringSet("groups", groups).apply() | ||||||
|  |                     sharedPreferences.edit().putString("currentGroup", "").apply() | ||||||
|  |  | ||||||
|         if (!isAdmin) { |                     if (groups.isEmpty()) { | ||||||
|             binding.renameButton.visibility = View.GONE |                         runOnUiThread { | ||||||
|  |                             val intent = Intent(this, GroupActivity::class.java) | ||||||
|  |                             startActivity(intent) | ||||||
|  |                             finish() | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     sharedPreferences.edit().putString("currentGroup", groups.toList()[0]).apply() | ||||||
|  |                     runOnUiThread { | ||||||
|  |                         finish() | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         setUpAdminRelatedButtons() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun amIAnAdminIn(groupId: Int): Boolean { |     private fun setUpMembers() { | ||||||
|         val sharedPreferences = EncryptedSharedPreferences.create( |         net.getUsersInGroup(groupId, { response -> | ||||||
|             "sensitive", |             val users = parseIntArray(response.body!!.string()) | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |             for (user in users) { | ||||||
|             applicationContext, |                 net.getUsernameById(user, { response -> | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |                     val groupMemberView = GroupMemberView(this, this as Context, response.body!!.string(), user) | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |                     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() |         val net = Net() | ||||||
|  |  | ||||||
|         net.token = sharedPreferences.getString("token", "")!! |         net.token = sharedPreferences.getString("token", "")!! | ||||||
|         net.server = sharedPreferences.getString("server", "")!! |         net.server = sharedPreferences.getString("server", "")!! | ||||||
|         net.language = sharedPreferences.getString("language", "en-US")!! |         net.language = sharedPreferences.getString("language", "en-US")!! | ||||||
|  |  | ||||||
|         val result = sharedPreferences.getInt("userId", 0) == net.getGroupAdminId(groupId).body!!.string().toInt() |         net.getGroupAdminId(groupId, { response -> | ||||||
|         return result |             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 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -5,12 +5,10 @@ import android.content.Intent | |||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.widget.LinearLayout | import android.widget.LinearLayout | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.databinding.ActivityMyGroupsBinding | import org.foxarmy.barcodescannerforemployees.databinding.ActivityMyGroupsBinding | ||||||
| import org.foxarmy.barcodescannerforemployees.parseArray | import org.foxarmy.barcodescannerforemployees.parseIntArray | ||||||
| import org.foxarmy.barcodescannerforemployees.views.GroupView | import org.foxarmy.barcodescannerforemployees.views.GroupView | ||||||
|  |  | ||||||
| class MyGroupsActivity : AppCompatActivity(){ | class MyGroupsActivity : AppCompatActivity(){ | ||||||
| @@ -22,13 +20,7 @@ class MyGroupsActivity : AppCompatActivity(){ | |||||||
|         binding = ActivityMyGroupsBinding.inflate(layoutInflater) |         binding = ActivityMyGroupsBinding.inflate(layoutInflater) | ||||||
|         setContentView(binding.root) |         setContentView(binding.root) | ||||||
|  |  | ||||||
|         val sharedPreferences = EncryptedSharedPreferences.create( |         val sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             applicationContext, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val net = Net() |         val net = Net() | ||||||
|  |  | ||||||
| @@ -36,20 +28,28 @@ class MyGroupsActivity : AppCompatActivity(){ | |||||||
|         net.server = sharedPreferences.getString("server", "")!! |         net.server = sharedPreferences.getString("server", "")!! | ||||||
|         net.language = sharedPreferences.getString("language", "en-US")!! |         net.language = sharedPreferences.getString("language", "en-US")!! | ||||||
|  |  | ||||||
|         val groups = parseArray(net.getMyGroups().body!!.string()) |         net.getMyGroups { response -> | ||||||
|  |             val groups = parseIntArray(response.body!!.string()) | ||||||
|         val container = findViewById<LinearLayout>(R.id.groupsLayout) |  | ||||||
|  |  | ||||||
|         for (group in groups) { |  | ||||||
|             val groupView = GroupView(this, this as Context, net.getGroupName(group), group) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             container.addView(groupView) |             val container = findViewById<LinearLayout>(R.id.groupsLayout) | ||||||
|         } |  | ||||||
|  |  | ||||||
|         binding.newGroup.setOnClickListener { |             for (group in groups) { | ||||||
|             val intent = Intent(this, GroupActivity::class.java) |                 net.getGroupName(group, {response -> | ||||||
|             startActivity(intent) |                     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) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -4,8 +4,8 @@ import android.app.Activity | |||||||
| import android.content.Intent | import android.content.Intent | ||||||
| import android.content.SharedPreferences | import android.content.SharedPreferences | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences | import org.foxarmy.barcodescannerforemployees.Net | ||||||
| import androidx.security.crypto.MasterKeys | import org.foxarmy.barcodescannerforemployees.parseStringList | ||||||
|  |  | ||||||
| class NavigatorActivity : Activity() { | class NavigatorActivity : Activity() { | ||||||
|  |  | ||||||
| @@ -14,34 +14,55 @@ class NavigatorActivity : Activity() { | |||||||
|     override fun onCreate(savedInstanceState: Bundle?) { |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|         super.onCreate(savedInstanceState) |         super.onCreate(savedInstanceState) | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             applicationContext, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         var intent = Intent(this, MainActivity::class.java) |         var intent = Intent(this, MainActivity::class.java) | ||||||
|  |  | ||||||
|         if (!isInGroup()) { |         isInGroup { flag -> | ||||||
|             intent = Intent(this, GroupActivity::class.java) |             if (!flag && !isOffline()) { | ||||||
|  |                 intent = Intent(this, GroupActivity::class.java) | ||||||
|  |             } | ||||||
|  |             if (!isAuthenticated() && !isOffline()) { | ||||||
|  |                 intent = Intent(this, LoginActivity::class.java); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             runOnUiThread { | ||||||
|  |                 startActivity(intent); | ||||||
|  |                 finish(); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|         if (!isAuthenticated()) { |     private fun isOffline(): Boolean { | ||||||
|             intent = Intent(this, LoginActivity::class.java); |         return sharedPreferences.getString("currentGroup", "")!! == "offline" | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         startActivity(intent); |  | ||||||
|         finish(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun isAuthenticated(): Boolean { |     private fun isAuthenticated(): Boolean { | ||||||
|         return sharedPreferences.getString("token", "") != "" |         return sharedPreferences.getString("token", "") != "" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun isInGroup(): Boolean { |     private fun isInGroup(callback: (Boolean) -> Unit) { | ||||||
|         return sharedPreferences.getStringSet("groups", emptySet())!!.isNotEmpty() |         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) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -6,8 +6,6 @@ import android.os.Bundle | |||||||
| import android.view.View | import android.view.View | ||||||
| import android.widget.ArrayAdapter | import android.widget.ArrayAdapter | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
| import org.foxarmy.barcodescannerforemployees.databinding.ActivitySettingsBinding | import org.foxarmy.barcodescannerforemployees.databinding.ActivitySettingsBinding | ||||||
|  |  | ||||||
| @@ -17,17 +15,12 @@ class SettingsActivity : AppCompatActivity() { | |||||||
|     lateinit var myGroups: List<String> |     lateinit var myGroups: List<String> | ||||||
|     lateinit var currentGroup: String |     lateinit var currentGroup: String | ||||||
|     lateinit var groupsNames: MutableList<String> |     lateinit var groupsNames: MutableList<String> | ||||||
|  |     lateinit var namesMap: MutableMap<Int, String> | ||||||
|  |  | ||||||
|     override fun onCreate(savedInstanceState: Bundle?) { |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|         super.onCreate(savedInstanceState) |         super.onCreate(savedInstanceState) | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         sharedPreferences = org.foxarmy.barcodescannerforemployees.getPreferences(this) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             applicationContext, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         myGroups = sharedPreferences.getStringSet("groups", emptySet())!!.toList() |         myGroups = sharedPreferences.getStringSet("groups", emptySet())!!.toList() | ||||||
|         currentGroup = sharedPreferences.getString("currentGroup", "offline")!! |         currentGroup = sharedPreferences.getString("currentGroup", "offline")!! | ||||||
| @@ -37,23 +30,35 @@ class SettingsActivity : AppCompatActivity() { | |||||||
|         net.language = sharedPreferences.getString("language", "")!! |         net.language = sharedPreferences.getString("language", "")!! | ||||||
|         net.server = sharedPreferences.getString("server", "")!! |         net.server = sharedPreferences.getString("server", "")!! | ||||||
|  |  | ||||||
|         groupsNames = mutableListOf() |  | ||||||
|  |  | ||||||
|         for (myGroup in myGroups) { |  | ||||||
|             groupsNames.add(net.getGroupName(myGroup.toInt())) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         binding = ActivitySettingsBinding.inflate(layoutInflater) |         binding = ActivitySettingsBinding.inflate(layoutInflater) | ||||||
|         setContentView(binding.root) |         setContentView(binding.root) | ||||||
|  |  | ||||||
|         setUpImageCompressionSeekBar() |         setUpImageCompressionSeekBar() | ||||||
|         fillUpCurrentGroupSpinner() |  | ||||||
|  |  | ||||||
|  |         if (isOffline()) { | ||||||
|  |             binding.currentGroupSetting.visibility = View.GONE | ||||||
|  |         } else { | ||||||
|  |             namesMap = mutableMapOf() | ||||||
|  |  | ||||||
|  |             for (myGroup in myGroups) { | ||||||
|  |                 net.getGroupName(myGroup.toInt(), { name -> | ||||||
|  |                     namesMap[myGroup.toInt()] = name | ||||||
|  |                     fillUpCurrentGroupSpinner() | ||||||
|  |                 }) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         binding.saveButton.setOnClickListener { |         binding.saveButton.setOnClickListener { | ||||||
|             sharedPreferences.edit().putString("currentGroup", net.getGroupId(binding.currentGroupSpinner.selectedItem.toString())).apply() |             net.getGroupId(binding.currentGroupSpinner.selectedItem.toString(), { groupId -> | ||||||
|             sharedPreferences.edit().putInt("imageCompression", binding.imageCompressionFactorSeekBar.progress + 1).apply() |                 sharedPreferences.edit().putString("currentGroup", groupId).apply() | ||||||
|             setResult(Activity.RESULT_OK) |                 sharedPreferences.edit().putInt("imageCompression", binding.imageCompressionFactorSeekBar.progress + 1) | ||||||
|             finish() |                     .apply() | ||||||
|  |                 runOnUiThread { | ||||||
|  |                     setResult(Activity.RESULT_OK) | ||||||
|  |                     finish() | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         binding.cancelButton.setOnClickListener { |         binding.cancelButton.setOnClickListener { | ||||||
| @@ -62,22 +67,37 @@ class SettingsActivity : AppCompatActivity() { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun isOffline(): Boolean { | ||||||
|  |         return sharedPreferences.getString("currentGroup", "offline") == "offline" | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun setUpImageCompressionSeekBar() { |     private fun setUpImageCompressionSeekBar() { | ||||||
|         val compressionFactor = sharedPreferences.getInt("imageCompression", 1) |         val compressionFactor = sharedPreferences.getInt("imageCompression", 1) | ||||||
|         binding.imageCompressionFactorSeekBar.progress = compressionFactor - 1 |         binding.imageCompressionFactorSeekBar.progress = compressionFactor - 1 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun fillUpCurrentGroupSpinner() { |     private fun fillUpCurrentGroupSpinner() { | ||||||
|  |         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 = | ||||||
|         if (currentGroup == "offline") { |                 ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, groupsNames) | ||||||
|             binding.currentGroupSetting.visibility = View.GONE |             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 | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         val arrayAdapter = |  | ||||||
|             ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, groupsNames) |  | ||||||
|         arrayAdapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item) |  | ||||||
|         binding.currentGroupSpinner.adapter = arrayAdapter |  | ||||||
|         binding.currentGroupSpinner.setSelection(myGroups.indexOf(currentGroup)) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -163,7 +163,7 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) { | |||||||
|  |  | ||||||
|         with(cursor) { |         with(cursor) { | ||||||
|             while (moveToNext()) { |             while (moveToNext()) { | ||||||
|                 val id = getInt(getColumnIndexOrThrow(android.provider.BaseColumns._ID)) |                 val id = getInt(getColumnIndexOrThrow(BaseColumns._ID)) | ||||||
|                 val productName = |                 val productName = | ||||||
|                     getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME)) |                     getString(getColumnIndexOrThrow(AbstractProductContract.AbstractProductEntry.PRODUCT_NAME)) | ||||||
|                 val imageFilename = |                 val imageFilename = | ||||||
| @@ -308,7 +308,7 @@ class AbstractProductDAO(private val dbHelper: DBStorageController) { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             "barcodeless" -> { |             "barcodeless" -> { | ||||||
|                 selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = ? OR ${AbstractProductContract.AbstractProductEntry.BARCODE} = ?" |                 selection = "${AbstractProductContract.AbstractProductEntry.BARCODE} = ? OR ${AbstractProductContract.AbstractProductEntry.BARCODE} = ? OR ${AbstractProductContract.AbstractProductEntry.BARCODE} = ?" | ||||||
|                 selectionArgs = filter |                 selectionArgs = filter | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package org.foxarmy.barcodescannerforemployees.dataclasses | |||||||
| import android.os.Parcel | import android.os.Parcel | ||||||
| import android.os.Parcelable | import android.os.Parcelable | ||||||
| import org.foxarmy.barcodescannerforemployees.md5 | import org.foxarmy.barcodescannerforemployees.md5 | ||||||
|  | import org.json.JSONObject | ||||||
|  |  | ||||||
| class AbstractProduct() : Parcelable { | class AbstractProduct() : Parcelable { | ||||||
|     var id: Int = 0 |     var id: Int = 0 | ||||||
| @@ -59,5 +60,17 @@ class AbstractProduct() : Parcelable { | |||||||
|         override fun newArray(size: Int): Array<AbstractProduct?> { |         override fun newArray(size: Int): Array<AbstractProduct?> { | ||||||
|             return arrayOfNulls(size) |             return arrayOfNulls(size) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         fun createFromJSON(json: JSONObject): AbstractProduct { | ||||||
|  |             return AbstractProduct( | ||||||
|  |                 json["local_id"].toString().toInt(), | ||||||
|  |                 json["barcode"].toString(), | ||||||
|  |                 json["name"].toString(), | ||||||
|  |                 json["net_weight"].toString().toDouble(), | ||||||
|  |                 json["image_filename"].toString(), | ||||||
|  |                 json["category"].toString().toInt(), | ||||||
|  |                 json["unit"].toString().toInt() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -3,6 +3,7 @@ package org.foxarmy.barcodescannerforemployees.dataclasses | |||||||
| import android.os.Parcel | import android.os.Parcel | ||||||
| import android.os.Parcelable | import android.os.Parcelable | ||||||
| import org.foxarmy.barcodescannerforemployees.md5 | import org.foxarmy.barcodescannerforemployees.md5 | ||||||
|  | import org.json.JSONObject | ||||||
|  |  | ||||||
| class Category() : Parcelable { | class Category() : Parcelable { | ||||||
|     var id = 0 |     var id = 0 | ||||||
| @@ -40,5 +41,12 @@ class Category() : Parcelable { | |||||||
|         override fun newArray(size: Int): Array<Category?> { |         override fun newArray(size: Int): Array<Category?> { | ||||||
|             return arrayOfNulls(size) |             return arrayOfNulls(size) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         fun createFromJSON(json: JSONObject): Category { | ||||||
|  |             return Category( | ||||||
|  |                 json["local_id"].toString().toInt(), | ||||||
|  |                 json["name"].toString() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -5,7 +5,9 @@ import android.os.Parcel | |||||||
| import android.os.Parcelable | import android.os.Parcelable | ||||||
| import androidx.annotation.RequiresApi | import androidx.annotation.RequiresApi | ||||||
| import org.foxarmy.barcodescannerforemployees.calculateProductFreshness | import org.foxarmy.barcodescannerforemployees.calculateProductFreshness | ||||||
|  | import org.foxarmy.barcodescannerforemployees.convertToUnixEpochTimestamp | ||||||
| import org.foxarmy.barcodescannerforemployees.md5 | import org.foxarmy.barcodescannerforemployees.md5 | ||||||
|  | import org.json.JSONObject | ||||||
|  |  | ||||||
| class Product() : Parcelable { | class Product() : Parcelable { | ||||||
|     var id = 0 |     var id = 0 | ||||||
| @@ -59,5 +61,16 @@ class Product() : Parcelable { | |||||||
|         override fun newArray(size: Int): Array<Product?> { |         override fun newArray(size: Int): Array<Product?> { | ||||||
|             return arrayOfNulls(size) |             return arrayOfNulls(size) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         @RequiresApi(Build.VERSION_CODES.O) | ||||||
|  |         fun createFromJSON(json: JSONObject): Product { | ||||||
|  |             return Product( | ||||||
|  |                 json["local_id"].toString().toInt(), | ||||||
|  |                 json["abstract_product_id"].toString().toInt(), | ||||||
|  |                 json["amount"].toString().toInt(), | ||||||
|  |                 convertToUnixEpochTimestamp(json["date_of_production"].toString()), | ||||||
|  |                 convertToUnixEpochTimestamp(json["expiry_date"].toString()) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -11,36 +11,31 @@ 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 androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.activities.AddCategoryActivity | import org.foxarmy.barcodescannerforemployees.activities.AddCategoryActivity | ||||||
| import org.foxarmy.barcodescannerforemployees.database.CategoryDAO | import org.foxarmy.barcodescannerforemployees.database.CategoryDAO | ||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | 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 sharedPreferences: SharedPreferences | ||||||
|     private lateinit var categoryDAO: CategoryDAO |     private lateinit var categoryDAO: CategoryDAO | ||||||
|  |     private var updateInProgress = false | ||||||
|  |  | ||||||
|     override fun onCreate(savedInstanceState: Bundle?) { |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|         super.onCreate(savedInstanceState) |         super.onCreate(savedInstanceState) | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         sharedPreferences = getPreferences(requireContext()) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             requireContext(), |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         prepareDatabaseConnection() |         prepareDatabaseConnection() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun prepareDatabaseConnection() { |     private fun prepareDatabaseConnection() { | ||||||
|         val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "database")!!) |         val dbHelper = DBStorageController(requireContext(), sharedPreferences.getString("currentGroup", "offline")!!) | ||||||
|         categoryDAO = CategoryDAO(dbHelper) |         categoryDAO = CategoryDAO(dbHelper) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -58,56 +53,75 @@ class CategoriesFragment : Fragment() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun removeSelected() { |     fun removeSelected() { | ||||||
|         val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) |         thread { | ||||||
|  |             val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) | ||||||
|  |  | ||||||
|         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) { | ||||||
|                 val net = Net() |                     val net = Net() | ||||||
|                 net.token = sharedPreferences.getString("token", "")!! |                     net.token = sharedPreferences.getString("token", "")!! | ||||||
|                 net.server = sharedPreferences.getString("server", "")!! |                     net.server = sharedPreferences.getString("server", "")!! | ||||||
|                 net.language = sharedPreferences.getString("language", "en-US")!! |                     net.language = sharedPreferences.getString("language", "en-US")!! | ||||||
|                 val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt() |                     val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt() | ||||||
|  |  | ||||||
|                 categoryDAO.eraseCategory(view.category.id, requireContext()) |                     categoryDAO.eraseCategory(view.category.id, requireContext()) | ||||||
|                 val response = net.deleteCategory(currentGroup, view.category.id) |                     net.deleteCategory(currentGroup, view.category.id, {response -> | ||||||
|                 activity!!.runOnUiThread{ |                         activity!!.runOnUiThread{ | ||||||
|                     Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show() |                             Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show() | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                     }) | ||||||
|  |                     deleted = true | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 deleted = true |  | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!deleted) { |             if (!deleted) { | ||||||
|             Toast.makeText(requireContext(), getString(R.string.nothing_to_delete), Toast.LENGTH_SHORT).show() |                 activity!!.runOnUiThread { | ||||||
|  |                     Toast.makeText(requireContext(), getString(R.string.nothing_to_delete), Toast.LENGTH_SHORT).show() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             updateContent() | ||||||
|         } |         } | ||||||
|         updateContent() |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun updateSelected() { |     fun updateSelected() { | ||||||
|         val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) |         thread { | ||||||
|         for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) { |             val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) | ||||||
|             if (view.isCategorySelected) { |             for (view: CategoryView in layout?.children!!.iterator() as Iterator<CategoryView>) { | ||||||
|                 val addCategoryIntent = Intent(context, AddCategoryActivity::class.java) |                 if (view.isCategorySelected) { | ||||||
|                 val extras = Bundle() |                     val addCategoryIntent = Intent(context, AddCategoryActivity::class.java) | ||||||
|                 extras.putParcelable("category", view.category) |                     val extras = Bundle() | ||||||
|                 addCategoryIntent.putExtras(extras) |                     extras.putParcelable("category", view.category) | ||||||
|                 ContextCompat.startActivity(context!!, addCategoryIntent, extras) |                     addCategoryIntent.putExtras(extras) | ||||||
|  |                     activity!!.runOnUiThread { | ||||||
|  |                         ContextCompat.startActivity(context!!, addCategoryIntent, extras) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun updateContent() { |     fun updateContent() { | ||||||
|         prepareDatabaseConnection() |         thread { | ||||||
|         val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) |             if (updateInProgress) return@thread | ||||||
|         layout?.removeAllViews() |             updateInProgress = true | ||||||
|  |             prepareDatabaseConnection() | ||||||
|  |             val layout = view?.findViewById<LinearLayout>(R.id.categoriesLayout) | ||||||
|  |             activity!!.runOnUiThread { | ||||||
|  |                 layout?.removeAllViews() | ||||||
|  |             } | ||||||
|  |  | ||||||
|         val categories = categoryDAO.getAllCategories() |             val categories = categoryDAO.getAllCategories() | ||||||
|  |  | ||||||
|         for (category in categories) { |             for (category in categories) { | ||||||
|             val categoryView = CategoryView(requireActivity(), requireContext(), category) |                 val categoryView = CategoryView(requireActivity(), requireContext(), category) | ||||||
|             layout?.addView(categoryView) |                 activity!!.runOnUiThread { | ||||||
|  |                     layout?.addView(categoryView) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             updateInProgress = false | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,14 +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 androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.activities.AddProductActivity | import org.foxarmy.barcodescannerforemployees.activities.AddProductActivity | ||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | import org.foxarmy.barcodescannerforemployees.database.DBStorageController | ||||||
| import org.foxarmy.barcodescannerforemployees.database.ProductDAO | import org.foxarmy.barcodescannerforemployees.database.ProductDAO | ||||||
| import org.foxarmy.barcodescannerforemployees.databinding.FragmentShelfBinding | import org.foxarmy.barcodescannerforemployees.databinding.FragmentShelfBinding | ||||||
|  | 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 | ||||||
|  |  | ||||||
| @@ -38,16 +37,9 @@ class ShelfFragment : Fragment() { | |||||||
|     override fun onCreate(savedInstanceState: Bundle?) { |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|         super.onCreate(savedInstanceState) |         super.onCreate(savedInstanceState) | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         sharedPreferences = getPreferences(requireContext()) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             requireContext(), |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         prepareDatabaseConnection() |         prepareDatabaseConnection() | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun onCreateView( |     override fun onCreateView( | ||||||
| @@ -122,11 +114,12 @@ class ShelfFragment : Fragment() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun updateContent() { |     fun updateContent() { | ||||||
|         prepareDatabaseConnection() |  | ||||||
|         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() | ||||||
|             } |             } | ||||||
| @@ -144,7 +137,6 @@ class ShelfFragment : Fragment() { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             updateInProgress = false |             updateInProgress = false | ||||||
|  |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -165,11 +157,12 @@ class ShelfFragment : Fragment() { | |||||||
|                     val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt() |                     val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt() | ||||||
|  |  | ||||||
|                     productDAO.eraseProduct(view.product.id) |                     productDAO.eraseProduct(view.product.id) | ||||||
|                     val response = net.deleteProduct(currentGroup, view.product.id) |                     net.deleteProduct(currentGroup, view.product.id, {response -> | ||||||
|                     activity!!.runOnUiThread{ |                         activity!!.runOnUiThread{ | ||||||
|                         Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show() |                             Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show() | ||||||
|                     } |                         } | ||||||
|  |                     }) | ||||||
|  |                      | ||||||
|                     deleted = true |                     deleted = true | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -7,11 +7,10 @@ import android.view.LayoutInflater | |||||||
| import android.view.View | import android.view.View | ||||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||||
| import android.widget.* | import android.widget.* | ||||||
|  | import androidx.constraintlayout.widget.ConstraintLayout | ||||||
| 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 androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.Net | import org.foxarmy.barcodescannerforemployees.Net | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.activities.AddAbstractProductActivity | import org.foxarmy.barcodescannerforemployees.activities.AddAbstractProductActivity | ||||||
| @@ -20,6 +19,7 @@ import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO | |||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | import org.foxarmy.barcodescannerforemployees.database.DBStorageController | ||||||
| import org.foxarmy.barcodescannerforemployees.databinding.FragmentStorageBinding | import org.foxarmy.barcodescannerforemployees.databinding.FragmentStorageBinding | ||||||
| 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 | ||||||
|  |  | ||||||
| @@ -32,23 +32,18 @@ class StorageFragment : Fragment() { | |||||||
|     private lateinit var sharedPreferences: SharedPreferences |     private lateinit var sharedPreferences: SharedPreferences | ||||||
|     private lateinit var abstractProductDAO: AbstractProductDAO |     private lateinit var abstractProductDAO: AbstractProductDAO | ||||||
|  |  | ||||||
|  |     private var updateInProcess = false | ||||||
|  |  | ||||||
|     override fun onCreateView( |     override fun onCreateView( | ||||||
|         inflater: LayoutInflater, container: ViewGroup?, |         inflater: LayoutInflater, container: ViewGroup?, | ||||||
|         savedInstanceState: Bundle? |         savedInstanceState: Bundle? | ||||||
|     ): View { |     ): View { | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         binding = FragmentStorageBinding.inflate(inflater) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |         sharedPreferences = getPreferences(requireContext()) | ||||||
|             requireContext(), |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         prepareDatabaseConnection() |         prepareDatabaseConnection() | ||||||
|  |  | ||||||
|         binding = FragmentStorageBinding.inflate(inflater) |  | ||||||
|  |  | ||||||
|         fillUpSortBySpinner() |         fillUpSortBySpinner() | ||||||
|  |  | ||||||
|         binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { |         binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { | ||||||
| @@ -107,10 +102,12 @@ class StorageFragment : Fragment() { | |||||||
|                     val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt() |                     val currentGroup = sharedPreferences.getString("currentGroup", "")!!.toInt() | ||||||
|  |  | ||||||
|                     abstractProductDAO.eraseAbstractProduct(view.abstractProduct.id, requireContext()) |                     abstractProductDAO.eraseAbstractProduct(view.abstractProduct.id, requireContext()) | ||||||
|                     val response = net.deleteAbstractProduct(currentGroup, view.abstractProduct.id) |                     net.deleteAbstractProduct(currentGroup, view.abstractProduct.id, {response -> | ||||||
|                     activity!!.runOnUiThread{ |                         activity!!.runOnUiThread{ | ||||||
|                         Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show() |                             Toast.makeText(context, response.body!!.string(), Toast.LENGTH_SHORT).show() | ||||||
|                     } |                         } | ||||||
|  |                     }) | ||||||
|  |  | ||||||
|  |  | ||||||
|                     deleted = true |                     deleted = true | ||||||
|                 } |                 } | ||||||
| @@ -141,10 +138,14 @@ class StorageFragment : Fragment() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun updateContent() { |     fun updateContent() { | ||||||
|         prepareDatabaseConnection() |  | ||||||
|         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() | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -160,11 +161,25 @@ class StorageFragment : Fragment() { | |||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|                 if (filterBy == "barcodeless") { |                 if (filterBy == "barcodeless") { | ||||||
|                     abstractProductView.setOnClickListener { |                     abstractProductView.also { | ||||||
|                         (activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct) |                         it.setOnClickListener { | ||||||
|                     } |                             (activity as FindBarcodelessAbstractProduct).selected(abstractProductView.abstractProduct) | ||||||
|                     abstractProductView.findViewById<TextView>(R.id.productNameView).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) | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -172,14 +187,10 @@ class StorageFragment : Fragment() { | |||||||
|                     grv.addView(abstractProductView) |                     grv.addView(abstractProductView) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }.join() |             updateInProcess = false | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |  | ||||||
|         super.onViewCreated(view, savedInstanceState) |  | ||||||
|  |  | ||||||
|         updateContent() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun filterByCategory(id: Int) { |     fun filterByCategory(id: Int) { | ||||||
|         filterBy = "category" |         filterBy = "category" | ||||||
| @@ -194,7 +205,7 @@ class StorageFragment : Fragment() { | |||||||
|             val args = Bundle() |             val args = Bundle() | ||||||
|             args.putString("filterBy", filterBy) |             args.putString("filterBy", filterBy) | ||||||
|             args.putStringArray("filter", filter) |             args.putStringArray("filter", filter) | ||||||
|             fragment.setArguments(args) |             fragment.arguments = args | ||||||
|  |  | ||||||
|             return fragment |             return fragment | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -14,19 +14,15 @@ 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 androidx.security.crypto.EncryptedSharedPreferences | import org.foxarmy.barcodescannerforemployees.* | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.R |  | ||||||
| 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.CategoryDAO | ||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | import org.foxarmy.barcodescannerforemployees.database.DBStorageController | ||||||
| import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct | import org.foxarmy.barcodescannerforemployees.dataclasses.AbstractProduct | ||||||
| import org.foxarmy.barcodescannerforemployees.getActivity |  | ||||||
| import org.foxarmy.barcodescannerforemployees.getImageUri |  | ||||||
| import org.foxarmy.barcodescannerforemployees.getUnitNameById |  | ||||||
| import java.io.File | import java.io.File | ||||||
|  |  | ||||||
| class AbstractProductView: LinearLayout { | class AbstractProductView : LinearLayout { | ||||||
|     private var productLayout: ConstraintLayout? = null |     private var productLayout: ConstraintLayout? = null | ||||||
|     private var productPicture: ImageView? = null |     private var productPicture: ImageView? = null | ||||||
|     private var productNameField: TextView? = null |     private var productNameField: TextView? = null | ||||||
| @@ -42,16 +38,10 @@ class AbstractProductView: LinearLayout { | |||||||
|  |  | ||||||
|     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 = EncryptedSharedPreferences.create( |         sharedPreferences = getPreferences(context) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             context, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!) |         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!) | ||||||
|         categoryDAO = CategoryDAO(dbHelper) |         categoryDAO = CategoryDAO(dbHelper) | ||||||
| @@ -60,23 +50,17 @@ class AbstractProductView: LinearLayout { | |||||||
|     constructor(activity: Activity, context: Context, abstractProduct: AbstractProduct) : super(context) { |     constructor(activity: Activity, context: Context, abstractProduct: AbstractProduct) : super(context) { | ||||||
|         this.abstractProduct = abstractProduct |         this.abstractProduct = abstractProduct | ||||||
|         this.activity = activity |         this.activity = activity | ||||||
|         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 = EncryptedSharedPreferences.create( |         sharedPreferences = getPreferences(context) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             context, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!) |         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!) | ||||||
|         categoryDAO = CategoryDAO(dbHelper) |         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) | ||||||
| @@ -84,30 +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 |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         productNameField!!.setOnClickListener { |  | ||||||
|             Toast.makeText(activity, productNameField!!.text, Toast.LENGTH_SHORT).show() |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun update() { |     fun update() { | ||||||
| @@ -117,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 = categoryDAO.getCategoryNameById(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 | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -7,16 +7,16 @@ 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 androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.activities.MainActivity | import org.foxarmy.barcodescannerforemployees.activities.MainActivity | ||||||
| import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO | import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO | ||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | import org.foxarmy.barcodescannerforemployees.database.DBStorageController | ||||||
| import org.foxarmy.barcodescannerforemployees.dataclasses.Category | 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 | ||||||
| @@ -27,13 +27,7 @@ class CategoryView : LinearLayout { | |||||||
|     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 = EncryptedSharedPreferences.create( |         sharedPreferences = getPreferences(context) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             context, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!) |         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!) | ||||||
|         abstractProductDAO = AbstractProductDAO(dbHelper) |         abstractProductDAO = AbstractProductDAO(dbHelper) | ||||||
| @@ -41,7 +35,7 @@ class CategoryView : LinearLayout { | |||||||
|         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) | ||||||
| @@ -49,13 +43,47 @@ class CategoryView : LinearLayout { | |||||||
|         categoryName.text = category.name |         categoryName.text = category.name | ||||||
|         amountOfProducts.text = abstractProductDAO.getAmountOfAbstractProductsInCategory(category.id).toString() |         amountOfProducts.text = abstractProductDAO.getAmountOfAbstractProductsInCategory(category.id).toString() | ||||||
|  |  | ||||||
|         setOnLongClickListener { |         setUpClicks() | ||||||
|             isCategorySelected = !isCategorySelected |     } | ||||||
|             this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline) |  | ||||||
|             true |     private fun setUpClicks() { | ||||||
|  |         categoryName.setOnClickListener { onCategoryClick() } | ||||||
|  |         amountOfProducts.setOnClickListener { onCategoryClick() } | ||||||
|  |  | ||||||
|  |         categoryName.setOnLongClickListener { onAnyLongClick() } | ||||||
|  |         amountOfProducts.setOnLongClickListener { onAnyLongClick() } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun onCategoryClick(): Boolean { | ||||||
|  |         if (context !is MainActivity) return false | ||||||
|  |         val mainActivity = context as MainActivity | ||||||
|  |  | ||||||
|  |         if (mainActivity.selectionMode) return onAnyLongClick() | ||||||
|  |         else (context as MainActivity).filterAbstractProductsByCategory(category.id) | ||||||
|  |  | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun onAnyLongClick(): Boolean { | ||||||
|  |         if (context !is MainActivity) return false | ||||||
|  |         val mainActivity = context as MainActivity | ||||||
|  |  | ||||||
|  |         isCategorySelected = !isCategorySelected | ||||||
|  |         updateStroke() | ||||||
|  |  | ||||||
|  |         if (mainActivity.selectionMode) { | ||||||
|  |             if (isCategorySelected) mainActivity.addSelection() | ||||||
|  |             else mainActivity.removeSelection() | ||||||
|  |         } else { | ||||||
|  |             mainActivity.addSelection() | ||||||
|         } |         } | ||||||
|         setOnClickListener { |         return true | ||||||
|             (activity as MainActivity).filterAbstractProductsByCategory(category.id) |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun updateStroke() { | ||||||
|  |         (context as Activity).runOnUiThread { | ||||||
|  |             this.background = ContextCompat.getDrawable(context, if (isCategorySelected) R.drawable.outline_selected else R.drawable.outline) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -7,12 +7,11 @@ 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 androidx.security.crypto.EncryptedSharedPreferences |  | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.R | import org.foxarmy.barcodescannerforemployees.R | ||||||
| import org.foxarmy.barcodescannerforemployees.activities.ExpiryCalendarActivity | import org.foxarmy.barcodescannerforemployees.activities.ExpiryCalendarActivity | ||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | import org.foxarmy.barcodescannerforemployees.database.DBStorageController | ||||||
| import org.foxarmy.barcodescannerforemployees.database.ProductDAO | import org.foxarmy.barcodescannerforemployees.database.ProductDAO | ||||||
|  | import org.foxarmy.barcodescannerforemployees.getPreferences | ||||||
| import java.text.SimpleDateFormat | import java.text.SimpleDateFormat | ||||||
|  |  | ||||||
| class ExpiryGroupView : LinearLayout { | class ExpiryGroupView : LinearLayout { | ||||||
| @@ -24,13 +23,7 @@ class ExpiryGroupView : LinearLayout { | |||||||
|  |  | ||||||
|     constructor(activity: Activity, context: Context, representingDate: Long) : super(context) { |     constructor(activity: Activity, context: Context, representingDate: Long) : super(context) { | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         sharedPreferences = getPreferences(context) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             context, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!) |         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "database")!!) | ||||||
|         productDAO = ProductDAO(dbHelper) |         productDAO = ProductDAO(dbHelper) | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import org.foxarmy.barcodescannerforemployees.R | |||||||
| class GroupMemberView : LinearLayout { | class GroupMemberView : LinearLayout { | ||||||
|  |  | ||||||
|     private var username: String |     private var username: String | ||||||
|     private var userId: Int |     var userId: Int | ||||||
|  |  | ||||||
|     constructor(activity: Activity, context: Context, username: String, userId: Int) : super(context) { |     constructor(activity: Activity, context: Context, username: String, userId: Int) : super(context) { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,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 | ||||||
| @@ -21,29 +22,26 @@ import androidx.core.graphics.blue | |||||||
| import androidx.core.graphics.green | import androidx.core.graphics.green | ||||||
| import androidx.core.graphics.red | import androidx.core.graphics.red | ||||||
| import androidx.core.math.MathUtils.clamp | import androidx.core.math.MathUtils.clamp | ||||||
| import androidx.security.crypto.EncryptedSharedPreferences | import org.foxarmy.barcodescannerforemployees.* | ||||||
| import androidx.security.crypto.MasterKeys |  | ||||||
| import org.foxarmy.barcodescannerforemployees.R |  | ||||||
| import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity | import org.foxarmy.barcodescannerforemployees.activities.FullscreenActivity | ||||||
| import org.foxarmy.barcodescannerforemployees.calculateProductFreshness | import org.foxarmy.barcodescannerforemployees.activities.MainActivity | ||||||
| import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO | import org.foxarmy.barcodescannerforemployees.database.AbstractProductDAO | ||||||
| import org.foxarmy.barcodescannerforemployees.database.CategoryDAO | import org.foxarmy.barcodescannerforemployees.database.CategoryDAO | ||||||
| import org.foxarmy.barcodescannerforemployees.database.DBStorageController | import org.foxarmy.barcodescannerforemployees.database.DBStorageController | ||||||
| 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.getActivity |  | ||||||
| import org.foxarmy.barcodescannerforemployees.getImageUri |  | ||||||
| import java.io.File | import java.io.File | ||||||
| import java.text.DecimalFormat | import java.text.DecimalFormat | ||||||
| import java.text.SimpleDateFormat | import java.text.SimpleDateFormat | ||||||
| import java.util.* | import java.util.* | ||||||
| import kotlin.concurrent.thread | 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 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 | ||||||
| @@ -63,13 +61,7 @@ class ProductView: LinearLayout { | |||||||
|     constructor(context: Context, a: AttributeSet) : super(context, a) { |     constructor(context: Context, a: AttributeSet) : super(context, a) { | ||||||
|         activity = getActivity(context)!! |         activity = getActivity(context)!! | ||||||
|  |  | ||||||
|         sharedPreferences = EncryptedSharedPreferences.create( |         sharedPreferences = getPreferences(context) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             context, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!) |         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!) | ||||||
|         abstractProductDAO = AbstractProductDAO(dbHelper) |         abstractProductDAO = AbstractProductDAO(dbHelper) | ||||||
| @@ -86,18 +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 = EncryptedSharedPreferences.create( |         sharedPreferences = getPreferences(context) | ||||||
|             "sensitive", |  | ||||||
|             MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), |  | ||||||
|             context, |  | ||||||
|             EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |  | ||||||
|             EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!) |         val dbHelper = DBStorageController(context, sharedPreferences.getString("currentGroup", "offline")!!) | ||||||
|         abstractProductDAO = AbstractProductDAO(dbHelper) |         abstractProductDAO = AbstractProductDAO(dbHelper) | ||||||
|         categoryDAO = CategoryDAO(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) | ||||||
| @@ -106,68 +93,152 @@ 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 = abstractProductDAO.findAbstractProductById(product.abstractProductId) |  | ||||||
|             extras.putString("imagehash", abstractProduct!!.imageHash) |  | ||||||
|             fullscreenIntent.putExtras(extras) |  | ||||||
|             startActivity(context, fullscreenIntent, extras) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         update() |         update() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @RequiresApi(Build.VERSION_CODES.O) |     @RequiresApi(Build.VERSION_CODES.O) | ||||||
|     fun update () { |     private fun setUpClicks() { | ||||||
|         val linkedAbstractProduct: AbstractProduct = abstractProductDAO.findAbstractProductById(product.abstractProductId) ?: return |         productLayout.setOnLongClickListener { onAnyLongClick() } | ||||||
|  |         productImageView.setOnLongClickListener { onImageLongClick() } | ||||||
|  |         productNameTextView.setOnLongClickListener { onNameLongClick() } | ||||||
|  |         productNetWeightTextView.setOnLongClickListener { onAnyLongClick() } | ||||||
|  |         productAmountTextView.setOnLongClickListener { onAnyLongClick() } | ||||||
|  |         productCategoryView.setOnLongClickListener { onAnyLongClick() } | ||||||
|  |         productLifeSpan.setOnLongClickListener { onAnyLongClick() } | ||||||
|  |         productFreshnessTextView.setOnLongClickListener { onAnyLongClick() } | ||||||
|  |  | ||||||
|  |         productLayout.setOnClickListener { onAnyClick() } | ||||||
|  |         productImageView.setOnClickListener { onImageLongClick() } | ||||||
|  |         productNameTextView.setOnClickListener { onNameLongClick() } | ||||||
|  |         productNetWeightTextView.setOnClickListener { onAnyClick() } | ||||||
|  |         productAmountTextView.setOnClickListener { onAnyClick() } | ||||||
|  |         productCategoryView.setOnClickListener { onAnyClick() } | ||||||
|  |         productLifeSpan.setOnClickListener { onAnyClick() } | ||||||
|  |         productFreshnessTextView.setOnClickListener { onAnyClick() } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @RequiresApi(Build.VERSION_CODES.O) | ||||||
|  |     private fun onAnyClick(): Boolean { | ||||||
|  |         if (activity !is MainActivity) return false | ||||||
|  |         val mainActivity = activity as MainActivity | ||||||
|  |  | ||||||
|  |         if (mainActivity.selectionMode) { | ||||||
|  |             isProductSelected = !isProductSelected | ||||||
|  |  | ||||||
|  |             if (isProductSelected) mainActivity.addSelection() | ||||||
|  |             else mainActivity.removeSelection() | ||||||
|  |  | ||||||
|  |             updateStroke() | ||||||
|  |  | ||||||
|  |             return true | ||||||
|  |         } else { | ||||||
|  |             return false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @RequiresApi(Build.VERSION_CODES.O) | ||||||
|  |     private fun onAnyLongClick(): Boolean { | ||||||
|  |         if (activity !is MainActivity) return false | ||||||
|  |         val mainActivity = activity as MainActivity | ||||||
|  |  | ||||||
|  |         isProductSelected = !isProductSelected | ||||||
|  |         updateStroke() | ||||||
|  |  | ||||||
|  |         if (mainActivity.selectionMode) { | ||||||
|  |             if (isProductSelected) mainActivity.addSelection() | ||||||
|  |             else mainActivity.removeSelection() | ||||||
|  |         } else { | ||||||
|  |             mainActivity.addSelection() | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun onNameLongClick(): Boolean { | ||||||
|  |         if (activity !is MainActivity) { | ||||||
|  |             Toast.makeText(activity, productNameTextView.text, Toast.LENGTH_SHORT).show() | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |         val mainActivity = activity as MainActivity | ||||||
|  |  | ||||||
|  |         if (mainActivity.selectionMode) return onAnyLongClick() | ||||||
|  |         else Toast.makeText(activity, productNameTextView.text, Toast.LENGTH_SHORT).show() | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun onImageLongClick(): Boolean { | ||||||
|  |         val abstractProduct = abstractProductDAO.findAbstractProductById(product.abstractProductId) | ||||||
|  |  | ||||||
|  |         if (activity !is MainActivity) { | ||||||
|  |             val fullscreenIntent = Intent(activity, FullscreenActivity::class.java) | ||||||
|  |             val extras = Bundle() | ||||||
|  |             extras.putString("imagehash", abstractProduct!!.imageHash) | ||||||
|  |             fullscreenIntent.putExtras(extras) | ||||||
|  |             startActivity(context, fullscreenIntent, extras) | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |         val mainActivity = activity as MainActivity | ||||||
|  |  | ||||||
|  |         if (mainActivity.selectionMode) { | ||||||
|  |             return onAnyLongClick() | ||||||
|  |         } else { | ||||||
|  |             val fullscreenIntent = Intent(activity, FullscreenActivity::class.java) | ||||||
|  |             val extras = Bundle() | ||||||
|  |             extras.putString("imagehash", abstractProduct!!.imageHash) | ||||||
|  |             fullscreenIntent.putExtras(extras) | ||||||
|  |             startActivity(context, fullscreenIntent, extras) | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @RequiresApi(Build.VERSION_CODES.O) | ||||||
|  |     fun update() { | ||||||
|  |         val linkedAbstractProduct: AbstractProduct = | ||||||
|  |             abstractProductDAO.findAbstractProductById(product.abstractProductId) ?: return | ||||||
|  |  | ||||||
|         val thumbnailsDir = File(activity.cacheDir, "thumbnails") |         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 = categoryDAO.getCategoryNameById(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) | ||||||
|         } else { |             } else { | ||||||
|             "${DecimalFormat("#.#").format(product.freshness*100)}%" |                 "${DecimalFormat("#.#").format(product.freshness * 100)}%" | ||||||
|         } |             } | ||||||
|  |  | ||||||
|         updateStroke() |         updateStroke() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @RequiresApi(Build.VERSION_CODES.O) |     @RequiresApi(Build.VERSION_CODES.O) | ||||||
|     fun updateStroke() { |     fun updateStroke() { | ||||||
|          if (isProductSelected) { |         activity.runOnUiThread { | ||||||
|              this.background = ContextCompat.getDrawable(context, R.drawable.outline_selected) |             if (isProductSelected) { | ||||||
|         } else { |                 this.background = ContextCompat.getDrawable(context, R.drawable.outline_selected) | ||||||
|              if (product.id != 0) { |             } else { | ||||||
|                  thread { |                 if (product.id != 0) { | ||||||
|                      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 | ||||||
|                      this.outline.setColor(backgroundColor) |                         this.outline.setColor(backgroundColor) | ||||||
|                      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) |                 } | ||||||
|              } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -178,13 +249,15 @@ 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 | ||||||
|  |  | ||||||
|         val red = clamp((startColor.red + gradientPosition * (endColor.red - startColor.red )).toInt(), 0, 255) |         val red = clamp((startColor.red + gradientPosition * (endColor.red - startColor.red)).toInt(), 0, 255) | ||||||
|         val green = clamp((startColor.green  + gradientPosition * (endColor.green - startColor.green)).toInt(), 0, 255) |         val green = clamp((startColor.green + gradientPosition * (endColor.green - startColor.green)).toInt(), 0, 255) | ||||||
|         val blue = clamp((startColor.blue + gradientPosition * (endColor.blue - startColor.blue)).toInt(), 0, 255) |         val blue = clamp((startColor.blue + gradientPosition * (endColor.blue - startColor.blue)).toInt(), 0, 255) | ||||||
|  |  | ||||||
|         var redHex = Integer.toHexString(red) |         var redHex = Integer.toHexString(red) | ||||||
| @@ -194,7 +267,7 @@ class ProductView: LinearLayout { | |||||||
|         if (greenHex.length == 1) greenHex = "0$greenHex" |         if (greenHex.length == 1) greenHex = "0$greenHex" | ||||||
|  |  | ||||||
|         var blueHex = Integer.toHexString(blue) |         var blueHex = Integer.toHexString(blue) | ||||||
|         if (blueHex.length == 1 ) blueHex = "0$blueHex" |         if (blueHex.length == 1) blueHex = "0$blueHex" | ||||||
|  |  | ||||||
|  |  | ||||||
|         val colorString = "#$redHex$greenHex$blueHex" |         val colorString = "#$redHex$greenHex$blueHex" | ||||||
| @@ -202,7 +275,7 @@ class ProductView: LinearLayout { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @RequiresApi(Build.VERSION_CODES.O) |     @RequiresApi(Build.VERSION_CODES.O) | ||||||
|     fun darkenColor(color: Int, darkPercent: Double) : Int { |     fun darkenColor(color: Int, darkPercent: Double): Int { | ||||||
|         val c = Color.valueOf(color) |         val c = Color.valueOf(color) | ||||||
|  |  | ||||||
|         val red = c.red() * (1 - darkPercent) |         val red = c.red() * (1 - darkPercent) | ||||||
|   | |||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -1,10 +1,15 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <androidx.core.widget.NestedScrollView | <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|         xmlns:android="http://schemas.android.com/apk/res/android" |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|         xmlns:tools="http://schemas.android.com/tools" |         xmlns:app="http://schemas.android.com/apk/res-auto" 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.ShelfFragment" |  | ||||||
|         android:id="@+id/content"> |  | ||||||
|  |  | ||||||
| </androidx.core.widget.NestedScrollView> |     <androidx.core.widget.NestedScrollView | ||||||
|  |             android:layout_height="0dp" | ||||||
|  |             tools:context=".fragments.ShelfFragment" | ||||||
|  |             android:id="@+id/content" android:layout_width="0dp" app:layout_constraintTop_toTopOf="parent" | ||||||
|  |             app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintEnd_toEndOf="parent"> | ||||||
|  |  | ||||||
|  |     </androidx.core.widget.NestedScrollView> | ||||||
|  | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
|   | |||||||
| @@ -39,12 +39,13 @@ | |||||||
|             <androidx.viewpager.widget.ViewPager |             <androidx.viewpager.widget.ViewPager | ||||||
|                     android:id="@+id/tab_viewpager" |                     android:id="@+id/tab_viewpager" | ||||||
|                     android:layout_width="0dp" |                     android:layout_width="0dp" | ||||||
|                     android:layout_height="777dp" |                     android:layout_height="0dp" | ||||||
|                     app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_gravity="bottom" |                     app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_gravity="bottom" | ||||||
|                     android:layout_marginTop="2dp" |                     android:layout_marginTop="2dp" | ||||||
|                     app:layout_constraintTop_toBottomOf="@+id/appBarLayout" |                     app:layout_constraintTop_toBottomOf="@+id/appBarLayout" | ||||||
|                     app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="6dp" |                     app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="6dp" | ||||||
|                     app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="6dp"/> |                     app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="6dp" | ||||||
|  |                     app:layout_constraintBottom_toBottomOf="parent"/> | ||||||
|             <com.google.android.material.floatingactionbutton.FloatingActionButton |             <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" | ||||||
|   | |||||||
| @@ -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> |  | ||||||
| @@ -62,7 +62,7 @@ | |||||||
|                 android:ems="10" |                 android:ems="10" | ||||||
|                 android:id="@+id/barcodeTextEdit" app:layout_constraintTop_toBottomOf="@+id/imageView" |                 android:id="@+id/barcodeTextEdit" app:layout_constraintTop_toBottomOf="@+id/imageView" | ||||||
|                 app:layout_constraintStart_toStartOf="parent" |                 app:layout_constraintStart_toStartOf="parent" | ||||||
|                 android:layout_marginTop="8dp" android:hint="Barcode" android:textColorHint="#737373" |                 android:layout_marginTop="8dp" android:hint="@string/barcode" android:textColorHint="#737373" | ||||||
|                 android:layout_marginStart="5dp"/> |                 android:layout_marginStart="5dp"/> | ||||||
|         <CheckBox |         <CheckBox | ||||||
|                 android:text="@string/no_barcode" |                 android:text="@string/no_barcode" | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|              xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" |              xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" | ||||||
|              android:layout_height="match_parent" |              android:layout_height="match_parent" | ||||||
|              android:background="#141218"> | > | ||||||
|  |  | ||||||
|     <androidx.constraintlayout.widget.ConstraintLayout |     <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
| @@ -26,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" | ||||||
|   | |||||||
| @@ -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" | <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|              android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|              android:layout_height="match_parent" |         android:layout_height="match_parent" | ||||||
|              android:id="@+id/fragment_storage" |         xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||||
|              tools:context=".fragments.StorageFragment"> |  | ||||||
|     <androidx.constraintlayout.widget.ConstraintLayout |  | ||||||
|             android:layout_width="match_parent" |  | ||||||
|             android:layout_height="match_parent"> |  | ||||||
|  |  | ||||||
|         <TextView |         <TextView | ||||||
|                 android:text="@string/sort_by" |                 android:text="@string/sort_by" | ||||||
| @@ -16,7 +12,7 @@ | |||||||
|                 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" | ||||||
| @@ -34,15 +30,16 @@ | |||||||
|                 android:layout_marginEnd="10dp" app:layout_constraintTop_toTopOf="parent"/> |                 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" |                 android:layout_height="0dp" | ||||||
|                 android:id="@+id/scrollView2" |                 android:id="@+id/scrollView2" | ||||||
|                 app:layout_constraintTop_toBottomOf="@+id/dropFiltersButton"> |                 app:layout_constraintTop_toBottomOf="@+id/dropFiltersButton" | ||||||
|  |                 android:layout_marginTop="4dp" app:layout_constraintStart_toStartOf="parent" | ||||||
|  |                 app:layout_constraintEnd_toEndOf="parent"> | ||||||
|             <androidx.gridlayout.widget.GridLayout |             <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> |  | ||||||
| @@ -12,4 +12,5 @@ | |||||||
|     <item |     <item | ||||||
|             android:id="@+id/nav_settings" |             android:id="@+id/nav_settings" | ||||||
|             android:title="@string/settings"/> |             android:title="@string/settings"/> | ||||||
|  |     <item android:title="@string/logout" android:id="@+id/nav_logout"/> | ||||||
| </menu> | </menu> | ||||||
							
								
								
									
										6
									
								
								app/src/main/res/menu/user_pop_menu.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/src/main/res/menu/user_pop_menu.xml
									
									
									
									
									
										Normal 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> | ||||||
| @@ -114,6 +114,18 @@ | |||||||
|     <string name="image_compress_factor">Степень сжатия изображения</string> |     <string name="image_compress_factor">Степень сжатия изображения</string> | ||||||
|     <string name="current_group">Текущая группа</string> |     <string name="current_group">Текущая группа</string> | ||||||
|     <string name="cancel">Отмена</string> |     <string name="cancel">Отмена</string> | ||||||
|  |     <string name="no_internet_connection">Не могу установить соединение с сервером. Возможно, вы отключены от сети?</string> | ||||||
|  |     <string name="ok">Ок</string> | ||||||
|  |     <string name="logout">Выйти</string> | ||||||
|  |     <string name="online_only_feature">Эта возможность доступна только с онлайн аккаунтом</string> | ||||||
|  |     <string 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"> |     <string-array name="languages"> | ||||||
|         <item>en-US</item> |         <item>en-US</item> | ||||||
|         <item>ru-RU</item> |         <item>ru-RU</item> | ||||||
|   | |||||||
| @@ -112,6 +112,18 @@ | |||||||
|     <string name="image_compress_factor">Image compression factor</string> |     <string name="image_compress_factor">Image compression factor</string> | ||||||
|     <string name="current_group">Current group</string> |     <string name="current_group">Current group</string> | ||||||
|     <string name="cancel">Cancel</string> |     <string name="cancel">Cancel</string> | ||||||
|  |     <string name="no_internet_connection">Cannot reach the server. Maybe, you lost internet connection?</string> | ||||||
|  |     <string name="ok">OK</string> | ||||||
|  |     <string name="logout">Log out</string> | ||||||
|  |     <string name="online_only_feature">This feature is online only</string> | ||||||
|  |     <string 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"> |     <string-array name="languages"> | ||||||
|         <item>en-US</item> |         <item>en-US</item> | ||||||
|         <item>ru-RU</item> |         <item>ru-RU</item> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user