12 Commits
Author SHA1 Message Date
rapafun 3a34979173 Add index.html 2026-08-16 12:44:21 +00:00
rapafun b79a44f2b0 Merge remote-tracking branch 'main/master' 2026-08-16 14:28:12 +02:00
rapafun cd09e21230 ver 1.1 2026-08-16 14:27:59 +02:00
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 20c683370d ikona 2026-08-15 19:38:00 +02: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
rapafun 9d13101ceb Add update.json 2026-08-15 17:02:08 +00:00
rapafun e40c50f6c0 ikona 2026-08-15 16:44:03 +02:00
34 changed files with 954 additions and 195 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>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>
+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)
Binary file not shown.
Binary file not shown.
+37
View File
@@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.bartek.iwentaryzacja",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 26
}
Binary file not shown.
+3
View File
@@ -2,6 +2,8 @@
<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" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@@ -11,6 +13,7 @@
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">
<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,47 +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.content.Context
import android.net.Uri
import android.os.Bundle import android.os.Bundle
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.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.apache.poi.ss.usermodel.WorkbookFactory
import org.json.JSONArray
import org.json.JSONObject
import java.io.InputStream
// ==========================================
// 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()
fun loadState(context: Context) {
val prefs = context.getSharedPreferences("inventory_prefs", Context.MODE_PRIVATE)
_notes.value = prefs.getString("notes", "") ?: ""
val machinesJson = prefs.getString("machines", null)
if (machinesJson != null) {
try {
val jsonArray = JSONArray(machinesJson)
val loadedList = mutableListOf<Machine>()
for (i in 0 until jsonArray.length()) {
val obj = jsonArray.getJSONObject(i)
loadedList.add(
Machine(
id = obj.getString("id"),
name = obj.getString("name"),
category = obj.getString("category"),
originalStatus = obj.getString("originalStatus"),
isChecked = obj.getBoolean("isChecked")
)
)
}
_machines.value = loadedList
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun saveState(context: Context) {
val prefs = context.getSharedPreferences("inventory_prefs", Context.MODE_PRIVATE)
val editor = prefs.edit()
editor.putString("notes", _notes.value)
val jsonArray = JSONArray()
_machines.value.forEach { machine ->
val obj = JSONObject()
obj.put("id", machine.id)
obj.put("name", machine.name)
obj.put("category", machine.category)
obj.put("originalStatus", machine.originalStatus)
obj.put("isChecked", machine.isChecked)
jsonArray.put(obj)
}
editor.putString("machines", jsonArray.toString())
editor.apply()
}
fun setSearchQuery(query: String) {
_searchQuery.value = query
}
fun setNotes(context: Context, newNotes: String) {
_notes.value = newNotes
saveState(context)
}
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(context: Context, statuses: Set<String>, isChecked: Boolean) {
_machines.value = _machines.value.map { machine ->
if (statuses.contains(machine.originalStatus)) {
machine.copy(isChecked = isChecked)
} else {
machine
}
}
saveState(context)
}
fun toggleMachineCheck(context: Context, machineId: String) {
_machines.value = _machines.value.map { machine ->
if (machine.id == machineId) {
machine.copy(isChecked = !machine.isChecked)
} else {
machine
}
}
saveState(context)
}
// Parser zoptymalizowany pod strukturę raportu z systemu
fun parseXlsFile(context: Context, 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()
saveState(context)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// ==========================================
// 3. GŁÓWNA AKTYWNOŚĆ
// ==========================================
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
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()
} }
} }
} }
} }
} }
// ==========================================
// 4. EKRAN GŁÓWNY
// ==========================================
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Greeting(name: String, modifier: Modifier = Modifier) { 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()
var showStatusDialog by remember { mutableStateOf(false) }
var showNotesDialog by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.loadState(context)
}
// 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(context, inputStream)
}
}
}
if (showStatusDialog) {
StatusSelectionDialog(
statuses = viewModel.getUniqueStatuses(),
onDismiss = { showStatusDialog = false },
onConfirm = { selectedStatuses ->
viewModel.markMachinesByStatuses(context, selectedStatuses, true)
showStatusDialog = false
}
)
}
if (showNotesDialog) {
NotesDialog(
notes = notes,
onNotesChange = { viewModel.setNotes(context, 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( Text(
text = "Hello $name!", "$checkedCount / $totalCount (${(progress * 100).toInt()}%)",
modifier = modifier 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(context, 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
) )
} }
@Preview(showBackground = true)
@Composable @Composable
fun GreetingPreview() { fun NotesDialog(
IwentARyzacjaTheme { notes: String,
Greeting("Android") 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)
)
}
}
}
}
}
@Composable
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
)
}
}
}
@Composable
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)
)
}
}
} }
} }
@@ -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
@@ -1,170 +1,74 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
android:width="108dp"
android:height="108dp" android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> xmlns:android="http://schemas.android.com/apk/res/android">
<path <path android:fillColor="#3DDC84"
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/> android:pathData="M0,0h108v108h-108z"/>
<path <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M9,0L9,108" <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:pathData="M19,0L19,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M29,0L29,108" <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:pathData="M39,0L39,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M49,0L49,108" <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:pathData="M59,0L59,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M69,0L69,108" <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:pathData="M79,0L79,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M89,0L89,108" <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:pathData="M99,0L99,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M0,9L108,9" <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:pathData="M0,19L108,19" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M0,29L108,29" <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector> </vector>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" /> <monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" /> <monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+77
View File
@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BBJ Solution - Pobierz Aplikację</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
}
body {
background-color: #0f172a;
color: #f8fafc;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.card {
background-color: #1e293b;
border-radius: 16px;
padding: 32px 24px;
max-width: 420px;
width: 100%;
text-align: center;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
border: 1px solid #334155;
}
h1 {
font-size: 24px;
margin-bottom: 8px;
color: #38bdf8;
}
p {
color: #94a3b8;
font-size: 14px;
margin-bottom: 24px;
line-height: 1.5;
}
.download-btn {
display: inline-block;
width: 100%;
padding: 14px 20px;
background-color: #0284c7;
color: #ffffff;
font-weight: bold;
font-size: 16px;
text-decoration: none;
border-radius: 8px;
transition: background-color 0.2s ease;
}
.download-btn:active {
background-color: #0369a1;
}
.info {
margin-top: 16px;
font-size: 12px;
color: #64748b;
}
</style>
</head>
<body>
<div class="card">
<h1>BBJ Solution</h1>
<p>Aplikacja mobilna do obsługi magazynu i urządzeń.</p>
<!-- Podmień app-release.apk na dokładną nazwę swojego pliku APK -->
<a href="app-release.apk" class="download-btn" download>Pobierz aplikację (APK)</a>
<p class="info">Po pobraniu zezwól w telefonie na instalację z nieznanych źródeł.</p>
</div>
</body>
</html>
+4
View File
@@ -0,0 +1,4 @@
{
"versionCode": 1,
"url": "bbjsolution.org/AR/InwentARyzacja/releases/download/ver1.1/ver1.1.apk"
}