diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index ca16a99..bf39c02 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,13 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index a7fab1c..29a5cc0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c1817af..1fd91e3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,7 +11,7 @@ android { defaultConfig { applicationId = "com.bartek.iwentaryzacja" - minSdk = 24 + minSdk = 26 targetSdk = 37 versionCode = 1 versionName = "1.0" @@ -36,6 +36,17 @@ android { } 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 + implementation("org.apache.poi:poi:5.2.3") + implementation("org.apache.poi:poi-ooxml:5.2.3") implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.material3) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b508a32..fbd0327 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + + + + + + . + */ + package com.bartek.iwentaryzacja +import android.app.DownloadManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.Uri +import android.os.Build import android.os.Bundle +import android.os.Environment import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.List +import androidx.compose.material.icons.filled.* +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.bartek.iwentaryzacja.ui.theme.IwentARyzacjaTheme +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.compose.viewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.apache.poi.ss.usermodel.WorkbookFactory +import org.json.JSONObject +import java.io.File +import java.io.InputStream +import java.net.HttpURLConnection +import java.net.URL +import java.util.Scanner +// ========================================== +// 1. MODEL DANYCH MASZYNY +// ========================================== +data class Machine( + val id: String, // Nr ewid. (np. 7065/K) + val name: String, // Nazwa sprzętu + val category: String, // Kategoria (np. minikoparki) + val originalStatus: String, // Status z kolumny H + val isChecked: Boolean = false +) + +// ========================================== +// 2. VIEWMODEL (DOPASOWANY PARSER .XLSX) +// ========================================== +class InventoryViewModel : ViewModel() { + private val _machines = MutableStateFlow>(emptyList()) + val machines: StateFlow> = _machines.asStateFlow() + + private val _searchQuery = MutableStateFlow("") + val searchQuery: StateFlow = _searchQuery.asStateFlow() + + private val _filterMissing = MutableStateFlow(false) + val filterMissing: StateFlow = _filterMissing.asStateFlow() + + private val _notes = MutableStateFlow("") + val notes: StateFlow = _notes.asStateFlow() + + private val _updateInfo = MutableStateFlow(null) + val updateInfo: StateFlow = _updateInfo.asStateFlow() + + data class UpdateInfo(val versionCode: Int, val apkUrl: String) + + fun checkForUpdates(context: Context) { + viewModelScope.launch(Dispatchers.IO) { + try { + val url = URL("https://bbjsolution.org/AR/InwentARyzacja/src/branch/master/update.json") + val connection = url.openConnection() as HttpURLConnection + connection.connectTimeout = 5000 + connection.readTimeout = 5000 + + if (connection.responseCode == 200) { + val scanner = Scanner(connection.inputStream).useDelimiter("\\A") + val result = if (scanner.hasNext()) scanner.next() else "" + val json = JSONObject(result) + + val newVersionCode = json.getInt("versionCode") + val apkUrl = json.getString("url") + + val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) + @Suppress("DEPRECATION") + val currentVersionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + packageInfo.longVersionCode.toInt() + } else { + packageInfo.versionCode + } + + if (newVersionCode > currentVersionCode) { + _updateInfo.value = UpdateInfo(newVersionCode, apkUrl) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + fun downloadAndInstall(context: Context, apkUrl: String) { + val request = DownloadManager.Request(Uri.parse(apkUrl)) + .setTitle("Aktualizacja inwentARyzacja") + .setDescription("Pobieranie nowej wersji...") + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + .setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, "update.apk") + .setAllowedOverMetered(true) + .setAllowedOverRoaming(true) + + val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + downloadManager.enqueue(request) + } + + fun setSearchQuery(query: String) { + _searchQuery.value = query + } + + fun setNotes(newNotes: String) { + _notes.value = newNotes + } + + fun appendDigit(digit: String) { + _searchQuery.value += digit + } + + fun backspace() { + if (_searchQuery.value.isNotEmpty()) { + _searchQuery.value = _searchQuery.value.dropLast(1) + } + } + + fun clearSearch() { + _searchQuery.value = "" + } + + fun toggleFilterMissing() { + _filterMissing.value = !_filterMissing.value + } + + fun getUniqueStatuses(): List { + return _machines.value + .map { it.originalStatus } + .filter { it.isNotEmpty() } + .distinct() + .sorted() + } + + fun markMachinesByStatuses(statuses: Set, isChecked: Boolean) { + _machines.value = _machines.value.map { machine -> + if (statuses.contains(machine.originalStatus)) { + machine.copy(isChecked = isChecked) + } else { + machine + } + } + } + + fun toggleMachineCheck(machineId: String) { + _machines.value = _machines.value.map { machine -> + if (machine.id == machineId) { + machine.copy(isChecked = !machine.isChecked) + } else { + machine + } + } + } + + // Parser zoptymalizowany pod strukturę raportu z systemu + fun parseXlsFile(inputStream: InputStream) { + try { + // WorkbookFactory automatycznie obsługuje zarówno .xls jak i .xlsx + val workbook = WorkbookFactory.create(inputStream) + val sheet = workbook.getSheetAt(0) + val parsedList = mutableListOf() + + // Dane zaczynają się od wiersza indeks 2 (po 2 wierszach nagłówka) + for (rowIndex in 2..sheet.lastRowNum) { + val row = sheet.getRow(rowIndex) ?: continue + + val category = row.getCell(1)?.toString()?.trim() ?: "" + val id = row.getCell(2)?.toString()?.trim() ?: "" + val name = row.getCell(3)?.toString()?.trim() ?: "" + val status = row.getCell(7)?.toString()?.trim() ?: "" + + // Wczytujemy tylko, gdy pozycja ma nazwę i nr ewidencyjny + if (name.isNotEmpty() && id.isNotEmpty()) { + parsedList.add( + Machine( + id = id, + name = name, + category = category, + originalStatus = status + ) + ) + } + } + _machines.value = parsedList + workbook.close() + } catch (e: Exception) { + e.printStackTrace() + } + } +} + +// ========================================== +// 3. GŁÓWNA AKTYWNOŚĆ +// ========================================== class MainActivity : ComponentActivity() { + private val onDownloadComplete = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) + if (id != -1L) { + installApk(context) + } + } + } + + private fun installApk(context: Context) { + val file = File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "update.apk") + if (file.exists()) { + val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) + val intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(uri, "application/vnd.android.package-archive") + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() + + ContextCompat.registerReceiver( + this, + onDownloadComplete, + IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), + ContextCompat.RECEIVER_EXPORTED + ) + setContent { - IwentARyzacjaTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Greeting( - name = "Android", - modifier = Modifier.padding(innerPadding) + MaterialTheme( + colorScheme = darkColorScheme( + primary = Color(0xFF007ACC), + background = Color(0xFF121824), + surface = Color(0xFF1E2638) + ) + ) { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + InventoryScreen() + } + } + } + } + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(onDownloadComplete) + } +} + +// ========================================== +// 4. EKRAN GŁÓWNY +// ========================================== +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) { + val context = LocalContext.current + val machines by viewModel.machines.collectAsState() + val searchQuery by viewModel.searchQuery.collectAsState() + val filterMissing by viewModel.filterMissing.collectAsState() + val notes by viewModel.notes.collectAsState() + val updateInfo by viewModel.updateInfo.collectAsState() + + var showStatusDialog by remember { mutableStateOf(false) } + var showNotesDialog by remember { mutableStateOf(false) } + var showUpdateDialog by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + viewModel.checkForUpdates(context) + } + + LaunchedEffect(updateInfo) { + if (updateInfo != null) { + showUpdateDialog = true + } + } + + if (showUpdateDialog && updateInfo != null) { + AlertDialog( + onDismissRequest = { showUpdateDialog = false }, + title = { Text("Dostępna aktualizacja", color = Color.White) }, + text = { Text("Czy chcesz pobrać i zainstalować nową wersję aplikacji?", color = Color.White) }, + confirmButton = { + Button(onClick = { + viewModel.downloadAndInstall(context, updateInfo!!.apkUrl) + showUpdateDialog = false + }) { + Text("Pobierz") + } + }, + dismissButton = { + TextButton(onClick = { showUpdateDialog = false }) { + Text("Później") + } + }, + containerColor = Color(0xFF1E2638) + ) + } + + // Launcher akceptujący pliki .xlsx oraz .xls + val filePickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent() + ) { uri: Uri? -> + uri?.let { + context.contentResolver.openInputStream(it)?.use { inputStream -> + viewModel.parseXlsFile(inputStream) + } + } + } + + if (showStatusDialog) { + StatusSelectionDialog( + statuses = viewModel.getUniqueStatuses(), + onDismiss = { showStatusDialog = false }, + onConfirm = { selectedStatuses -> + viewModel.markMachinesByStatuses(selectedStatuses, true) + showStatusDialog = false + } + ) + } + + if (showNotesDialog) { + NotesDialog( + notes = notes, + onNotesChange = { viewModel.setNotes(it) }, + onDismiss = { showNotesDialog = false } + ) + } + + val filteredMachines = remember(machines, searchQuery, filterMissing) { + var list = if (searchQuery.isBlank()) machines + else machines.filter { + 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 } + } + list + } + + val checkedCount = machines.count { it.isChecked } + val totalCount = machines.size + val progress = if (totalCount > 0) checkedCount.toFloat() / totalCount.toFloat() else 0f + + Scaffold( + modifier = Modifier.fillMaxSize(), + containerColor = MaterialTheme.colorScheme.background, + contentWindowInsets = WindowInsets.safeDrawing + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + // Cztery małe kafelki w jednej linii + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + DashboardTile( + title = "Wczytaj", + icon = Icons.Default.FileDownload, + color = Color(0xFF007ACC), + onClick = { filePickerLauncher.launch("*/*") }, + modifier = Modifier.weight(1f) + ) + DashboardTile( + title = if (filterMissing) "Wszystkie" else "Brakujące", + icon = if (filterMissing) Icons.AutoMirrored.Filled.List else Icons.Default.Search, + color = if (filterMissing) Color(0xFFFF9800) else Color(0xFFE91E63), + onClick = { viewModel.toggleFilterMissing() }, + modifier = Modifier.weight(1f) + ) + DashboardTile( + title = "Statusy", + icon = Icons.Default.Info, + color = Color(0xFF9C27B0), + onClick = { showStatusDialog = true }, + modifier = Modifier.weight(1f) + ) + DashboardTile( + title = "Dopisz", + icon = Icons.Default.EditNote, + color = Color(0xFF4CAF50), + onClick = { showNotesDialog = true }, + modifier = Modifier.weight(1f) + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + if (machines.isNotEmpty()) { + Card( + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), + shape = RoundedCornerShape(8.dp), + modifier = Modifier.fillMaxWidth() + ) { + Column(modifier = Modifier.padding(8.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( + progress = { progress }, + modifier = Modifier + .fillMaxWidth() + .height(4.dp) + .clip(RoundedCornerShape(2.dp)), + color = Color(0xFF11CAA0), + trackColor = Color(0xFF2A3447) + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Lista zajmuje elastyczną przestrzeń, ale zostanie ograniczona przez klawiaturę + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.weight(1f) // Używamy weight, aby zajęła resztę miejsca przed klawiaturą + ) { + items(filteredMachines, key = { it.id }) { machine -> + MachineItem( + machine = machine, + onToggle = { viewModel.toggleMachineCheck(machine.id) } + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Bardzo mały pasek szukania nad klawiaturą + OutlinedTextField( + value = searchQuery, + onValueChange = { viewModel.setSearchQuery(it) }, + placeholder = { Text("Szukaj...", fontSize = 12.sp, color = Color.Gray) }, + modifier = Modifier.fillMaxWidth().height(48.dp), + shape = RoundedCornerShape(8.dp), + singleLine = true, + textStyle = LocalTextStyle.current.copy(fontSize = 13.sp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), + unfocusedBorderColor = Color(0xFF2A3447), + focusedContainerColor = MaterialTheme.colorScheme.surface, + unfocusedContainerColor = MaterialTheme.colorScheme.surface, + focusedTextColor = Color.White, + unfocusedTextColor = Color.White + ) + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // Klawiatura numeryczna na dole + NumericKeyboard( + onDigitClick = { viewModel.appendDigit(it) }, + onBackspace = { viewModel.backspace() }, + onClear = { viewModel.clearSearch() } + ) + } else { + Box( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "Brak wczytanych maszyn.\nWybierz plik .xlsx wygenerowany z systemu.", + color = Color.Gray, + fontSize = 16.sp, + textAlign = TextAlign.Center + ) + } + } + } + } +} + +// ========================================== +// 5. POJEDYNCZY ELEMENT LISTY +// ========================================== +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DashboardTile( + title: String, + icon: ImageVector, + color: Color, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Card( + onClick = onClick, + modifier = modifier.height(70.dp), // Zmniejszona wysokość + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = color.copy(alpha = 0.15f)), + border = androidx.compose.foundation.BorderStroke(1.dp, color.copy(alpha = 0.3f)) + ) { + Column( + modifier = Modifier.fillMaxSize().padding(4.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon(icon, contentDescription = null, tint = color, modifier = Modifier.size(24.dp)) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = title, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + textAlign = TextAlign.Center, + lineHeight = 12.sp + ) + } + } +} + +@Composable +fun StatusSelectionDialog( + statuses: List, + onDismiss: () -> Unit, + onConfirm: (Set) -> Unit +) { + var selectedStatuses by remember { mutableStateOf(setOf()) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Wybierz statusy do zaznaczenia") }, + text = { + if (statuses.isEmpty()) { + Text("Brak wczytanych maszyn ze statusami.") + } else { + LazyColumn(modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp)) { + items(statuses) { status -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + selectedStatuses = if (selectedStatuses.contains(status)) { + selectedStatuses - status + } else { + selectedStatuses + status + } + } + .padding(vertical = 8.dp) + ) { + Checkbox( + checked = selectedStatuses.contains(status), + onCheckedChange = null // Obsłużone w Row.clickable + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(status, color = Color.White) + } + } + } + } + }, + confirmButton = { + TextButton(onClick = { onConfirm(selectedStatuses) }) { + Text("Zastosuj") + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text("Anuluj") + } + }, + containerColor = Color(0xFF1E2638), + titleContentColor = Color.White, + textContentColor = Color.White + ) +} + +@Composable +fun NotesDialog( + notes: String, + onNotesChange: (String) -> Unit, + onDismiss: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Notatnik", color = Color.White) }, + text = { + OutlinedTextField( + value = notes, + onValueChange = onNotesChange, + modifier = Modifier.fillMaxWidth().height(200.dp), + placeholder = { Text("Wpisz notatki tutaj...", color = Color.Gray) }, + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = Color.White, + unfocusedTextColor = Color.White, + focusedContainerColor = Color(0xFF2A3447), + unfocusedContainerColor = Color(0xFF2A3447) + ) + ) + }, + confirmButton = { + TextButton(onClick = onDismiss) { + Text("Zamknij") + } + }, + containerColor = Color(0xFF1E2638) + ) +} + +@Composable +fun NumericKeyboard( + onDigitClick: (String) -> Unit, + onBackspace: () -> Unit, + onClear: () -> Unit +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + val buttons = listOf( + listOf("1", "2", "3"), + listOf("4", "5", "6"), + listOf("7", "8", "9"), + listOf("C", "0", "⌫") + ) + + buttons.forEach { row -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + row.forEach { label -> + val isSpecial = label == "C" || label == "⌫" + KeyButton( + label = label, + onClick = { + when (label) { + "C" -> onClear() + "⌫" -> onBackspace() + else -> onDigitClick(label) + } + }, + color = if (isSpecial) Color(0xFF2A3447) else Color(0xFF1E2638), + textColor = if (label == "⌫") Color(0xFFE91E63) else if (label == "C") Color(0xFFFF9800) else Color.White, + modifier = Modifier.weight(1f) ) } } @@ -31,17 +708,76 @@ class MainActivity : ComponentActivity() { } @Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) +fun KeyButton( + label: String, + onClick: () -> Unit, + color: Color, + textColor: Color, + modifier: Modifier = Modifier +) { + Surface( + onClick = onClick, + modifier = modifier.height(54.dp), + shape = RoundedCornerShape(12.dp), + color = color, + border = androidx.compose.foundation.BorderStroke(1.dp, Color.White.copy(alpha = 0.1f)) + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = label, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = textColor + ) + } + } } -@Preview(showBackground = true) @Composable -fun GreetingPreview() { - IwentARyzacjaTheme { - Greeting("Android") +fun MachineItem( + machine: Machine, + onToggle: () -> Unit +) { + Card( + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = if (machine.isChecked) Color(0xFF183B32) else MaterialTheme.colorScheme.surface + ), + modifier = Modifier + .fillMaxWidth() + .clickable { onToggle() } + ) { + Row( + modifier = Modifier + .padding(8.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = machine.id, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + color = Color.White + ) + Text( + text = machine.name, + fontSize = 11.sp, + color = Color.LightGray, + maxLines = 1 + ) + } + + if (machine.originalStatus.isNotEmpty()) { + Text( + text = machine.originalStatus, + fontSize = 10.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF11CAA0), + modifier = Modifier.padding(start = 8.dp) + ) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Color.kt b/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Color.kt index 4525372..aa0685b 100644 --- a/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Color.kt +++ b/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Color.kt @@ -1,3 +1,18 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.bartek.iwentaryzacja.ui.theme import androidx.compose.ui.graphics.Color diff --git a/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Theme.kt b/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Theme.kt index a428e0d..6e07a0c 100644 --- a/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Theme.kt +++ b/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Theme.kt @@ -1,3 +1,18 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.bartek.iwentaryzacja.ui.theme import android.app.Activity diff --git a/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Type.kt b/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Type.kt index 5a3ddb2..64e9b89 100644 --- a/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Type.kt +++ b/app/src/main/java/com/bartek/iwentaryzacja/ui/theme/Type.kt @@ -1,3 +1,18 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.bartek.iwentaryzacja.ui.theme import androidx.compose.material3.Typography diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..80cce83 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..3a78c05 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..8e5a309 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..715c80d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..9279ba9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..7f68867 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file