6 Commits
Author SHA1 Message Date
rapafun 3c5b28bb4f Update update.json 2026-08-16 12:13:01 +00:00
rapafun 1d34801eca Update update.json 2026-08-15 18:46:17 +00:00
rapafun 570c9d33d8 Update update.json 2026-08-15 18:38:27 +00:00
rapafun de01fdb359 Merge remote-tracking branch 'main/master' 2026-08-15 19:36:53 +02:00
rapafun 5a128ffaa7 ikona 2026-08-15 19:36:18 +02:00
rapafun 47578c01c7 Update update.json 2026-08-15 17:16:00 +00:00
16 changed files with 844 additions and 25 deletions
+7
View File
@@ -4,6 +4,13 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-08-15T15:00:18.810512300Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\rapaf\.android\avd\Pixel_9a.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection /> <DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="jbr-25" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="jbr-25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
+12 -1
View File
@@ -11,7 +11,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "com.bartek.iwentaryzacja" applicationId = "com.bartek.iwentaryzacja"
minSdk = 24 minSdk = 26
targetSdk = 37 targetSdk = 37
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"
@@ -36,6 +36,17 @@ 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
implementation("org.apache.poi:poi:5.2.3")
implementation("org.apache.poi:poi-ooxml:5.2.3")
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)
+14
View File
@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@@ -11,6 +14,17 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.IwentARyzacja"> android:theme="@style/Theme.IwentARyzacja">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

@@ -1,28 +1,705 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.bartek.iwentaryzacja 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.Bundle
import android.os.Environment
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.*
import androidx.compose.material3.Scaffold import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable 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.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.draw.clip
import com.bartek.iwentaryzacja.ui.theme.IwentARyzacjaTheme 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<List<Machine>>(emptyList())
val machines: StateFlow<List<Machine>> = _machines.asStateFlow()
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
private val _filterMissing = MutableStateFlow(false)
val filterMissing: StateFlow<Boolean> = _filterMissing.asStateFlow()
private val _notes = MutableStateFlow("")
val notes: StateFlow<String> = _notes.asStateFlow()
private val _updateInfo = MutableStateFlow<UpdateInfo?>(null)
val updateInfo: StateFlow<UpdateInfo?> = _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<String> {
return _machines.value
.map { it.originalStatus }
.filter { it.isNotEmpty() }
.distinct()
.sorted()
}
fun markMachinesByStatuses(statuses: Set<String>, 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<Machine>()
// 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() { 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
ContextCompat.registerReceiver(
this,
onDownloadComplete,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
ContextCompat.RECEIVER_EXPORTED
)
setContent { setContent {
IwentARyzacjaTheme { MaterialTheme(
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> colorScheme = darkColorScheme(
Greeting( primary = Color(0xFF007ACC),
name = "Android", background = Color(0xFF121824),
modifier = Modifier.padding(innerPadding) 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<String>,
onDismiss: () -> Unit,
onConfirm: (Set<String>) -> Unit
) {
var selectedStatuses by remember { mutableStateOf(setOf<String>()) }
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 @Composable
fun Greeting(name: String, modifier: Modifier = Modifier) { fun KeyButton(
Text( label: String,
text = "Hello $name!", onClick: () -> Unit,
modifier = modifier 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 @Composable
fun GreetingPreview() { fun MachineItem(
IwentARyzacjaTheme { machine: Machine,
Greeting("Android") 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)
)
}
}
} }
} }
@@ -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 <https://www.gnu.org/licenses/>.
*/
package com.bartek.iwentaryzacja.ui.theme package com.bartek.iwentaryzacja.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -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 <https://www.gnu.org/licenses/>.
*/
package com.bartek.iwentaryzacja.ui.theme package com.bartek.iwentaryzacja.ui.theme
import android.app.Activity import android.app.Activity
@@ -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 <https://www.gnu.org/licenses/>.
*/
package com.bartek.iwentaryzacja.ui.theme package com.bartek.iwentaryzacja.ui.theme
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="." />
<cache-path name="cache_files" path="." />
<files-path name="internal_files" path="." />
</paths>
+2 -2
View File
@@ -1,4 +1,4 @@
{ {
"versionCode": 2, "versionCode": 1,
"url": "https://twoja-strona.pl/sciezka/do/aplikacji.apk" "url": "bbjsolution.org/AR/InwentARyzacja/releases/download/ver1.1/ver1.1.apk"
} }