ver 1.6 kosmetyka

This commit is contained in:
2026-09-18 18:51:55 +02:00
parent 7a96731cee
commit 4c5c06dde3
10 changed files with 249 additions and 78 deletions
+50
View File
@@ -0,0 +1,50 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
</profile>
</component>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PlanningModeManager">
<option name="approvalStates">
<map>
<entry key="9f4c6190-a68b-4097-9498-21cddca37196" value="false" />
</map>
</option>
</component>
</project>
+5 -8
View File
@@ -36,25 +36,22 @@ android {
} }
dependencies { dependencies {
// Jetpack Compose & ViewModel
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
// Obsługa plików Excel .xls // Obsługa plików Excel .xls
implementation("org.apache.poi:poi:5.2.3") implementation("org.apache.poi:poi:5.2.3")
implementation("org.apache.poi:poi-ooxml:5.2.3") implementation("org.apache.poi:poi-ooxml:5.2.3")
// Jetpack Compose & Core
implementation(platform(libs.androidx.compose.bom)) implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation("androidx.compose.material:material-icons-extended")
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4) androidTestImplementation(libs.androidx.compose.ui.test.junit4)
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -11,8 +11,8 @@
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"attributes": [], "attributes": [],
"versionCode": 1, "versionCode": 8,
"versionName": "1.0", "versionName": "1.5",
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }
], ],
Binary file not shown.
@@ -19,6 +19,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -54,12 +55,15 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.core.content.edit
import androidx.core.net.toUri
import org.apache.poi.ss.usermodel.WorkbookFactory import org.apache.poi.ss.usermodel.WorkbookFactory
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.io.InputStream import java.io.InputStream
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import java.net.URLEncoder
import java.util.Scanner import java.util.Scanner
// ========================================== // ==========================================
@@ -101,6 +105,9 @@ class InventoryViewModel : ViewModel() {
private val _updateInfo = MutableStateFlow<UpdateInfo?>(null) private val _updateInfo = MutableStateFlow<UpdateInfo?>(null)
val updateInfo: StateFlow<UpdateInfo?> = _updateInfo.asStateFlow() val updateInfo: StateFlow<UpdateInfo?> = _updateInfo.asStateFlow()
private val _isLocked = MutableStateFlow(false)
val isLocked: StateFlow<Boolean> = _isLocked.asStateFlow()
data class UpdateInfo(val versionCode: Int) data class UpdateInfo(val versionCode: Int)
fun loadState(context: Context) { fun loadState(context: Context) {
@@ -135,10 +142,10 @@ class InventoryViewModel : ViewModel() {
private fun saveState(context: Context) { private fun saveState(context: Context) {
val prefs = context.getSharedPreferences("inventory_prefs", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("inventory_prefs", Context.MODE_PRIVATE)
val editor = prefs.edit() prefs.edit {
editor.putString("notes", _notes.value) putString("notes", _notes.value)
editor.putString("branch", _branchName.value) putString("branch", _branchName.value)
editor.putString("date", _reportDate.value) putString("date", _reportDate.value)
val jsonArray = JSONArray() val jsonArray = JSONArray()
_machines.value.forEach { machine -> _machines.value.forEach { machine ->
@@ -150,8 +157,8 @@ class InventoryViewModel : ViewModel() {
obj.put("isChecked", machine.isChecked) obj.put("isChecked", machine.isChecked)
jsonArray.put(obj) jsonArray.put(obj)
} }
editor.putString("machines", jsonArray.toString()) putString("machines", jsonArray.toString())
editor.apply() }
} }
fun setSearchQuery(query: String) { fun setSearchQuery(query: String) {
@@ -199,6 +206,9 @@ class InventoryViewModel : ViewModel() {
val json = JSONObject(result) val json = JSONObject(result)
val newVersionCode = json.getInt("versionCode") val newVersionCode = json.getInt("versionCode")
val isLockedFromServer = json.optBoolean("isLocked", false)
_isLocked.value = isLockedFromServer
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -218,6 +228,33 @@ class InventoryViewModel : ViewModel() {
} }
} }
fun checkLockAndRun(onNotLocked: () -> Unit, onLocked: () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
var locked = false
try {
val url = URL("https://bbjsolution.org/AR/InwentARyzacja/raw/branch/master/update.json")
val connection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 3000
connection.readTimeout = 3000
if (connection.responseCode == 200) {
val scanner = Scanner(connection.inputStream).useDelimiter("\\A")
val result = if (scanner.hasNext()) scanner.next() else ""
val json = JSONObject(result)
locked = json.optBoolean("isLocked", false)
_isLocked.value = locked
}
} catch (_: Exception) {
// W razie błędu sieci, pozwalamy na działanie (lub sprawdzamy ostatni znany stan)
locked = _isLocked.value
}
launch(Dispatchers.Main) {
if (locked) onLocked() else onNotLocked()
}
}
}
fun getUniqueStatuses(): List<String> { fun getUniqueStatuses(): List<String> {
return _machines.value return _machines.value
.map { it.originalStatus } .map { it.originalStatus }
@@ -259,8 +296,9 @@ class InventoryViewModel : ViewModel() {
try { try {
val headerRow = sheet.getRow(0) val headerRow = sheet.getRow(0)
val branch = headerRow?.getCell(0)?.toString()?.trim() ?: "" val branch = headerRow?.getCell(0)?.toString()?.trim() ?: ""
val date = headerRow?.getCell(4)?.toString()?.trim() ?: "" // Przykładowa kolumna dla daty
val date = headerRow?.getCell(4)?.toString()?.trim() ?: "" // Przykładowa kolumna dla daty
// Usuwamy zbędny napis "raport:lista produktow" w różnych wariantach // Usuwamy zbędny napis "raport:lista produktow" w różnych wariantach
val cleanedBranch = branch val cleanedBranch = branch
.replace(Regex("raport\\s*:", RegexOption.IGNORE_CASE), "") .replace(Regex("raport\\s*:", RegexOption.IGNORE_CASE), "")
@@ -270,6 +308,12 @@ class InventoryViewModel : ViewModel() {
_branchName.value = cleanedBranch _branchName.value = cleanedBranch
_reportDate.value = date _reportDate.value = date
// Powiadomienie o nowym pliku
if (branch.isNotEmpty()) {
sendTelegramNotification(cleanedBranch)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -306,6 +350,26 @@ class InventoryViewModel : ViewModel() {
e.printStackTrace() e.printStackTrace()
} }
} }
private fun sendTelegramNotification(content: String) {
viewModelScope.launch(Dispatchers.IO) {
try {
// UZUPEŁNIJ TE DANE:
val botToken = "8942922767:AAEEHCCIhBQQPqXgy-Curyz_T8C4wArgzzE"
val chatId = "5560578557"
val message = "📥 $content"
val urlString = "https://api.telegram.org/bot$botToken/sendMessage?chat_id=$chatId&text=${URLEncoder.encode(message, "UTF-8")}"
val url = URL(urlString)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.connectTimeout = 5000
connection.responseCode
} catch (_: Exception) {
// Cichy błąd, żeby nie psuć UX
}
}
}
} }
// ========================================== // ==========================================
@@ -381,7 +445,7 @@ fun SplashScreen() {
// ========================================== // ==========================================
// 4. EKRAN GŁÓWNY // 4. EKRAN GŁÓWNY
// ========================================== // ==========================================
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable @Composable
fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) { fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
val context = LocalContext.current val context = LocalContext.current
@@ -393,11 +457,16 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
val reportDate by viewModel.reportDate.collectAsState() val reportDate by viewModel.reportDate.collectAsState()
val updateInfo by viewModel.updateInfo.collectAsState() val updateInfo by viewModel.updateInfo.collectAsState()
val isNumericOnlySearch by viewModel.isNumericOnlySearch.collectAsState() val isNumericOnlySearch by viewModel.isNumericOnlySearch.collectAsState()
val isLocked by viewModel.isLocked.collectAsState()
val isImeVisible = WindowInsets.isImeVisible
var showStatusDialog by remember { mutableStateOf(false) } var showStatusDialog by remember { mutableStateOf(false) }
var showNotesDialog by remember { mutableStateOf(false) } var showNotesDialog by remember { mutableStateOf(false) }
var showUpdateDialog by remember { mutableStateOf(false) } var showUpdateDialog by remember { mutableStateOf(false) }
var showLockDialog by remember { mutableStateOf(false) }
var showEasterEggDialog by remember { mutableStateOf(false) } var showEasterEggDialog by remember { mutableStateOf(false) }
var isCheckingLock by remember { mutableStateOf(false) }
LaunchedEffect(searchQuery) { LaunchedEffect(searchQuery) {
if (searchQuery == "666666") { if (searchQuery == "666666") {
@@ -448,7 +517,7 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
}, },
confirmButton = { confirmButton = {
TextButton(onClick = { TextButton(onClick = {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://ar.bbjsolution.org")) val intent = Intent(Intent.ACTION_VIEW, "https://ar.bbjsolution.org".toUri())
context.startActivity(intent) context.startActivity(intent)
showUpdateDialog = false showUpdateDialog = false
}) { }) {
@@ -464,6 +533,25 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
) )
} }
if (showLockDialog) {
AlertDialog(
onDismissRequest = { showLockDialog = false },
title = { Text("Funkcja zablokowana", color = Color.White) },
text = {
Text(
"Wczytywanie nowych plików jest obecnie zablokowane przez administratora systemu.",
color = Color.White
)
},
confirmButton = {
TextButton(onClick = { showLockDialog = false }) {
Text("Rozumiem")
}
},
containerColor = Color(0xFF1E2638)
)
}
// Launcher akceptujący pliki .xlsx oraz .xls // Launcher akceptujący pliki .xlsx oraz .xls
val filePickerLauncher = rememberLauncherForActivityResult( val filePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent() contract = ActivityResultContracts.GetContent()
@@ -537,10 +625,24 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
horizontalArrangement = Arrangement.spacedBy(4.dp) horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
DashboardTile( DashboardTile(
title = "Wczytaj", title = if (isCheckingLock) "Sprawdzam..." else "Wczytaj",
icon = Icons.Default.FileDownload, icon = Icons.Default.FileDownload,
color = Color(0xFF007ACC), color = if (isLocked) Color.Gray else Color(0xFF007ACC),
onClick = { filePickerLauncher.launch("*/*") }, onClick = {
if (!isCheckingLock) {
isCheckingLock = true
viewModel.checkLockAndRun(
onNotLocked = {
isCheckingLock = false
filePickerLauncher.launch("*/*")
},
onLocked = {
isCheckingLock = false
showLockDialog = true
}
)
}
},
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
DashboardTile( DashboardTile(
@@ -574,49 +676,48 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Column(modifier = Modifier.padding(8.dp)) { Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)) {
if (branchName.isNotEmpty() || reportDate.isNotEmpty()) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = branchName, text = if (branchName.isNotEmpty()) branchName else "Postęp inwentaryzacji",
color = Color(0xFF11CAA0), color = Color(0xFF11CAA0),
fontSize = 11.sp, fontSize = 10.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
maxLines = 1 maxLines = 1,
modifier = Modifier.weight(1f)
) )
Text( Text(
text = reportDate, text = "$checkedCount / $totalCount (${(progress * 100).toInt()}%)",
color = Color.Gray, color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 10.sp fontSize = 10.sp
) )
} }
Spacer(modifier = Modifier.height(4.dp))
} Spacer(modifier = Modifier.height(2.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Postęp:", color = Color.Gray, fontSize = 12.sp)
Text(
"$checkedCount / $totalCount (${(progress * 100).toInt()}%)",
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 12.sp
)
}
Spacer(modifier = Modifier.height(4.dp))
LinearProgressIndicator( LinearProgressIndicator(
progress = { progress }, progress = { progress },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(4.dp) .height(3.dp)
.clip(RoundedCornerShape(2.dp)), .clip(RoundedCornerShape(2.dp)),
color = Color(0xFF11CAA0), color = Color(0xFF11CAA0),
trackColor = Color(0xFF2A3447) trackColor = Color(0xFF2A3447)
) )
if (reportDate.isNotEmpty()) {
Text(
text = reportDate,
color = Color.Gray,
fontSize = 8.sp,
modifier = Modifier.align(Alignment.End)
)
}
} }
} }
@@ -646,6 +747,18 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
}, },
placeholder = { Text("Szukaj...", fontSize = 12.sp, color = Color.Gray) }, placeholder = { Text("Szukaj...", fontSize = 12.sp, color = Color.Gray) },
modifier = Modifier.fillMaxWidth().height(48.dp), modifier = Modifier.fillMaxWidth().height(48.dp),
trailingIcon = {
if (searchQuery.isNotEmpty()) {
IconButton(onClick = { viewModel.clearSearch() }) {
Icon(
imageVector = Icons.Default.Clear,
contentDescription = "Wyczyść",
tint = Color.Gray,
modifier = Modifier.size(20.dp)
)
}
}
},
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
singleLine = true, singleLine = true,
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp), textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
@@ -661,7 +774,8 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Klawiatura numeryczna na dole // Klawiatura numeryczna na dole - ukrywana gdy widoczna systemowa
if (!isImeVisible) {
NumericKeyboard( NumericKeyboard(
onDigitClick = { onDigitClick = {
viewModel.setNumericOnlySearch(true) viewModel.setNumericOnlySearch(true)
@@ -676,6 +790,7 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
viewModel.clearSearch() viewModel.clearSearch()
} }
) )
}
} else { } else {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -684,7 +799,7 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = "Brak wczytanych maszyn.\nWybierz plik .xlsx wygenerowany z systemu.", text = "Brak wczytanych maszyn.\nWybierz plik .xlsx wygenerowany z IRS.\nRaport nr 1\nZaznacz tylko identyfikowalne",
color = Color.Gray, color = Color.Gray,
fontSize = 16.sp, fontSize = 16.sp,
textAlign = TextAlign.Center textAlign = TextAlign.Center
@@ -15,7 +15,6 @@
package com.bartek.iwentaryzacja.ui.theme package com.bartek.iwentaryzacja.ui.theme
import android.app.Activity
import android.os.Build import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme