This commit is contained in:
2026-08-18 19:49:28 +02:00
parent 7e947374b0
commit abd70c3c54
2 changed files with 167 additions and 13 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ android {
applicationId = "com.bartek.iwentaryzacja"
minSdk = 26
targetSdk = 37
versionCode = 1
versionCode = 4
versionName = "1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -24,6 +24,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -48,6 +49,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -87,6 +89,15 @@ class InventoryViewModel : ViewModel() {
private val _notes = MutableStateFlow("")
val notes: StateFlow<String> = _notes.asStateFlow()
private val _branchName = MutableStateFlow("")
val branchName: StateFlow<String> = _branchName.asStateFlow()
private val _reportDate = MutableStateFlow("")
val reportDate: StateFlow<String> = _reportDate.asStateFlow()
private val _isNumericOnlySearch = MutableStateFlow(true)
val isNumericOnlySearch: StateFlow<Boolean> = _isNumericOnlySearch.asStateFlow()
private val _updateInfo = MutableStateFlow<UpdateInfo?>(null)
val updateInfo: StateFlow<UpdateInfo?> = _updateInfo.asStateFlow()
@@ -95,6 +106,8 @@ class InventoryViewModel : ViewModel() {
fun loadState(context: Context) {
val prefs = context.getSharedPreferences("inventory_prefs", Context.MODE_PRIVATE)
_notes.value = prefs.getString("notes", "") ?: ""
_branchName.value = prefs.getString("branch", "") ?: ""
_reportDate.value = prefs.getString("date", "") ?: ""
val machinesJson = prefs.getString("machines", null)
if (machinesJson != null) {
@@ -124,6 +137,8 @@ class InventoryViewModel : ViewModel() {
val prefs = context.getSharedPreferences("inventory_prefs", Context.MODE_PRIVATE)
val editor = prefs.edit()
editor.putString("notes", _notes.value)
editor.putString("branch", _branchName.value)
editor.putString("date", _reportDate.value)
val jsonArray = JSONArray()
_machines.value.forEach { machine ->
@@ -143,6 +158,10 @@ class InventoryViewModel : ViewModel() {
_searchQuery.value = query
}
fun setNumericOnlySearch(isNumeric: Boolean) {
_isNumericOnlySearch.value = isNumeric
}
fun setNotes(context: Context, newNotes: String) {
_notes.value = newNotes
saveState(context)
@@ -235,6 +254,26 @@ class InventoryViewModel : ViewModel() {
// WorkbookFactory automatycznie obsługuje zarówno .xls jak i .xlsx
val workbook = WorkbookFactory.create(inputStream)
val sheet = workbook.getSheetAt(0)
// Próba pobrania nazwy oddziału i daty z nagłówka (zazwyczaj wiersz 0 lub 1)
try {
val headerRow = sheet.getRow(0)
val branch = headerRow?.getCell(0)?.toString()?.trim() ?: ""
val date = headerRow?.getCell(4)?.toString()?.trim() ?: "" // Przykładowa kolumna dla daty
// Usuwamy zbędny napis "raport:lista produktow" w różnych wariantach
val cleanedBranch = branch
.replace(Regex("raport\\s*:", RegexOption.IGNORE_CASE), "")
.replace(Regex("lista\\s*produktów", RegexOption.IGNORE_CASE), "")
.replace(Regex("lista\\s*produktow", RegexOption.IGNORE_CASE), "")
.trim()
_branchName.value = cleanedBranch
_reportDate.value = date
} catch (e: Exception) {
e.printStackTrace()
}
val parsedList = mutableListOf<Machine>()
// Dane zaczynają się od wiersza indeks 2 (po 2 wierszach nagłówka)
@@ -248,12 +287,14 @@ class InventoryViewModel : ViewModel() {
// Wczytujemy tylko, gdy pozycja ma nazwę i nr ewidencyjny
if (name.isNotEmpty() && id.isNotEmpty()) {
val isRented = status.contains("wypo", ignoreCase = true)
parsedList.add(
Machine(
id = id,
name = name,
category = category,
originalStatus = status
originalStatus = status,
isChecked = isRented // Automatycznie zaznaczamy wypożyczone
)
)
}
@@ -283,15 +324,58 @@ class MainActivity : ComponentActivity() {
surface = Color(0xFF1E2638)
)
) {
var showSplashScreen by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
delay(2000) // Ekran startowy przez 2 sekundy
showSplashScreen = false
}
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
if (showSplashScreen) {
SplashScreen()
} else {
InventoryScreen()
}
}
}
}
}
}
@Composable
fun SplashScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFF121824)),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
imageVector = Icons.Default.Inventory,
contentDescription = null,
tint = Color(0xFF007ACC),
modifier = Modifier.size(100.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "inwentARyzacja",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "System Zarządzania Sprzętem",
fontSize = 14.sp,
color = Color.Gray
)
}
}
}
// ==========================================
@@ -305,11 +389,41 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
val searchQuery by viewModel.searchQuery.collectAsState()
val filterMissing by viewModel.filterMissing.collectAsState()
val notes by viewModel.notes.collectAsState()
val branchName by viewModel.branchName.collectAsState()
val reportDate by viewModel.reportDate.collectAsState()
val updateInfo by viewModel.updateInfo.collectAsState()
val isNumericOnlySearch by viewModel.isNumericOnlySearch.collectAsState()
var showStatusDialog by remember { mutableStateOf(false) }
var showNotesDialog by remember { mutableStateOf(false) }
var showUpdateDialog by remember { mutableStateOf(false) }
var showEasterEggDialog by remember { mutableStateOf(false) }
LaunchedEffect(searchQuery) {
if (searchQuery == "666666") {
showEasterEggDialog = true
viewModel.clearSearch()
}
}
if (showEasterEggDialog) {
AlertDialog(
onDismissRequest = { showEasterEggDialog = false },
title = { Text("O aplikacji", color = Color.White) },
text = {
Text(
"Aplikacja zrobiona w całości przez Bartka J, w wolnym czasie i na prywatnych komputerach",
color = Color.White
)
},
confirmButton = {
TextButton(onClick = { showEasterEggDialog = false }) {
Text("OK")
}
},
containerColor = Color(0xFF1E2638)
)
}
LaunchedEffect(Unit) {
viewModel.loadState(context)
@@ -380,13 +494,17 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
)
}
val filteredMachines = remember(machines, searchQuery, filterMissing) {
val filteredMachines = remember(machines, searchQuery, filterMissing, isNumericOnlySearch) {
var list = if (searchQuery.isBlank()) machines
else machines.filter {
if (isNumericOnlySearch) {
it.id.contains(searchQuery, ignoreCase = true)
} else {
it.name.contains(searchQuery, ignoreCase = true) ||
it.id.contains(searchQuery, ignoreCase = true) ||
it.category.contains(searchQuery, ignoreCase = true)
}
}
if (filterMissing) {
list = list.filter { !it.isChecked }
@@ -394,8 +512,12 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
list
}
val checkedCount = machines.count { it.isChecked }
val totalCount = machines.size
val checkedCount = machines.count {
it.isChecked && !it.originalStatus.contains("wypo", ignoreCase = true)
}
val totalCount = machines.count {
!it.originalStatus.contains("wypo", ignoreCase = true)
}
val progress = if (totalCount > 0) checkedCount.toFloat() / totalCount.toFloat() else 0f
Scaffold(
@@ -453,6 +575,26 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier.padding(8.dp)) {
if (branchName.isNotEmpty() || reportDate.isNotEmpty()) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = branchName,
color = Color(0xFF11CAA0),
fontSize = 11.sp,
fontWeight = FontWeight.Bold,
maxLines = 1
)
Text(
text = reportDate,
color = Color.Gray,
fontSize = 10.sp
)
}
Spacer(modifier = Modifier.height(4.dp))
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
@@ -498,7 +640,10 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
// Bardzo mały pasek szukania nad klawiaturą
OutlinedTextField(
value = searchQuery,
onValueChange = { viewModel.setSearchQuery(it) },
onValueChange = {
viewModel.setSearchQuery(it)
viewModel.setNumericOnlySearch(false)
},
placeholder = { Text("Szukaj...", fontSize = 12.sp, color = Color.Gray) },
modifier = Modifier.fillMaxWidth().height(48.dp),
shape = RoundedCornerShape(8.dp),
@@ -518,9 +663,18 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
// Klawiatura numeryczna na dole
NumericKeyboard(
onDigitClick = { viewModel.appendDigit(it) },
onBackspace = { viewModel.backspace() },
onClear = { viewModel.clearSearch() }
onDigitClick = {
viewModel.setNumericOnlySearch(true)
viewModel.appendDigit(it)
},
onBackspace = {
viewModel.setNumericOnlySearch(true)
viewModel.backspace()
},
onClear = {
viewModel.setNumericOnlySearch(true)
viewModel.clearSearch()
}
)
} else {
Box(