11 Commits
Author SHA1 Message Date
rapafun 7e947374b0 Merge remote-tracking branch 'main/master' 2026-08-18 18:18:30 +02:00
rapafun 889b1b03eb ver 1.2 2026-08-18 18:18:12 +02:00
rapafun 68c4e857a3 Update update.json 2026-08-17 16:30:48 +00:00
rapafun 54694ddf5c Update update.json 2026-08-17 16:27:30 +00:00
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
12 changed files with 229 additions and 115 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-08-15T15:00:18.810512300Z">
<DropdownSelection timestamp="2026-08-16T12:33:41.457151500Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\rapaf\.android\avd\Pixel_9a.avd" />
+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
@@ -14,7 +14,7 @@ android {
minSdk = 26
targetSdk = 37
versionCode = 1
versionName = "1.0"
versionName = "1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
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.
-11
View File
@@ -3,7 +3,6 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:allowBackup="true"
@@ -15,16 +14,6 @@
android:supportsRtl="true"
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
android:name=".MainActivity"
android:exported="true"
@@ -15,15 +15,10 @@
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
@@ -49,8 +44,6 @@ 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
@@ -59,10 +52,9 @@ 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.JSONArray
import org.json.JSONObject
import java.io.File
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
@@ -98,61 +90,62 @@ class InventoryViewModel : ViewModel() {
private val _updateInfo = MutableStateFlow<UpdateInfo?>(null)
val updateInfo: StateFlow<UpdateInfo?> = _updateInfo.asStateFlow()
data class UpdateInfo(val versionCode: Int, val apkUrl: String)
data class UpdateInfo(val versionCode: Int)
fun checkForUpdates(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
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 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)
}
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()
}
}
}
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)
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(newNotes: String) {
fun setNotes(context: Context, newNotes: String) {
_notes.value = newNotes
saveState(context)
}
fun appendDigit(digit: String) {
@@ -173,6 +166,39 @@ class InventoryViewModel : ViewModel() {
_filterMissing.value = !_filterMissing.value
}
fun checkForUpdates(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
try {
val url = URL("https://bbjsolution.org/AR/InwentARyzacja/raw/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 packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
@Suppress("DEPRECATION")
val currentVersionCode = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
packageInfo.longVersionCode.toInt()
} else {
packageInfo.versionCode
}
if (newVersionCode > currentVersionCode) {
_updateInfo.value = UpdateInfo(newVersionCode)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun getUniqueStatuses(): List<String> {
return _machines.value
.map { it.originalStatus }
@@ -181,7 +207,7 @@ class InventoryViewModel : ViewModel() {
.sorted()
}
fun markMachinesByStatuses(statuses: Set<String>, isChecked: Boolean) {
fun markMachinesByStatuses(context: Context, statuses: Set<String>, isChecked: Boolean) {
_machines.value = _machines.value.map { machine ->
if (statuses.contains(machine.originalStatus)) {
machine.copy(isChecked = isChecked)
@@ -189,9 +215,10 @@ class InventoryViewModel : ViewModel() {
machine
}
}
saveState(context)
}
fun toggleMachineCheck(machineId: String) {
fun toggleMachineCheck(context: Context, machineId: String) {
_machines.value = _machines.value.map { machine ->
if (machine.id == machineId) {
machine.copy(isChecked = !machine.isChecked)
@@ -199,10 +226,11 @@ class InventoryViewModel : ViewModel() {
machine
}
}
saveState(context)
}
// Parser zoptymalizowany pod strukturę raportu z systemu
fun parseXlsFile(inputStream: InputStream) {
fun parseXlsFile(context: Context, inputStream: InputStream) {
try {
// WorkbookFactory automatycznie obsługuje zarówno .xls jak i .xlsx
val workbook = WorkbookFactory.create(inputStream)
@@ -232,6 +260,7 @@ class InventoryViewModel : ViewModel() {
}
_machines.value = parsedList
workbook.close()
saveState(context)
} catch (e: Exception) {
e.printStackTrace()
}
@@ -242,38 +271,10 @@ class InventoryViewModel : ViewModel() {
// 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 {
MaterialTheme(
colorScheme = darkColorScheme(
@@ -291,10 +292,6 @@ class MainActivity : ComponentActivity() {
}
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(onDownloadComplete)
}
}
// ==========================================
@@ -315,6 +312,7 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
var showUpdateDialog by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.loadState(context)
viewModel.checkForUpdates(context)
}
@@ -324,17 +322,23 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
}
}
if (showUpdateDialog && updateInfo != null) {
if (showUpdateDialog) {
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) },
title = { Text("Nowa wersja dostępna", color = Color.White) },
text = {
Text(
"Dostępna jest nowsza wersja aplikacji. Pobierz ją ze strony:\nar.bbjsolution.org",
color = Color.White
)
},
confirmButton = {
Button(onClick = {
viewModel.downloadAndInstall(context, updateInfo!!.apkUrl)
TextButton(onClick = {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://ar.bbjsolution.org"))
context.startActivity(intent)
showUpdateDialog = false
}) {
Text("Pobierz")
Text("Otwórz stronę")
}
},
dismissButton = {
@@ -352,7 +356,7 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
) { uri: Uri? ->
uri?.let {
context.contentResolver.openInputStream(it)?.use { inputStream ->
viewModel.parseXlsFile(inputStream)
viewModel.parseXlsFile(context, inputStream)
}
}
}
@@ -362,7 +366,7 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
statuses = viewModel.getUniqueStatuses(),
onDismiss = { showStatusDialog = false },
onConfirm = { selectedStatuses ->
viewModel.markMachinesByStatuses(selectedStatuses, true)
viewModel.markMachinesByStatuses(context, selectedStatuses, true)
showStatusDialog = false
}
)
@@ -371,7 +375,7 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
if (showNotesDialog) {
NotesDialog(
notes = notes,
onNotesChange = { viewModel.setNotes(it) },
onNotesChange = { viewModel.setNotes(context, it) },
onDismiss = { showNotesDialog = false }
)
}
@@ -484,7 +488,7 @@ fun InventoryScreen(viewModel: InventoryViewModel = viewModel()) {
items(filteredMachines, key = { it.id }) { machine ->
MachineItem(
machine = machine,
onToggle = { viewModel.toggleMachineCheck(machine.id) }
onToggle = { viewModel.toggleMachineCheck(context, machine.id) }
)
}
}
@@ -772,7 +776,7 @@ fun MachineItem(
if (machine.originalStatus.isNotEmpty()) {
Text(
text = machine.originalStatus,
fontSize = 10.sp,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF11CAA0),
modifier = Modifier.padding(start = 8.dp)
-6
View File
@@ -1,6 +0,0 @@
<?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>
+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>
+2 -2
View File
@@ -1,4 +1,4 @@
{
"versionCode": 2,
"url": "bbjsolution.org/AR/InwentARyzacja/releases/download/ver1/ver1.apk"
"versionCode": 4,
"url": "bbjsolution.org/AR/InwentARyzacja/releases/download/ver1.1/ver1.1.apk"
}