From d88febe3ce095f54b1e89b7be617edb8e5c27d45 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:36:17 +0200 Subject: [PATCH 01/18] Name is not required anymore --- .../parceltracker/ui/views/AddEditParcelView.kt | 17 +++++------------ app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-th/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 11 files changed, 15 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index 43ebadb..18389b5 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -35,6 +35,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp @@ -55,9 +56,9 @@ fun AddEditParcelView( onCompleted: (Parcel) -> Unit, ) { val isEdit = parcel != null + val context = LocalContext.current var humanName by remember { mutableStateOf(parcel?.humanName ?: "") } - var nameError by remember { mutableStateOf(false) } var trackingId by remember { mutableStateOf(parcel?.parcelId ?: "") } var idError by remember { mutableStateOf(false) } var specifyPostalCode by remember { mutableStateOf(parcel?.postalCode != null) } @@ -70,16 +71,11 @@ fun AddEditParcelView( fun validateInputs(): Boolean { // reset error states first - nameError = false idError = false serviceError = false postalCodeError = false var success = true - if (humanName.isBlank()) { - success = false - nameError = true - } if (trackingId.isBlank()) { success = false idError = true @@ -132,15 +128,12 @@ fun AddEditParcelView( value = humanName, onValueChange = { humanName = it - nameError = false }, singleLine = true, label = { Text(stringResource(R.string.parcel_name)) }, modifier = Modifier.fillMaxWidth(), - isError = nameError, - supportingText = { - if (nameError) Text(stringResource(R.string.human_name_error_text)) - }) + +) OutlinedTextField( value = trackingId, @@ -244,7 +237,7 @@ fun AddEditParcelView( onCompleted( Parcel( id = parcel?.id ?: 0, - humanName = humanName, + humanName = if (humanName.isBlank()) context.getString(R.string.undefinied_packagename) else humanName, parcelId = trackingId, service = service, postalCode = diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 666ec55..57b0790 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -17,7 +17,6 @@ Upravit Upravit zásilku Přejít zpět - Název nesmí být prázdný. Ignorovat Licence Další možnosti @@ -66,4 +65,5 @@ Sledovací ID nesmí být prázdné. Aktualizovat pouze na neměřených sítích Pokud je povoleno, bude aplikace Parcel zjišťovat aktualizace pouze na neměřených připojeních, jako je vaše domácí Wi-Fi. + Balíček diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 56d766a..e179b26 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -20,7 +20,6 @@ Paket bearbeiten Dieser Zustelldienst erfordert einen API-Schlüssel, aber es wurde keiner angegeben. Weitere Informationen finden Sie in den App-Einstellungen. Zurück - Name darf nicht leer sein. Ignorieren Lizenz Weitere Optionen @@ -73,4 +72,5 @@ Sendungsnummer darf nicht leer sein. Nur bei unbegrenzten Netzwerken aktualisieren Wenn aktiviert, sucht die App nur über unbegrenzte Verbindungen wie WLAN nach Paketupdates. + Paket diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 206a034..4df1727 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -17,7 +17,6 @@ Szerkesztés Csomag szerkesztése Vissza - A név nem lehet üres. Figyelmen kívül hagy Licenc További lehetőségek @@ -67,4 +66,5 @@ A csomagszám nem lehet üres. Csak korlátlan hálózaton való frissítés A Parcel csak akkor fog frissítéseket keresni automatikusan, ha nem forgalmidíjas hálózatra van csatlakozva a készülék, pl. otthoni Wi-Fi. + Csomag diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c4146a4..058adda 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -20,7 +20,6 @@ 荷物を編集 この配送サービスには API キーが必要ですが、提供されていません。詳細はアプリの設定をご確認ください。 戻る - 名前は空白にできません。 無視 ライセンス その他のオプション @@ -73,4 +72,5 @@ 追跡 ID は空白にできません。 定額制ネットワークでのみ更新する 有効化すると Parcel は自宅の Wi-Fi などの定額制接続時でのみ更新を検索します。 + 荷物 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index da00d98..f79fb9d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -20,7 +20,6 @@ Edytuj paczkę Ten serwis wymaga klucza API, ale żaden został podany. Sprawdż ustawienia aplikacji po więcej informacji. Wróć - Nazwa nie może być pusta. Ignoruj Licencja Więcej opcji @@ -89,4 +88,5 @@ Identyfikator śledzenia nie może być pusty. Aktualizuj tylko w sieciach bez limitu Jeśli ta opcja jest włączona, Parcel będzie szukać aktualizacji przesyłek tylko w przypadku połączeń nielimitowanych, takich jak domowe Wi-Fi. + Paczka diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 7d4d4a0..6966e91 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -19,7 +19,6 @@ Redigera paket Denna leveransservice kräver en API-nyckel men ingen angavs. Kontrollera appens inställningar för mer information. Tillbaka - Namn får inte lämnas tom. Tysta Licens Fler alternativ @@ -72,4 +71,5 @@ Spårningsnummer får inte lämnas tom. Uppdatera endast på obegränsade nätverk Om aktiverat söker Parcel efter uppdateringar endast via obegränsade anslutningar, t.ex. ditt hem-Wi-Fi. + Paket diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 8888227..f27f110 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -14,7 +14,6 @@ แก้ไข แก้ไขพัสดุ กลับ - ชื่อพัสดุไม่สามารถเว้นว่างได้ ลิขสิทธิ์ ตัวเลือกเพิ่มเติม อาจจะเป็นไปได้ว่า อุปกรณ์ของคุณไม่มีสัญญาณในขณะนี้ หรือเกิดปัญหาที่เซิฟเวอร์ฝั่งผู้ให้บริการขนส่ง หรือคุณใส่รายละเอียดของพัสดุไม่ถูกต้อง @@ -62,4 +61,5 @@ รหัสพัสดุไม่สามารถเว้นว่างได้ อัปเดตเมื่อเชื่อมต่อเครือข่ายที่ไม่จำกัดการใช้งานเท่านั้น เมื่อเปิด Parcel จะตรวจสอบสถานะของพัสดุเมื่อเชื่อมต่อเครือข่ายที่ไม่จำกัดการใช้งานเท่านั้น เช่น Wi-Fi บ้าน + พัสดุ diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index ab96fa1..c964512 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -19,7 +19,6 @@ Koli düzenle Bu teslimat servisi bir API anahtarı gerektiriyor, ancak hiçbiri sağlanmadı. Daha fazla bilgi için uygulamanın ayarlarını kontrol edin. Geri dön - İsim boş bırakılamaz. Yoksay Lisans Daha fazla seçenek @@ -71,4 +70,5 @@ Takip numarası boş bırakılamaz. Yalnızca sınırsız ağlarda güncelle Etkinleştirildiğinde, Parcel koli güncellemelerini yalnızca ev Wi-Fi gibi sınırsız bağlantılarda kontrol eder. + Koli diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index e54ca18..a6ad9fe 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -19,7 +19,6 @@ Редагувати посилку Ця доставка потребує ключ API, але він не наданий. Перевірте налаштування застосунку для більшої інформації. Повернутись - Ім\'я не може бути пустим. Ігнорувати Ліцензія More options @@ -71,4 +70,5 @@ Трекінг-ID не може бути пустим. Оновлюватись тільки на необмежених мережах Якщо ввімкнено, то Parcel буде сповіщувати про дані посилки тільки на необмежених з\'єднаннях, такі як ваш домашній Wi-Fi. + Посилка diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2fdd3f..73aab7c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,7 +21,6 @@ Edit parcel This delivery service requires an API key, but none was provided. Check the app\'s settings for more information. Go back - Name must not be blank. Ignore License More options @@ -103,4 +102,5 @@ Tracking ID must not be blank. Update only on unmetered networks If enabled, Parcel will look for parcel updates only on unmetered connections, such as your home Wi-Fi. + Package From 45ccdf42a406547d3cc442e5e715e84287a2d5ae Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:12:12 +0200 Subject: [PATCH 02/18] Navbar for homepage and packet page --- .../dev/itsvic/parceltracker/MainActivity.kt | 67 ++++++++++----- .../ui/components/BottomNavBar.kt | 43 ++++++++++ .../ui/components/ParcelActionBar.kt | 81 +++++++++++++++++++ .../itsvic/parceltracker/ui/views/HomeView.kt | 58 +------------ .../parceltracker/ui/views/ParcelView.kt | 58 +++---------- .../parceltracker/ui/views/SettingsView.kt | 21 +++++ app/src/main/res/values-hu/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + 8 files changed, 213 insertions(+), 123 deletions(-) create mode 100644 app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt create mode 100644 app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt diff --git a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt index 281adf4..0ca85ec 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt @@ -22,9 +22,12 @@ import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.collectAsState @@ -41,6 +44,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.content.ContextCompat import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute import dev.itsvic.parceltracker.api.APIKeyMissingException @@ -55,6 +59,7 @@ import dev.itsvic.parceltracker.db.ParcelWithStatus import dev.itsvic.parceltracker.db.deleteParcel import dev.itsvic.parceltracker.db.demoModeParcels import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme +import dev.itsvic.parceltracker.ui.components.BottomNavBar import dev.itsvic.parceltracker.ui.views.AddEditParcelView import dev.itsvic.parceltracker.ui.views.HomeView import dev.itsvic.parceltracker.ui.views.ParcelView @@ -149,25 +154,48 @@ fun ParcelAppNavigation(parcelToOpen: Int) { } val animDuration = 300 - - NavHost( - navController = navController, - startDestination = HomePage, - enterTransition = { - slideIntoContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Start, - animationSpec = tween(animDuration), - initialOffset = { it / 4 }) + fadeIn(tween(animDuration)) - }, - exitTransition = { fadeOut(tween(animDuration)) + scaleOut(tween(500), 0.9f) }, - popEnterTransition = { fadeIn(tween(animDuration)) + scaleIn(tween(500), 0.9f) }, - popExitTransition = { - slideOutOfContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Start, - animationSpec = tween(animDuration), - targetOffset = { -it / 4 }) + fadeOut(tween(animDuration)) - }, - ) { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route ?: "HomePage" + + Scaffold( + bottomBar = { + if (currentRoute.contains("HomePage") || currentRoute.contains("SettingsPage") || currentRoute.contains("AddParcelPage")) { + BottomNavBar( + currentRoute = currentRoute, + onNavigateToHome = { + navController.navigate(route = HomePage) { + popUpTo(HomePage) { inclusive = true } + } + }, + onNavigateToAddParcel = { + navController.navigate(route = AddParcelPage) + }, + onNavigateToSettings = { + navController.navigate(route = SettingsPage) + } + ) + } + } + ) { innerPadding -> + NavHost( + navController = navController, + startDestination = HomePage, + enterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween(animDuration), + initialOffset = { it / 4 }) + fadeIn(tween(animDuration)) + }, + exitTransition = { fadeOut(tween(animDuration)) + scaleOut(tween(500), 0.9f) }, + popEnterTransition = { fadeIn(tween(animDuration)) + scaleIn(tween(500), 0.9f) }, + popExitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween(animDuration), + targetOffset = { -it / 4 }) + fadeOut(tween(animDuration)) + }, + modifier = Modifier.padding(innerPadding) + ) { composable { val parcels = if (demoMode) derivedStateOf { demoModeParcels } @@ -394,4 +422,5 @@ fun ParcelAppNavigation(parcelToOpen: Int) { ) } } + } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt new file mode 100644 index 0000000..cdb3d6a --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package dev.itsvic.parceltracker.ui.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import dev.itsvic.parceltracker.R + +@Composable +fun BottomNavBar( + currentRoute: String, + onNavigateToHome: () -> Unit, + onNavigateToAddParcel: () -> Unit, + onNavigateToSettings: () -> Unit, +) { + NavigationBar { + NavigationBarItem( + icon = { Icon(Icons.Filled.Home, contentDescription = stringResource(R.string.home)) }, + label = { Text(stringResource(R.string.home)) }, + selected = currentRoute.contains("HomePage"), + onClick = onNavigateToHome + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.add)) }, + label = { Text(stringResource(R.string.add)) }, + selected = currentRoute.contains("AddParcelPage"), + onClick = onNavigateToAddParcel + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Settings, contentDescription = stringResource(R.string.settings)) }, + label = { Text(stringResource(R.string.settings)) }, + selected = currentRoute.contains("SettingsPage"), + onClick = onNavigateToSettings + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt new file mode 100644 index 0000000..37d3e0a --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package dev.itsvic.parceltracker.ui.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit +import androidx.compose.ui.res.painterResource +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource +import dev.itsvic.parceltracker.R +import dev.itsvic.parceltracker.api.Status + +@Composable +fun ParcelActionBar( + status: Status?, + onEdit: () -> Unit, + onArchive: () -> Unit, + onDelete: () -> Unit +) { + var showDeleteDialog by remember { mutableStateOf(false) } + + NavigationBar { + NavigationBarItem( + icon = { Icon(Icons.Filled.Edit, contentDescription = stringResource(R.string.edit)) }, + label = { Text(stringResource(R.string.edit)) }, + selected = false, + onClick = onEdit + ) + + if (status == Status.Delivered) { + NavigationBarItem( + icon = { Icon(painterResource(R.drawable.archive), contentDescription = stringResource(R.string.archive)) }, + label = { Text(stringResource(R.string.archive)) }, + selected = false, + onClick = onArchive + ) + } + + NavigationBarItem( + icon = { Icon(Icons.Filled.Delete, contentDescription = stringResource(R.string.delete)) }, + label = { Text(stringResource(R.string.delete)) }, + selected = false, + onClick = { showDeleteDialog = true } + ) + } + + if (showDeleteDialog) { + AlertDialog( + onDismissRequest = { showDeleteDialog = false }, + title = { Text(stringResource(R.string.delete)) }, + text = { Text(stringResource(R.string.delete_confirmation)) }, + confirmButton = { + TextButton( + onClick = { + showDeleteDialog = false + onDelete() + } + ) { + Text(stringResource(R.string.delete)) + } + }, + dismissButton = { + TextButton( + onClick = { showDeleteDialog = false } + ) { + Text(stringResource(R.string.cancel)) + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt index cb015ee..f07b84f 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt @@ -4,26 +4,13 @@ package dev.itsvic.parceltracker.ui.views import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Info -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue + import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource @@ -35,9 +22,7 @@ import dev.itsvic.parceltracker.api.Status import dev.itsvic.parceltracker.db.Parcel import dev.itsvic.parceltracker.db.ParcelStatus import dev.itsvic.parceltracker.db.ParcelWithStatus -import dev.itsvic.parceltracker.ui.components.AboutDialog import dev.itsvic.parceltracker.ui.components.ParcelRow -import dev.itsvic.parceltracker.ui.theme.MenuItemContentPadding import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import java.time.Instant @@ -50,51 +35,14 @@ fun HomeView( onNavigateToSettings: () -> Unit, ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - var expanded by remember { mutableStateOf(false) } - var aboutDialogOpen by remember { mutableStateOf(false) } Scaffold( topBar = { LargeTopAppBar( title = { Text(stringResource(R.string.app_name)) }, scrollBehavior = scrollBehavior, - actions = { - IconButton(onClick = { expanded = !expanded }) { - Icon(Icons.Filled.MoreVert, stringResource(R.string.more_options)) - } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - DropdownMenuItem( - leadingIcon = { - Icon(Icons.Filled.Settings, stringResource(R.string.settings)) - }, - text = { Text(stringResource(R.string.settings)) }, - onClick = { - expanded = false - onNavigateToSettings() - }, - contentPadding = MenuItemContentPadding, - ) - DropdownMenuItem( - leadingIcon = { Icon(Icons.Filled.Info, stringResource(R.string.about_app)) }, - text = { Text(stringResource(R.string.about_app)) }, - onClick = { - expanded = false - aboutDialogOpen = true - }, - contentPadding = MenuItemContentPadding, - ) - } - }, ) }, - floatingActionButton = { - FloatingActionButton(onClick = onNavigateToAddParcel) { - Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_a_parcel)) - } - }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { innerPadding -> LazyColumn(modifier = Modifier.padding(innerPadding)) { if (parcels.isEmpty()) @@ -108,10 +56,6 @@ fun HomeView( ParcelRow(parcel.parcel, parcel.status?.status) { onNavigateToParcel(parcel.parcel) } } } - - if (aboutDialogOpen) { - AboutDialog { aboutDialogOpen = false } - } } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt index 807adfe..9888fe9 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt @@ -12,13 +12,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.Button import androidx.compose.material3.Card -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.HorizontalDivider @@ -30,14 +25,10 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource + import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewLightDark @@ -48,8 +39,9 @@ import dev.itsvic.parceltracker.api.ParcelHistoryItem import dev.itsvic.parceltracker.api.Service import dev.itsvic.parceltracker.api.Status import dev.itsvic.parceltracker.api.getDeliveryServiceName +import dev.itsvic.parceltracker.ui.components.ParcelActionBar import dev.itsvic.parceltracker.ui.components.ParcelHistoryItemRow -import dev.itsvic.parceltracker.ui.theme.MenuItemContentPadding + import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import java.time.LocalDateTime @@ -68,7 +60,6 @@ fun ParcelView( onArchivePromptDismissal: () -> Unit, ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - var expanded by remember { mutableStateOf(false) } Scaffold( topBar = { @@ -79,44 +70,17 @@ fun ParcelView( Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) } }, - actions = { - IconButton(onClick = { expanded = !expanded }) { - Icon(Icons.Filled.MoreVert, stringResource(R.string.more_options)) - } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - DropdownMenuItem( - leadingIcon = { Icon(Icons.Filled.Edit, stringResource(R.string.edit)) }, - text = { Text(stringResource(R.string.edit)) }, - onClick = { - expanded = false - onEdit() - }, - contentPadding = MenuItemContentPadding, - ) - if (!isArchived) - DropdownMenuItem( - leadingIcon = { - Icon( - painterResource(R.drawable.archive), stringResource(R.string.archive)) - }, - text = { Text(stringResource(R.string.archive)) }, - onClick = onArchive, - contentPadding = MenuItemContentPadding, - ) - DropdownMenuItem( - leadingIcon = { Icon(Icons.Filled.Delete, stringResource(R.string.delete)) }, - text = { Text(stringResource(R.string.delete)) }, - onClick = onDelete, - contentPadding = MenuItemContentPadding, - ) - } - }, scrollBehavior = scrollBehavior, ) }, + bottomBar = { + ParcelActionBar( + status = parcel.currentStatus, + onEdit = onEdit, + onArchive = onArchive, + onDelete = onDelete + ) + }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { innerPadding -> LazyColumn( modifier = Modifier.padding(innerPadding).padding(16.dp, 0.dp), diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt index 087ca56..8ce2953 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Info import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon @@ -25,7 +26,10 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -53,6 +57,7 @@ import dev.itsvic.parceltracker.dataStore import dev.itsvic.parceltracker.db.Parcel import dev.itsvic.parceltracker.enqueueNotificationWorker import dev.itsvic.parceltracker.sendNotification +import dev.itsvic.parceltracker.ui.components.AboutDialog import dev.itsvic.parceltracker.ui.components.LogcatButton import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import java.time.LocalDateTime @@ -70,6 +75,7 @@ fun SettingsView( context.dataStore.data.map { it[UNMETERED_ONLY] == true }.collectAsState(false) val coroutineScope = rememberCoroutineScope() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + var aboutDialogOpen by remember { mutableStateOf(false) } val dhlApiKey by context.dataStore.data.map { it[DHL_API_KEY] ?: "" }.collectAsState("") @@ -184,6 +190,17 @@ fun SettingsView( LogcatButton(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) + FilledTonalButton( + onClick = { aboutDialogOpen = true }, + modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth() + ) { + Icon(Icons.Filled.Info, contentDescription = stringResource(R.string.about_app)) + Text( + text = " ${stringResource(R.string.about_app)}", + modifier = Modifier.padding(start = 8.dp) + ) + } + Text( "Parcel ${BuildConfig.VERSION_NAME}", modifier = Modifier.padding(16.dp, 8.dp), @@ -191,6 +208,10 @@ fun SettingsView( color = MaterialTheme.colorScheme.onSurfaceVariant, ) } + + if (aboutDialogOpen) { + AboutDialog { aboutDialogOpen = false } + } } } diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 4df1727..34fe38b 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1,10 +1,13 @@ App névjegye + Hozzáadás Csomag hozzáadása Csomag hozzáadása Archivál Biztosan archiválod ezt a csomagot? Ha archiválod, akkor a csomag előzmény az eszközön kerül eltárolásra és frissítés sem történik. + Mégse + Biztosan törölni szeretnéd ezt a csomagot? Értesítés(ek) a csomag jelenlegi állapotáról Csomag események Jelenlegi állapot @@ -17,6 +20,7 @@ Szerkesztés Csomag szerkesztése Vissza + Főoldal Figyelmen kívül hagy Licenc További lehetőségek diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 73aab7c..85fb407 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,11 +1,14 @@ About app + Add Add a parcel Add parcel Parcel Archive Do you want to archive this parcel? If you archive this parcel, its history will be preserved on device. It will not be periodically checked for updates. + Cancel + Are you sure you want to delete this parcel? Notifications about the current status of the parcel Parcel events Current status @@ -21,6 +24,7 @@ Edit parcel This delivery service requires an API key, but none was provided. Check the app\'s settings for more information. Go back + Home Ignore License More options From 44d0c3ba6a7786b18fa3dfd0d51f189a34083bcd Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:22:14 +0200 Subject: [PATCH 03/18] Paste button with opt out --- .../java/dev/itsvic/parceltracker/Settings.kt | 1 + .../ui/views/AddEditParcelView.kt | 29 +++++++++++++++++++ .../parceltracker/ui/views/SettingsView.kt | 20 +++++++++++++ app/src/main/res/values-hu/strings.xml | 3 ++ app/src/main/res/values/strings.xml | 3 ++ 5 files changed, 56 insertions(+) diff --git a/app/src/main/java/dev/itsvic/parceltracker/Settings.kt b/app/src/main/java/dev/itsvic/parceltracker/Settings.kt index 1de2f70..e9cea5a 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/Settings.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/Settings.kt @@ -11,6 +11,7 @@ import androidx.datastore.preferences.preferencesDataStore val Context.dataStore: DataStore by preferencesDataStore(name = "settings") val DEMO_MODE = booleanPreferencesKey("demoMode") val UNMETERED_ONLY = booleanPreferencesKey("unmeteredOnly") +val CLIPBOARD_PASTE_ENABLED = booleanPreferencesKey("clipboardPasteEnabled") // API key settings val DHL_API_KEY = stringPreferencesKey("dhlApiKey") diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index 18389b5..c73d93a 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn @@ -12,6 +13,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add import androidx.compose.material3.Button import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenuItem @@ -28,6 +30,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -35,12 +38,17 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.datastore.preferences.core.edit +import dev.itsvic.parceltracker.CLIPBOARD_PASTE_ENABLED import dev.itsvic.parceltracker.R +import dev.itsvic.parceltracker.dataStore +import kotlinx.coroutines.flow.map import dev.itsvic.parceltracker.api.Service import dev.itsvic.parceltracker.api.getDeliveryService import dev.itsvic.parceltracker.api.getDeliveryServiceName @@ -57,6 +65,9 @@ fun AddEditParcelView( ) { val isEdit = parcel != null val context = LocalContext.current + val clipboardManager = LocalClipboardManager.current + val clipboardPasteEnabled by + context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) var humanName by remember { mutableStateOf(parcel?.humanName ?: "") } var trackingId by remember { mutableStateOf(parcel?.parcelId ?: "") } @@ -145,6 +156,24 @@ fun AddEditParcelView( label = { Text(stringResource(R.string.tracking_id)) }, modifier = Modifier.fillMaxWidth(), isError = idError, + trailingIcon = { + if (clipboardPasteEnabled) { + IconButton( + onClick = { + clipboardManager.getText()?.text?.let { clipboardText -> + trackingId = clipboardText + idError = false + } + } + ) { + Icon( + Icons.Filled.Add, + contentDescription = stringResource(R.string.clipboard_paste), + modifier = Modifier.size(20.dp) + ) + } + } + }, supportingText = { if (idError) Text(stringResource(R.string.tracking_id_error_text)) }) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt index 8ce2953..e4b912f 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.unit.dp import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import dev.itsvic.parceltracker.BuildConfig +import dev.itsvic.parceltracker.CLIPBOARD_PASTE_ENABLED import dev.itsvic.parceltracker.DEMO_MODE import dev.itsvic.parceltracker.DHL_API_KEY import dev.itsvic.parceltracker.R @@ -73,6 +74,8 @@ fun SettingsView( val demoMode by context.dataStore.data.map { it[DEMO_MODE] == true }.collectAsState(false) val unmeteredOnly by context.dataStore.data.map { it[UNMETERED_ONLY] == true }.collectAsState(false) + val clipboardPasteEnabled by + context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) val coroutineScope = rememberCoroutineScope() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() var aboutDialogOpen by remember { mutableStateOf(false) } @@ -123,6 +126,23 @@ fun SettingsView( Switch(checked = unmeteredOnly, onCheckedChange = { setUnmeteredOnly(it) }) } + Row( + modifier = + Modifier.clickable { setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.clipboard_paste_enabled)) + Text( + stringResource(R.string.clipboard_paste_description), + style = MaterialTheme.typography.bodyMedium) + } + Switch(checked = clipboardPasteEnabled, onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) }) + } + Text( stringResource(R.string.settings_api_keys), modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 34fe38b..f36bd49 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -7,6 +7,9 @@ Biztosan archiválod ezt a csomagot? Ha archiválod, akkor a csomag előzmény az eszközön kerül eltárolásra és frissítés sem történik. Mégse + Beillesztés vágólapról + Beillesztés gomb engedélyezése + Beillesztés a vágólapról gomb megjelenítése Biztosan törölni szeretnéd ezt a csomagot? Értesítés(ek) a csomag jelenlegi állapotáról Csomag események diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 85fb407..a21b158 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,6 +8,9 @@ Do you want to archive this parcel? If you archive this parcel, its history will be preserved on device. It will not be periodically checked for updates. Cancel + Paste from clipboard + Enable clipboard paste button + Show a paste button next to the tracking number field Are you sure you want to delete this parcel? Notifications about the current status of the parcel Parcel events From 14ce0c55d2c681a828c0dff77c2c80f439a5080a Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:37:49 +0200 Subject: [PATCH 04/18] Preferred region and country --- .../java/dev/itsvic/parceltracker/Settings.kt | 1 + .../ui/views/AddEditParcelView.kt | 106 +++++++++- .../parceltracker/ui/views/SettingsView.kt | 187 ++++++++++++++++++ app/src/main/res/values-hu/strings.xml | 22 ++- app/src/main/res/values/strings.xml | 20 ++ 5 files changed, 331 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/Settings.kt b/app/src/main/java/dev/itsvic/parceltracker/Settings.kt index e9cea5a..0f8a308 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/Settings.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/Settings.kt @@ -12,6 +12,7 @@ val Context.dataStore: DataStore by preferencesDataStore(name = "se val DEMO_MODE = booleanPreferencesKey("demoMode") val UNMETERED_ONLY = booleanPreferencesKey("unmeteredOnly") val CLIPBOARD_PASTE_ENABLED = booleanPreferencesKey("clipboardPasteEnabled") +val PREFERRED_REGION = stringPreferencesKey("preferredRegion") // API key settings val DHL_API_KEY = stringPreferencesKey("dhlApiKey") diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index c73d93a..a910b7d 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -44,8 +44,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.datastore.preferences.core.edit import dev.itsvic.parceltracker.CLIPBOARD_PASTE_ENABLED +import dev.itsvic.parceltracker.PREFERRED_REGION import dev.itsvic.parceltracker.R import dev.itsvic.parceltracker.dataStore import kotlinx.coroutines.flow.map @@ -68,6 +68,8 @@ fun AddEditParcelView( val clipboardManager = LocalClipboardManager.current val clipboardPasteEnabled by context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) + val preferredRegion by + context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") var humanName by remember { mutableStateOf(parcel?.humanName ?: "") } var trackingId by remember { mutableStateOf(parcel?.parcelId ?: "") } @@ -108,8 +110,67 @@ fun AddEditParcelView( var expanded by remember { mutableStateOf(false) } val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - val sortedServiceOptions = - serviceOptions.sortedBy { getDeliveryService(it)?.acceptsFormat(trackingId)?.not() } + val sortedServiceOptions = serviceOptions.sortedWith(compareBy { + val isPreferredRegion = when (preferredRegion) { + "international" -> it in listOf(Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX) + "north_america" -> it == Service.UNIUNI + "europe" -> it in listOf(Service.BELPOST, Service.SAMEDAY_BG, Service.PACKETA, Service.DPD_UK, Service.EVRI, + Service.AN_POST, Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST, + Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.DPD_GER, Service.HERMES, + Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA) + "asia" -> it in listOf(Service.EKART, Service.SPX_TH) + "belarus" -> it == Service.BELPOST + "bulgaria" -> it == Service.SAMEDAY_BG + "czech" -> it == Service.PACKETA + "uk" -> it in listOf(Service.DPD_UK, Service.EVRI) + "ireland" -> it == Service.AN_POST + "poland" -> it in listOf(Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST) + "hungary" -> it in listOf(Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.PACKETA) + "germany" -> it in listOf(Service.DPD_GER, Service.HERMES) + "italy" -> it == Service.POSTE_ITALIANE + "romania" -> it == Service.SAMEDAY_RO + "scandinavia" -> it == Service.POSTNORD + "ukraine" -> it in listOf(Service.NOVA_POSHTA, Service.UKRPOSHTA) + "india" -> it == Service.EKART + "thailand" -> it == Service.SPX_TH + else -> false + } + if (isPreferredRegion) 0 else 1 + }.thenBy { + when (it) { + Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX -> 0 + Service.UNIUNI -> 1 + Service.BELPOST, Service.SAMEDAY_BG, Service.PACKETA, Service.DPD_UK, Service.EVRI, + Service.AN_POST, Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST, + Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.DPD_GER, Service.HERMES, + Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA -> 2 + Service.EKART, Service.SPX_TH -> 3 + else -> 4 + } + }.thenBy { + when (it) { + Service.BELPOST -> "A_Belarus" + Service.SAMEDAY_BG -> "B_Bulgaria" + Service.PACKETA -> "C_Czech" + Service.DPD_UK, Service.EVRI -> "D_UK" + Service.AN_POST -> "E_Ireland" + Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST -> "F_Poland" + Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU -> "G_Hungary" + Service.DPD_GER, Service.HERMES -> "H_Germany" + Service.POSTE_ITALIANE -> "I_Italy" + Service.SAMEDAY_RO -> "J_Romania" + Service.POSTNORD -> "K_Scandinavia" + Service.NOVA_POSHTA, Service.UKRPOSHTA -> "L_Ukraine" + else -> it.name + } + }.thenBy { + if (trackingId.isNotBlank()) { + val backend = getDeliveryService(it) + if (backend?.acceptsFormat(trackingId) == true) 0 else 1 + } else { + 0 + } + }) Scaffold( topBar = { @@ -202,9 +263,46 @@ fun AddEditParcelView( ExposedDropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }) { + var currentCategory = "" sortedServiceOptions.forEach { option -> + val category = when (option) { + Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX -> "Nemzetközi" + Service.UNIUNI -> "Észak-Amerika" + Service.BELPOST -> "Európa - Fehéroroszország" + Service.SAMEDAY_BG -> "Európa - Bulgária" + Service.PACKETA -> "Európa - Csehország" + Service.DPD_UK, Service.EVRI -> "Európa - Egyesült Királyság" + Service.AN_POST -> "Európa - Írország" + Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST -> "Európa - Lengyelország" + Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU -> "Európa - Magyarország" + Service.DPD_GER, Service.HERMES -> "Európa - Németország" + Service.POSTE_ITALIANE -> "Európa - Olaszország" + Service.SAMEDAY_RO -> "Európa - Románia" + Service.POSTNORD -> "Európa - Skandinávia" + Service.NOVA_POSHTA, Service.UKRPOSHTA -> "Európa - Ukrajna" + Service.EKART -> "Ázsia - India" + Service.SPX_TH -> "Ázsia - Thaiföld" + else -> "Egyéb" + } + + if (category != currentCategory) { + currentCategory = category + DropdownMenuItem( + text = { + Text( + text = category, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary + ) + }, + onClick = { }, + enabled = false, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + ) + } + DropdownMenuItem( - text = { Text(stringResource(getDeliveryServiceName(option)!!)) }, + text = { Text(" " + stringResource(getDeliveryServiceName(option)!!)) }, onClick = { service = option expanded = false diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt index e4b912f..ccccf0f 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt @@ -12,7 +12,10 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -49,6 +52,7 @@ import dev.itsvic.parceltracker.BuildConfig import dev.itsvic.parceltracker.CLIPBOARD_PASTE_ENABLED import dev.itsvic.parceltracker.DEMO_MODE import dev.itsvic.parceltracker.DHL_API_KEY +import dev.itsvic.parceltracker.PREFERRED_REGION import dev.itsvic.parceltracker.R import dev.itsvic.parceltracker.UNMETERED_ONLY import dev.itsvic.parceltracker.api.ParcelHistoryItem @@ -76,7 +80,10 @@ fun SettingsView( context.dataStore.data.map { it[UNMETERED_ONLY] == true }.collectAsState(false) val clipboardPasteEnabled by context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) + val preferredRegion by + context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") val coroutineScope = rememberCoroutineScope() + var regionDropdownExpanded by remember { mutableStateOf(false) } val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() var aboutDialogOpen by remember { mutableStateOf(false) } @@ -143,6 +150,186 @@ fun SettingsView( Switch(checked = clipboardPasteEnabled, onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) }) } + // Preferred Region Setting + Column(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) { + Text(stringResource(R.string.preferred_region)) + Text( + stringResource(R.string.preferred_region_description), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 8.dp) + ) + + ExposedDropdownMenuBox( + expanded = regionDropdownExpanded, + onExpandedChange = { regionDropdownExpanded = !regionDropdownExpanded } + ) { + OutlinedTextField( + value = when (preferredRegion) { + "international" -> stringResource(R.string.region_international) + "north_america" -> stringResource(R.string.region_north_america) + "europe" -> stringResource(R.string.region_europe) + "asia" -> stringResource(R.string.region_asia) + "belarus" -> stringResource(R.string.country_belarus) + "bulgaria" -> stringResource(R.string.country_bulgaria) + "czech" -> stringResource(R.string.country_czech) + "uk" -> stringResource(R.string.country_uk) + "ireland" -> stringResource(R.string.country_ireland) + "poland" -> stringResource(R.string.country_poland) + "hungary" -> stringResource(R.string.country_hungary) + "germany" -> stringResource(R.string.country_germany) + "italy" -> stringResource(R.string.country_italy) + "romania" -> stringResource(R.string.country_romania) + "scandinavia" -> stringResource(R.string.country_scandinavia) + "ukraine" -> stringResource(R.string.country_ukraine) + "india" -> stringResource(R.string.country_india) + "thailand" -> stringResource(R.string.country_thailand) + else -> stringResource(R.string.region_international) + }, + onValueChange = { }, + readOnly = true, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = regionDropdownExpanded) }, + modifier = Modifier.menuAnchor().fillMaxWidth() + ) + + ExposedDropdownMenu( + expanded = regionDropdownExpanded, + onDismissRequest = { regionDropdownExpanded = false } + ) { + // Continental regions + DropdownMenuItem( + text = { Text(stringResource(R.string.region_international)) }, + onClick = { + setValue(PREFERRED_REGION, "international") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.region_north_america)) }, + onClick = { + setValue(PREFERRED_REGION, "north_america") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.region_europe)) }, + onClick = { + setValue(PREFERRED_REGION, "europe") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.region_asia)) }, + onClick = { + setValue(PREFERRED_REGION, "asia") + regionDropdownExpanded = false + } + ) + + // European countries + DropdownMenuItem( + text = { Text(stringResource(R.string.country_belarus)) }, + onClick = { + setValue(PREFERRED_REGION, "belarus") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_bulgaria)) }, + onClick = { + setValue(PREFERRED_REGION, "bulgaria") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_czech)) }, + onClick = { + setValue(PREFERRED_REGION, "czech") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_uk)) }, + onClick = { + setValue(PREFERRED_REGION, "uk") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_ireland)) }, + onClick = { + setValue(PREFERRED_REGION, "ireland") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_poland)) }, + onClick = { + setValue(PREFERRED_REGION, "poland") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_hungary)) }, + onClick = { + setValue(PREFERRED_REGION, "hungary") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_germany)) }, + onClick = { + setValue(PREFERRED_REGION, "germany") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_italy)) }, + onClick = { + setValue(PREFERRED_REGION, "italy") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_romania)) }, + onClick = { + setValue(PREFERRED_REGION, "romania") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_scandinavia)) }, + onClick = { + setValue(PREFERRED_REGION, "scandinavia") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_ukraine)) }, + onClick = { + setValue(PREFERRED_REGION, "ukraine") + regionDropdownExpanded = false + } + ) + + // Asian countries + DropdownMenuItem( + text = { Text(stringResource(R.string.country_india)) }, + onClick = { + setValue(PREFERRED_REGION, "india") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_thailand)) }, + onClick = { + setValue(PREFERRED_REGION, "thailand") + regionDropdownExpanded = false + } + ) + } + } + } + Text( stringResource(R.string.settings_api_keys), modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index f36bd49..a9ef9a0 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -9,7 +9,27 @@ Mégse Beillesztés vágólapról Beillesztés gomb engedélyezése - Beillesztés a vágólapról gomb megjelenítése + Beillesztés gomb megjelenítése a csomagszám mező mellett + Előnyben részesített régió + A régiód futárszolgálatainak előnyben részesítése + Nemzetközi + Észak-Amerika + Európa + Ázsia + Fehéroroszország + Bulgária + Csehország + Egyesült Királyság + Írország + Lengyelország + Magyarország + Németország + Olaszország + Románia + Skandinávia + Ukrajna + India + Thaiföld Biztosan törölni szeretnéd ezt a csomagot? Értesítés(ek) a csomag jelenlegi állapotáról Csomag események diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a21b158..4d4b6b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,26 @@ Paste from clipboard Enable clipboard paste button Show a paste button next to the tracking number field + Preferred region + Prioritize delivery services from your region + International + North America + Europe + Asia + Belarus + Bulgaria + Czech Republic + United Kingdom + Ireland + Poland + Hungary + Germany + Italy + Romania + Scandinavia + Ukraine + India + Thailand Are you sure you want to delete this parcel? Notifications about the current status of the parcel Parcel events From f9faeecf0f902032645cb60ca1f58611e48dfd68 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Fri, 18 Jul 2025 22:30:08 +0200 Subject: [PATCH 05/18] Tablet view, right paste icon --- app/build.gradle.kts | 1 + .../dev/itsvic/parceltracker/MainActivity.kt | 178 +++++++++++++- .../parceltracker/ui/components/ParcelRow.kt | 11 +- .../parceltracker/ui/views/AdaptiveView.kt | 58 +++++ .../ui/views/AddEditParcelView.kt | 12 +- .../parceltracker/ui/views/ParcelView.kt | 7 +- .../parceltracker/ui/views/TabletView.kt | 227 ++++++++++++++++++ .../res/drawable-anydpi/ic_contentpaste.xml | 11 + .../res/drawable-hdpi/ic_contentpaste.png | Bin 0 -> 352 bytes .../res/drawable-mdpi/ic_contentpaste.png | Bin 0 -> 260 bytes .../res/drawable-xhdpi/ic_contentpaste.png | Bin 0 -> 450 bytes .../res/drawable-xxhdpi/ic_contentpaste.png | Bin 0 -> 785 bytes app/src/main/res/values-hu/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- gradle/libs.versions.toml | 1 + 15 files changed, 494 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt create mode 100644 app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt create mode 100644 app/src/main/res/drawable-anydpi/ic_contentpaste.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_contentpaste.png create mode 100644 app/src/main/res/drawable-mdpi/ic_contentpaste.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_contentpaste.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_contentpaste.png diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bbfc0f3..cd761ef 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.androidx.material3.window.size) implementation(libs.androidx.material.icons) implementation(libs.okhttp) implementation(libs.okhttp.coroutines) diff --git a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt index 0ca85ec..cc896ec 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt @@ -41,6 +41,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.core.content.ContextCompat import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -61,18 +63,23 @@ import dev.itsvic.parceltracker.db.demoModeParcels import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import dev.itsvic.parceltracker.ui.components.BottomNavBar import dev.itsvic.parceltracker.ui.views.AddEditParcelView +import dev.itsvic.parceltracker.ui.views.AdaptiveParcelApp import dev.itsvic.parceltracker.ui.views.HomeView import dev.itsvic.parceltracker.ui.views.ParcelView import dev.itsvic.parceltracker.ui.views.SettingsView +import dev.itsvic.parceltracker.ui.views.TabletNavigationItem +import dev.itsvic.parceltracker.ui.views.TabletView import java.time.LocalDateTime import java.time.ZoneId import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import okio.IOException class MainActivity : ComponentActivity() { + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() @@ -83,10 +90,11 @@ class MainActivity : ComponentActivity() { setContent { val parcelToOpen by parcelToOpen + val windowSizeClass = calculateWindowSizeClass(this) ParcelTrackerTheme { Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background)) { - ParcelAppNavigation(parcelToOpen) + ParcelAppNavigation(parcelToOpen, windowSizeClass) } } } @@ -140,7 +148,7 @@ class MainActivity : ComponentActivity() { @Serializable data class EditParcelPage(val parcelDbId: Int) @Composable -fun ParcelAppNavigation(parcelToOpen: Int) { +fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.material3.windowsizeclass.WindowSizeClass) { val db = ParcelApplication.db val navController = rememberNavController() val scope = rememberCoroutineScope() @@ -156,6 +164,168 @@ fun ParcelAppNavigation(parcelToOpen: Int) { val animDuration = 300 val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route ?: "HomePage" + + val isTablet = windowSizeClass.widthSizeClass >= androidx.compose.material3.windowsizeclass.WindowWidthSizeClass.Medium + + var selectedParcel by remember { mutableStateOf(null) } + var apiParcel by remember { mutableStateOf(null) } + var isLoadingParcel by remember { mutableStateOf(false) } + var currentTabletNavItem by remember { mutableStateOf(TabletNavigationItem.HOME) } + + val parcels = + if (demoMode) derivedStateOf { demoModeParcels } + else db.parcelDao().getAllWithStatus().collectAsState(initial = emptyList()) + + LaunchedEffect(selectedParcel) { + if (selectedParcel != null) { + isLoadingParcel = true + launch(Dispatchers.IO) { + try { + if (selectedParcel!!.isArchived) { + val localHistory = db.parcelHistoryDao().getAllById(selectedParcel!!.id).first() + if (localHistory.isNotEmpty()) { + apiParcel = APIParcel( + selectedParcel!!.parcelId, + localHistory.map { dev.itsvic.parceltracker.api.ParcelHistoryItem(it.description, it.time, it.location) }, + Status.Delivered // Assume delivered for archived parcels + ) + } else { + apiParcel = context.getParcel(selectedParcel!!.parcelId, selectedParcel!!.postalCode, selectedParcel!!.service) + } + } else { + apiParcel = context.getParcel(selectedParcel!!.parcelId, selectedParcel!!.postalCode, selectedParcel!!.service) + + if (!demoMode) { + val zone = ZoneId.systemDefault() + val lastChange = apiParcel!!.history.first().time.atZone(zone).toInstant() + val status = ParcelStatus(selectedParcel!!.id, apiParcel!!.currentStatus, lastChange) + val existingStatus = db.parcelStatusDao().get(selectedParcel!!.id) + if (existingStatus == null) { + db.parcelStatusDao().insert(status) + } else { + db.parcelStatusDao().update(status) + } + } + } + } catch (e: Exception) { + Log.w("MainActivity", "Failed fetch: $e") + apiParcel = APIParcel( + selectedParcel!!.parcelId, + listOf(ParcelHistoryItem(context.getString(R.string.network_failure_detail), LocalDateTime.now(), "")), + Status.NetworkFailure + ) + } + isLoadingParcel = false + } + } else { + apiParcel = null + isLoadingParcel = false + } + } + + if (isTablet) { + TabletView( + parcels = parcels.value, + selectedParcel = selectedParcel, + apiParcel = apiParcel, + isLoading = isLoadingParcel, + currentNavigationItem = currentTabletNavItem, + onNavigateToItem = { currentTabletNavItem = it }, + onNavigateToParcel = { selectedParcel = it }, + onNavigateToAddParcel = { currentTabletNavItem = TabletNavigationItem.ADD_PARCEL }, + onNavigateToSettings = { currentTabletNavItem = TabletNavigationItem.SETTINGS }, + onEditParcel = { parcel -> + selectedParcel = parcel + currentTabletNavItem = TabletNavigationItem.EDIT_PARCEL + }, + onDeleteParcel = { parcel -> + if (demoMode) { + Toast.makeText(context, context.getString(R.string.demo_mode_action_block), Toast.LENGTH_SHORT).show() + return@TabletView + } + scope.launch(Dispatchers.IO) { + deleteParcel(parcel) + selectedParcel = null + } + }, + onArchiveParcel = { parcel -> + if (parcel.isArchived || demoMode) { + if (demoMode) { + Toast.makeText(context, context.getString(R.string.demo_mode_action_block), Toast.LENGTH_SHORT).show() + } + return@TabletView + } + scope.launch(Dispatchers.IO) { + val updatedParcel = parcel.copy(isArchived = true) + db.parcelDao().update(updatedParcel) + if (apiParcel != null) { + db.parcelHistoryDao().insert( + apiParcel!!.history.map { + dev.itsvic.parceltracker.db.ParcelHistoryItem( + description = it.description, + location = it.location, + time = it.time, + parcelId = parcel.id + ) + } + ) + } + selectedParcel = updatedParcel + } + }, + onArchivePromptDismissal = { parcel -> + if (demoMode) { + Toast.makeText(context, context.getString(R.string.demo_mode_action_block), Toast.LENGTH_SHORT).show() + return@TabletView + } + scope.launch(Dispatchers.IO) { + val updatedParcel = parcel.copy(archivePromptDismissed = true) + db.parcelDao().update(updatedParcel) + selectedParcel = updatedParcel + } + }, + settingsContent = { + SettingsView(onBackPressed = { currentTabletNavItem = TabletNavigationItem.HOME }) + }, + addParcelContent = { + AddEditParcelView( + null, + onBackPressed = { currentTabletNavItem = TabletNavigationItem.HOME }, + onCompleted = { parcel -> + if (demoMode) { + Toast.makeText(context, context.getString(R.string.demo_mode_action_block), Toast.LENGTH_SHORT).show() + return@AddEditParcelView + } + scope.launch(Dispatchers.IO) { + val id = db.parcelDao().insert(parcel) + currentTabletNavItem = TabletNavigationItem.HOME + selectedParcel = parcel.copy(id = id.toInt()) + } + } + ) + }, + editParcelContent = { + selectedParcel?.let { parcel -> + AddEditParcelView( + parcel, + onBackPressed = { currentTabletNavItem = TabletNavigationItem.HOME }, + onCompleted = { updatedParcel -> + if (demoMode) { + Toast.makeText(context, context.getString(R.string.demo_mode_action_block), Toast.LENGTH_SHORT).show() + return@AddEditParcelView + } + scope.launch(Dispatchers.IO) { + db.parcelDao().update(updatedParcel) + currentTabletNavItem = TabletNavigationItem.HOME + selectedParcel = updatedParcel + } + } + ) + } + } + ) + return + } Scaffold( bottomBar = { @@ -197,10 +367,6 @@ fun ParcelAppNavigation(parcelToOpen: Int) { modifier = Modifier.padding(innerPadding) ) { composable { - val parcels = - if (demoMode) derivedStateOf { demoModeParcels } - else db.parcelDao().getAllWithStatus().collectAsState(initial = emptyList()) - HomeView( parcels = parcels.value, onNavigateToAddParcel = { navController.navigate(route = AddParcelPage) }, diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt index 45c3289..8f2c949 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt @@ -31,9 +31,16 @@ import dev.itsvic.parceltracker.db.Parcel import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme @Composable -fun ParcelRow(parcel: Parcel, status: Status?, onClick: () -> Unit) { +fun ParcelRow(parcel: Parcel, status: Status?, isSelected: Boolean = false, onClick: () -> Unit) { Row( - modifier = Modifier.clickable(onClick = onClick).fillMaxWidth().padding(16.dp, 12.dp), + modifier = Modifier + .clickable(onClick = onClick) + .fillMaxWidth() + .background( + if (isSelected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) + else MaterialTheme.colorScheme.surface + ) + .padding(16.dp, 12.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) { if (status != null) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt new file mode 100644 index 0000000..82c1b3b --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package dev.itsvic.parceltracker.ui.views + +import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import dev.itsvic.parceltracker.api.Parcel as APIParcel +import dev.itsvic.parceltracker.db.Parcel +import dev.itsvic.parceltracker.db.ParcelWithStatus + +@Composable +fun AdaptiveParcelApp( + windowSizeClass: WindowSizeClass, + parcels: List, + selectedParcel: Parcel?, + apiParcel: APIParcel?, + isLoading: Boolean, + onNavigateToParcel: (Parcel) -> Unit, + onNavigateToAddParcel: () -> Unit, + onNavigateToSettings: () -> Unit, + onEditParcel: (Parcel) -> Unit, + onDeleteParcel: (Parcel) -> Unit, + onArchiveParcel: (Parcel) -> Unit, + onArchivePromptDismissal: (Parcel) -> Unit, + settingsContent: @Composable () -> Unit, + addParcelContent: @Composable () -> Unit, + homeContent: @Composable () -> Unit +) { + val isTablet = windowSizeClass.widthSizeClass >= WindowWidthSizeClass.Medium + + if (isTablet) { + var currentNavigationItem by remember { mutableStateOf(TabletNavigationItem.HOME) } + + TabletView( + parcels = parcels, + selectedParcel = selectedParcel, + apiParcel = apiParcel, + isLoading = isLoading, + currentNavigationItem = currentNavigationItem, + onNavigateToItem = { currentNavigationItem = it }, + onNavigateToParcel = onNavigateToParcel, + onNavigateToAddParcel = onNavigateToAddParcel, + onNavigateToSettings = onNavigateToSettings, + onEditParcel = onEditParcel, + onDeleteParcel = onDeleteParcel, + onArchiveParcel = onArchiveParcel, + onArchivePromptDismissal = onArchivePromptDismissal, + settingsContent = settingsContent, + addParcelContent = addParcelContent + ) + } else { + homeContent() + } +} diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index a910b7d..4f5f61d 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp @@ -114,18 +115,17 @@ fun AddEditParcelView( val isPreferredRegion = when (preferredRegion) { "international" -> it in listOf(Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX) "north_america" -> it == Service.UNIUNI - "europe" -> it in listOf(Service.BELPOST, Service.SAMEDAY_BG, Service.PACKETA, Service.DPD_UK, Service.EVRI, + "europe" -> it in listOf(Service.BELPOST, Service.SAMEDAY_BG, Service.DPD_UK, Service.EVRI, Service.AN_POST, Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST, Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.DPD_GER, Service.HERMES, - Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA) + Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA, Service.PACKETA) "asia" -> it in listOf(Service.EKART, Service.SPX_TH) "belarus" -> it == Service.BELPOST "bulgaria" -> it == Service.SAMEDAY_BG - "czech" -> it == Service.PACKETA "uk" -> it in listOf(Service.DPD_UK, Service.EVRI) "ireland" -> it == Service.AN_POST "poland" -> it in listOf(Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST) - "hungary" -> it in listOf(Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.PACKETA) + "hungary" -> it in listOf(Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU) "germany" -> it in listOf(Service.DPD_GER, Service.HERMES) "italy" -> it == Service.POSTE_ITALIANE "romania" -> it == Service.SAMEDAY_RO @@ -151,7 +151,7 @@ fun AddEditParcelView( when (it) { Service.BELPOST -> "A_Belarus" Service.SAMEDAY_BG -> "B_Bulgaria" - Service.PACKETA -> "C_Czech" + Service.PACKETA -> "C_Europe" Service.DPD_UK, Service.EVRI -> "D_UK" Service.AN_POST -> "E_Ireland" Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST -> "F_Poland" @@ -228,7 +228,7 @@ fun AddEditParcelView( } ) { Icon( - Icons.Filled.Add, + painter = painterResource(id = R.drawable.ic_contentpaste), contentDescription = stringResource(R.string.clipboard_paste), modifier = Modifier.size(20.dp) ) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt index 9888fe9..bc748c0 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt @@ -58,6 +58,7 @@ fun ParcelView( onDelete: () -> Unit, onArchive: () -> Unit, onArchivePromptDismissal: () -> Unit, + showBackButton: Boolean = true, ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -66,8 +67,10 @@ fun ParcelView( MediumTopAppBar( title = { Text(humanName) }, navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) + if (showBackButton) { + IconButton(onClick = onBackPressed) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) + } } }, scrollBehavior = scrollBehavior, diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt new file mode 100644 index 0000000..4c84efe --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package dev.itsvic.parceltracker.ui.views + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Card +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import dev.itsvic.parceltracker.R +import dev.itsvic.parceltracker.api.Parcel as APIParcel +import dev.itsvic.parceltracker.api.Service +import dev.itsvic.parceltracker.db.Parcel +import dev.itsvic.parceltracker.db.ParcelWithStatus +import dev.itsvic.parceltracker.ui.components.ParcelRow + +enum class TabletNavigationItem { + HOME, + ADD_PARCEL, + EDIT_PARCEL, + SETTINGS +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TabletView( + parcels: List, + selectedParcel: Parcel?, + apiParcel: APIParcel?, + isLoading: Boolean, + currentNavigationItem: TabletNavigationItem, + onNavigateToItem: (TabletNavigationItem) -> Unit, + onNavigateToParcel: (Parcel) -> Unit, + onNavigateToAddParcel: () -> Unit, + onNavigateToSettings: () -> Unit, + onEditParcel: (Parcel) -> Unit, + onDeleteParcel: (Parcel) -> Unit, + onArchiveParcel: (Parcel) -> Unit, + onArchivePromptDismissal: (Parcel) -> Unit, + settingsContent: @Composable () -> Unit = {}, + addParcelContent: @Composable () -> Unit = {}, + editParcelContent: @Composable () -> Unit = {} +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Row(modifier = Modifier.fillMaxSize()) { + Card( + modifier = Modifier + .width(400.dp) + .fillMaxHeight() + .padding(8.dp) + ) { + Column { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp) + ) + + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .padding(horizontal = 8.dp) + ) { + if (parcels.isEmpty()) { + item { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + ) { + Text( + stringResource(R.string.no_parcels_flavor), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(16.dp) + ) + } + } + } else { + items(parcels.reversed()) { parcel -> + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + ParcelRow( + parcel.parcel, + parcel.status?.status, + isSelected = selectedParcel?.id == parcel.parcel.id + ) { + onNavigateToParcel(parcel.parcel) + } + } + } + } + } + + HorizontalDivider() + + NavigationBar( + modifier = Modifier.fillMaxWidth() + ) { + NavigationBarItem( + icon = { Icon(Icons.Filled.Home, contentDescription = null) }, + label = { Text(stringResource(R.string.home)) }, + selected = currentNavigationItem == TabletNavigationItem.HOME, + onClick = { onNavigateToItem(TabletNavigationItem.HOME) } + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Add, contentDescription = null) }, + label = { Text(stringResource(R.string.add_parcel)) }, + selected = currentNavigationItem == TabletNavigationItem.ADD_PARCEL, + onClick = { onNavigateToItem(TabletNavigationItem.ADD_PARCEL) } + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Settings, contentDescription = null) }, + label = { Text(stringResource(R.string.settings)) }, + selected = currentNavigationItem == TabletNavigationItem.SETTINGS, + onClick = { onNavigateToItem(TabletNavigationItem.SETTINGS) } + ) + } + } + } + + // Right panel: Content area + Card( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(8.dp) + ) { + when (currentNavigationItem) { + TabletNavigationItem.HOME -> { + if (selectedParcel != null) { + if (isLoading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } else if (apiParcel != null) { + ParcelView( + parcel = apiParcel, + humanName = selectedParcel.humanName, + service = selectedParcel.service, + isArchived = selectedParcel.isArchived, + archivePromptDismissed = selectedParcel.archivePromptDismissed, + onBackPressed = { /* No back button in tablet mode */ }, + onEdit = { onEditParcel(selectedParcel) }, + onDelete = { onDeleteParcel(selectedParcel) }, + onArchive = { onArchiveParcel(selectedParcel) }, + onArchivePromptDismissal = { onArchivePromptDismissal(selectedParcel) }, + showBackButton = false + ) + } + } else { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.padding(bottom = 16.dp) + ) + Text( + text = stringResource(R.string.select_parcel_to_view), + style = MaterialTheme.typography.bodyLarge + ) + } + } + } + } + + TabletNavigationItem.ADD_PARCEL -> { + addParcelContent() + } + + TabletNavigationItem.EDIT_PARCEL -> { + editParcelContent() + } + + TabletNavigationItem.SETTINGS -> { + settingsContent() + } + } + } + } +} diff --git a/app/src/main/res/drawable-anydpi/ic_contentpaste.xml b/app/src/main/res/drawable-anydpi/ic_contentpaste.xml new file mode 100644 index 0000000..3c13649 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_contentpaste.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_contentpaste.png b/app/src/main/res/drawable-hdpi/ic_contentpaste.png new file mode 100644 index 0000000000000000000000000000000000000000..da7d251f3d43114d26be2cf0f3aa6015f3eed0b6 GIT binary patch literal 352 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$UKzz^Lx&;uum9_jblvufqWXt*_l; zMII}&@NgAQj6ZRzX-DDJUh9S2^IZ!r>hT8Nzh>M$v8d|o6*EDm&j%ZC&$c#>_m=iZ z->iJ%^Pf$tR&C#;bAMvkwN1yL|7+Dbz3%m{e_X6d;Wc$Rei2PAR+fid*(Wc1DdQVI zkzY=X>zu3Sq?itl_{SdpFLj?+<_VmbQ#5(sgWaDOZFLP?^P?(Wp>&(AHOH*9m<-qK zjd?$#;tjGl0tN1UoGbQVaqYF*HSc#m+~;`S{Vj8+`rYGID}V2QgTe~DWM4f5aXMq literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_contentpaste.png b/app/src/main/res/drawable-mdpi/ic_contentpaste.png new file mode 100644 index 0000000000000000000000000000000000000000..4f4d006167bc9b5a5b25d2e3952784974fe7fe82 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjr#)R9Ln`8~PIcruWFW%&I?APA zVaJPgDw%ngdbbO#R|@^I?O%iD)2S(3v6deeni$*f%DTKprKRBg?|)xs&z_fmsJgz_ zu#&1}YjCgh zM{gz72`9r&@N06t6S>2w7sK^y`@TkzN3J_3&zZ*drjfgKy5;w+FY{KtV)#;PUlRL$ zTF|c(C+91h|GM+4BUS&?rjM-q>b%dyKi*huyIm^jV0moRmF1e^y?))RJU;F}Eac)*k0M z_Oo}KiukqqZO~txwWW6I8&AJI658hVEKO(aar4UcpLbsTtK+wzSnB!pokuxK{2u?Y z+HTQ!E^|)&b20S`mc5@}-+9DhwY;!yZuA_7=O*XY7jug_6xh=N@8{~1y?cI={>zY} zHA|+vE3rRqz+<}e$?b5l_)n=POkdjx-SzKYAL&2mhk6b35k`X-N%qlwXguh-w)n)|zEYt-FmKmWHy9P-q8c(!c!-;c{Xi`PH<{8QJi z{?d`9`2~VYJD10%pHrE6Hcfrwg8UeV%wNxS#JXpnnJx2iTh-aq@g+%~`ZLWnA2pR% zzC7*s-+k`R|01)`KFf^x((@=eeQ#X8(!U+^U)=cgJXw9t{o|{@bG}b5l3|sP$nm*s z(jO_tU45+h*q82C?+@*}=<-8+TIAt%wl?X$IxmHHy9qWf*D-9bt*$sXr=s;<^8JKI zYqushT0TE{?Eb=bn{>ZBFLkZi4;NJbI&YzP=k4!DAOBYTnfv_B&%b?(6AD%)oM1n= z@pAd4Wd8H-+Mlx>eh}ms8?bPJ-im+#@eqv_+*%@6SVg&BHF2?)I<_|LT2ORwtM;to zh2L5K=(9I}ZAy8u_P23_zDL`=UG}3~l+ajiwC8!kt!;l#d~lw>?)ABf^9N0QKXUW6NRIjB* bv2{%4N7df3Yb^QzOzI4tu6{1-oD!MUkrajna India Thaiföld + Válassz egy csomagot a részletek megtekintéséhez + Főoldal Biztosan törölni szeretnéd ezt a csomagot? Értesítés(ek) a csomag jelenlegi állapotáról Csomag események @@ -43,7 +45,6 @@ Szerkesztés Csomag szerkesztése Vissza - Főoldal Figyelmen kívül hagy Licenc További lehetőségek diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d4b6b4..51c1d25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,6 +31,8 @@ Ukraine India Thailand + Select a parcel to view its details + Home Are you sure you want to delete this parcel? Notifications about the current status of the parcel Parcel events @@ -47,7 +49,6 @@ Edit parcel This delivery service requires an API key, but none was provided. Check the app\'s settings for more information. Go back - Home Ignore License More options diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1a187b2..97f9cf6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,6 +37,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } +androidx-material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class" } androidx-material-icons = { group = "androidx.compose.material", name = "material-icons-core" } moshi-kotlin-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshiKotlinCodegen" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } From 7cafae277a5233351a31aeecd7477f2f6f988c12 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Sun, 20 Jul 2025 18:51:26 +0200 Subject: [PATCH 06/18] Express one support --- app/build.gradle.kts | 1 + .../java/dev/itsvic/parceltracker/api/Core.kt | 2 + .../api/ExpressOneDeliveryService.kt | 115 ++++++++++++++++++ .../ui/views/AddEditParcelView.kt | 12 +- app/src/main/res/values/strings.xml | 1 + gradle/libs.versions.toml | 2 + 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/dev/itsvic/parceltracker/api/ExpressOneDeliveryService.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cd761ef..d56d964 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -108,6 +108,7 @@ dependencies { implementation(libs.work.runtime.ktx) implementation(libs.kotlinx.coroutines.guava) implementation(libs.androidx.browser) + implementation(libs.jsoup) ksp(libs.room.compiler) ksp(libs.moshi.kotlin.codegen) diff --git a/app/src/main/java/dev/itsvic/parceltracker/api/Core.kt b/app/src/main/java/dev/itsvic/parceltracker/api/Core.kt index 2e1edee..044eb14 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/api/Core.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/api/Core.kt @@ -36,6 +36,7 @@ enum class Service { AN_POST, BELPOST, DPD_GER, + EXPRESS_ONE, GLS_HUNGARY, HERMES, MAGYAR_POSTA, @@ -80,6 +81,7 @@ fun getDeliveryService(service: Service): DeliveryService? { Service.AN_POST -> AnPostDeliveryService Service.BELPOST -> BelpostDeliveryService Service.DPD_GER -> DpdGerDeliveryService + Service.EXPRESS_ONE -> ExpressOneDeliveryService Service.GLS_HUNGARY -> GLSHungaryDeliveryService Service.HERMES -> HermesDeliveryService Service.MAGYAR_POSTA -> MagyarPostaDeliveryService diff --git a/app/src/main/java/dev/itsvic/parceltracker/api/ExpressOneDeliveryService.kt b/app/src/main/java/dev/itsvic/parceltracker/api/ExpressOneDeliveryService.kt new file mode 100644 index 0000000..e11f144 --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/api/ExpressOneDeliveryService.kt @@ -0,0 +1,115 @@ +package dev.itsvic.parceltracker.api + +import dev.itsvic.parceltracker.R +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.Locale +import okhttp3.Request + +object ExpressOneDeliveryService : DeliveryService { + override val nameResource: Int = R.string.service_express_one + override val acceptsPostCode: Boolean = false + override val requiresPostCode: Boolean = false + + override suspend fun getParcel(trackingId: String, postalCode: String?): Parcel { + val url = "https://tracking.expressone.hu/?plc_number=${trackingId}&sender_id=" + + val request = Request.Builder().url(url).build() + + val response = api_client.newCall(request).execute() + + if (!response.isSuccessful) { + throw ParcelNonExistentException() + } + + val html = response.body?.string() ?: throw ParcelNonExistentException() + + if (html.contains("Nincs találat") || html.contains("No results") || html.contains("error")) { + throw ParcelNonExistentException() + } + + var currentStatus = Status.Unknown + val htmlLower = html.lowercase(Locale.getDefault()) + + when { + htmlLower.contains("kézbesítve") || htmlLower.contains("delivered") -> { + currentStatus = Status.Delivered + } + htmlLower.contains("kiszállítás") || + htmlLower.contains("out for delivery") || + htmlLower.contains("kiadva futárnak") -> { + currentStatus = Status.OutForDelivery + } + htmlLower.contains("depóba érkezett") || htmlLower.contains("arrived at depot") -> { + currentStatus = Status.InWarehouse + } + htmlLower.contains("feldolgozás") || + htmlLower.contains("processing") || + htmlLower.contains("átszállítás") -> { + currentStatus = Status.InTransit + } + htmlLower.contains("átvétel") || htmlLower.contains("pickup") -> { + currentStatus = Status.AwaitingPickup + } + else -> { + currentStatus = Status.InTransit + } + } + + val history = mutableListOf() + + val datePattern = "
(\\d{4}-\\d{2}-\\d{2})
".toRegex() + val rowPattern = + "]*>(\\d{2}:\\d{2}:\\d{2})\\s*]*>([^<]*)\\s*]*>([^<]*)" + .toRegex() + + val dateMatches = datePattern.findAll(html) + + for (dateMatch in dateMatches) { + val dateText = dateMatch.groupValues[1] + + val startIndex = dateMatch.range.last + val nextDateMatch = datePattern.find(html, startIndex + 1) + val endIndex = nextDateMatch?.range?.first ?: html.length + + val tableSection = html.substring(startIndex, endIndex) + val rowMatches = rowPattern.findAll(tableSection) + + for (rowMatch in rowMatches) { + val timeText = rowMatch.groupValues[1] + val codeText = rowMatch.groupValues[2].trim() + val descriptionText = rowMatch.groupValues[3].trim() + + if (timeText.isNotEmpty() && descriptionText.isNotEmpty()) { + try { + val dateTimeString = "$dateText $timeText" + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") + val dateTime = LocalDateTime.parse(dateTimeString, formatter) + + var cleanDescription = descriptionText.replace(" ", " ").trim() + val locationMatch = "\\(([^)]+)\\)".toRegex().find(cleanDescription) + val location = locationMatch?.groupValues?.get(1) ?: "" + + if (location.isNotEmpty()) { + cleanDescription = cleanDescription.replace("($location)", "").trim() + } + + history.add(ParcelHistoryItem(cleanDescription, dateTime, location)) + } catch (e: Exception) { + continue + } + } + } + } + + history.sortByDescending { it.time } + + if (history.isEmpty()) { + history.add( + ParcelHistoryItem( + "Csomag nyomon követése elindítva", LocalDateTime.now(), "Express One Hungary")) + } + + return Parcel(trackingId, history, currentStatus) + } +} diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index 4f5f61d..7789a23 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -116,16 +116,16 @@ fun AddEditParcelView( "international" -> it in listOf(Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX) "north_america" -> it == Service.UNIUNI "europe" -> it in listOf(Service.BELPOST, Service.SAMEDAY_BG, Service.DPD_UK, Service.EVRI, - Service.AN_POST, Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST, - Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.DPD_GER, Service.HERMES, - Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA, Service.PACKETA) + Service.AN_POST, Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST, + Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.DPD_GER, Service.HERMES, + Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA, Service.PACKETA, Service.EXPRESS_ONE) "asia" -> it in listOf(Service.EKART, Service.SPX_TH) "belarus" -> it == Service.BELPOST "bulgaria" -> it == Service.SAMEDAY_BG "uk" -> it in listOf(Service.DPD_UK, Service.EVRI) "ireland" -> it == Service.AN_POST "poland" -> it in listOf(Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST) - "hungary" -> it in listOf(Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU) + "hungary" -> it in listOf(Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.EXPRESS_ONE) "germany" -> it in listOf(Service.DPD_GER, Service.HERMES) "italy" -> it == Service.POSTE_ITALIANE "romania" -> it == Service.SAMEDAY_RO @@ -142,7 +142,7 @@ fun AddEditParcelView( Service.UNIUNI -> 1 Service.BELPOST, Service.SAMEDAY_BG, Service.PACKETA, Service.DPD_UK, Service.EVRI, Service.AN_POST, Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST, - Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.DPD_GER, Service.HERMES, + Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.EXPRESS_ONE, Service.DPD_GER, Service.HERMES, Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA -> 2 Service.EKART, Service.SPX_TH -> 3 else -> 4 @@ -155,7 +155,7 @@ fun AddEditParcelView( Service.DPD_UK, Service.EVRI -> "D_UK" Service.AN_POST -> "E_Ireland" Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST -> "F_Poland" - Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU -> "G_Hungary" + Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.EXPRESS_ONE -> "G_Hungary" Service.DPD_GER, Service.HERMES -> "H_Germany" Service.POSTE_ITALIANE -> "I_Italy" Service.SAMEDAY_RO -> "J_Romania" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 51c1d25..a49eae2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,6 +76,7 @@ You must select a service. Evri Example Post + Express One Hungary GLS GLS Hungary Hermes diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 97f9cf6..5f4679e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ work = "2.10.0" kotlinxCoroutinesGuava = "1.10.1" material3 = "1.4.0-alpha11" browser = "1.8.0" +jsoup = "1.17.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -58,6 +59,7 @@ work-runtime = { group = "androidx.work", name = "work-runtime", version.ref = " work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" } kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava" } androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "browser" } +jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoup" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 8c60f86c089022e491eb1d106dd3172b10f6cb35 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Sun, 20 Jul 2025 18:57:18 +0200 Subject: [PATCH 07/18] Formatting --- .../dev/itsvic/parceltracker/MainActivity.kt | 18 ++-- .../ui/views/AddEditParcelView.kt | 83 +++++++++++++--- .../parceltracker/ui/views/ParcelView.kt | 94 +++++++++---------- .../parceltracker/ui/views/SettingsView.kt | 28 +++--- 4 files changed, 139 insertions(+), 84 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt index cc896ec..eed9f8f 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt @@ -32,7 +32,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -112,20 +111,19 @@ class MainActivity : ComponentActivity() { @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun handleNotificationPermissionStuff() { val requestPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean - -> - if (isGranted) { - Log.d("MainActivity", "Notification permissions granted") - } else { - Log.d("MainActivity", "Notification permissions NOT granted") - } + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean -> + if (isGranted) { + Log.d("MainActivity", "Notification permissions granted") + } else { + Log.d("MainActivity", "Notification permissions NOT granted") } + } // Notification checks when { ContextCompat.checkSelfPermission( - applicationContext, - Manifest.permission.POST_NOTIFICATIONS, + applicationContext, + Manifest.permission.POST_NOTIFICATIONS, ) == PackageManager.PERMISSION_GRANTED -> { // We can post notifications } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index 7789a23..9a27a4b 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -113,19 +113,53 @@ fun AddEditParcelView( val sortedServiceOptions = serviceOptions.sortedWith(compareBy { val isPreferredRegion = when (preferredRegion) { - "international" -> it in listOf(Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX) + "international" -> + it in + listOf(Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX) "north_america" -> it == Service.UNIUNI - "europe" -> it in listOf(Service.BELPOST, Service.SAMEDAY_BG, Service.DPD_UK, Service.EVRI, - Service.AN_POST, Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST, - Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.DPD_GER, Service.HERMES, - Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA, Service.PACKETA, Service.EXPRESS_ONE) + "europe" -> + it in + listOf( + Service.BELPOST, + Service.SAMEDAY_BG, + Service.DPD_UK, + Service.EVRI, + Service.AN_POST, + Service.ALLEGRO_ONEBOX, + Service.INPOST, + Service.ORLEN_PACZKA, + Service.POLISH_POST, + Service.GLS_HUNGARY, + Service.MAGYAR_POSTA, + Service.SAMEDAY_HU, + Service.DPD_GER, + Service.HERMES, + Service.POSTE_ITALIANE, + Service.SAMEDAY_RO, + Service.POSTNORD, + Service.NOVA_POSHTA, + Service.UKRPOSHTA, + Service.PACKETA, + Service.EXPRESS_ONE) "asia" -> it in listOf(Service.EKART, Service.SPX_TH) "belarus" -> it == Service.BELPOST "bulgaria" -> it == Service.SAMEDAY_BG "uk" -> it in listOf(Service.DPD_UK, Service.EVRI) "ireland" -> it == Service.AN_POST - "poland" -> it in listOf(Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST) - "hungary" -> it in listOf(Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.EXPRESS_ONE) + "poland" -> + it in + listOf( + Service.ALLEGRO_ONEBOX, + Service.INPOST, + Service.ORLEN_PACZKA, + Service.POLISH_POST) + "hungary" -> + it in + listOf( + Service.GLS_HUNGARY, + Service.MAGYAR_POSTA, + Service.SAMEDAY_HU, + Service.EXPRESS_ONE) "germany" -> it in listOf(Service.DPD_GER, Service.HERMES) "italy" -> it == Service.POSTE_ITALIANE "romania" -> it == Service.SAMEDAY_RO @@ -140,10 +174,27 @@ fun AddEditParcelView( when (it) { Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX -> 0 Service.UNIUNI -> 1 - Service.BELPOST, Service.SAMEDAY_BG, Service.PACKETA, Service.DPD_UK, Service.EVRI, - Service.AN_POST, Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST, - Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.EXPRESS_ONE, Service.DPD_GER, Service.HERMES, - Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, Service.UKRPOSHTA -> 2 + Service.BELPOST, + Service.SAMEDAY_BG, + Service.PACKETA, + Service.DPD_UK, + Service.EVRI, + Service.AN_POST, + Service.ALLEGRO_ONEBOX, + Service.INPOST, + Service.ORLEN_PACZKA, + Service.POLISH_POST, + Service.GLS_HUNGARY, + Service.MAGYAR_POSTA, + Service.SAMEDAY_HU, + Service.EXPRESS_ONE, + Service.DPD_GER, + Service.HERMES, + Service.POSTE_ITALIANE, + Service.SAMEDAY_RO, + Service.POSTNORD, + Service.NOVA_POSHTA, + Service.UKRPOSHTA -> 2 Service.EKART, Service.SPX_TH -> 3 else -> 4 } @@ -154,8 +205,14 @@ fun AddEditParcelView( Service.PACKETA -> "C_Europe" Service.DPD_UK, Service.EVRI -> "D_UK" Service.AN_POST -> "E_Ireland" - Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST -> "F_Poland" - Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.EXPRESS_ONE -> "G_Hungary" + Service.ALLEGRO_ONEBOX, + Service.INPOST, + Service.ORLEN_PACZKA, + Service.POLISH_POST -> "F_Poland" + Service.GLS_HUNGARY, + Service.MAGYAR_POSTA, + Service.SAMEDAY_HU, + Service.EXPRESS_ONE -> "G_Hungary" Service.DPD_GER, Service.HERMES -> "H_Germany" Service.POSTE_ITALIANE -> "I_Italy" Service.SAMEDAY_RO -> "J_Romania" diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt index bc748c0..a004812 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt @@ -28,7 +28,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext - import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewLightDark @@ -41,7 +40,6 @@ import dev.itsvic.parceltracker.api.Status import dev.itsvic.parceltracker.api.getDeliveryServiceName import dev.itsvic.parceltracker.ui.components.ParcelActionBar import dev.itsvic.parceltracker.ui.components.ParcelHistoryItemRow - import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import java.time.LocalDateTime @@ -93,36 +91,36 @@ fun ParcelView( Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - getDeliveryServiceName(service)?.let { - Text( - stringResource(it), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant) - } + getDeliveryServiceName(service)?.let { + Text( + stringResource(it), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant) + } - SelectionContainer { - Text( - parcel.id, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant) - } - } + SelectionContainer { + Text( + parcel.id, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } } items(parcel.properties.entries.toList()) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Text( - stringResource(it.key), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant) - Text( - it.value, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.End) - } + Text( + stringResource(it.key), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant) + Text( + it.value, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.End) + } } item { @@ -136,31 +134,31 @@ fun ParcelView( if (!isArchived && !archivePromptDismissed && (parcel.currentStatus == Status.Delivered || parcel.currentStatus == Status.PickedUp)) - item { - Card( - shape = RoundedCornerShape(16.dp), - modifier = Modifier.padding(bottom = 16.dp)) { - Column( - Modifier.padding(24.dp), - verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - stringResource(R.string.archive_prompt_question), - style = MaterialTheme.typography.titleMedium) - Text(stringResource(R.string.archive_prompt_text)) - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth()) { - FilledTonalButton( - onArchivePromptDismissal, modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.ignore)) - } - Button(onArchive, modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.archive)) - } - } - } + item { + Card( + shape = RoundedCornerShape(16.dp), + modifier = Modifier.padding(bottom = 16.dp)) { + Column( + Modifier.padding(24.dp), + verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + stringResource(R.string.archive_prompt_question), + style = MaterialTheme.typography.titleMedium) + Text(stringResource(R.string.archive_prompt_text)) + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxWidth()) { + FilledTonalButton( + onArchivePromptDismissal, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.ignore)) } + Button(onArchive, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.archive)) + } + } + } } + } items(parcel.history.size) { index -> if (index > 0) HorizontalDivider(Modifier.padding(top = 8.dp, bottom = 16.dp)) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt index ccccf0f..4f2eb8d 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt @@ -117,12 +117,12 @@ fun SettingsView( ) { innerPadding -> Column(Modifier.padding(innerPadding).verticalScroll(rememberScrollState())) { Row( - modifier = - Modifier.clickable { setUnmeteredOnly(unmeteredOnly.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.clickable { setUnmeteredOnly(unmeteredOnly.not()) } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { Column(modifier = Modifier.fillMaxWidth(0.8f)) { Text(stringResource(R.string.unmetered_only_setting)) @@ -134,12 +134,12 @@ fun SettingsView( } Row( - modifier = - Modifier.clickable { setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.clickable { setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { Column(modifier = Modifier.fillMaxWidth(0.8f)) { Text(stringResource(R.string.clipboard_paste_enabled)) @@ -147,7 +147,9 @@ fun SettingsView( stringResource(R.string.clipboard_paste_description), style = MaterialTheme.typography.bodyMedium) } - Switch(checked = clipboardPasteEnabled, onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) }) + Switch( + checked = clipboardPasteEnabled, + onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) }) } // Preferred Region Setting From 609800f274245cd755fb2d4ba825d1fd73cf1f97 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:14:53 +0200 Subject: [PATCH 08/18] test --- .../ui/components/BottomNavBar.kt | 50 +++--- .../ui/components/ParcelActionBar.kt | 64 +++---- .../parceltracker/ui/components/ParcelRow.kt | 107 ++++++------ .../parceltracker/ui/views/AdaptiveView.kt | 4 +- .../itsvic/parceltracker/ui/views/HomeView.kt | 45 +++-- .../parceltracker/ui/views/TabletView.kt | 164 +++++++++--------- 6 files changed, 219 insertions(+), 215 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt index cdb3d6a..9fe6f35 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt @@ -15,29 +15,31 @@ import dev.itsvic.parceltracker.R @Composable fun BottomNavBar( - currentRoute: String, - onNavigateToHome: () -> Unit, - onNavigateToAddParcel: () -> Unit, - onNavigateToSettings: () -> Unit, + currentRoute: String, + onNavigateToHome: () -> Unit, + onNavigateToAddParcel: () -> Unit, + onNavigateToSettings: () -> Unit, ) { - NavigationBar { - NavigationBarItem( - icon = { Icon(Icons.Filled.Home, contentDescription = stringResource(R.string.home)) }, - label = { Text(stringResource(R.string.home)) }, - selected = currentRoute.contains("HomePage"), - onClick = onNavigateToHome - ) - NavigationBarItem( - icon = { Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.add)) }, - label = { Text(stringResource(R.string.add)) }, - selected = currentRoute.contains("AddParcelPage"), - onClick = onNavigateToAddParcel - ) - NavigationBarItem( - icon = { Icon(Icons.Filled.Settings, contentDescription = stringResource(R.string.settings)) }, - label = { Text(stringResource(R.string.settings)) }, - selected = currentRoute.contains("SettingsPage"), - onClick = onNavigateToSettings - ) - } + NavigationBar { + NavigationBarItem( + icon = { Icon(Icons.Filled.Home, contentDescription = stringResource(R.string.home)) }, + label = { Text(stringResource(R.string.home)) }, + selected = currentRoute.contains("HomePage"), + onClick = onNavigateToHome + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.add)) }, + label = { Text(stringResource(R.string.add)) }, + selected = currentRoute.contains("AddParcelPage"), + onClick = onNavigateToAddParcel + ) + NavigationBarItem( + icon = { + Icon(Icons.Filled.Settings, contentDescription = stringResource(R.string.settings)) + }, + label = { Text(stringResource(R.string.settings)) }, + selected = currentRoute.contains("SettingsPage"), + onClick = onNavigateToSettings + ) + } } \ No newline at end of file diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt index 37d3e0a..4c97db5 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt @@ -4,7 +4,6 @@ package dev.itsvic.parceltracker.ui.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit -import androidx.compose.ui.res.painterResource import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar @@ -16,44 +15,51 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import dev.itsvic.parceltracker.R import dev.itsvic.parceltracker.api.Status @Composable fun ParcelActionBar( - status: Status?, - onEdit: () -> Unit, - onArchive: () -> Unit, - onDelete: () -> Unit + status: Status?, + onEdit: () -> Unit, + onArchive: () -> Unit, + onDelete: () -> Unit ) { - var showDeleteDialog by remember { mutableStateOf(false) } - - NavigationBar { - NavigationBarItem( - icon = { Icon(Icons.Filled.Edit, contentDescription = stringResource(R.string.edit)) }, - label = { Text(stringResource(R.string.edit)) }, - selected = false, - onClick = onEdit - ) + var showDeleteDialog by remember { mutableStateOf(false) } - if (status == Status.Delivered) { - NavigationBarItem( - icon = { Icon(painterResource(R.drawable.archive), contentDescription = stringResource(R.string.archive)) }, - label = { Text(stringResource(R.string.archive)) }, - selected = false, - onClick = onArchive - ) - } - - NavigationBarItem( - icon = { Icon(Icons.Filled.Delete, contentDescription = stringResource(R.string.delete)) }, - label = { Text(stringResource(R.string.delete)) }, - selected = false, - onClick = { showDeleteDialog = true } - ) + NavigationBar { + NavigationBarItem( + icon = { Icon(Icons.Filled.Edit, contentDescription = stringResource(R.string.edit)) }, + label = { Text(stringResource(R.string.edit)) }, + selected = false, + onClick = onEdit + ) + + if (status == Status.Delivered) { + NavigationBarItem( + icon = { + Icon( + painterResource(R.drawable.archive), + contentDescription = stringResource(R.string.archive)) + }, + label = { Text(stringResource(R.string.archive)) }, + selected = false, + onClick = onArchive + ) } + NavigationBarItem( + icon = { + Icon(Icons.Filled.Delete, contentDescription = stringResource(R.string.delete)) + }, + label = { Text(stringResource(R.string.delete)) }, + selected = false, + onClick = { showDeleteDialog = true } + ) + } + if (showDeleteDialog) { AlertDialog( onDismissRequest = { showDeleteDialog = false }, diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt index 8f2c949..d8f6636 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt @@ -33,63 +33,60 @@ import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme @Composable fun ParcelRow(parcel: Parcel, status: Status?, isSelected: Boolean = false, onClick: () -> Unit) { Row( - modifier = Modifier - .clickable(onClick = onClick) - .fillMaxWidth() - .background( - if (isSelected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) - else MaterialTheme.colorScheme.surface - ) - .padding(16.dp, 12.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically) { - if (status != null) - Box( - modifier = - Modifier.size(40.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.primaryContainer), - contentAlignment = Alignment.Center) { - Icon( - painterResource( - when (status) { - Status.Preadvice -> R.drawable.outline_other_admission_24 - Status.LockerboxAcceptedParcel -> - R.drawable.outline_deployed_code_update_24 - Status.PickedUpByCourier -> R.drawable.outline_deployed_code_account_24 - Status.InTransit -> R.drawable.outline_local_shipping_24 - Status.InWarehouse -> R.drawable.outline_warehouse_24 - Status.Customs -> R.drawable.outline_search_24 - Status.OutForDelivery -> R.drawable.outline_delivery_truck_speed_24 - Status.DeliveryFailure -> R.drawable.outline_error_24 - Status.PickupTimeEndingSoon -> - R.drawable.outline_notifications_active_24 - Status.AwaitingPickup -> R.drawable.outline_pin_drop_24 - Status.Delivered, - Status.PickedUp -> R.drawable.outline_check_24 - Status.DeliveredToNeighbor -> R.drawable.outline_holiday_village_24 - Status.DeliveredToASafePlace -> R.drawable.outline_roofing_24 - Status.DroppedAtCustomerService -> R.drawable.outline_support_agent_24 - Status.ReturningToSender -> R.drawable.outline_arrow_top_left_24 - Status.ReturnedToSender -> R.drawable.outline_arrow_top_left_24 - Status.Delayed -> R.drawable.outline_deployed_code_history_24 - Status.Damaged -> R.drawable.outline_deployed_code_alert_24 - Status.Destroyed -> R.drawable.outline_destruction_24 - else -> R.drawable.outline_question_mark_24 - }), - stringResource(status.nameResource), - tint = MaterialTheme.colorScheme.primary) - } + modifier = + Modifier.clickable(onClick = onClick) + .fillMaxWidth() + .background( + if (isSelected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) + else MaterialTheme.colorScheme.surface) + .padding(16.dp, 12.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically) { + if (status != null) + Box( + modifier = + Modifier.size(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center) { + Icon( + painterResource( + when (status) { + Status.Preadvice -> R.drawable.outline_other_admission_24 + Status.LockerboxAcceptedParcel -> R.drawable.outline_deployed_code_update_24 + Status.PickedUpByCourier -> R.drawable.outline_deployed_code_account_24 + Status.InTransit -> R.drawable.outline_local_shipping_24 + Status.InWarehouse -> R.drawable.outline_warehouse_24 + Status.Customs -> R.drawable.outline_search_24 + Status.OutForDelivery -> R.drawable.outline_delivery_truck_speed_24 + Status.DeliveryFailure -> R.drawable.outline_error_24 + Status.PickupTimeEndingSoon -> R.drawable.outline_notifications_active_24 + Status.AwaitingPickup -> R.drawable.outline_pin_drop_24 + Status.Delivered, + Status.PickedUp -> R.drawable.outline_check_24 + Status.DeliveredToNeighbor -> R.drawable.outline_holiday_village_24 + Status.DeliveredToASafePlace -> R.drawable.outline_roofing_24 + Status.DroppedAtCustomerService -> R.drawable.outline_support_agent_24 + Status.ReturningToSender -> R.drawable.outline_arrow_top_left_24 + Status.ReturnedToSender -> R.drawable.outline_arrow_top_left_24 + Status.Delayed -> R.drawable.outline_deployed_code_history_24 + Status.Damaged -> R.drawable.outline_deployed_code_alert_24 + Status.Destroyed -> R.drawable.outline_destruction_24 + else -> R.drawable.outline_question_mark_24 + }), + stringResource(status.nameResource), + tint = MaterialTheme.colorScheme.primary) + } - Column { - Text(parcel.humanName, color = MaterialTheme.colorScheme.onBackground) + Column { + Text(parcel.humanName, color = MaterialTheme.colorScheme.onBackground) - Text( - "${stringResource(getDeliveryServiceName(parcel.service)!!)}: ${parcel.parcelId}", - fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant) - } - } + Text( + "${stringResource(getDeliveryServiceName(parcel.service)!!)}: ${parcel.parcelId}", + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } } @Composable diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt index 82c1b3b..53a2e2d 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt @@ -31,10 +31,10 @@ fun AdaptiveParcelApp( homeContent: @Composable () -> Unit ) { val isTablet = windowSizeClass.widthSizeClass >= WindowWidthSizeClass.Medium - + if (isTablet) { var currentNavigationItem by remember { mutableStateOf(TabletNavigationItem.HOME) } - + TabletView( parcels = parcels, selectedParcel = selectedParcel, diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt index f07b84f..d58a209 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt @@ -10,7 +10,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable - import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource @@ -29,34 +28,34 @@ import java.time.Instant @OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeView( - parcels: List, - onNavigateToAddParcel: () -> Unit, - onNavigateToParcel: (Parcel) -> Unit, - onNavigateToSettings: () -> Unit, + parcels: List, + onNavigateToAddParcel: () -> Unit, + onNavigateToParcel: (Parcel) -> Unit, + onNavigateToSettings: () -> Unit, ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( - topBar = { - LargeTopAppBar( - title = { Text(stringResource(R.string.app_name)) }, - scrollBehavior = scrollBehavior, - ) - }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { innerPadding -> - LazyColumn(modifier = Modifier.padding(innerPadding)) { - if (parcels.isEmpty()) - item { - Text( - stringResource(R.string.no_parcels_flavor), - modifier = Modifier.padding(horizontal = 16.dp)) - } - - items(parcels.reversed()) { parcel -> - ParcelRow(parcel.parcel, parcel.status?.status) { onNavigateToParcel(parcel.parcel) } - } + topBar = { + LargeTopAppBar( + title = { Text(stringResource(R.string.app_name)) }, + scrollBehavior = scrollBehavior, + ) + }, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { innerPadding -> + LazyColumn(modifier = Modifier.padding(innerPadding)) { + if (parcels.isEmpty()) + item { + Text( + stringResource(R.string.no_parcels_flavor), + modifier = Modifier.padding(horizontal = 16.dp)) } + + items(parcels.reversed()) { parcel -> + ParcelRow(parcel.parcel, parcel.status?.status) { onNavigateToParcel(parcel.parcel) } } + } + } } @Composable diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt index 4c84efe..c488464 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt @@ -56,70 +56,70 @@ enum class TabletNavigationItem { @OptIn(ExperimentalMaterial3Api::class) @Composable fun TabletView( - parcels: List, - selectedParcel: Parcel?, - apiParcel: APIParcel?, - isLoading: Boolean, - currentNavigationItem: TabletNavigationItem, - onNavigateToItem: (TabletNavigationItem) -> Unit, - onNavigateToParcel: (Parcel) -> Unit, - onNavigateToAddParcel: () -> Unit, - onNavigateToSettings: () -> Unit, - onEditParcel: (Parcel) -> Unit, - onDeleteParcel: (Parcel) -> Unit, - onArchiveParcel: (Parcel) -> Unit, - onArchivePromptDismissal: (Parcel) -> Unit, - settingsContent: @Composable () -> Unit = {}, - addParcelContent: @Composable () -> Unit = {}, - editParcelContent: @Composable () -> Unit = {} + parcels: List, + selectedParcel: Parcel?, + apiParcel: APIParcel?, + isLoading: Boolean, + currentNavigationItem: TabletNavigationItem, + onNavigateToItem: (TabletNavigationItem) -> Unit, + onNavigateToParcel: (Parcel) -> Unit, + onNavigateToAddParcel: () -> Unit, + onNavigateToSettings: () -> Unit, + onEditParcel: (Parcel) -> Unit, + onDeleteParcel: (Parcel) -> Unit, + onArchiveParcel: (Parcel) -> Unit, + onArchivePromptDismissal: (Parcel) -> Unit, + settingsContent: @Composable () -> Unit = {}, + addParcelContent: @Composable () -> Unit = {}, + editParcelContent: @Composable () -> Unit = {} ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - + Row(modifier = Modifier.fillMaxSize()) { Card( - modifier = Modifier - .width(400.dp) - .fillMaxHeight() - .padding(8.dp) + modifier = Modifier + .width(400.dp) + .fillMaxHeight() + .padding(8.dp) ) { Column { Text( - text = stringResource(R.string.app_name), - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp) + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp) ) LazyColumn( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - .padding(horizontal = 8.dp) + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .padding(horizontal = 8.dp) ) { if (parcels.isEmpty()) { item { Card( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) ) { Text( - stringResource(R.string.no_parcels_flavor), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(16.dp) + stringResource(R.string.no_parcels_flavor), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(16.dp) ) } } } else { items(parcels.reversed()) { parcel -> Card( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp) + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) ) { ParcelRow( - parcel.parcel, - parcel.status?.status, - isSelected = selectedParcel?.id == parcel.parcel.id + parcel.parcel, + parcel.status?.status, + isSelected = selectedParcel?.id == parcel.parcel.id ) { onNavigateToParcel(parcel.parcel) } @@ -131,25 +131,25 @@ fun TabletView( HorizontalDivider() NavigationBar( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth() ) { NavigationBarItem( - icon = { Icon(Icons.Filled.Home, contentDescription = null) }, - label = { Text(stringResource(R.string.home)) }, - selected = currentNavigationItem == TabletNavigationItem.HOME, - onClick = { onNavigateToItem(TabletNavigationItem.HOME) } + icon = { Icon(Icons.Filled.Home, contentDescription = null) }, + label = { Text(stringResource(R.string.home)) }, + selected = currentNavigationItem == TabletNavigationItem.HOME, + onClick = { onNavigateToItem(TabletNavigationItem.HOME) } ) NavigationBarItem( - icon = { Icon(Icons.Filled.Add, contentDescription = null) }, - label = { Text(stringResource(R.string.add_parcel)) }, - selected = currentNavigationItem == TabletNavigationItem.ADD_PARCEL, - onClick = { onNavigateToItem(TabletNavigationItem.ADD_PARCEL) } + icon = { Icon(Icons.Filled.Add, contentDescription = null) }, + label = { Text(stringResource(R.string.add_parcel)) }, + selected = currentNavigationItem == TabletNavigationItem.ADD_PARCEL, + onClick = { onNavigateToItem(TabletNavigationItem.ADD_PARCEL) } ) NavigationBarItem( - icon = { Icon(Icons.Filled.Settings, contentDescription = null) }, - label = { Text(stringResource(R.string.settings)) }, - selected = currentNavigationItem == TabletNavigationItem.SETTINGS, - onClick = { onNavigateToItem(TabletNavigationItem.SETTINGS) } + icon = { Icon(Icons.Filled.Settings, contentDescription = null) }, + label = { Text(stringResource(R.string.settings)) }, + selected = currentNavigationItem == TabletNavigationItem.SETTINGS, + onClick = { onNavigateToItem(TabletNavigationItem.SETTINGS) } ) } } @@ -157,67 +157,67 @@ fun TabletView( // Right panel: Content area Card( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(8.dp) + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(8.dp) ) { when (currentNavigationItem) { TabletNavigationItem.HOME -> { if (selectedParcel != null) { if (isLoading) { Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } else if (apiParcel != null) { ParcelView( - parcel = apiParcel, - humanName = selectedParcel.humanName, - service = selectedParcel.service, - isArchived = selectedParcel.isArchived, - archivePromptDismissed = selectedParcel.archivePromptDismissed, - onBackPressed = { /* No back button in tablet mode */ }, - onEdit = { onEditParcel(selectedParcel) }, - onDelete = { onDeleteParcel(selectedParcel) }, - onArchive = { onArchiveParcel(selectedParcel) }, - onArchivePromptDismissal = { onArchivePromptDismissal(selectedParcel) }, - showBackButton = false + parcel = apiParcel, + humanName = selectedParcel.humanName, + service = selectedParcel.service, + isArchived = selectedParcel.isArchived, + archivePromptDismissed = selectedParcel.archivePromptDismissed, + onBackPressed = { /* No back button in tablet mode */ }, + onEdit = { onEditParcel(selectedParcel) }, + onDelete = { onDeleteParcel(selectedParcel) }, + onArchive = { onArchiveParcel(selectedParcel) }, + onArchivePromptDismissal = { onArchivePromptDismissal(selectedParcel) }, + showBackButton = false ) } } else { Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center ) { Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { Text( - text = stringResource(R.string.app_name), - style = MaterialTheme.typography.headlineMedium, - modifier = Modifier.padding(bottom = 16.dp) + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.padding(bottom = 16.dp) ) Text( - text = stringResource(R.string.select_parcel_to_view), - style = MaterialTheme.typography.bodyLarge + text = stringResource(R.string.select_parcel_to_view), + style = MaterialTheme.typography.bodyLarge ) } } } } - + TabletNavigationItem.ADD_PARCEL -> { addParcelContent() } - + TabletNavigationItem.EDIT_PARCEL -> { editParcelContent() } - + TabletNavigationItem.SETTINGS -> { settingsContent() } From 6197cb264c8d3b0d2e47f38b3c517ec3ed03a607 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:16:59 +0200 Subject: [PATCH 09/18] test2 --- .../parceltracker/ui/views/TabletView.kt | 309 ++++++++---------- 1 file changed, 141 insertions(+), 168 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt index c488464..92b1382 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later package dev.itsvic.parceltracker.ui.views -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -19,209 +18,183 @@ import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.itsvic.parceltracker.R import dev.itsvic.parceltracker.api.Parcel as APIParcel -import dev.itsvic.parceltracker.api.Service import dev.itsvic.parceltracker.db.Parcel import dev.itsvic.parceltracker.db.ParcelWithStatus import dev.itsvic.parceltracker.ui.components.ParcelRow enum class TabletNavigationItem { - HOME, - ADD_PARCEL, - EDIT_PARCEL, - SETTINGS + HOME, + ADD_PARCEL, + EDIT_PARCEL, + SETTINGS } @OptIn(ExperimentalMaterial3Api::class) @Composable fun TabletView( - parcels: List, - selectedParcel: Parcel?, - apiParcel: APIParcel?, - isLoading: Boolean, - currentNavigationItem: TabletNavigationItem, - onNavigateToItem: (TabletNavigationItem) -> Unit, - onNavigateToParcel: (Parcel) -> Unit, - onNavigateToAddParcel: () -> Unit, - onNavigateToSettings: () -> Unit, - onEditParcel: (Parcel) -> Unit, - onDeleteParcel: (Parcel) -> Unit, - onArchiveParcel: (Parcel) -> Unit, - onArchivePromptDismissal: (Parcel) -> Unit, - settingsContent: @Composable () -> Unit = {}, - addParcelContent: @Composable () -> Unit = {}, - editParcelContent: @Composable () -> Unit = {} + parcels: List, + selectedParcel: Parcel?, + apiParcel: APIParcel?, + isLoading: Boolean, + currentNavigationItem: TabletNavigationItem, + onNavigateToItem: (TabletNavigationItem) -> Unit, + onNavigateToParcel: (Parcel) -> Unit, + onNavigateToAddParcel: () -> Unit, + onNavigateToSettings: () -> Unit, + onEditParcel: (Parcel) -> Unit, + onDeleteParcel: (Parcel) -> Unit, + onArchiveParcel: (Parcel) -> Unit, + onArchivePromptDismissal: (Parcel) -> Unit, + settingsContent: @Composable () -> Unit = {}, + addParcelContent: @Composable () -> Unit = {}, + editParcelContent: @Composable () -> Unit = {} ) { - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - Row(modifier = Modifier.fillMaxSize()) { - Card( - modifier = Modifier - .width(400.dp) - .fillMaxHeight() - .padding(8.dp) - ) { - Column { - Text( - text = stringResource(R.string.app_name), - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp) - ) - - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - .padding(horizontal = 8.dp) - ) { - if (parcels.isEmpty()) { - item { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) { + Row(modifier = Modifier.fillMaxSize()) { + Card(modifier = Modifier.width(400.dp).fillMaxHeight().padding(8.dp)) { + Column { Text( - stringResource(R.string.no_parcels_flavor), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(16.dp) + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp) ) - } - } - } else { - items(parcels.reversed()) { parcel -> - Card( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp) - ) { - ParcelRow( - parcel.parcel, - parcel.status?.status, - isSelected = selectedParcel?.id == parcel.parcel.id + + LazyColumn( + modifier = Modifier.fillMaxWidth().weight(1f).padding(horizontal = 8.dp) ) { - onNavigateToParcel(parcel.parcel) + if (parcels.isEmpty()) { + item { + Card(modifier = Modifier.fillMaxWidth().padding(8.dp)) { + Text( + stringResource(R.string.no_parcels_flavor), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(16.dp) + ) + } + } + } else { + items(parcels.reversed()) { parcel -> + Card( + modifier = + Modifier.fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + ParcelRow( + parcel.parcel, + parcel.status?.status, + isSelected = selectedParcel?.id == parcel.parcel.id + ) { + onNavigateToParcel(parcel.parcel) + } + } + } + } } - } - } - } - } - - HorizontalDivider() - NavigationBar( - modifier = Modifier.fillMaxWidth() - ) { - NavigationBarItem( - icon = { Icon(Icons.Filled.Home, contentDescription = null) }, - label = { Text(stringResource(R.string.home)) }, - selected = currentNavigationItem == TabletNavigationItem.HOME, - onClick = { onNavigateToItem(TabletNavigationItem.HOME) } - ) - NavigationBarItem( - icon = { Icon(Icons.Filled.Add, contentDescription = null) }, - label = { Text(stringResource(R.string.add_parcel)) }, - selected = currentNavigationItem == TabletNavigationItem.ADD_PARCEL, - onClick = { onNavigateToItem(TabletNavigationItem.ADD_PARCEL) } - ) - NavigationBarItem( - icon = { Icon(Icons.Filled.Settings, contentDescription = null) }, - label = { Text(stringResource(R.string.settings)) }, - selected = currentNavigationItem == TabletNavigationItem.SETTINGS, - onClick = { onNavigateToItem(TabletNavigationItem.SETTINGS) } - ) - } - } - } - - // Right panel: Content area - Card( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(8.dp) - ) { - when (currentNavigationItem) { - TabletNavigationItem.HOME -> { - if (selectedParcel != null) { - if (isLoading) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } else if (apiParcel != null) { - ParcelView( - parcel = apiParcel, - humanName = selectedParcel.humanName, - service = selectedParcel.service, - isArchived = selectedParcel.isArchived, - archivePromptDismissed = selectedParcel.archivePromptDismissed, - onBackPressed = { /* No back button in tablet mode */ }, - onEdit = { onEditParcel(selectedParcel) }, - onDelete = { onDeleteParcel(selectedParcel) }, - onArchive = { onArchiveParcel(selectedParcel) }, - onArchivePromptDismissal = { onArchivePromptDismissal(selectedParcel) }, - showBackButton = false - ) - } - } else { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - text = stringResource(R.string.app_name), - style = MaterialTheme.typography.headlineMedium, - modifier = Modifier.padding(bottom = 16.dp) - ) - Text( - text = stringResource(R.string.select_parcel_to_view), - style = MaterialTheme.typography.bodyLarge - ) - } - } - } - } + HorizontalDivider() - TabletNavigationItem.ADD_PARCEL -> { - addParcelContent() - } - - TabletNavigationItem.EDIT_PARCEL -> { - editParcelContent() + NavigationBar(modifier = Modifier.fillMaxWidth()) { + NavigationBarItem( + icon = { Icon(Icons.Filled.Home, contentDescription = null) }, + label = { Text(stringResource(R.string.home)) }, + selected = currentNavigationItem == TabletNavigationItem.HOME, + onClick = { onNavigateToItem(TabletNavigationItem.HOME) } + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Add, contentDescription = null) }, + label = { Text(stringResource(R.string.add_parcel)) }, + selected = currentNavigationItem == TabletNavigationItem.ADD_PARCEL, + onClick = { onNavigateToItem(TabletNavigationItem.ADD_PARCEL) } + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Settings, contentDescription = null) }, + label = { Text(stringResource(R.string.settings)) }, + selected = currentNavigationItem == TabletNavigationItem.SETTINGS, + onClick = { onNavigateToItem(TabletNavigationItem.SETTINGS) } + ) + } + } } - TabletNavigationItem.SETTINGS -> { - settingsContent() + // Right panel: Content area + Card(modifier = Modifier.weight(1f).fillMaxHeight().padding(8.dp)) { + when (currentNavigationItem) { + TabletNavigationItem.HOME -> { + if (selectedParcel != null) { + if (isLoading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } else if (apiParcel != null) { + ParcelView( + parcel = apiParcel, + humanName = selectedParcel.humanName, + service = selectedParcel.service, + isArchived = selectedParcel.isArchived, + archivePromptDismissed = selectedParcel.archivePromptDismissed, + onBackPressed = { /* No back button in tablet mode */}, + onEdit = { onEditParcel(selectedParcel) }, + onDelete = { onDeleteParcel(selectedParcel) }, + onArchive = { onArchiveParcel(selectedParcel) }, + onArchivePromptDismissal = { + onArchivePromptDismissal(selectedParcel) + }, + showBackButton = false + ) + } + } else { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.padding(bottom = 16.dp) + ) + Text( + text = stringResource(R.string.select_parcel_to_view), + style = MaterialTheme.typography.bodyLarge + ) + } + } + } + } + TabletNavigationItem.ADD_PARCEL -> { + addParcelContent() + } + TabletNavigationItem.EDIT_PARCEL -> { + editParcelContent() + } + TabletNavigationItem.SETTINGS -> { + settingsContent() + } + } } - } } - } } From 305306e7e570d05c3cc319f51757cb5aa5e8cfc3 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:19:41 +0200 Subject: [PATCH 10/18] test3 --- .../parceltracker/ui/views/SettingsView.kt | 675 +++++++++--------- 1 file changed, 344 insertions(+), 331 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt index 4f2eb8d..e6c7dea 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt @@ -74,362 +74,375 @@ import kotlinx.coroutines.launch fun SettingsView( onBackPressed: () -> Unit, ) { - val context = LocalContext.current - val demoMode by context.dataStore.data.map { it[DEMO_MODE] == true }.collectAsState(false) - val unmeteredOnly by - context.dataStore.data.map { it[UNMETERED_ONLY] == true }.collectAsState(false) - val clipboardPasteEnabled by - context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) - val preferredRegion by - context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") - val coroutineScope = rememberCoroutineScope() - var regionDropdownExpanded by remember { mutableStateOf(false) } - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - var aboutDialogOpen by remember { mutableStateOf(false) } + val context = LocalContext.current + val demoMode by context.dataStore.data.map { it[DEMO_MODE] == true }.collectAsState(false) + val unmeteredOnly by + context.dataStore.data.map { it[UNMETERED_ONLY] == true }.collectAsState(false) + val clipboardPasteEnabled by + context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) + val preferredRegion by + context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") + val coroutineScope = rememberCoroutineScope() + var regionDropdownExpanded by remember { mutableStateOf(false) } + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + var aboutDialogOpen by remember { mutableStateOf(false) } - val dhlApiKey by context.dataStore.data.map { it[DHL_API_KEY] ?: "" }.collectAsState("") + val dhlApiKey by context.dataStore.data.map { it[DHL_API_KEY] ?: "" }.collectAsState("") - fun setValue(key: Preferences.Key, value: T) { - coroutineScope.launch { context.dataStore.edit { it[key] = value } } - } - - val setUnmeteredOnly: (Boolean) -> Unit = { value -> - coroutineScope.launch { - context.dataStore.edit { it[UNMETERED_ONLY] = value } - // reschedule notification worker to update constraints - context.enqueueNotificationWorker() + fun setValue(key: Preferences.Key, value: T) { + coroutineScope.launch { context.dataStore.edit { it[key] = value } } } - } - - Scaffold( - topBar = { - LargeTopAppBar( - title = { Text(stringResource(R.string.settings)) }, - navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) - } - }, - scrollBehavior = scrollBehavior, - ) - }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - ) { innerPadding -> - Column(Modifier.padding(innerPadding).verticalScroll(rememberScrollState())) { - Row( - modifier = - Modifier.clickable { setUnmeteredOnly(unmeteredOnly.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.unmetered_only_setting)) - Text( - stringResource(R.string.unmetered_only_setting_detail), - style = MaterialTheme.typography.bodyMedium) - } - Switch(checked = unmeteredOnly, onCheckedChange = { setUnmeteredOnly(it) }) - } - Row( - modifier = - Modifier.clickable { setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.clipboard_paste_enabled)) - Text( - stringResource(R.string.clipboard_paste_description), - style = MaterialTheme.typography.bodyMedium) + val setUnmeteredOnly: (Boolean) -> Unit = { value -> + coroutineScope.launch { + context.dataStore.edit { it[UNMETERED_ONLY] = value } + context.enqueueNotificationWorker() } - Switch( - checked = clipboardPasteEnabled, - onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) }) - } + } - // Preferred Region Setting - Column(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) { - Text(stringResource(R.string.preferred_region)) - Text( - stringResource(R.string.preferred_region_description), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 8.dp) - ) - - ExposedDropdownMenuBox( - expanded = regionDropdownExpanded, - onExpandedChange = { regionDropdownExpanded = !regionDropdownExpanded } - ) { - OutlinedTextField( - value = when (preferredRegion) { - "international" -> stringResource(R.string.region_international) - "north_america" -> stringResource(R.string.region_north_america) - "europe" -> stringResource(R.string.region_europe) - "asia" -> stringResource(R.string.region_asia) - "belarus" -> stringResource(R.string.country_belarus) - "bulgaria" -> stringResource(R.string.country_bulgaria) - "czech" -> stringResource(R.string.country_czech) - "uk" -> stringResource(R.string.country_uk) - "ireland" -> stringResource(R.string.country_ireland) - "poland" -> stringResource(R.string.country_poland) - "hungary" -> stringResource(R.string.country_hungary) - "germany" -> stringResource(R.string.country_germany) - "italy" -> stringResource(R.string.country_italy) - "romania" -> stringResource(R.string.country_romania) - "scandinavia" -> stringResource(R.string.country_scandinavia) - "ukraine" -> stringResource(R.string.country_ukraine) - "india" -> stringResource(R.string.country_india) - "thailand" -> stringResource(R.string.country_thailand) - else -> stringResource(R.string.region_international) - }, - onValueChange = { }, - readOnly = true, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = regionDropdownExpanded) }, - modifier = Modifier.menuAnchor().fillMaxWidth() - ) - - ExposedDropdownMenu( - expanded = regionDropdownExpanded, - onDismissRequest = { regionDropdownExpanded = false } - ) { - // Continental regions - DropdownMenuItem( - text = { Text(stringResource(R.string.region_international)) }, - onClick = { - setValue(PREFERRED_REGION, "international") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_north_america)) }, - onClick = { - setValue(PREFERRED_REGION, "north_america") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_europe)) }, - onClick = { - setValue(PREFERRED_REGION, "europe") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_asia)) }, - onClick = { - setValue(PREFERRED_REGION, "asia") - regionDropdownExpanded = false - } - ) - - // European countries - DropdownMenuItem( - text = { Text(stringResource(R.string.country_belarus)) }, - onClick = { - setValue(PREFERRED_REGION, "belarus") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_bulgaria)) }, - onClick = { - setValue(PREFERRED_REGION, "bulgaria") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_czech)) }, - onClick = { - setValue(PREFERRED_REGION, "czech") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_uk)) }, - onClick = { - setValue(PREFERRED_REGION, "uk") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_ireland)) }, - onClick = { - setValue(PREFERRED_REGION, "ireland") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_poland)) }, - onClick = { - setValue(PREFERRED_REGION, "poland") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_hungary)) }, - onClick = { - setValue(PREFERRED_REGION, "hungary") - regionDropdownExpanded = false - } + Scaffold( + topBar = { + LargeTopAppBar( + title = { Text(stringResource(R.string.settings)) }, + navigationIcon = { + IconButton(onClick = onBackPressed) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) + } + }, + scrollBehavior = scrollBehavior, ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_germany)) }, - onClick = { - setValue(PREFERRED_REGION, "germany") - regionDropdownExpanded = false + }, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + ) { innerPadding -> + Column(Modifier.padding(innerPadding).verticalScroll(rememberScrollState())) { + Row( + modifier = + Modifier.clickable { setUnmeteredOnly(unmeteredOnly.not()) } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.unmetered_only_setting)) + Text( + stringResource(R.string.unmetered_only_setting_detail), + style = MaterialTheme.typography.bodyMedium + ) } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_italy)) }, - onClick = { - setValue(PREFERRED_REGION, "italy") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_romania)) }, - onClick = { - setValue(PREFERRED_REGION, "romania") - regionDropdownExpanded = false + Switch(checked = unmeteredOnly, onCheckedChange = { setUnmeteredOnly(it) }) + } + + Row( + modifier = + Modifier.clickable { + setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) + } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.clipboard_paste_enabled)) + Text( + stringResource(R.string.clipboard_paste_description), + style = MaterialTheme.typography.bodyMedium + ) } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_scandinavia)) }, - onClick = { - setValue(PREFERRED_REGION, "scandinavia") - regionDropdownExpanded = false + Switch( + checked = clipboardPasteEnabled, + onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) } + ) + } + + Column(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) { + Text(stringResource(R.string.preferred_region)) + Text( + stringResource(R.string.preferred_region_description), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 8.dp) + ) + + ExposedDropdownMenuBox( + expanded = regionDropdownExpanded, + onExpandedChange = { regionDropdownExpanded = !regionDropdownExpanded } + ) { + OutlinedTextField( + value = + when (preferredRegion) { + "international" -> stringResource(R.string.region_international) + "north_america" -> stringResource(R.string.region_north_america) + "europe" -> stringResource(R.string.region_europe) + "asia" -> stringResource(R.string.region_asia) + "belarus" -> stringResource(R.string.country_belarus) + "bulgaria" -> stringResource(R.string.country_bulgaria) + "czech" -> stringResource(R.string.country_czech) + "uk" -> stringResource(R.string.country_uk) + "ireland" -> stringResource(R.string.country_ireland) + "poland" -> stringResource(R.string.country_poland) + "hungary" -> stringResource(R.string.country_hungary) + "germany" -> stringResource(R.string.country_germany) + "italy" -> stringResource(R.string.country_italy) + "romania" -> stringResource(R.string.country_romania) + "scandinavia" -> stringResource(R.string.country_scandinavia) + "ukraine" -> stringResource(R.string.country_ukraine) + "india" -> stringResource(R.string.country_india) + "thailand" -> stringResource(R.string.country_thailand) + else -> stringResource(R.string.region_international) + }, + onValueChange = {}, + readOnly = true, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon( + expanded = regionDropdownExpanded + ) + }, + modifier = Modifier.menuAnchor().fillMaxWidth() + ) + + ExposedDropdownMenu( + expanded = regionDropdownExpanded, + onDismissRequest = { regionDropdownExpanded = false } + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.region_international)) }, + onClick = { + setValue(PREFERRED_REGION, "international") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.region_north_america)) }, + onClick = { + setValue(PREFERRED_REGION, "north_america") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.region_europe)) }, + onClick = { + setValue(PREFERRED_REGION, "europe") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.region_asia)) }, + onClick = { + setValue(PREFERRED_REGION, "asia") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_belarus)) }, + onClick = { + setValue(PREFERRED_REGION, "belarus") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_bulgaria)) }, + onClick = { + setValue(PREFERRED_REGION, "bulgaria") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_czech)) }, + onClick = { + setValue(PREFERRED_REGION, "czech") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_uk)) }, + onClick = { + setValue(PREFERRED_REGION, "uk") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_ireland)) }, + onClick = { + setValue(PREFERRED_REGION, "ireland") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_poland)) }, + onClick = { + setValue(PREFERRED_REGION, "poland") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_hungary)) }, + onClick = { + setValue(PREFERRED_REGION, "hungary") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_germany)) }, + onClick = { + setValue(PREFERRED_REGION, "germany") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_italy)) }, + onClick = { + setValue(PREFERRED_REGION, "italy") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_romania)) }, + onClick = { + setValue(PREFERRED_REGION, "romania") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_scandinavia)) }, + onClick = { + setValue(PREFERRED_REGION, "scandinavia") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_ukraine)) }, + onClick = { + setValue(PREFERRED_REGION, "ukraine") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_india)) }, + onClick = { + setValue(PREFERRED_REGION, "india") + regionDropdownExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_thailand)) }, + onClick = { + setValue(PREFERRED_REGION, "thailand") + regionDropdownExpanded = false + } + ) + } } + } + + Text( + stringResource(R.string.settings_api_keys), + modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_ukraine)) }, - onClick = { - setValue(PREFERRED_REGION, "ukraine") - regionDropdownExpanded = false - } + + OutlinedTextField( + dhlApiKey, + { setValue(DHL_API_KEY, it) }, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).fillMaxWidth(), + label = { Text(stringResource(R.string.service_dhl)) }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), ) - - // Asian countries - DropdownMenuItem( - text = { Text(stringResource(R.string.country_india)) }, - onClick = { - setValue(PREFERRED_REGION, "india") - regionDropdownExpanded = false - } + + Text( + AnnotatedString.fromHtml( + stringResource(R.string.dhl_api_key_flavor_text), + linkStyles = + TextLinkStyles( + style = + SpanStyle( + textDecoration = TextDecoration.Underline, + color = MaterialTheme.colorScheme.primary + ) + ) + ), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_thailand)) }, - onClick = { - setValue(PREFERRED_REGION, "thailand") - regionDropdownExpanded = false - } + + Text( + stringResource(R.string.settings_experimental), + modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) - } - } - } - Text( - stringResource(R.string.settings_api_keys), - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + Row( + modifier = + Modifier.clickable { setValue(DEMO_MODE, demoMode.not()) } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.demo_mode)) + Text( + stringResource(R.string.demo_mode_detail), + style = MaterialTheme.typography.bodyMedium + ) + } + Switch(checked = demoMode, onCheckedChange = { setValue(DEMO_MODE, it) }) + } - OutlinedTextField( - dhlApiKey, - { setValue(DHL_API_KEY, it) }, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).fillMaxWidth(), - label = { Text(stringResource(R.string.service_dhl)) }, - singleLine = true, - visualTransformation = PasswordVisualTransformation(), - ) + if (BuildConfig.DEBUG) + FilledTonalButton( + onClick = { + context.sendNotification( + Parcel(0xf100f, "Cool stuff", "", null, Service.EXAMPLE), + Status.OutForDelivery, + ParcelHistoryItem( + "The courier has picked up the package", + LocalDateTime.now(), + "" + ) + ) + }, + modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth() + ) { + Text("Send test notification") + } - Text( - AnnotatedString.fromHtml( - stringResource(R.string.dhl_api_key_flavor_text), - linkStyles = - TextLinkStyles( - style = - SpanStyle( - textDecoration = TextDecoration.Underline, - color = MaterialTheme.colorScheme.primary))), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) + LogcatButton(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) - Text( - stringResource(R.string.settings_experimental), - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + FilledTonalButton( + onClick = { aboutDialogOpen = true }, + modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth() + ) { + Icon(Icons.Filled.Info, contentDescription = stringResource(R.string.about_app)) + Text( + text = " ${stringResource(R.string.about_app)}", + modifier = Modifier.padding(start = 8.dp) + ) + } - Row( - modifier = - Modifier.clickable { setValue(DEMO_MODE, demoMode.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.demo_mode)) - Text( - stringResource(R.string.demo_mode_detail), - style = MaterialTheme.typography.bodyMedium) + Text( + "Parcel ${BuildConfig.VERSION_NAME}", + modifier = Modifier.padding(16.dp, 8.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } - Switch(checked = demoMode, onCheckedChange = { setValue(DEMO_MODE, it) }) - } - - if (BuildConfig.DEBUG) - FilledTonalButton( - onClick = { - context.sendNotification( - Parcel(0xf100f, "Cool stuff", "", null, Service.EXAMPLE), - Status.OutForDelivery, - ParcelHistoryItem( - "The courier has picked up the package", LocalDateTime.now(), "")) - }, - modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) { - Text("Send test notification") - } - - LogcatButton(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) - FilledTonalButton( - onClick = { aboutDialogOpen = true }, - modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth() - ) { - Icon(Icons.Filled.Info, contentDescription = stringResource(R.string.about_app)) - Text( - text = " ${stringResource(R.string.about_app)}", - modifier = Modifier.padding(start = 8.dp) - ) - } - - Text( - "Parcel ${BuildConfig.VERSION_NAME}", - modifier = Modifier.padding(16.dp, 8.dp), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - if (aboutDialogOpen) { - AboutDialog { aboutDialogOpen = false } + if (aboutDialogOpen) { + AboutDialog { aboutDialogOpen = false } + } } - } } @Composable @PreviewLightDark private fun SettingsViewPreview() { - ParcelTrackerTheme { - SettingsView( - onBackPressed = {}, - ) - } + ParcelTrackerTheme { + SettingsView( + onBackPressed = {}, + ) + } } From 031852d0a0e9496247398f99e3862990a7af5c71 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:36:40 +0200 Subject: [PATCH 11/18] format with ktfmt --- .idea/codeStyles/Project.xml | 46 ++ .../api/ExpressOneDeliveryService.kt | 20 +- .../ui/components/BottomNavBar.kt | 8 +- .../ui/components/ParcelActionBar.kt | 61 +- .../parceltracker/ui/components/ParcelRow.kt | 24 +- .../parceltracker/ui/views/AdaptiveView.kt | 76 +- .../ui/views/AddEditParcelView.kt | 639 +++++++++-------- .../itsvic/parceltracker/ui/views/HomeView.kt | 24 +- .../parceltracker/ui/views/ParcelView.kt | 265 +++---- .../parceltracker/ui/views/SettingsView.kt | 678 +++++++++--------- .../parceltracker/ui/views/TabletView.kt | 268 ++++--- 11 files changed, 1077 insertions(+), 1032 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 7643783..64a4c97 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,6 +1,47 @@ + + + + @@ -118,6 +159,11 @@ \ No newline at end of file diff --git a/app/src/main/java/dev/itsvic/parceltracker/api/ExpressOneDeliveryService.kt b/app/src/main/java/dev/itsvic/parceltracker/api/ExpressOneDeliveryService.kt index e11f144..a8facf0 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/api/ExpressOneDeliveryService.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/api/ExpressOneDeliveryService.kt @@ -36,16 +36,16 @@ object ExpressOneDeliveryService : DeliveryService { currentStatus = Status.Delivered } htmlLower.contains("kiszállítás") || - htmlLower.contains("out for delivery") || - htmlLower.contains("kiadva futárnak") -> { + htmlLower.contains("out for delivery") || + htmlLower.contains("kiadva futárnak") -> { currentStatus = Status.OutForDelivery } htmlLower.contains("depóba érkezett") || htmlLower.contains("arrived at depot") -> { currentStatus = Status.InWarehouse } htmlLower.contains("feldolgozás") || - htmlLower.contains("processing") || - htmlLower.contains("átszállítás") -> { + htmlLower.contains("processing") || + htmlLower.contains("átszállítás") -> { currentStatus = Status.InTransit } htmlLower.contains("átvétel") || htmlLower.contains("pickup") -> { @@ -60,8 +60,8 @@ object ExpressOneDeliveryService : DeliveryService { val datePattern = "
(\\d{4}-\\d{2}-\\d{2})
".toRegex() val rowPattern = - "]*>(\\d{2}:\\d{2}:\\d{2})\\s*]*>([^<]*)\\s*]*>([^<]*)" - .toRegex() + "]*>(\\d{2}:\\d{2}:\\d{2})\\s*]*>([^<]*)\\s*]*>([^<]*)" + .toRegex() val dateMatches = datePattern.findAll(html) @@ -106,8 +106,12 @@ object ExpressOneDeliveryService : DeliveryService { if (history.isEmpty()) { history.add( - ParcelHistoryItem( - "Csomag nyomon követése elindítva", LocalDateTime.now(), "Express One Hungary")) + ParcelHistoryItem( + "Csomag nyomon követése elindítva", + LocalDateTime.now(), + "Express One Hungary", + ) + ) } return Parcel(trackingId, history, currentStatus) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt index 9fe6f35..0001296 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt @@ -25,13 +25,13 @@ fun BottomNavBar( icon = { Icon(Icons.Filled.Home, contentDescription = stringResource(R.string.home)) }, label = { Text(stringResource(R.string.home)) }, selected = currentRoute.contains("HomePage"), - onClick = onNavigateToHome + onClick = onNavigateToHome, ) NavigationBarItem( icon = { Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.add)) }, label = { Text(stringResource(R.string.add)) }, selected = currentRoute.contains("AddParcelPage"), - onClick = onNavigateToAddParcel + onClick = onNavigateToAddParcel, ) NavigationBarItem( icon = { @@ -39,7 +39,7 @@ fun BottomNavBar( }, label = { Text(stringResource(R.string.settings)) }, selected = currentRoute.contains("SettingsPage"), - onClick = onNavigateToSettings + onClick = onNavigateToSettings, ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt index 4c97db5..51b3ccd 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt @@ -25,7 +25,7 @@ fun ParcelActionBar( status: Status?, onEdit: () -> Unit, onArchive: () -> Unit, - onDelete: () -> Unit + onDelete: () -> Unit, ) { var showDeleteDialog by remember { mutableStateOf(false) } @@ -34,7 +34,7 @@ fun ParcelActionBar( icon = { Icon(Icons.Filled.Edit, contentDescription = stringResource(R.string.edit)) }, label = { Text(stringResource(R.string.edit)) }, selected = false, - onClick = onEdit + onClick = onEdit, ) if (status == Status.Delivered) { @@ -42,46 +42,41 @@ fun ParcelActionBar( icon = { Icon( painterResource(R.drawable.archive), - contentDescription = stringResource(R.string.archive)) + contentDescription = stringResource(R.string.archive), + ) }, label = { Text(stringResource(R.string.archive)) }, selected = false, - onClick = onArchive + onClick = onArchive, ) } NavigationBarItem( - icon = { - Icon(Icons.Filled.Delete, contentDescription = stringResource(R.string.delete)) - }, + icon = { Icon(Icons.Filled.Delete, contentDescription = stringResource(R.string.delete)) }, label = { Text(stringResource(R.string.delete)) }, selected = false, - onClick = { showDeleteDialog = true } + onClick = { showDeleteDialog = true }, ) } - if (showDeleteDialog) { - AlertDialog( - onDismissRequest = { showDeleteDialog = false }, - title = { Text(stringResource(R.string.delete)) }, - text = { Text(stringResource(R.string.delete_confirmation)) }, - confirmButton = { - TextButton( - onClick = { - showDeleteDialog = false - onDelete() - } - ) { - Text(stringResource(R.string.delete)) - } - }, - dismissButton = { - TextButton( - onClick = { showDeleteDialog = false } - ) { - Text(stringResource(R.string.cancel)) - } - } - ) - } -} \ No newline at end of file + if (showDeleteDialog) { + AlertDialog( + onDismissRequest = { showDeleteDialog = false }, + title = { Text(stringResource(R.string.delete)) }, + text = { Text(stringResource(R.string.delete_confirmation)) }, + confirmButton = { + TextButton( + onClick = { + showDeleteDialog = false + onDelete() + } + ) { + Text(stringResource(R.string.delete)) + } + }, + dismissButton = { + TextButton(onClick = { showDeleteDialog = false }) { Text(stringResource(R.string.cancel)) } + }, + ) + } +} diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt index d8f6636..16336c7 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelRow.kt @@ -38,17 +38,20 @@ fun ParcelRow(parcel: Parcel, status: Status?, isSelected: Boolean = false, onCl .fillMaxWidth() .background( if (isSelected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) - else MaterialTheme.colorScheme.surface) + else MaterialTheme.colorScheme.surface + ) .padding(16.dp, 12.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically) { + verticalAlignment = Alignment.CenterVertically, + ) { if (status != null) Box( modifier = Modifier.size(40.dp) .clip(CircleShape) .background(MaterialTheme.colorScheme.primaryContainer), - contentAlignment = Alignment.Center) { + contentAlignment = Alignment.Center, + ) { Icon( painterResource( when (status) { @@ -73,9 +76,11 @@ fun ParcelRow(parcel: Parcel, status: Status?, isSelected: Boolean = false, onCl Status.Damaged -> R.drawable.outline_deployed_code_alert_24 Status.Destroyed -> R.drawable.outline_destruction_24 else -> R.drawable.outline_question_mark_24 - }), + } + ), stringResource(status.nameResource), - tint = MaterialTheme.colorScheme.primary) + tint = MaterialTheme.colorScheme.primary, + ) } Column { @@ -84,7 +89,8 @@ fun ParcelRow(parcel: Parcel, status: Status?, isSelected: Boolean = false, onCl Text( "${stringResource(getDeliveryServiceName(parcel.service)!!)}: ${parcel.parcelId}", fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant) + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } } } @@ -95,9 +101,9 @@ fun ParcelRowPreview() { ParcelTrackerTheme { Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background)) { ParcelRow( - Parcel(0, "My precious package", "EXMPL0001", null, Service.EXAMPLE), - status = Status.InTransit, - onClick = {}, + Parcel(0, "My precious package", "EXMPL0001", null, Service.EXAMPLE), + status = Status.InTransit, + onClick = {}, ) } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt index 53a2e2d..0ac6eee 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AdaptiveView.kt @@ -14,45 +14,45 @@ import dev.itsvic.parceltracker.db.ParcelWithStatus @Composable fun AdaptiveParcelApp( - windowSizeClass: WindowSizeClass, - parcels: List, - selectedParcel: Parcel?, - apiParcel: APIParcel?, - isLoading: Boolean, - onNavigateToParcel: (Parcel) -> Unit, - onNavigateToAddParcel: () -> Unit, - onNavigateToSettings: () -> Unit, - onEditParcel: (Parcel) -> Unit, - onDeleteParcel: (Parcel) -> Unit, - onArchiveParcel: (Parcel) -> Unit, - onArchivePromptDismissal: (Parcel) -> Unit, - settingsContent: @Composable () -> Unit, - addParcelContent: @Composable () -> Unit, - homeContent: @Composable () -> Unit + windowSizeClass: WindowSizeClass, + parcels: List, + selectedParcel: Parcel?, + apiParcel: APIParcel?, + isLoading: Boolean, + onNavigateToParcel: (Parcel) -> Unit, + onNavigateToAddParcel: () -> Unit, + onNavigateToSettings: () -> Unit, + onEditParcel: (Parcel) -> Unit, + onDeleteParcel: (Parcel) -> Unit, + onArchiveParcel: (Parcel) -> Unit, + onArchivePromptDismissal: (Parcel) -> Unit, + settingsContent: @Composable () -> Unit, + addParcelContent: @Composable () -> Unit, + homeContent: @Composable () -> Unit, ) { - val isTablet = windowSizeClass.widthSizeClass >= WindowWidthSizeClass.Medium + val isTablet = windowSizeClass.widthSizeClass >= WindowWidthSizeClass.Medium - if (isTablet) { - var currentNavigationItem by remember { mutableStateOf(TabletNavigationItem.HOME) } + if (isTablet) { + var currentNavigationItem by remember { mutableStateOf(TabletNavigationItem.HOME) } - TabletView( - parcels = parcels, - selectedParcel = selectedParcel, - apiParcel = apiParcel, - isLoading = isLoading, - currentNavigationItem = currentNavigationItem, - onNavigateToItem = { currentNavigationItem = it }, - onNavigateToParcel = onNavigateToParcel, - onNavigateToAddParcel = onNavigateToAddParcel, - onNavigateToSettings = onNavigateToSettings, - onEditParcel = onEditParcel, - onDeleteParcel = onDeleteParcel, - onArchiveParcel = onArchiveParcel, - onArchivePromptDismissal = onArchivePromptDismissal, - settingsContent = settingsContent, - addParcelContent = addParcelContent - ) - } else { - homeContent() - } + TabletView( + parcels = parcels, + selectedParcel = selectedParcel, + apiParcel = apiParcel, + isLoading = isLoading, + currentNavigationItem = currentNavigationItem, + onNavigateToItem = { currentNavigationItem = it }, + onNavigateToParcel = onNavigateToParcel, + onNavigateToAddParcel = onNavigateToAddParcel, + onNavigateToSettings = onNavigateToSettings, + onEditParcel = onEditParcel, + onDeleteParcel = onDeleteParcel, + onArchiveParcel = onArchiveParcel, + onArchivePromptDismissal = onArchivePromptDismissal, + settingsContent = settingsContent, + addParcelContent = addParcelContent, + ) + } else { + homeContent() + } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index 9a27a4b..3abdfed 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -5,15 +5,14 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Add import androidx.compose.material3.Button import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenuItem @@ -48,29 +47,25 @@ import androidx.compose.ui.unit.sp import dev.itsvic.parceltracker.CLIPBOARD_PASTE_ENABLED import dev.itsvic.parceltracker.PREFERRED_REGION import dev.itsvic.parceltracker.R -import dev.itsvic.parceltracker.dataStore -import kotlinx.coroutines.flow.map import dev.itsvic.parceltracker.api.Service import dev.itsvic.parceltracker.api.getDeliveryService import dev.itsvic.parceltracker.api.getDeliveryServiceName import dev.itsvic.parceltracker.api.serviceOptions +import dev.itsvic.parceltracker.dataStore import dev.itsvic.parceltracker.db.Parcel import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme +import kotlinx.coroutines.flow.map @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AddEditParcelView( - parcel: Parcel?, - onBackPressed: () -> Unit, - onCompleted: (Parcel) -> Unit, -) { +fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: (Parcel) -> Unit) { val isEdit = parcel != null val context = LocalContext.current val clipboardManager = LocalClipboardManager.current val clipboardPasteEnabled by - context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) + context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) val preferredRegion by - context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") + context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") var humanName by remember { mutableStateOf(parcel?.humanName ?: "") } var trackingId by remember { mutableStateOf(parcel?.parcelId ?: "") } @@ -98,8 +93,10 @@ fun AddEditParcelView( success = false serviceError = true } - if (((backend?.acceptsPostCode == true && specifyPostalCode) || - (backend?.requiresPostCode == true)) && postalCode.isBlank()) { + if ( + ((backend?.acceptsPostCode == true && specifyPostalCode) || + (backend?.requiresPostCode == true)) && postalCode.isBlank() + ) { success = false postalCodeError = true } @@ -111,17 +108,82 @@ fun AddEditParcelView( var expanded by remember { mutableStateOf(false) } val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - val sortedServiceOptions = serviceOptions.sortedWith(compareBy { - val isPreferredRegion = when (preferredRegion) { - "international" -> - it in - listOf(Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX) - "north_america" -> it == Service.UNIUNI - "europe" -> - it in - listOf( + val sortedServiceOptions = + serviceOptions.sortedWith( + compareBy { + val isPreferredRegion = + when (preferredRegion) { + "international" -> + it in listOf(Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX) + "north_america" -> it == Service.UNIUNI + "europe" -> + it in + listOf( + Service.BELPOST, + Service.SAMEDAY_BG, + Service.DPD_UK, + Service.EVRI, + Service.AN_POST, + Service.ALLEGRO_ONEBOX, + Service.INPOST, + Service.ORLEN_PACZKA, + Service.POLISH_POST, + Service.GLS_HUNGARY, + Service.MAGYAR_POSTA, + Service.SAMEDAY_HU, + Service.DPD_GER, + Service.HERMES, + Service.POSTE_ITALIANE, + Service.SAMEDAY_RO, + Service.POSTNORD, + Service.NOVA_POSHTA, + Service.UKRPOSHTA, + Service.PACKETA, + Service.EXPRESS_ONE, + ) + "asia" -> it in listOf(Service.EKART, Service.SPX_TH) + "belarus" -> it == Service.BELPOST + "bulgaria" -> it == Service.SAMEDAY_BG + "uk" -> it in listOf(Service.DPD_UK, Service.EVRI) + "ireland" -> it == Service.AN_POST + "poland" -> + it in + listOf( + Service.ALLEGRO_ONEBOX, + Service.INPOST, + Service.ORLEN_PACZKA, + Service.POLISH_POST, + ) + "hungary" -> + it in + listOf( + Service.GLS_HUNGARY, + Service.MAGYAR_POSTA, + Service.SAMEDAY_HU, + Service.EXPRESS_ONE, + ) + "germany" -> it in listOf(Service.DPD_GER, Service.HERMES) + "italy" -> it == Service.POSTE_ITALIANE + "romania" -> it == Service.SAMEDAY_RO + "scandinavia" -> it == Service.POSTNORD + "ukraine" -> it in listOf(Service.NOVA_POSHTA, Service.UKRPOSHTA) + "india" -> it == Service.EKART + "thailand" -> it == Service.SPX_TH + else -> false + } + if (isPreferredRegion) 0 else 1 + } + .thenBy { + when (it) { + Service.CAINIAO, + Service.DHL, + Service.GLS, + Service.UPS, + Service.FPX -> 0 + Service.UNIUNI -> 1 Service.BELPOST, Service.SAMEDAY_BG, + Service.PACKETA, Service.DPD_UK, Service.EVRI, Service.AN_POST, @@ -132,323 +194,278 @@ fun AddEditParcelView( Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, + Service.EXPRESS_ONE, Service.DPD_GER, Service.HERMES, Service.POSTE_ITALIANE, Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, - Service.UKRPOSHTA, - Service.PACKETA, - Service.EXPRESS_ONE) - "asia" -> it in listOf(Service.EKART, Service.SPX_TH) - "belarus" -> it == Service.BELPOST - "bulgaria" -> it == Service.SAMEDAY_BG - "uk" -> it in listOf(Service.DPD_UK, Service.EVRI) - "ireland" -> it == Service.AN_POST - "poland" -> - it in - listOf( + Service.UKRPOSHTA -> 2 + Service.EKART, + Service.SPX_TH -> 3 + else -> 4 + } + } + .thenBy { + when (it) { + Service.BELPOST -> "A_Belarus" + Service.SAMEDAY_BG -> "B_Bulgaria" + Service.PACKETA -> "C_Europe" + Service.DPD_UK, + Service.EVRI -> "D_UK" + Service.AN_POST -> "E_Ireland" Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, - Service.POLISH_POST) - "hungary" -> - it in - listOf( + Service.POLISH_POST -> "F_Poland" Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, - Service.EXPRESS_ONE) - "germany" -> it in listOf(Service.DPD_GER, Service.HERMES) - "italy" -> it == Service.POSTE_ITALIANE - "romania" -> it == Service.SAMEDAY_RO - "scandinavia" -> it == Service.POSTNORD - "ukraine" -> it in listOf(Service.NOVA_POSHTA, Service.UKRPOSHTA) - "india" -> it == Service.EKART - "thailand" -> it == Service.SPX_TH - else -> false - } - if (isPreferredRegion) 0 else 1 - }.thenBy { - when (it) { - Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX -> 0 - Service.UNIUNI -> 1 - Service.BELPOST, - Service.SAMEDAY_BG, - Service.PACKETA, - Service.DPD_UK, - Service.EVRI, - Service.AN_POST, - Service.ALLEGRO_ONEBOX, - Service.INPOST, - Service.ORLEN_PACZKA, - Service.POLISH_POST, - Service.GLS_HUNGARY, - Service.MAGYAR_POSTA, - Service.SAMEDAY_HU, - Service.EXPRESS_ONE, - Service.DPD_GER, - Service.HERMES, - Service.POSTE_ITALIANE, - Service.SAMEDAY_RO, - Service.POSTNORD, - Service.NOVA_POSHTA, - Service.UKRPOSHTA -> 2 - Service.EKART, Service.SPX_TH -> 3 - else -> 4 - } - }.thenBy { - when (it) { - Service.BELPOST -> "A_Belarus" - Service.SAMEDAY_BG -> "B_Bulgaria" - Service.PACKETA -> "C_Europe" - Service.DPD_UK, Service.EVRI -> "D_UK" - Service.AN_POST -> "E_Ireland" - Service.ALLEGRO_ONEBOX, - Service.INPOST, - Service.ORLEN_PACZKA, - Service.POLISH_POST -> "F_Poland" - Service.GLS_HUNGARY, - Service.MAGYAR_POSTA, - Service.SAMEDAY_HU, - Service.EXPRESS_ONE -> "G_Hungary" - Service.DPD_GER, Service.HERMES -> "H_Germany" - Service.POSTE_ITALIANE -> "I_Italy" - Service.SAMEDAY_RO -> "J_Romania" - Service.POSTNORD -> "K_Scandinavia" - Service.NOVA_POSHTA, Service.UKRPOSHTA -> "L_Ukraine" - else -> it.name - } - }.thenBy { - if (trackingId.isNotBlank()) { - val backend = getDeliveryService(it) - if (backend?.acceptsFormat(trackingId) == true) 0 else 1 - } else { - 0 - } - }) + Service.EXPRESS_ONE -> "G_Hungary" + Service.DPD_GER, + Service.HERMES -> "H_Germany" + Service.POSTE_ITALIANE -> "I_Italy" + Service.SAMEDAY_RO -> "J_Romania" + Service.POSTNORD -> "K_Scandinavia" + Service.NOVA_POSHTA, + Service.UKRPOSHTA -> "L_Ukraine" + else -> it.name + } + } + .thenBy { + if (trackingId.isNotBlank()) { + val backend = getDeliveryService(it) + if (backend?.acceptsFormat(trackingId) == true) 0 else 1 + } else { + 0 + } + } + ) Scaffold( - topBar = { - TopAppBar( - title = { - Text(stringResource(if (isEdit) R.string.edit_parcel else R.string.add_a_parcel)) - }, - navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) - } - }, - scrollBehavior = scrollBehavior, + topBar = { + TopAppBar( + title = { + Text(stringResource(if (isEdit) R.string.edit_parcel else R.string.add_a_parcel)) + }, + navigationIcon = { + IconButton(onClick = onBackPressed) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) + } + }, + scrollBehavior = scrollBehavior, + ) + }, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + ) { innerPadding -> + Column( + modifier = + Modifier.padding(innerPadding).fillMaxWidth().verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column( + modifier = Modifier.padding(horizontal = 16.dp).sizeIn(maxWidth = 488.dp).fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + OutlinedTextField( + value = humanName, + onValueChange = { humanName = it }, + singleLine = true, + label = { Text(stringResource(R.string.parcel_name)) }, + modifier = Modifier.fillMaxWidth(), ) - }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { innerPadding -> - Column( - modifier = - Modifier.padding(innerPadding).fillMaxWidth().verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally) { - Column( - modifier = - Modifier.padding(horizontal = 16.dp).sizeIn(maxWidth = 488.dp).fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp), + + OutlinedTextField( + value = trackingId, + onValueChange = { + trackingId = it + idError = false + }, + singleLine = true, + label = { Text(stringResource(R.string.tracking_id)) }, + modifier = Modifier.fillMaxWidth(), + isError = idError, + trailingIcon = { + if (clipboardPasteEnabled) { + IconButton( + onClick = { + clipboardManager.getText()?.text?.let { clipboardText -> + trackingId = clipboardText + idError = false + } + } ) { - OutlinedTextField( - value = humanName, - onValueChange = { - humanName = it - }, - singleLine = true, - label = { Text(stringResource(R.string.parcel_name)) }, - modifier = Modifier.fillMaxWidth(), + Icon( + painter = painterResource(id = R.drawable.ic_contentpaste), + contentDescription = stringResource(R.string.clipboard_paste), + modifier = Modifier.size(20.dp), + ) + } + } + }, + supportingText = { if (idError) Text(stringResource(R.string.tracking_id_error_text)) }, + ) -) + // Service dropdown + ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) { + OutlinedTextField( + value = + if (service == Service.UNDEFINED) "" + else stringResource(getDeliveryServiceName(service)!!), + onValueChange = {}, + modifier = + Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable).fillMaxWidth(), + readOnly = true, + label = { Text(stringResource(R.string.delivery_service)) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + isError = serviceError, + supportingText = { if (serviceError) Text(stringResource(R.string.service_error_text)) }, + ) - OutlinedTextField( - value = trackingId, - onValueChange = { - trackingId = it - idError = false - }, - singleLine = true, - label = { Text(stringResource(R.string.tracking_id)) }, - modifier = Modifier.fillMaxWidth(), - isError = idError, - trailingIcon = { - if (clipboardPasteEnabled) { - IconButton( - onClick = { - clipboardManager.getText()?.text?.let { clipboardText -> - trackingId = clipboardText - idError = false - } - } - ) { - Icon( - painter = painterResource(id = R.drawable.ic_contentpaste), - contentDescription = stringResource(R.string.clipboard_paste), - modifier = Modifier.size(20.dp) - ) - } - } - }, - supportingText = { - if (idError) Text(stringResource(R.string.tracking_id_error_text)) - }) + ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + var currentCategory = "" + sortedServiceOptions.forEach { option -> + val category = + when (option) { + Service.CAINIAO, + Service.DHL, + Service.GLS, + Service.UPS, + Service.FPX -> "Nemzetközi" + Service.UNIUNI -> "Észak-Amerika" + Service.BELPOST -> "Európa - Fehéroroszország" + Service.SAMEDAY_BG -> "Európa - Bulgária" + Service.PACKETA -> "Európa - Csehország" + Service.DPD_UK, + Service.EVRI -> "Európa - Egyesült Királyság" + Service.AN_POST -> "Európa - Írország" + Service.ALLEGRO_ONEBOX, + Service.INPOST, + Service.ORLEN_PACZKA, + Service.POLISH_POST -> "Európa - Lengyelország" + Service.GLS_HUNGARY, + Service.MAGYAR_POSTA, + Service.SAMEDAY_HU -> "Európa - Magyarország" + Service.DPD_GER, + Service.HERMES -> "Európa - Németország" + Service.POSTE_ITALIANE -> "Európa - Olaszország" + Service.SAMEDAY_RO -> "Európa - Románia" + Service.POSTNORD -> "Európa - Skandinávia" + Service.NOVA_POSHTA, + Service.UKRPOSHTA -> "Európa - Ukrajna" + Service.EKART -> "Ázsia - India" + Service.SPX_TH -> "Ázsia - Thaiföld" + else -> "Egyéb" + } - // Service dropdown - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = it }, - ) { - OutlinedTextField( - value = - if (service == Service.UNDEFINED) "" - else stringResource(getDeliveryServiceName(service)!!), - onValueChange = {}, - modifier = - Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable) - .fillMaxWidth(), - readOnly = true, - label = { Text(stringResource(R.string.delivery_service)) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - isError = serviceError, - supportingText = { - if (serviceError) Text(stringResource(R.string.service_error_text)) - }) + if (category != currentCategory) { + currentCategory = category + DropdownMenuItem( + text = { + Text( + text = category, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary, + ) + }, + onClick = {}, + enabled = false, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + ) + } - ExposedDropdownMenu( - expanded = expanded, onDismissRequest = { expanded = false }) { - var currentCategory = "" - sortedServiceOptions.forEach { option -> - val category = when (option) { - Service.CAINIAO, Service.DHL, Service.GLS, Service.UPS, Service.FPX -> "Nemzetközi" - Service.UNIUNI -> "Észak-Amerika" - Service.BELPOST -> "Európa - Fehéroroszország" - Service.SAMEDAY_BG -> "Európa - Bulgária" - Service.PACKETA -> "Európa - Csehország" - Service.DPD_UK, Service.EVRI -> "Európa - Egyesült Királyság" - Service.AN_POST -> "Európa - Írország" - Service.ALLEGRO_ONEBOX, Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST -> "Európa - Lengyelország" - Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU -> "Európa - Magyarország" - Service.DPD_GER, Service.HERMES -> "Európa - Németország" - Service.POSTE_ITALIANE -> "Európa - Olaszország" - Service.SAMEDAY_RO -> "Európa - Románia" - Service.POSTNORD -> "Európa - Skandinávia" - Service.NOVA_POSHTA, Service.UKRPOSHTA -> "Európa - Ukrajna" - Service.EKART -> "Ázsia - India" - Service.SPX_TH -> "Ázsia - Thaiföld" - else -> "Egyéb" - } - - if (category != currentCategory) { - currentCategory = category - DropdownMenuItem( - text = { - Text( - text = category, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.primary - ) - }, - onClick = { }, - enabled = false, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - ) - } - - DropdownMenuItem( - text = { Text(" " + stringResource(getDeliveryServiceName(option)!!)) }, - onClick = { - service = option - expanded = false - serviceError = false - }, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - ) - } - } - } + DropdownMenuItem( + text = { Text(" " + stringResource(getDeliveryServiceName(option)!!)) }, + onClick = { + service = option + expanded = false + serviceError = false + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + ) + } + } + } - AnimatedVisibility(backend?.acceptsPostCode == true && !backend.requiresPostCode) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.specify_a_postal_code)) - Text( - stringResource(R.string.specify_postal_code_flavor_text), - fontSize = 14.sp, - lineHeight = 21.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant) - } - Checkbox( - checked = specifyPostalCode, - onCheckedChange = { specifyPostalCode = it }, - ) - } - } + AnimatedVisibility(backend?.acceptsPostCode == true && !backend.requiresPostCode) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth(), + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.specify_a_postal_code)) + Text( + stringResource(R.string.specify_postal_code_flavor_text), + fontSize = 14.sp, + lineHeight = 21.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Checkbox(checked = specifyPostalCode, onCheckedChange = { specifyPostalCode = it }) + } + } - AnimatedVisibility( - backend?.requiresPostCode == true || - (backend?.requiresPostCode == false && - backend.acceptsPostCode && - specifyPostalCode)) { - OutlinedTextField( - value = postalCode, - onValueChange = { - postalCode = it - postalCodeError = false - }, - singleLine = true, - label = { Text(stringResource(R.string.postal_code)) }, - modifier = Modifier.fillMaxWidth(), - isError = postalCodeError, - supportingText = { - if (postalCodeError) - Text(stringResource(R.string.postal_code_error_text)) - }) - } + AnimatedVisibility( + backend?.requiresPostCode == true || + (backend?.requiresPostCode == false && backend.acceptsPostCode && specifyPostalCode) + ) { + OutlinedTextField( + value = postalCode, + onValueChange = { + postalCode = it + postalCodeError = false + }, + singleLine = true, + label = { Text(stringResource(R.string.postal_code)) }, + modifier = Modifier.fillMaxWidth(), + isError = postalCodeError, + supportingText = { + if (postalCodeError) Text(stringResource(R.string.postal_code_error_text)) + }, + ) + } - Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) { - Button( - onClick = { - val isOk = validateInputs() - if (isOk) { - // data valid, pass it along - onCompleted( - Parcel( - id = parcel?.id ?: 0, - humanName = if (humanName.isBlank()) context.getString(R.string.undefinied_packagename) else humanName, - parcelId = trackingId, - service = service, - postalCode = - if (backend?.requiresPostCode == true || - (backend?.acceptsPostCode == true && specifyPostalCode)) - postalCode - else null)) - } - }) { - Text(stringResource(if (isEdit) R.string.save else R.string.add_parcel)) - } - } + Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) { + Button( + onClick = { + val isOk = validateInputs() + if (isOk) { + // data valid, pass it along + onCompleted( + Parcel( + id = parcel?.id ?: 0, + humanName = + if (humanName.isBlank()) context.getString(R.string.undefinied_packagename) + else humanName, + parcelId = trackingId, + service = service, + postalCode = + if ( + backend?.requiresPostCode == true || + (backend?.acceptsPostCode == true && specifyPostalCode) + ) + postalCode + else null, + ) + ) } } + ) { + Text(stringResource(if (isEdit) R.string.save else R.string.add_parcel)) + } + } } + } + } } @Composable @PreviewLightDark fun AddParcelPreview() { - ParcelTrackerTheme { - AddEditParcelView( - null, - onBackPressed = {}, - onCompleted = {}, - ) - } + ParcelTrackerTheme { AddEditParcelView(null, onBackPressed = {}, onCompleted = {}) } } @Composable @@ -456,9 +473,9 @@ fun AddParcelPreview() { fun EditParcelPreview() { ParcelTrackerTheme { AddEditParcelView( - Parcel(0, "Test", "Test", null, Service.EXAMPLE), - onBackPressed = {}, - onCompleted = {}, + Parcel(0, "Test", "Test", null, Service.EXAMPLE), + onBackPressed = {}, + onCompleted = {}, ) } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt index d58a209..92a5a6b 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt @@ -42,13 +42,15 @@ fun HomeView( scrollBehavior = scrollBehavior, ) }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { innerPadding -> + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + ) { innerPadding -> LazyColumn(modifier = Modifier.padding(innerPadding)) { if (parcels.isEmpty()) item { Text( stringResource(R.string.no_parcels_flavor), - modifier = Modifier.padding(horizontal = 16.dp)) + modifier = Modifier.padding(horizontal = 16.dp), + ) } items(parcels.reversed()) { parcel -> @@ -63,14 +65,16 @@ fun HomeView( fun HomeViewPreview() { ParcelTrackerTheme { HomeView( - parcels = - listOf( - ParcelWithStatus( - Parcel(0, "My precious package", "EXMPL0001", null, Service.EXAMPLE), - ParcelStatus(0, Status.InTransit, Instant.now()))), - onNavigateToAddParcel = {}, - onNavigateToParcel = {}, - onNavigateToSettings = {}, + parcels = + listOf( + ParcelWithStatus( + Parcel(0, "My precious package", "EXMPL0001", null, Service.EXAMPLE), + ParcelStatus(0, Status.InTransit, Instant.now()), + ) + ), + onNavigateToAddParcel = {}, + onNavigateToParcel = {}, + onNavigateToSettings = {}, ) } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt index a004812..58cf3e2 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt @@ -46,163 +46,166 @@ import java.time.LocalDateTime @OptIn(ExperimentalMaterial3Api::class) @Composable fun ParcelView( - parcel: Parcel, - humanName: String, - service: Service, - isArchived: Boolean, - archivePromptDismissed: Boolean, - onBackPressed: () -> Unit, - onEdit: () -> Unit, - onDelete: () -> Unit, - onArchive: () -> Unit, - onArchivePromptDismissal: () -> Unit, - showBackButton: Boolean = true, + parcel: Parcel, + humanName: String, + service: Service, + isArchived: Boolean, + archivePromptDismissed: Boolean, + onBackPressed: () -> Unit, + onEdit: () -> Unit, + onDelete: () -> Unit, + onArchive: () -> Unit, + onArchivePromptDismissal: () -> Unit, + showBackButton: Boolean = true, ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( - topBar = { - MediumTopAppBar( - title = { Text(humanName) }, - navigationIcon = { - if (showBackButton) { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) - } - } - }, - scrollBehavior = scrollBehavior, - ) - }, - bottomBar = { - ParcelActionBar( - status = parcel.currentStatus, - onEdit = onEdit, - onArchive = onArchive, - onDelete = onDelete - ) - }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { innerPadding -> - LazyColumn( - modifier = Modifier.padding(innerPadding).padding(16.dp, 0.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - item { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween) { - getDeliveryServiceName(service)?.let { - Text( - stringResource(it), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant) - } - - SelectionContainer { - Text( - parcel.id, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant) - } + topBar = { + MediumTopAppBar( + title = { Text(humanName) }, + navigationIcon = { + if (showBackButton) { + IconButton(onClick = onBackPressed) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) } } - - items(parcel.properties.entries.toList()) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween) { - Text( - stringResource(it.key), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant) - Text( - it.value, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.End) - } + }, + scrollBehavior = scrollBehavior, + ) + }, + bottomBar = { + ParcelActionBar( + status = parcel.currentStatus, + onEdit = onEdit, + onArchive = onArchive, + onDelete = onDelete, + ) + }, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + ) { innerPadding -> + LazyColumn( + modifier = Modifier.padding(innerPadding).padding(16.dp, 0.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + item { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + getDeliveryServiceName(service)?.let { + Text( + stringResource(it), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } - item { + SelectionContainer { Text( - LocalContext.current.getString(parcel.currentStatus.nameResource), - style = MaterialTheme.typography.headlineLarge, - modifier = Modifier.padding(vertical = 16.dp), + parcel.id, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } + } + } + + items(parcel.properties.entries.toList()) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + stringResource(it.key), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Text( + it.value, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.End, + ) + } + } + + item { + Text( + LocalContext.current.getString(parcel.currentStatus.nameResource), + style = MaterialTheme.typography.headlineLarge, + modifier = Modifier.padding(vertical = 16.dp), + ) + } - if (!isArchived && - !archivePromptDismissed && - (parcel.currentStatus == Status.Delivered || parcel.currentStatus == Status.PickedUp)) - item { - Card( - shape = RoundedCornerShape(16.dp), - modifier = Modifier.padding(bottom = 16.dp)) { - Column( - Modifier.padding(24.dp), - verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - stringResource(R.string.archive_prompt_question), - style = MaterialTheme.typography.titleMedium) - Text(stringResource(R.string.archive_prompt_text)) - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth()) { - FilledTonalButton( - onArchivePromptDismissal, modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.ignore)) - } - Button(onArchive, modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.archive)) - } - } + if ( + !isArchived && + !archivePromptDismissed && + (parcel.currentStatus == Status.Delivered || parcel.currentStatus == Status.PickedUp) + ) + item { + Card(shape = RoundedCornerShape(16.dp), modifier = Modifier.padding(bottom = 16.dp)) { + Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + stringResource(R.string.archive_prompt_question), + style = MaterialTheme.typography.titleMedium, + ) + Text(stringResource(R.string.archive_prompt_text)) + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxWidth(), + ) { + FilledTonalButton(onArchivePromptDismissal, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.ignore)) + } + Button(onArchive, modifier = Modifier.weight(1f)) { + Text(stringResource(R.string.archive)) } } } - - items(parcel.history.size) { index -> - if (index > 0) HorizontalDivider(Modifier.padding(top = 8.dp, bottom = 16.dp)) - ParcelHistoryItemRow(parcel.history[index]) } } + + items(parcel.history.size) { index -> + if (index > 0) HorizontalDivider(Modifier.padding(top = 8.dp, bottom = 16.dp)) + ParcelHistoryItemRow(parcel.history[index]) } + } + } } @Composable @PreviewLightDark private fun ParcelViewPreview() { val parcel = - Parcel( - "EXMPL0001", - listOf( - ParcelHistoryItem( - "The package got lost. Whoops!", - LocalDateTime.of(2025, 1, 1, 12, 0, 0), - "Warsaw, Poland"), - ParcelHistoryItem( - "Arrived at local warehouse", - LocalDateTime.of(2025, 1, 1, 10, 0, 0), - "Warsaw, Poland"), - ParcelHistoryItem( - "En route to local warehouse", - LocalDateTime.of(2024, 12, 1, 12, 0, 0), - "Netherlands"), - ParcelHistoryItem( - "Label created", LocalDateTime.of(2024, 12, 1, 12, 0, 0), "Netherlands"), - ), - Status.DeliveryFailure) + Parcel( + "EXMPL0001", + listOf( + ParcelHistoryItem( + "The package got lost. Whoops!", + LocalDateTime.of(2025, 1, 1, 12, 0, 0), + "Warsaw, Poland", + ), + ParcelHistoryItem( + "Arrived at local warehouse", + LocalDateTime.of(2025, 1, 1, 10, 0, 0), + "Warsaw, Poland", + ), + ParcelHistoryItem( + "En route to local warehouse", + LocalDateTime.of(2024, 12, 1, 12, 0, 0), + "Netherlands", + ), + ParcelHistoryItem("Label created", LocalDateTime.of(2024, 12, 1, 12, 0, 0), "Netherlands"), + ), + Status.DeliveryFailure, + ) ParcelTrackerTheme { ParcelView( - parcel, - "My precious package", - Service.EXAMPLE, - isArchived = false, - archivePromptDismissed = false, - onBackPressed = {}, - onEdit = {}, - onDelete = {}, - onArchive = {}, - onArchivePromptDismissal = {}, + parcel, + "My precious package", + Service.EXAMPLE, + isArchived = false, + archivePromptDismissed = false, + onBackPressed = {}, + onEdit = {}, + onDelete = {}, + onArchive = {}, + onArchivePromptDismissal = {}, ) } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt index e6c7dea..e07f2a1 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt @@ -71,378 +71,364 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingsView( - onBackPressed: () -> Unit, -) { - val context = LocalContext.current - val demoMode by context.dataStore.data.map { it[DEMO_MODE] == true }.collectAsState(false) - val unmeteredOnly by - context.dataStore.data.map { it[UNMETERED_ONLY] == true }.collectAsState(false) - val clipboardPasteEnabled by - context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) - val preferredRegion by - context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") - val coroutineScope = rememberCoroutineScope() - var regionDropdownExpanded by remember { mutableStateOf(false) } - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - var aboutDialogOpen by remember { mutableStateOf(false) } +fun SettingsView(onBackPressed: () -> Unit) { + val context = LocalContext.current + val demoMode by context.dataStore.data.map { it[DEMO_MODE] == true }.collectAsState(false) + val unmeteredOnly by + context.dataStore.data.map { it[UNMETERED_ONLY] == true }.collectAsState(false) + val clipboardPasteEnabled by + context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) + val preferredRegion by + context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") + val coroutineScope = rememberCoroutineScope() + var regionDropdownExpanded by remember { mutableStateOf(false) } + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + var aboutDialogOpen by remember { mutableStateOf(false) } - val dhlApiKey by context.dataStore.data.map { it[DHL_API_KEY] ?: "" }.collectAsState("") + val dhlApiKey by context.dataStore.data.map { it[DHL_API_KEY] ?: "" }.collectAsState("") - fun setValue(key: Preferences.Key, value: T) { - coroutineScope.launch { context.dataStore.edit { it[key] = value } } - } + fun setValue(key: Preferences.Key, value: T) { + coroutineScope.launch { context.dataStore.edit { it[key] = value } } + } - val setUnmeteredOnly: (Boolean) -> Unit = { value -> - coroutineScope.launch { - context.dataStore.edit { it[UNMETERED_ONLY] = value } - context.enqueueNotificationWorker() - } + val setUnmeteredOnly: (Boolean) -> Unit = { value -> + coroutineScope.launch { + context.dataStore.edit { it[UNMETERED_ONLY] = value } + context.enqueueNotificationWorker() } + } - Scaffold( - topBar = { - LargeTopAppBar( - title = { Text(stringResource(R.string.settings)) }, - navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) - } - }, - scrollBehavior = scrollBehavior, - ) + Scaffold( + topBar = { + LargeTopAppBar( + title = { Text(stringResource(R.string.settings)) }, + navigationIcon = { + IconButton(onClick = onBackPressed) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) + } }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - ) { innerPadding -> - Column(Modifier.padding(innerPadding).verticalScroll(rememberScrollState())) { - Row( - modifier = - Modifier.clickable { setUnmeteredOnly(unmeteredOnly.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.unmetered_only_setting)) - Text( - stringResource(R.string.unmetered_only_setting_detail), - style = MaterialTheme.typography.bodyMedium - ) - } - Switch(checked = unmeteredOnly, onCheckedChange = { setUnmeteredOnly(it) }) - } - - Row( - modifier = - Modifier.clickable { - setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) - } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.clipboard_paste_enabled)) - Text( - stringResource(R.string.clipboard_paste_description), - style = MaterialTheme.typography.bodyMedium - ) - } - Switch( - checked = clipboardPasteEnabled, - onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) } - ) - } + scrollBehavior = scrollBehavior, + ) + }, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + ) { innerPadding -> + Column(Modifier.padding(innerPadding).verticalScroll(rememberScrollState())) { + Row( + modifier = + Modifier.clickable { setUnmeteredOnly(unmeteredOnly.not()) } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.unmetered_only_setting)) + Text( + stringResource(R.string.unmetered_only_setting_detail), + style = MaterialTheme.typography.bodyMedium, + ) + } + Switch(checked = unmeteredOnly, onCheckedChange = { setUnmeteredOnly(it) }) + } - Column(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) { - Text(stringResource(R.string.preferred_region)) - Text( - stringResource(R.string.preferred_region_description), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 8.dp) - ) + Row( + modifier = + Modifier.clickable { setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.clipboard_paste_enabled)) + Text( + stringResource(R.string.clipboard_paste_description), + style = MaterialTheme.typography.bodyMedium, + ) + } + Switch( + checked = clipboardPasteEnabled, + onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) }, + ) + } - ExposedDropdownMenuBox( - expanded = regionDropdownExpanded, - onExpandedChange = { regionDropdownExpanded = !regionDropdownExpanded } - ) { - OutlinedTextField( - value = - when (preferredRegion) { - "international" -> stringResource(R.string.region_international) - "north_america" -> stringResource(R.string.region_north_america) - "europe" -> stringResource(R.string.region_europe) - "asia" -> stringResource(R.string.region_asia) - "belarus" -> stringResource(R.string.country_belarus) - "bulgaria" -> stringResource(R.string.country_bulgaria) - "czech" -> stringResource(R.string.country_czech) - "uk" -> stringResource(R.string.country_uk) - "ireland" -> stringResource(R.string.country_ireland) - "poland" -> stringResource(R.string.country_poland) - "hungary" -> stringResource(R.string.country_hungary) - "germany" -> stringResource(R.string.country_germany) - "italy" -> stringResource(R.string.country_italy) - "romania" -> stringResource(R.string.country_romania) - "scandinavia" -> stringResource(R.string.country_scandinavia) - "ukraine" -> stringResource(R.string.country_ukraine) - "india" -> stringResource(R.string.country_india) - "thailand" -> stringResource(R.string.country_thailand) - else -> stringResource(R.string.region_international) - }, - onValueChange = {}, - readOnly = true, - trailingIcon = { - ExposedDropdownMenuDefaults.TrailingIcon( - expanded = regionDropdownExpanded - ) - }, - modifier = Modifier.menuAnchor().fillMaxWidth() - ) + Column(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) { + Text(stringResource(R.string.preferred_region)) + Text( + stringResource(R.string.preferred_region_description), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 8.dp), + ) - ExposedDropdownMenu( - expanded = regionDropdownExpanded, - onDismissRequest = { regionDropdownExpanded = false } - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.region_international)) }, - onClick = { - setValue(PREFERRED_REGION, "international") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_north_america)) }, - onClick = { - setValue(PREFERRED_REGION, "north_america") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_europe)) }, - onClick = { - setValue(PREFERRED_REGION, "europe") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_asia)) }, - onClick = { - setValue(PREFERRED_REGION, "asia") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_belarus)) }, - onClick = { - setValue(PREFERRED_REGION, "belarus") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_bulgaria)) }, - onClick = { - setValue(PREFERRED_REGION, "bulgaria") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_czech)) }, - onClick = { - setValue(PREFERRED_REGION, "czech") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_uk)) }, - onClick = { - setValue(PREFERRED_REGION, "uk") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_ireland)) }, - onClick = { - setValue(PREFERRED_REGION, "ireland") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_poland)) }, - onClick = { - setValue(PREFERRED_REGION, "poland") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_hungary)) }, - onClick = { - setValue(PREFERRED_REGION, "hungary") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_germany)) }, - onClick = { - setValue(PREFERRED_REGION, "germany") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_italy)) }, - onClick = { - setValue(PREFERRED_REGION, "italy") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_romania)) }, - onClick = { - setValue(PREFERRED_REGION, "romania") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_scandinavia)) }, - onClick = { - setValue(PREFERRED_REGION, "scandinavia") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_ukraine)) }, - onClick = { - setValue(PREFERRED_REGION, "ukraine") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_india)) }, - onClick = { - setValue(PREFERRED_REGION, "india") - regionDropdownExpanded = false - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_thailand)) }, - onClick = { - setValue(PREFERRED_REGION, "thailand") - regionDropdownExpanded = false - } - ) - } - } - } + ExposedDropdownMenuBox( + expanded = regionDropdownExpanded, + onExpandedChange = { regionDropdownExpanded = !regionDropdownExpanded }, + ) { + OutlinedTextField( + value = + when (preferredRegion) { + "international" -> stringResource(R.string.region_international) + "north_america" -> stringResource(R.string.region_north_america) + "europe" -> stringResource(R.string.region_europe) + "asia" -> stringResource(R.string.region_asia) + "belarus" -> stringResource(R.string.country_belarus) + "bulgaria" -> stringResource(R.string.country_bulgaria) + "czech" -> stringResource(R.string.country_czech) + "uk" -> stringResource(R.string.country_uk) + "ireland" -> stringResource(R.string.country_ireland) + "poland" -> stringResource(R.string.country_poland) + "hungary" -> stringResource(R.string.country_hungary) + "germany" -> stringResource(R.string.country_germany) + "italy" -> stringResource(R.string.country_italy) + "romania" -> stringResource(R.string.country_romania) + "scandinavia" -> stringResource(R.string.country_scandinavia) + "ukraine" -> stringResource(R.string.country_ukraine) + "india" -> stringResource(R.string.country_india) + "thailand" -> stringResource(R.string.country_thailand) + else -> stringResource(R.string.region_international) + }, + onValueChange = {}, + readOnly = true, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(expanded = regionDropdownExpanded) + }, + modifier = Modifier.menuAnchor().fillMaxWidth(), + ) - Text( - stringResource(R.string.settings_api_keys), - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + ExposedDropdownMenu( + expanded = regionDropdownExpanded, + onDismissRequest = { regionDropdownExpanded = false }, + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.region_international)) }, + onClick = { + setValue(PREFERRED_REGION, "international") + regionDropdownExpanded = false + }, ) - - OutlinedTextField( - dhlApiKey, - { setValue(DHL_API_KEY, it) }, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).fillMaxWidth(), - label = { Text(stringResource(R.string.service_dhl)) }, - singleLine = true, - visualTransformation = PasswordVisualTransformation(), + DropdownMenuItem( + text = { Text(stringResource(R.string.region_north_america)) }, + onClick = { + setValue(PREFERRED_REGION, "north_america") + regionDropdownExpanded = false + }, ) - - Text( - AnnotatedString.fromHtml( - stringResource(R.string.dhl_api_key_flavor_text), - linkStyles = - TextLinkStyles( - style = - SpanStyle( - textDecoration = TextDecoration.Underline, - color = MaterialTheme.colorScheme.primary - ) - ) - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + DropdownMenuItem( + text = { Text(stringResource(R.string.region_europe)) }, + onClick = { + setValue(PREFERRED_REGION, "europe") + regionDropdownExpanded = false + }, ) - - Text( - stringResource(R.string.settings_experimental), - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + DropdownMenuItem( + text = { Text(stringResource(R.string.region_asia)) }, + onClick = { + setValue(PREFERRED_REGION, "asia") + regionDropdownExpanded = false + }, ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_belarus)) }, + onClick = { + setValue(PREFERRED_REGION, "belarus") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_bulgaria)) }, + onClick = { + setValue(PREFERRED_REGION, "bulgaria") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_czech)) }, + onClick = { + setValue(PREFERRED_REGION, "czech") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_uk)) }, + onClick = { + setValue(PREFERRED_REGION, "uk") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_ireland)) }, + onClick = { + setValue(PREFERRED_REGION, "ireland") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_poland)) }, + onClick = { + setValue(PREFERRED_REGION, "poland") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_hungary)) }, + onClick = { + setValue(PREFERRED_REGION, "hungary") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_germany)) }, + onClick = { + setValue(PREFERRED_REGION, "germany") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_italy)) }, + onClick = { + setValue(PREFERRED_REGION, "italy") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_romania)) }, + onClick = { + setValue(PREFERRED_REGION, "romania") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_scandinavia)) }, + onClick = { + setValue(PREFERRED_REGION, "scandinavia") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_ukraine)) }, + onClick = { + setValue(PREFERRED_REGION, "ukraine") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_india)) }, + onClick = { + setValue(PREFERRED_REGION, "india") + regionDropdownExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.country_thailand)) }, + onClick = { + setValue(PREFERRED_REGION, "thailand") + regionDropdownExpanded = false + }, + ) + } + } + } - Row( - modifier = - Modifier.clickable { setValue(DEMO_MODE, demoMode.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.demo_mode)) - Text( - stringResource(R.string.demo_mode_detail), - style = MaterialTheme.typography.bodyMedium - ) - } - Switch(checked = demoMode, onCheckedChange = { setValue(DEMO_MODE, it) }) - } - - if (BuildConfig.DEBUG) - FilledTonalButton( - onClick = { - context.sendNotification( - Parcel(0xf100f, "Cool stuff", "", null, Service.EXAMPLE), - Status.OutForDelivery, - ParcelHistoryItem( - "The courier has picked up the package", - LocalDateTime.now(), - "" - ) - ) - }, - modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth() - ) { - Text("Send test notification") - } + Text( + stringResource(R.string.settings_api_keys), + modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) - LogcatButton(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) + OutlinedTextField( + dhlApiKey, + { setValue(DHL_API_KEY, it) }, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).fillMaxWidth(), + label = { Text(stringResource(R.string.service_dhl)) }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + ) - FilledTonalButton( - onClick = { aboutDialogOpen = true }, - modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth() - ) { - Icon(Icons.Filled.Info, contentDescription = stringResource(R.string.about_app)) - Text( - text = " ${stringResource(R.string.about_app)}", - modifier = Modifier.padding(start = 8.dp) + Text( + AnnotatedString.fromHtml( + stringResource(R.string.dhl_api_key_flavor_text), + linkStyles = + TextLinkStyles( + style = + SpanStyle( + textDecoration = TextDecoration.Underline, + color = MaterialTheme.colorScheme.primary, ) - } + ), + ), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + ) - Text( - "Parcel ${BuildConfig.VERSION_NAME}", - modifier = Modifier.padding(16.dp, 8.dp), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + Text( + stringResource(R.string.settings_experimental), + modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + + Row( + modifier = + Modifier.clickable { setValue(DEMO_MODE, demoMode.not()) } + .padding(16.dp, 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.demo_mode)) + Text( + stringResource(R.string.demo_mode_detail), + style = MaterialTheme.typography.bodyMedium, + ) } + Switch(checked = demoMode, onCheckedChange = { setValue(DEMO_MODE, it) }) + } - if (aboutDialogOpen) { - AboutDialog { aboutDialogOpen = false } + if (BuildConfig.DEBUG) + FilledTonalButton( + onClick = { + context.sendNotification( + Parcel(0xf100f, "Cool stuff", "", null, Service.EXAMPLE), + Status.OutForDelivery, + ParcelHistoryItem("The courier has picked up the package", LocalDateTime.now(), ""), + ) + }, + modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth(), + ) { + Text("Send test notification") } + + LogcatButton(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) + + FilledTonalButton( + onClick = { aboutDialogOpen = true }, + modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth(), + ) { + Icon(Icons.Filled.Info, contentDescription = stringResource(R.string.about_app)) + Text( + text = " ${stringResource(R.string.about_app)}", + modifier = Modifier.padding(start = 8.dp), + ) + } + + Text( + "Parcel ${BuildConfig.VERSION_NAME}", + modifier = Modifier.padding(16.dp, 8.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + + if (aboutDialogOpen) { + AboutDialog { aboutDialogOpen = false } } + } } @Composable @PreviewLightDark private fun SettingsViewPreview() { - ParcelTrackerTheme { - SettingsView( - onBackPressed = {}, - ) - } + ParcelTrackerTheme { SettingsView(onBackPressed = {}) } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt index 92b1382..42ef185 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/TabletView.kt @@ -27,174 +27,158 @@ import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.itsvic.parceltracker.R -import dev.itsvic.parceltracker.api.Parcel as APIParcel import dev.itsvic.parceltracker.db.Parcel import dev.itsvic.parceltracker.db.ParcelWithStatus import dev.itsvic.parceltracker.ui.components.ParcelRow +import dev.itsvic.parceltracker.api.Parcel as APIParcel enum class TabletNavigationItem { - HOME, - ADD_PARCEL, - EDIT_PARCEL, - SETTINGS + HOME, + ADD_PARCEL, + EDIT_PARCEL, + SETTINGS, } @OptIn(ExperimentalMaterial3Api::class) @Composable fun TabletView( - parcels: List, - selectedParcel: Parcel?, - apiParcel: APIParcel?, - isLoading: Boolean, - currentNavigationItem: TabletNavigationItem, - onNavigateToItem: (TabletNavigationItem) -> Unit, - onNavigateToParcel: (Parcel) -> Unit, - onNavigateToAddParcel: () -> Unit, - onNavigateToSettings: () -> Unit, - onEditParcel: (Parcel) -> Unit, - onDeleteParcel: (Parcel) -> Unit, - onArchiveParcel: (Parcel) -> Unit, - onArchivePromptDismissal: (Parcel) -> Unit, - settingsContent: @Composable () -> Unit = {}, - addParcelContent: @Composable () -> Unit = {}, - editParcelContent: @Composable () -> Unit = {} + parcels: List, + selectedParcel: Parcel?, + apiParcel: APIParcel?, + isLoading: Boolean, + currentNavigationItem: TabletNavigationItem, + onNavigateToItem: (TabletNavigationItem) -> Unit, + onNavigateToParcel: (Parcel) -> Unit, + onNavigateToAddParcel: () -> Unit, + onNavigateToSettings: () -> Unit, + onEditParcel: (Parcel) -> Unit, + onDeleteParcel: (Parcel) -> Unit, + onArchiveParcel: (Parcel) -> Unit, + onArchivePromptDismissal: (Parcel) -> Unit, + settingsContent: @Composable () -> Unit = {}, + addParcelContent: @Composable () -> Unit = {}, + editParcelContent: @Composable () -> Unit = {}, ) { - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Row(modifier = Modifier.fillMaxSize()) { + Card(modifier = Modifier.width(400.dp).fillMaxHeight().padding(8.dp)) { + Column { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp), + ) - Row(modifier = Modifier.fillMaxSize()) { - Card(modifier = Modifier.width(400.dp).fillMaxHeight().padding(8.dp)) { - Column { + LazyColumn(modifier = Modifier.fillMaxWidth().weight(1f).padding(horizontal = 8.dp)) { + if (parcels.isEmpty()) { + item { + Card(modifier = Modifier.fillMaxWidth().padding(8.dp)) { Text( - text = stringResource(R.string.app_name), - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp) + stringResource(R.string.no_parcels_flavor), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(16.dp), ) - - LazyColumn( - modifier = Modifier.fillMaxWidth().weight(1f).padding(horizontal = 8.dp) + } + } + } else { + items(parcels.reversed()) { parcel -> + Card(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp)) { + ParcelRow( + parcel.parcel, + parcel.status?.status, + isSelected = selectedParcel?.id == parcel.parcel.id, ) { - if (parcels.isEmpty()) { - item { - Card(modifier = Modifier.fillMaxWidth().padding(8.dp)) { - Text( - stringResource(R.string.no_parcels_flavor), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(16.dp) - ) - } - } - } else { - items(parcels.reversed()) { parcel -> - Card( - modifier = - Modifier.fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp) - ) { - ParcelRow( - parcel.parcel, - parcel.status?.status, - isSelected = selectedParcel?.id == parcel.parcel.id - ) { - onNavigateToParcel(parcel.parcel) - } - } - } - } + onNavigateToParcel(parcel.parcel) } + } + } + } + } - HorizontalDivider() + HorizontalDivider() - NavigationBar(modifier = Modifier.fillMaxWidth()) { - NavigationBarItem( - icon = { Icon(Icons.Filled.Home, contentDescription = null) }, - label = { Text(stringResource(R.string.home)) }, - selected = currentNavigationItem == TabletNavigationItem.HOME, - onClick = { onNavigateToItem(TabletNavigationItem.HOME) } - ) - NavigationBarItem( - icon = { Icon(Icons.Filled.Add, contentDescription = null) }, - label = { Text(stringResource(R.string.add_parcel)) }, - selected = currentNavigationItem == TabletNavigationItem.ADD_PARCEL, - onClick = { onNavigateToItem(TabletNavigationItem.ADD_PARCEL) } - ) - NavigationBarItem( - icon = { Icon(Icons.Filled.Settings, contentDescription = null) }, - label = { Text(stringResource(R.string.settings)) }, - selected = currentNavigationItem == TabletNavigationItem.SETTINGS, - onClick = { onNavigateToItem(TabletNavigationItem.SETTINGS) } - ) - } - } + NavigationBar(modifier = Modifier.fillMaxWidth()) { + NavigationBarItem( + icon = { Icon(Icons.Filled.Home, contentDescription = null) }, + label = { Text(stringResource(R.string.home)) }, + selected = currentNavigationItem == TabletNavigationItem.HOME, + onClick = { onNavigateToItem(TabletNavigationItem.HOME) }, + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Add, contentDescription = null) }, + label = { Text(stringResource(R.string.add_parcel)) }, + selected = currentNavigationItem == TabletNavigationItem.ADD_PARCEL, + onClick = { onNavigateToItem(TabletNavigationItem.ADD_PARCEL) }, + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Settings, contentDescription = null) }, + label = { Text(stringResource(R.string.settings)) }, + selected = currentNavigationItem == TabletNavigationItem.SETTINGS, + onClick = { onNavigateToItem(TabletNavigationItem.SETTINGS) }, + ) } + } + } - // Right panel: Content area - Card(modifier = Modifier.weight(1f).fillMaxHeight().padding(8.dp)) { - when (currentNavigationItem) { - TabletNavigationItem.HOME -> { - if (selectedParcel != null) { - if (isLoading) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } else if (apiParcel != null) { - ParcelView( - parcel = apiParcel, - humanName = selectedParcel.humanName, - service = selectedParcel.service, - isArchived = selectedParcel.isArchived, - archivePromptDismissed = selectedParcel.archivePromptDismissed, - onBackPressed = { /* No back button in tablet mode */}, - onEdit = { onEditParcel(selectedParcel) }, - onDelete = { onDeleteParcel(selectedParcel) }, - onArchive = { onArchiveParcel(selectedParcel) }, - onArchivePromptDismissal = { - onArchivePromptDismissal(selectedParcel) - }, - showBackButton = false - ) - } - } else { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - text = stringResource(R.string.app_name), - style = MaterialTheme.typography.headlineMedium, - modifier = Modifier.padding(bottom = 16.dp) - ) - Text( - text = stringResource(R.string.select_parcel_to_view), - style = MaterialTheme.typography.bodyLarge - ) - } - } - } - } - TabletNavigationItem.ADD_PARCEL -> { - addParcelContent() - } - TabletNavigationItem.EDIT_PARCEL -> { - editParcelContent() - } - TabletNavigationItem.SETTINGS -> { - settingsContent() - } + // Right panel: Content area + Card(modifier = Modifier.weight(1f).fillMaxHeight().padding(8.dp)) { + when (currentNavigationItem) { + TabletNavigationItem.HOME -> { + if (selectedParcel != null) { + if (isLoading) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } else if (apiParcel != null) { + ParcelView( + parcel = apiParcel, + humanName = selectedParcel.humanName, + service = selectedParcel.service, + isArchived = selectedParcel.isArchived, + archivePromptDismissed = selectedParcel.archivePromptDismissed, + onBackPressed = { /* No back button in tablet mode */ }, + onEdit = { onEditParcel(selectedParcel) }, + onDelete = { onDeleteParcel(selectedParcel) }, + onArchive = { onArchiveParcel(selectedParcel) }, + onArchivePromptDismissal = { onArchivePromptDismissal(selectedParcel) }, + showBackButton = false, + ) } + } else { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.padding(bottom = 16.dp), + ) + Text( + text = stringResource(R.string.select_parcel_to_view), + style = MaterialTheme.typography.bodyLarge, + ) + } + } + } + } + TabletNavigationItem.ADD_PARCEL -> { + addParcelContent() + } + TabletNavigationItem.EDIT_PARCEL -> { + editParcelContent() + } + TabletNavigationItem.SETTINGS -> { + settingsContent() } + } } + } } From 18b1b4a508d9e9089ca38be700b2d95c86b845bb Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:58:43 +0200 Subject: [PATCH 12/18] Support for imile --- .../java/dev/itsvic/parceltracker/api/Core.kt | 2 + .../parceltracker/api/IMileDeliveryService.kt | 96 +++++++++++++++++++ .../ui/views/AddEditParcelView.kt | 22 +++-- app/src/main/res/values-hu/strings.xml | 2 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/dev/itsvic/parceltracker/api/IMileDeliveryService.kt diff --git a/app/src/main/java/dev/itsvic/parceltracker/api/Core.kt b/app/src/main/java/dev/itsvic/parceltracker/api/Core.kt index 044eb14..4645c36 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/api/Core.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/api/Core.kt @@ -56,6 +56,7 @@ enum class Service { // Asia EKART, SPX_TH, + IMILE, } val serviceOptions = @@ -100,6 +101,7 @@ fun getDeliveryService(service: Service): DeliveryService? { Service.EKART -> EKartDeliveryService Service.SPX_TH -> SPXThailandDeliveryService + Service.IMILE -> IMileDeliveryService Service.EXAMPLE -> ExampleDeliveryService else -> null diff --git a/app/src/main/java/dev/itsvic/parceltracker/api/IMileDeliveryService.kt b/app/src/main/java/dev/itsvic/parceltracker/api/IMileDeliveryService.kt new file mode 100644 index 0000000..faf0354 --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/api/IMileDeliveryService.kt @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package dev.itsvic.parceltracker.api + +import com.squareup.moshi.JsonClass +import dev.itsvic.parceltracker.R +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import retrofit2.Retrofit +import retrofit2.http.GET +import retrofit2.http.Query + +object IMileDeliveryService : DeliveryService { + override val nameResource: Int = R.string.service_imile + override val acceptsPostCode: Boolean = false + override val requiresPostCode: Boolean = false + + private const val BASE_URL = "https://www.imile.com/" + + private val retrofit = + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(api_client) + .addConverterFactory(api_factory) + .build() + + private val service = retrofit.create(API::class.java) + + override suspend fun getParcel(trackingId: String, postCode: String?): Parcel { + val response = service.getParcel(trackingId) + + if (response.status != "success" || response.resultObject == null) { + throw ParcelNonExistentException() + } + + val resultObject = response.resultObject + val trackInfos = resultObject.trackInfos + + if (trackInfos.isEmpty()) { + throw ParcelNonExistentException() + } + + val history = trackInfos.map { trackInfo -> + ParcelHistoryItem( + description = trackInfo.content, + time = LocalDateTime.parse(trackInfo.time, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), + location = trackInfo.operateStationName ?: "" + ) + } + + val currentStatus = when (trackInfos.firstOrNull()?.trackStage) { + 1001 -> Status.Preadvice + 2004 -> Status.InWarehouse + 1002 -> Status.InTransit + 1003 -> Status.OutForDelivery + else -> Status.Unknown + } + + return Parcel( + id = trackingId, + history = history, + currentStatus = currentStatus + ) + } + + private interface API { + @GET("saastms/mobileWeb/track/query") + suspend fun getParcel(@Query("waybillNo") waybillNo: String): IMileResponse + } + + @JsonClass(generateAdapter = true) + data class IMileResponse( + val status: String, + val resultCode: String, + val resultObject: IMileResultObject?, + val message: String + ) + + @JsonClass(generateAdapter = true) + data class IMileResultObject( + val waybillNo: String, + val sendSite: String?, + val dispatchStation: String?, + val country: String?, + val trackInfos: List + ) + + @JsonClass(generateAdapter = true) + data class IMileTrackInfo( + val content: String, + val trackStage: Int, + val trackStageTx: String, + val time: String, + val operateStationName: String?, + val proofs: Any? + ) +} diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index 3abdfed..22c7c8e 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -131,6 +131,7 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, + Service.IMILE, Service.DPD_GER, Service.HERMES, Service.POSTE_ITALIANE, @@ -161,6 +162,7 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( Service.MAGYAR_POSTA, Service.SAMEDAY_HU, Service.EXPRESS_ONE, + Service.IMILE, ) "germany" -> it in listOf(Service.DPD_GER, Service.HERMES) "italy" -> it == Service.POSTE_ITALIANE @@ -201,11 +203,12 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( Service.SAMEDAY_RO, Service.POSTNORD, Service.NOVA_POSHTA, - Service.UKRPOSHTA -> 2 Service.EKART, - Service.SPX_TH -> 3 + Service.SPX_TH, + Service.IMILE, + Service.UKRPOSHTA -> 2 else -> 4 - } + } as Comparable<*>? } .thenBy { when (it) { @@ -222,7 +225,8 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( Service.GLS_HUNGARY, Service.MAGYAR_POSTA, Service.SAMEDAY_HU, - Service.EXPRESS_ONE -> "G_Hungary" + Service.EXPRESS_ONE, + Service.IMILE -> "G_Hungary" Service.DPD_GER, Service.HERMES -> "H_Germany" Service.POSTE_ITALIANE -> "I_Italy" @@ -345,8 +349,8 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( Service.INPOST, Service.ORLEN_PACZKA, Service.POLISH_POST -> "Európa - Lengyelország" - Service.GLS_HUNGARY, - Service.MAGYAR_POSTA, + Service.GLS_HUNGARY -> "Európa - Magyarország" + Service.MAGYAR_POSTA -> "Európa - Magyarország" Service.SAMEDAY_HU -> "Európa - Magyarország" Service.DPD_GER, Service.HERMES -> "Európa - Németország" @@ -357,6 +361,8 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( Service.UKRPOSHTA -> "Európa - Ukrajna" Service.EKART -> "Ázsia - India" Service.SPX_TH -> "Ázsia - Thaiföld" + Service.IMILE -> "Európa - Magyarország" + Service.EXPRESS_ONE -> "Európa - Magyarország" else -> "Egyéb" } @@ -437,9 +443,7 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( onCompleted( Parcel( id = parcel?.id ?: 0, - humanName = - if (humanName.isBlank()) context.getString(R.string.undefinied_packagename) - else humanName, + humanName = humanName.ifBlank { context.getString(R.string.undefinied_packagename) }, parcelId = trackingId, service = service, postalCode = diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 887d032..49ee93d 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -65,6 +65,8 @@ Ki kell választani egy futárszolgálatot. Minta Posta GLS Magyarország + Express One Magyarország + iMile Magyar Posta Lengyel Posta Olasz Posta diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a49eae2..8e1c299 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,6 +81,7 @@ GLS Hungary Hermes Hungarian Post + iMile InPost Nova Post Orlen Paczka (Poland) From bc6a75d89dbf3203fd76529fbc598da5cb75738b Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Mon, 21 Jul 2025 21:53:11 +0200 Subject: [PATCH 13/18] Remove unused --- .../main/res/drawable-hdpi/ic_contentpaste.png | Bin 352 -> 0 bytes .../main/res/drawable-mdpi/ic_contentpaste.png | Bin 260 -> 0 bytes .../main/res/drawable-xhdpi/ic_contentpaste.png | Bin 450 -> 0 bytes .../main/res/drawable-xxhdpi/ic_contentpaste.png | Bin 785 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/src/main/res/drawable-hdpi/ic_contentpaste.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_contentpaste.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_contentpaste.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_contentpaste.png diff --git a/app/src/main/res/drawable-hdpi/ic_contentpaste.png b/app/src/main/res/drawable-hdpi/ic_contentpaste.png deleted file mode 100644 index da7d251f3d43114d26be2cf0f3aa6015f3eed0b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 352 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$UKzz^Lx&;uum9_jblvufqWXt*_l; zMII}&@NgAQj6ZRzX-DDJUh9S2^IZ!r>hT8Nzh>M$v8d|o6*EDm&j%ZC&$c#>_m=iZ z->iJ%^Pf$tR&C#;bAMvkwN1yL|7+Dbz3%m{e_X6d;Wc$Rei2PAR+fid*(Wc1DdQVI zkzY=X>zu3Sq?itl_{SdpFLj?+<_VmbQ#5(sgWaDOZFLP?^P?(Wp>&(AHOH*9m<-qK zjd?$#;tjGl0tN1UoGbQVaqYF*HSc#m+~;`S{Vj8+`rYGID}V2QgTe~DWM4f5aXMq diff --git a/app/src/main/res/drawable-mdpi/ic_contentpaste.png b/app/src/main/res/drawable-mdpi/ic_contentpaste.png deleted file mode 100644 index 4f4d006167bc9b5a5b25d2e3952784974fe7fe82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjr#)R9Ln`8~PIcruWFW%&I?APA zVaJPgDw%ngdbbO#R|@^I?O%iD)2S(3v6deeni$*f%DTKprKRBg?|)xs&z_fmsJgz_ zu#&1}YjCgh zM{gz72`9r&@N06t6S>2w7sK^y`@TkzN3J_3&zZ*drjfgKy5;w+FY{KtV)#;PUlRL$ zTF|c(C+91h|GM+4BUS&?rjM-q>b%dyKi*huyIm^jV0moRmF1e^y?))RJU;F}Eac)*k0M z_Oo}KiukqqZO~txwWW6I8&AJI658hVEKO(aar4UcpLbsTtK+wzSnB!pokuxK{2u?Y z+HTQ!E^|)&b20S`mc5@}-+9DhwY;!yZuA_7=O*XY7jug_6xh=N@8{~1y?cI={>zY} zHA|+vE3rRqz+<}e$?b5l_)n=POkdjx-SzKYAL&2mhk6b35k`X-N%qlwXguh-w)n)|zEYt-FmKmWHy9P-q8c(!c!-;c{Xi`PH<{8QJi z{?d`9`2~VYJD10%pHrE6Hcfrwg8UeV%wNxS#JXpnnJx2iTh-aq@g+%~`ZLWnA2pR% zzC7*s-+k`R|01)`KFf^x((@=eeQ#X8(!U+^U)=cgJXw9t{o|{@bG}b5l3|sP$nm*s z(jO_tU45+h*q82C?+@*}=<-8+TIAt%wl?X$IxmHHy9qWf*D-9bt*$sXr=s;<^8JKI zYqushT0TE{?Eb=bn{>ZBFLkZi4;NJbI&YzP=k4!DAOBYTnfv_B&%b?(6AD%)oM1n= z@pAd4Wd8H-+Mlx>eh}ms8?bPJ-im+#@eqv_+*%@6SVg&BHF2?)I<_|LT2ORwtM;to zh2L5K=(9I}ZAy8u_P23_zDL`=UG}3~l+ajiwC8!kt!;l#d~lw>?)ABf^9N0QKXUW6NRIjB* bv2{%4N7df3Yb^QzOzI4tu6{1-oD!M Date: Mon, 21 Jul 2025 21:54:16 +0200 Subject: [PATCH 14/18] Added new statuses to iMile --- .../parceltracker/api/IMileDeliveryService.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/api/IMileDeliveryService.kt b/app/src/main/java/dev/itsvic/parceltracker/api/IMileDeliveryService.kt index faf0354..4feb4c3 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/api/IMileDeliveryService.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/api/IMileDeliveryService.kt @@ -47,13 +47,7 @@ object IMileDeliveryService : DeliveryService { ) } - val currentStatus = when (trackInfos.firstOrNull()?.trackStage) { - 1001 -> Status.Preadvice - 2004 -> Status.InWarehouse - 1002 -> Status.InTransit - 1003 -> Status.OutForDelivery - else -> Status.Unknown - } + val currentStatus = mapTrackStageToStatus(trackInfos.firstOrNull()?.trackStage ?: 0) return Parcel( id = trackingId, @@ -62,6 +56,17 @@ object IMileDeliveryService : DeliveryService { ) } + private fun mapTrackStageToStatus(trackStage: Int): Status { + return when (trackStage) { + 1001 -> Status.Preadvice + 2004 -> Status.InWarehouse + 1002 -> Status.InTransit + 1003 -> Status.OutForDelivery + 2060 -> Status.Delivered + else -> logUnknownStatus("iMile", trackStage.toString()) + } + } + private interface API { @GET("saastms/mobileWeb/track/query") suspend fun getParcel(@Query("waybillNo") waybillNo: String): IMileResponse From 6b4e68185f0cc66e7491a6ed0374a7ad9c927ce4 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:05:44 +0200 Subject: [PATCH 15/18] New icons --- .../res/drawable-anydpi/ic_filterlist.xml | 11 +++++ app/src/main/res/drawable-anydpi/ic_label.xml | 12 ++++++ .../main/res/drawable-anydpi/ic_language.xml | 11 +++++ .../res/drawable-anydpi/ic_localshipping.xml | 12 ++++++ .../res/drawable-anydpi/ic_locationon.xml | 11 +++++ .../res/drawable-anydpi/ic_networkwifi.xml | 11 +++++ .../main/res/drawable-anydpi/ic_qrcode.xml | 41 +++++++++++++++++++ .../main/res/drawable-anydpi/ic_science.xml | 11 +++++ .../main/res/drawable-anydpi/ic_timeline.xml | 11 +++++ .../main/res/drawable-anydpi/ic_vpnkey.xml | 11 +++++ 10 files changed, 142 insertions(+) create mode 100644 app/src/main/res/drawable-anydpi/ic_filterlist.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_label.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_language.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_localshipping.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_locationon.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_networkwifi.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_qrcode.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_science.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_timeline.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_vpnkey.xml diff --git a/app/src/main/res/drawable-anydpi/ic_filterlist.xml b/app/src/main/res/drawable-anydpi/ic_filterlist.xml new file mode 100644 index 0000000..9820675 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_filterlist.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_label.xml b/app/src/main/res/drawable-anydpi/ic_label.xml new file mode 100644 index 0000000..e255848 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_label.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_language.xml b/app/src/main/res/drawable-anydpi/ic_language.xml new file mode 100644 index 0000000..8beebb7 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_language.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_localshipping.xml b/app/src/main/res/drawable-anydpi/ic_localshipping.xml new file mode 100644 index 0000000..76832c9 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_localshipping.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_locationon.xml b/app/src/main/res/drawable-anydpi/ic_locationon.xml new file mode 100644 index 0000000..4d88730 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_locationon.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_networkwifi.xml b/app/src/main/res/drawable-anydpi/ic_networkwifi.xml new file mode 100644 index 0000000..d508b16 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_networkwifi.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_qrcode.xml b/app/src/main/res/drawable-anydpi/ic_qrcode.xml new file mode 100644 index 0000000..ca6249b --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_qrcode.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-anydpi/ic_science.xml b/app/src/main/res/drawable-anydpi/ic_science.xml new file mode 100644 index 0000000..a00f40b --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_science.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_timeline.xml b/app/src/main/res/drawable-anydpi/ic_timeline.xml new file mode 100644 index 0000000..4566deb --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_timeline.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_vpnkey.xml b/app/src/main/res/drawable-anydpi/ic_vpnkey.xml new file mode 100644 index 0000000..ead0ae1 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_vpnkey.xml @@ -0,0 +1,11 @@ + + + From 19e6a8a1a11e83683a04bd2810f28b7ca2ceb4a6 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:05:57 +0200 Subject: [PATCH 16/18] New strings --- app/src/main/res/values-hu/strings.xml | 31 ++++++++++++++++++++++++ app/src/main/res/values/strings.xml | 33 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 49ee93d..6f22fa1 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -30,6 +30,23 @@ Ukrajna India Thaiföld + Nemzetközi + Észak-Amerika + Európa - Fehéroroszország + Európa - Bulgária + Európa - Csehország + Európa - Egyesült Királyság + Európa - Írország + Európa - Lengyelország + Európa - Magyarország + Európa - Németország + Európa - Olaszország + Európa - Románia + Európa - Skandinávia + Európa - Ukrajna + Ázsia - India + Ázsia - Thaiföld + Egyéb Válassz egy csomagot a részletek megtekintéséhez Főoldal Biztosan törölni szeretnéd ezt a csomagot? @@ -39,6 +56,11 @@ Törlés Futárszolgálat Teszt mód + Csomag részletei + %s részletei + Követési szám + További információk + Követési előzmények A művelet nem engedélyezett teszt módban Minta csomagokat jelenít meg az applikációban. Nem érinti a már meglévő adatokat. Támogatás @@ -97,4 +119,13 @@ Csak korlátlan hálózaton való frissítés A Parcel csak akkor fog frissítéseket keresni automatikusan, ha nem forgalmidíjas hálózatra van csatlakozva a készülék, pl. otthoni Wi-Fi. Csomag + Hálózat + Régió + Fejlesztői eszközök + Verzió %s + Teszt értesítés küldése + DHL API kulcs + Teszt csomag + A futár felvette a csomagot + Műveletek
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8e1c299..54cf68c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,9 +2,11 @@ About app Add Add a parcel + Actions Add parcel Parcel Archive + Collapse Do you want to archive this parcel? If you archive this parcel, its history will be preserved on device. It will not be periodically checked for updates. Cancel @@ -31,15 +33,38 @@ Ukraine India Thailand + International + North America + Europe - Belarus + Europe - Bulgaria + Europe - Czech Republic + Europe - United Kingdom + Europe - Ireland + Europe - Poland + Europe - Hungary + Europe - Germany + Europe - Italy + Europe - Romania + Europe - Scandinavia + Europe - Ukraine + Asia - India + Asia - Thailand + Other Select a parcel to view its details Home Are you sure you want to delete this parcel? Notifications about the current status of the parcel + Expand Parcel events Current status Delete Delivery service Demo mode + Parcel Details + %s details + Tracking Number + Additional Information + Tracking History Action not allowed in demo mode Shows example parcels for demo purposes. Does not affect user data. DHL requires us to ask users to provide an API key. You can get one for free from <a href=\"https://developer.dhl.com\">DHL\'s API Developer Portal</a> and signing up for the \"Shipment Tracking - Unified\" API. @@ -133,4 +158,12 @@ Update only on unmetered networks If enabled, Parcel will look for parcel updates only on unmetered connections, such as your home Wi-Fi. Package + Network + Region + Developer tools + Version %s + Send test notification + DHL API key + Test package + Courier picked up the package
From 948a4649fb0c7baf0b9a411e028b8828ff52f549 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:06:52 +0200 Subject: [PATCH 17/18] Better design to settings, parcel view and add/edit parcel page. --- .../dev/itsvic/parceltracker/MainActivity.kt | 18 +- .../ui/components/BottomNavBar.kt | 11 + .../ui/components/EditParcelDialog.kt | 55 ++ .../FloatingCollapsibleActionBar.kt | 229 ++++++++ .../ui/components/ParcelActionBar.kt | 11 + .../ui/views/AddEditParcelView.kt | 499 ++++++++++------ .../parceltracker/ui/views/ParcelView.kt | 340 ++++++++--- .../parceltracker/ui/views/SettingsView.kt | 544 +++++++++--------- 8 files changed, 1170 insertions(+), 537 deletions(-) create mode 100644 app/src/main/java/dev/itsvic/parceltracker/ui/components/EditParcelDialog.kt create mode 100644 app/src/main/java/dev/itsvic/parceltracker/ui/components/FloatingCollapsibleActionBar.kt diff --git a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt index eed9f8f..438aea3 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt @@ -61,6 +61,7 @@ import dev.itsvic.parceltracker.db.deleteParcel import dev.itsvic.parceltracker.db.demoModeParcels import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import dev.itsvic.parceltracker.ui.components.BottomNavBar +import dev.itsvic.parceltracker.ui.components.EditParcelDialog import dev.itsvic.parceltracker.ui.views.AddEditParcelView import dev.itsvic.parceltracker.ui.views.AdaptiveParcelApp import dev.itsvic.parceltracker.ui.views.HomeView @@ -119,7 +120,6 @@ class MainActivity : ComponentActivity() { } } - // Notification checks when { ContextCompat.checkSelfPermission( applicationContext, @@ -185,7 +185,7 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat apiParcel = APIParcel( selectedParcel!!.parcelId, localHistory.map { dev.itsvic.parceltracker.api.ParcelHistoryItem(it.description, it.time, it.location) }, - Status.Delivered // Assume delivered for archived parcels + Status.Delivered ) } else { apiParcel = context.getParcel(selectedParcel!!.parcelId, selectedParcel!!.postalCode, selectedParcel!!.service) @@ -229,7 +229,10 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat isLoading = isLoadingParcel, currentNavigationItem = currentTabletNavItem, onNavigateToItem = { currentTabletNavItem = it }, - onNavigateToParcel = { selectedParcel = it }, + onNavigateToParcel = { + selectedParcel = it + currentTabletNavItem = TabletNavigationItem.HOME + }, onNavigateToAddParcel = { currentTabletNavItem = TabletNavigationItem.ADD_PARCEL }, onNavigateToSettings = { currentTabletNavItem = TabletNavigationItem.SETTINGS }, onEditParcel = { parcel -> @@ -394,7 +397,6 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat context.getParcel(dbParcel.parcelId, dbParcel.postalCode, dbParcel.service) if (!demoMode) { - // update parcel status val zone = ZoneId.systemDefault() val lastChange = apiParcel!!.history.first().time.atZone(zone).toInstant() val status = @@ -565,9 +567,9 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat CircularProgressIndicator() } - AddEditParcelView( - parcel, - onBackPressed = { navController.popBackStack() }, + EditParcelDialog( + parcel = parcel!!, + onDismissRequest = { navController.popBackStack() }, onCompleted = { if (demoMode) { Toast.makeText( @@ -575,7 +577,7 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat context.getString(R.string.demo_mode_action_block), Toast.LENGTH_SHORT) .show() - return@AddEditParcelView + return@EditParcelDialog } scope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt index 0001296..4babfac 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/BottomNavBar.kt @@ -2,6 +2,7 @@ package dev.itsvic.parceltracker.ui.components import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Settings @@ -19,8 +20,18 @@ fun BottomNavBar( onNavigateToHome: () -> Unit, onNavigateToAddParcel: () -> Unit, onNavigateToSettings: () -> Unit, + onBackPressed: (() -> Unit)? = null, + showBackButton: Boolean = false, ) { NavigationBar { + if (showBackButton && onBackPressed != null) { + NavigationBarItem( + icon = { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.go_back)) }, + label = { Text(stringResource(R.string.go_back)) }, + selected = false, + onClick = onBackPressed, + ) + } NavigationBarItem( icon = { Icon(Icons.Filled.Home, contentDescription = stringResource(R.string.home)) }, label = { Text(stringResource(R.string.home)) }, diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/EditParcelDialog.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/EditParcelDialog.kt new file mode 100644 index 0000000..050b68b --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/EditParcelDialog.kt @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package dev.itsvic.parceltracker.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import dev.itsvic.parceltracker.R +import dev.itsvic.parceltracker.db.Parcel +import dev.itsvic.parceltracker.ui.views.AddEditParcelContent + +@Composable +fun EditParcelDialog( + parcel: Parcel, + onDismissRequest: () -> Unit, + onCompleted: (Parcel) -> Unit +) { + Dialog(onDismissRequest = onDismissRequest) { + Column( + modifier = Modifier + .fillMaxWidth() + .sizeIn(maxHeight = 700.dp, maxWidth = 500.dp) + .verticalScroll(rememberScrollState()) + ) { + Text( + text = stringResource(R.string.edit_parcel), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + AddEditParcelContent( + parcel = parcel, + onCompleted = onCompleted, + isDialog = true + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/FloatingCollapsibleActionBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/FloatingCollapsibleActionBar.kt new file mode 100644 index 0000000..e0a289f --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/FloatingCollapsibleActionBar.kt @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package dev.itsvic.parceltracker.ui.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import dev.itsvic.parceltracker.R +import dev.itsvic.parceltracker.api.Status + +@Composable +fun FloatingCollapsibleActionBar( + status: Status?, + onEdit: () -> Unit, + onArchive: () -> Unit, + onDelete: () -> Unit, + onBackPressed: (() -> Unit)? = null, + modifier: Modifier = Modifier, +) { + var isExpanded by remember { mutableStateOf(false) } + var showDeleteDialog by remember { mutableStateOf(false) } + + val rotationAngle by animateFloatAsState( + targetValue = if (isExpanded) 180f else 0f, + animationSpec = tween(300), + label = "rotation" + ) + + Box( + modifier = modifier + .fillMaxWidth() + .padding(16.dp), + contentAlignment = Alignment.BottomCenter + ) { + Card( + modifier = Modifier + .fillMaxWidth() + .shadow( + elevation = 8.dp, + shape = RoundedCornerShape(16.dp) + ), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { isExpanded = !isExpanded } + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.actions), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface + ) + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = if (isExpanded) stringResource(R.string.collapse) else stringResource(R.string.expand), + modifier = Modifier.rotate(rotationAngle), + tint = MaterialTheme.colorScheme.onSurface + ) + } + AnimatedVisibility( + visible = isExpanded, + enter = expandVertically( + animationSpec = tween(300) + ) + fadeIn(animationSpec = tween(300)), + exit = shrinkVertically( + animationSpec = tween(300) + ) + fadeOut(animationSpec = tween(300)) + ) { + Column( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) { + if (onBackPressed != null) { + ActionButton( + icon = { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.go_back)) }, + text = stringResource(R.string.go_back), + onClick = onBackPressed + ) + Spacer(modifier = Modifier.height(8.dp)) + } + ActionButton( + icon = { Icon(Icons.Filled.Edit, contentDescription = stringResource(R.string.edit)) }, + text = stringResource(R.string.edit), + onClick = onEdit + ) + + Spacer(modifier = Modifier.height(8.dp)) + + if (status == Status.Delivered) { + ActionButton( + icon = { + Icon( + painterResource(R.drawable.archive), + contentDescription = stringResource(R.string.archive) + ) + }, + text = stringResource(R.string.archive), + onClick = onArchive + ) + Spacer(modifier = Modifier.height(8.dp)) + } + ActionButton( + icon = { Icon(Icons.Filled.Delete, contentDescription = stringResource(R.string.delete)) }, + text = stringResource(R.string.delete), + onClick = { showDeleteDialog = true }, + isDestructive = true + ) + + Spacer(modifier = Modifier.height(8.dp)) + } + } + } + } + } + if (showDeleteDialog) { + AlertDialog( + onDismissRequest = { showDeleteDialog = false }, + title = { Text(stringResource(R.string.delete)) }, + text = { Text(stringResource(R.string.delete_confirmation)) }, + confirmButton = { + TextButton( + onClick = { + showDeleteDialog = false + onDelete() + } + ) { + Text(stringResource(R.string.delete)) + } + }, + dismissButton = { + TextButton(onClick = { showDeleteDialog = false }) { + Text(stringResource(R.string.cancel)) + } + }, + ) + } +} + +@Composable +fun ActionButton( + icon: @Composable () -> Unit, + text: String, + onClick: () -> Unit, + isDestructive: Boolean = false, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .clickable { onClick() } + .background( + if (isDestructive) + MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.1f) + else + MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.1f) + ) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier.size(24.dp), + contentAlignment = Alignment.Center + ) { + icon() + } + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = text, + style = MaterialTheme.typography.bodyLarge, + color = if (isDestructive) + MaterialTheme.colorScheme.error + else + MaterialTheme.colorScheme.onSurface + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt index 51b3ccd..90f7ce7 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelActionBar.kt @@ -2,6 +2,7 @@ package dev.itsvic.parceltracker.ui.components import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.AlertDialog @@ -26,10 +27,20 @@ fun ParcelActionBar( onEdit: () -> Unit, onArchive: () -> Unit, onDelete: () -> Unit, + onBackPressed: (() -> Unit)? = null, ) { var showDeleteDialog by remember { mutableStateOf(false) } NavigationBar { + if (onBackPressed != null) { + NavigationBarItem( + icon = { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.go_back)) }, + label = { Text(stringResource(R.string.go_back)) }, + selected = false, + onClick = onBackPressed, + ) + } + NavigationBarItem( icon = { Icon(Icons.Filled.Edit, contentDescription = stringResource(R.string.edit)) }, label = { Text(stringResource(R.string.edit)) }, diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index 22c7c8e..5fd6ef6 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -2,20 +2,29 @@ package dev.itsvic.parceltracker.ui.views import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.Button +import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuAnchorType import androidx.compose.material3.ExposedDropdownMenuBox @@ -25,9 +34,13 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -39,7 +52,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp @@ -59,6 +71,49 @@ import kotlinx.coroutines.flow.map @OptIn(ExperimentalMaterial3Api::class) @Composable fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: (Parcel) -> Unit) { + val isEdit = parcel != null + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Scaffold( + topBar = { + TopAppBar( + title = { + Text(stringResource(if (isEdit) R.string.edit_parcel else R.string.add_a_parcel)) + }, + scrollBehavior = scrollBehavior, + ) + }, + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)) + ) { + Column( + modifier = Modifier + .padding(innerPadding) + .fillMaxWidth() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AddEditParcelContent( + parcel = parcel, + onCompleted = onCompleted, + isDialog = false + ) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddEditParcelContent( + parcel: Parcel?, + onCompleted: (Parcel) -> Unit, + isDialog: Boolean = false +) { val isEdit = parcel != null val context = LocalContext.current val clipboardManager = LocalClipboardManager.current @@ -79,7 +134,6 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( val backend = if (service != Service.UNDEFINED) getDeliveryService(service) else null fun validateInputs(): Boolean { - // reset error states first idError = false serviceError = false postalCodeError = false @@ -247,199 +301,267 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( } ) - Scaffold( - topBar = { - TopAppBar( - title = { - Text(stringResource(if (isEdit) R.string.edit_parcel else R.string.add_a_parcel)) - }, - navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) - } - }, - scrollBehavior = scrollBehavior, - ) - }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - ) { innerPadding -> - Column( - modifier = - Modifier.padding(innerPadding).fillMaxWidth().verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Column( - modifier = Modifier.padding(horizontal = 16.dp).sizeIn(maxWidth = 488.dp).fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - OutlinedTextField( - value = humanName, - onValueChange = { humanName = it }, - singleLine = true, - label = { Text(stringResource(R.string.parcel_name)) }, - modifier = Modifier.fillMaxWidth(), - ) + Column( + modifier = Modifier + .padding(horizontal = if (isDialog) 0.dp else 16.dp) + .sizeIn(maxWidth = 600.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + if (!isDialog) { + Spacer(modifier = Modifier.height(8.dp)) + } + ElevatedCard( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.elevatedCardElevation(defaultElevation = 6.dp) + ) { + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + OutlinedTextField( + value = humanName, + onValueChange = { humanName = it }, + singleLine = true, + label = { Text(stringResource(R.string.parcel_name)) }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.ic_label), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp) + ) - OutlinedTextField( - value = trackingId, - onValueChange = { - trackingId = it - idError = false - }, - singleLine = true, - label = { Text(stringResource(R.string.tracking_id)) }, - modifier = Modifier.fillMaxWidth(), - isError = idError, - trailingIcon = { - if (clipboardPasteEnabled) { - IconButton( - onClick = { - clipboardManager.getText()?.text?.let { clipboardText -> - trackingId = clipboardText - idError = false + OutlinedTextField( + value = trackingId, + onValueChange = { + trackingId = it + idError = false + }, + singleLine = true, + label = { Text(stringResource(R.string.tracking_id)) }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.package_2), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + modifier = Modifier.fillMaxWidth(), + isError = idError, + trailingIcon = { + if (clipboardPasteEnabled) { + IconButton( + onClick = { + clipboardManager.getText()?.text?.let { clipboardText -> + trackingId = clipboardText + idError = false + } + } + ) { + Icon( + painter = painterResource(id = R.drawable.ic_contentpaste), + contentDescription = stringResource(R.string.clipboard_paste), + modifier = Modifier.size(20.dp), + ) + } } - } - ) { - Icon( - painter = painterResource(id = R.drawable.ic_contentpaste), - contentDescription = stringResource(R.string.clipboard_paste), - modifier = Modifier.size(20.dp), + }, + supportingText = { if (idError) Text(stringResource(R.string.tracking_id_error_text)) }, + shape = RoundedCornerShape(12.dp) + ) + ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) { + OutlinedTextField( + value = + if (service == Service.UNDEFINED) "" + else stringResource(getDeliveryServiceName(service)!!), + onValueChange = {}, + modifier = + Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable).fillMaxWidth(), + readOnly = true, + label = { Text(stringResource(R.string.delivery_service)) }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.outline_local_shipping_24), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + isError = serviceError, + supportingText = { if (serviceError) Text(stringResource(R.string.service_error_text)) }, + shape = RoundedCornerShape(12.dp) ) - } - } - }, - supportingText = { if (idError) Text(stringResource(R.string.tracking_id_error_text)) }, - ) - // Service dropdown - ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) { - OutlinedTextField( - value = - if (service == Service.UNDEFINED) "" - else stringResource(getDeliveryServiceName(service)!!), - onValueChange = {}, - modifier = - Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable).fillMaxWidth(), - readOnly = true, - label = { Text(stringResource(R.string.delivery_service)) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - isError = serviceError, - supportingText = { if (serviceError) Text(stringResource(R.string.service_error_text)) }, - ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier + .background( + MaterialTheme.colorScheme.surface, + RoundedCornerShape(12.dp) + ) + .border( + 1.dp, + MaterialTheme.colorScheme.outline.copy(alpha = 0.12f), + RoundedCornerShape(12.dp) + ) + ) { + var currentCategory = "" + sortedServiceOptions.forEach { option -> + val category = + when (option) { + Service.CAINIAO, + Service.DHL, + Service.GLS, + Service.UPS, + Service.FPX -> stringResource(R.string.category_international) + Service.UNIUNI -> stringResource(R.string.category_north_america) + Service.BELPOST -> stringResource(R.string.category_europe_belarus) + Service.SAMEDAY_BG -> stringResource(R.string.category_europe_bulgaria) + Service.PACKETA -> stringResource(R.string.category_europe_czech) + Service.DPD_UK, + Service.EVRI -> stringResource(R.string.category_europe_uk) + Service.AN_POST -> stringResource(R.string.category_europe_ireland) + Service.ALLEGRO_ONEBOX, + Service.INPOST, + Service.ORLEN_PACZKA, + Service.POLISH_POST -> stringResource(R.string.category_europe_poland) + Service.GLS_HUNGARY -> stringResource(R.string.category_europe_hungary) + Service.MAGYAR_POSTA -> stringResource(R.string.category_europe_hungary) + Service.SAMEDAY_HU -> stringResource(R.string.category_europe_hungary) + Service.DPD_GER, + Service.HERMES -> stringResource(R.string.category_europe_germany) + Service.POSTE_ITALIANE -> stringResource(R.string.category_europe_italy) + Service.SAMEDAY_RO -> stringResource(R.string.category_europe_romania) + Service.POSTNORD -> stringResource(R.string.category_europe_scandinavia) + Service.NOVA_POSHTA, + Service.UKRPOSHTA -> stringResource(R.string.category_europe_ukraine) + Service.EKART -> stringResource(R.string.category_asia_india) + Service.SPX_TH -> stringResource(R.string.category_asia_thailand) + Service.IMILE -> stringResource(R.string.category_europe_hungary) + Service.EXPRESS_ONE -> stringResource(R.string.category_europe_hungary) + else -> stringResource(R.string.category_other) + } - ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { - var currentCategory = "" - sortedServiceOptions.forEach { option -> - val category = - when (option) { - Service.CAINIAO, - Service.DHL, - Service.GLS, - Service.UPS, - Service.FPX -> "Nemzetközi" - Service.UNIUNI -> "Észak-Amerika" - Service.BELPOST -> "Európa - Fehéroroszország" - Service.SAMEDAY_BG -> "Európa - Bulgária" - Service.PACKETA -> "Európa - Csehország" - Service.DPD_UK, - Service.EVRI -> "Európa - Egyesült Királyság" - Service.AN_POST -> "Európa - Írország" - Service.ALLEGRO_ONEBOX, - Service.INPOST, - Service.ORLEN_PACZKA, - Service.POLISH_POST -> "Európa - Lengyelország" - Service.GLS_HUNGARY -> "Európa - Magyarország" - Service.MAGYAR_POSTA -> "Európa - Magyarország" - Service.SAMEDAY_HU -> "Európa - Magyarország" - Service.DPD_GER, - Service.HERMES -> "Európa - Németország" - Service.POSTE_ITALIANE -> "Európa - Olaszország" - Service.SAMEDAY_RO -> "Európa - Románia" - Service.POSTNORD -> "Európa - Skandinávia" - Service.NOVA_POSHTA, - Service.UKRPOSHTA -> "Európa - Ukrajna" - Service.EKART -> "Ázsia - India" - Service.SPX_TH -> "Ázsia - Thaiföld" - Service.IMILE -> "Európa - Magyarország" - Service.EXPRESS_ONE -> "Európa - Magyarország" - else -> "Egyéb" - } + if (category != currentCategory) { + currentCategory = category + DropdownMenuItem( + text = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Icon( + painter = painterResource(R.drawable.outline_local_shipping_24), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = category, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.SemiBold + ) + } + }, + onClick = {}, + enabled = false, + modifier = Modifier.background( + MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.1f) + ) + ) + } - if (category != currentCategory) { - currentCategory = category - DropdownMenuItem( - text = { - Text( - text = category, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.primary, + DropdownMenuItem( + text = { + Text( + text = " " + stringResource(getDeliveryServiceName(option)!!), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(start = 8.dp) + ) + }, + onClick = { + service = option + expanded = false + serviceError = false + }, + modifier = Modifier + .background( + if (service == option) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.2f) + else Color.Transparent + ) ) - }, - onClick = {}, - enabled = false, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - ) + } + } } + + AnimatedVisibility( + backend?.acceptsPostCode == true || backend?.requiresPostCode == true + ) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + AnimatedVisibility(backend?.acceptsPostCode == true && !backend.requiresPostCode) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth(), + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.specify_a_postal_code)) + Text( + stringResource(R.string.specify_postal_code_flavor_text), + fontSize = 14.sp, + lineHeight = 21.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Checkbox(checked = specifyPostalCode, onCheckedChange = { specifyPostalCode = it }) + } + } - DropdownMenuItem( - text = { Text(" " + stringResource(getDeliveryServiceName(option)!!)) }, - onClick = { - service = option - expanded = false - serviceError = false - }, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - ) - } - } - } - - AnimatedVisibility(backend?.acceptsPostCode == true && !backend.requiresPostCode) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth(), - ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.specify_a_postal_code)) - Text( - stringResource(R.string.specify_postal_code_flavor_text), - fontSize = 14.sp, - lineHeight = 21.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + AnimatedVisibility( + backend?.requiresPostCode == true || + (backend?.requiresPostCode == false && backend.acceptsPostCode && specifyPostalCode) + ) { + OutlinedTextField( + value = postalCode, + onValueChange = { + postalCode = it + postalCodeError = false + }, + singleLine = true, + label = { Text(stringResource(R.string.postal_code)) }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.outline_pin_drop_24), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + modifier = Modifier.fillMaxWidth(), + isError = postalCodeError, + supportingText = { + if (postalCodeError) Text(stringResource(R.string.postal_code_error_text)) + }, + shape = RoundedCornerShape(12.dp) + ) + } + } + } } - Checkbox(checked = specifyPostalCode, onCheckedChange = { specifyPostalCode = it }) } - } - - AnimatedVisibility( - backend?.requiresPostCode == true || - (backend?.requiresPostCode == false && backend.acceptsPostCode && specifyPostalCode) - ) { - OutlinedTextField( - value = postalCode, - onValueChange = { - postalCode = it - postalCodeError = false - }, - singleLine = true, - label = { Text(stringResource(R.string.postal_code)) }, - modifier = Modifier.fillMaxWidth(), - isError = postalCodeError, - supportingText = { - if (postalCodeError) Text(stringResource(R.string.postal_code_error_text)) - }, - ) - } - - Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) { Button( onClick = { val isOk = validateInputs() if (isOk) { - // data valid, pass it along onCompleted( Parcel( id = parcel?.id ?: 0, @@ -456,15 +578,16 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( ) ) } - } + }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp) ) { Text(stringResource(if (isEdit) R.string.save else R.string.add_parcel)) } - } - } - } - } -} + + Spacer(modifier = Modifier.height(16.dp)) + } + } @Composable @PreviewLightDark diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt index 58cf3e2..6b18c64 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt @@ -1,32 +1,34 @@ // SPDX-License-Identifier: GPL-3.0-or-later package dev.itsvic.parceltracker.ui.views +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.ui.res.painterResource import androidx.compose.material3.Button import androidx.compose.material3.Card -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.CardDefaults import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -38,12 +40,11 @@ import dev.itsvic.parceltracker.api.ParcelHistoryItem import dev.itsvic.parceltracker.api.Service import dev.itsvic.parceltracker.api.Status import dev.itsvic.parceltracker.api.getDeliveryServiceName -import dev.itsvic.parceltracker.ui.components.ParcelActionBar +import dev.itsvic.parceltracker.ui.components.FloatingCollapsibleActionBar import dev.itsvic.parceltracker.ui.components.ParcelHistoryItemRow import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import java.time.LocalDateTime -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ParcelView( parcel: Parcel, @@ -58,78 +59,213 @@ fun ParcelView( onArchivePromptDismissal: () -> Unit, showBackButton: Boolean = true, ) { - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - - Scaffold( - topBar = { - MediumTopAppBar( - title = { Text(humanName) }, - navigationIcon = { - if (showBackButton) { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) - } - } - }, - scrollBehavior = scrollBehavior, - ) - }, - bottomBar = { - ParcelActionBar( - status = parcel.currentStatus, - onEdit = onEdit, - onArchive = onArchive, - onDelete = onDelete, - ) - }, - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - ) { innerPadding -> + Box { + Scaffold( + bottomBar = {}, + ) { innerPadding -> LazyColumn( - modifier = Modifier.padding(innerPadding).padding(16.dp, 0.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(innerPadding).padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { item { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - getDeliveryServiceName(service)?.let { - Text( - stringResource(it), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - SelectionContainer { - Text( - parcel.id, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + shape = RoundedCornerShape(16.dp) + ) { + Column(modifier = Modifier.padding(20.dp)) { + Row( + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 16.dp) + ) { + Box( + modifier = Modifier + .size(40.dp) + .background( + MaterialTheme.colorScheme.primaryContainer, + CircleShape + ), + contentAlignment = androidx.compose.ui.Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.outline_local_shipping_24), + contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.size(20.dp) + ) + } + Spacer(modifier = Modifier.size(12.dp)) + Text( + text = stringResource(R.string.parcel_details_title, humanName), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + getDeliveryServiceName(service)?.let { + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.delivery_service), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = stringResource(it), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + } + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.tracking_number), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + SelectionContainer { + Text( + text = parcel.id, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + } } } } - - items(parcel.properties.entries.toList()) { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Text( - stringResource(it.key), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Text( - it.value, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.End, - ) + if (parcel.properties.isNotEmpty()) { + item { + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + shape = RoundedCornerShape(16.dp) + ) { + Column(modifier = Modifier.padding(20.dp)) { + Row( + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 16.dp) + ) { + Box( + modifier = Modifier + .size(40.dp) + .background( + MaterialTheme.colorScheme.secondaryContainer, + CircleShape + ), + contentAlignment = androidx.compose.ui.Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.package_2), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier.size(20.dp) + ) + } + Spacer(modifier = Modifier.size(12.dp)) + Text( + text = stringResource(R.string.additional_info), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + + parcel.properties.entries.forEachIndexed { index, entry -> + if (index > 0) { + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + } + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically + ) { + Text( + text = stringResource(entry.key), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = entry.value, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + } + } } } - item { - Text( - LocalContext.current.getString(parcel.currentStatus.nameResource), - style = MaterialTheme.typography.headlineLarge, - modifier = Modifier.padding(vertical = 16.dp), - ) + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = when (parcel.currentStatus) { + Status.Delivered, Status.PickedUp -> MaterialTheme.colorScheme.primaryContainer + Status.InTransit, Status.OutForDelivery -> MaterialTheme.colorScheme.tertiaryContainer + else -> MaterialTheme.colorScheme.surfaceVariant + } + ) + ) { + Column(modifier = Modifier.padding(20.dp)) { + Row( + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 12.dp) + ) { + Box( + modifier = Modifier + .size(40.dp) + .background( + when (parcel.currentStatus) { + Status.Delivered, Status.PickedUp -> MaterialTheme.colorScheme.primary + Status.InTransit, Status.OutForDelivery -> MaterialTheme.colorScheme.tertiary + else -> MaterialTheme.colorScheme.outline + }, + CircleShape + ), + contentAlignment = androidx.compose.ui.Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.outline_deployed_code_history_24), + contentDescription = null, + tint = when (parcel.currentStatus) { + Status.Delivered, Status.PickedUp -> MaterialTheme.colorScheme.onPrimary + Status.InTransit, Status.OutForDelivery -> MaterialTheme.colorScheme.onTertiary + else -> MaterialTheme.colorScheme.surface + }, + modifier = Modifier.size(20.dp) + ) + } + Spacer(modifier = Modifier.size(12.dp)) + Text( + text = stringResource(R.string.current_status), + style = MaterialTheme.typography.titleMedium, + color = when (parcel.currentStatus) { + Status.Delivered, Status.PickedUp -> MaterialTheme.colorScheme.onPrimaryContainer + Status.InTransit, Status.OutForDelivery -> MaterialTheme.colorScheme.onTertiaryContainer + else -> MaterialTheme.colorScheme.onSurfaceVariant + } + ) + } + Text( + text = LocalContext.current.getString(parcel.currentStatus.nameResource), + style = MaterialTheme.typography.headlineMedium, + color = when (parcel.currentStatus) { + Status.Delivered, Status.PickedUp -> MaterialTheme.colorScheme.onPrimaryContainer + Status.InTransit, Status.OutForDelivery -> MaterialTheme.colorScheme.onTertiaryContainer + else -> MaterialTheme.colorScheme.onSurfaceVariant + } + ) + } + } } if ( @@ -159,18 +295,67 @@ fun ParcelView( } } } - - items(parcel.history.size) { index -> - if (index > 0) HorizontalDivider(Modifier.padding(top = 8.dp, bottom = 16.dp)) - ParcelHistoryItemRow(parcel.history[index]) + if (parcel.history.isNotEmpty()) { + item { + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + shape = RoundedCornerShape(16.dp) + ) { + Column(modifier = Modifier.padding(20.dp)) { + Row( + verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 16.dp) + ) { + Box( + modifier = Modifier + .size(40.dp) + .background( + MaterialTheme.colorScheme.tertiaryContainer, + CircleShape + ), + contentAlignment = androidx.compose.ui.Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.outline_deployed_code_history_24), + contentDescription = null, + tint = MaterialTheme.colorScheme.onTertiaryContainer, + modifier = Modifier.size(20.dp) + ) + } + Spacer(modifier = Modifier.size(12.dp)) + Text( + text = stringResource(R.string.tracking_history), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + + parcel.history.forEachIndexed { index, historyItem -> + if (index > 0) { + HorizontalDivider(modifier = Modifier.padding(vertical = 12.dp)) + } + ParcelHistoryItemRow(historyItem) + } + } + } + } } } + FloatingCollapsibleActionBar( + status = parcel.currentStatus, + onEdit = onEdit, + onArchive = onArchive, + onDelete = onDelete, + onBackPressed = onBackPressed, + modifier = Modifier.align(Alignment.BottomCenter) + ) } } @Composable @PreviewLightDark -private fun ParcelViewPreview() { +fun ParcelViewPreview() { val parcel = Parcel( "EXMPL0001", @@ -209,3 +394,4 @@ private fun ParcelViewPreview() { ) } } +} diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt index e07f2a1..fe5174c 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt @@ -5,13 +5,18 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox @@ -22,6 +27,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -29,6 +35,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -37,10 +44,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.fromHtml import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextDecoration @@ -80,12 +89,15 @@ fun SettingsView(onBackPressed: () -> Unit) { context.dataStore.data.map { it[CLIPBOARD_PASTE_ENABLED] == true }.collectAsState(false) val preferredRegion by context.dataStore.data.map { it[PREFERRED_REGION] ?: "" }.collectAsState("") + val dhlApiKey by context.dataStore.data.map { it[DHL_API_KEY] ?: "" }.collectAsState("") + val coroutineScope = rememberCoroutineScope() var regionDropdownExpanded by remember { mutableStateOf(false) } val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() var aboutDialogOpen by remember { mutableStateOf(false) } - val dhlApiKey by context.dataStore.data.map { it[DHL_API_KEY] ?: "" }.collectAsState("") + val testPackageName = stringResource(R.string.settings_test_package_name) + val testPackageStatus = stringResource(R.string.settings_test_package_status) fun setValue(key: Preferences.Key, value: T) { coroutineScope.launch { context.dataStore.edit { it[key] = value } } @@ -102,71 +114,152 @@ fun SettingsView(onBackPressed: () -> Unit) { topBar = { LargeTopAppBar( title = { Text(stringResource(R.string.settings)) }, - navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.go_back)) - } - }, scrollBehavior = scrollBehavior, ) }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { innerPadding -> - Column(Modifier.padding(innerPadding).verticalScroll(rememberScrollState())) { - Row( - modifier = - Modifier.clickable { setUnmeteredOnly(unmeteredOnly.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + Column( + modifier = Modifier + .padding(innerPadding) + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) + ) { + Spacer(modifier = Modifier.height(8.dp)) + + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.unmetered_only_setting)) + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold + ) Text( - stringResource(R.string.unmetered_only_setting_detail), + text = stringResource(R.string.settings_version_label, BuildConfig.VERSION_NAME), style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) + Spacer(modifier = Modifier.height(12.dp)) + FilledTonalButton( + onClick = { aboutDialogOpen = true }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.about_app)) + } } - Switch(checked = unmeteredOnly, onCheckedChange = { setUnmeteredOnly(it) }) } + + Spacer(modifier = Modifier.height(16.dp)) - Row( - modifier = - Modifier.clickable { setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.clipboard_paste_enabled)) - Text( - stringResource(R.string.clipboard_paste_description), - style = MaterialTheme.typography.bodyMedium, - ) + Column(modifier = Modifier.padding(16.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 12.dp) + ) { + Icon( + painterResource(R.drawable.ic_networkwifi), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + Text( + text = stringResource(R.string.settings_network), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(start = 8.dp) + ) + } + + Row( + modifier = Modifier + .clickable { setUnmeteredOnly(unmeteredOnly.not()) } + .padding(vertical = 8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.unmetered_only_setting)) + Text( + stringResource(R.string.unmetered_only_setting_detail), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Switch(checked = unmeteredOnly, onCheckedChange = { setUnmeteredOnly(it) }) + } + + Row( + modifier = Modifier + .clickable { setValue(CLIPBOARD_PASTE_ENABLED, clipboardPasteEnabled.not()) } + .padding(vertical = 8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.clipboard_paste_enabled)) + Text( + stringResource(R.string.clipboard_paste_description), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Switch( + checked = clipboardPasteEnabled, + onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) }, + ) + } } - Switch( - checked = clipboardPasteEnabled, - onCheckedChange = { setValue(CLIPBOARD_PASTE_ENABLED, it) }, - ) } + + Spacer(modifier = Modifier.height(16.dp)) - Column(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) { - Text(stringResource(R.string.preferred_region)) - Text( - stringResource(R.string.preferred_region_description), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 8.dp), - ) + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 12.dp) + ) { + Icon( + painterResource(R.drawable.ic_language), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + Text( + text = stringResource(R.string.settings_region), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(start = 8.dp) + ) + } + + Text( + stringResource(R.string.preferred_region_description), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(bottom = 8.dp), + ) - ExposedDropdownMenuBox( - expanded = regionDropdownExpanded, - onExpandedChange = { regionDropdownExpanded = !regionDropdownExpanded }, - ) { - OutlinedTextField( - value = - when (preferredRegion) { + ExposedDropdownMenuBox( + expanded = regionDropdownExpanded, + onExpandedChange = { regionDropdownExpanded = !regionDropdownExpanded }, + ) { + OutlinedTextField( + value = when (preferredRegion) { "international" -> stringResource(R.string.region_international) "north_america" -> stringResource(R.string.region_north_america) "europe" -> stringResource(R.string.region_europe) @@ -187,238 +280,161 @@ fun SettingsView(onBackPressed: () -> Unit) { "thailand" -> stringResource(R.string.country_thailand) else -> stringResource(R.string.region_international) }, - onValueChange = {}, - readOnly = true, - trailingIcon = { - ExposedDropdownMenuDefaults.TrailingIcon(expanded = regionDropdownExpanded) - }, - modifier = Modifier.menuAnchor().fillMaxWidth(), - ) - - ExposedDropdownMenu( - expanded = regionDropdownExpanded, - onDismissRequest = { regionDropdownExpanded = false }, - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.region_international)) }, - onClick = { - setValue(PREFERRED_REGION, "international") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_north_america)) }, - onClick = { - setValue(PREFERRED_REGION, "north_america") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_europe)) }, - onClick = { - setValue(PREFERRED_REGION, "europe") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.region_asia)) }, - onClick = { - setValue(PREFERRED_REGION, "asia") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_belarus)) }, - onClick = { - setValue(PREFERRED_REGION, "belarus") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_bulgaria)) }, - onClick = { - setValue(PREFERRED_REGION, "bulgaria") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_czech)) }, - onClick = { - setValue(PREFERRED_REGION, "czech") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_uk)) }, - onClick = { - setValue(PREFERRED_REGION, "uk") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_ireland)) }, - onClick = { - setValue(PREFERRED_REGION, "ireland") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_poland)) }, - onClick = { - setValue(PREFERRED_REGION, "poland") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_hungary)) }, - onClick = { - setValue(PREFERRED_REGION, "hungary") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_germany)) }, - onClick = { - setValue(PREFERRED_REGION, "germany") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_italy)) }, - onClick = { - setValue(PREFERRED_REGION, "italy") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_romania)) }, - onClick = { - setValue(PREFERRED_REGION, "romania") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_scandinavia)) }, - onClick = { - setValue(PREFERRED_REGION, "scandinavia") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_ukraine)) }, - onClick = { - setValue(PREFERRED_REGION, "ukraine") - regionDropdownExpanded = false + onValueChange = {}, + readOnly = true, + label = { Text(stringResource(R.string.settings_region)) }, + leadingIcon = { + Icon( + painterResource(R.drawable.ic_language), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_india)) }, - onClick = { - setValue(PREFERRED_REGION, "india") - regionDropdownExpanded = false - }, - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.country_thailand)) }, - onClick = { - setValue(PREFERRED_REGION, "thailand") - regionDropdownExpanded = false + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(expanded = regionDropdownExpanded) }, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = MaterialTheme.colorScheme.primary, + unfocusedBorderColor = MaterialTheme.colorScheme.outline, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.onSurfaceVariant + ), + shape = RoundedCornerShape(12.dp), + modifier = Modifier.menuAnchor().fillMaxWidth(), ) + + ExposedDropdownMenu( + expanded = regionDropdownExpanded, + onDismissRequest = { regionDropdownExpanded = false }, + ) { + listOf( + "international" to R.string.region_international, + "north_america" to R.string.region_north_america, + "europe" to R.string.region_europe, + "asia" to R.string.region_asia, + "belarus" to R.string.country_belarus, + "bulgaria" to R.string.country_bulgaria, + "czech" to R.string.country_czech, + "uk" to R.string.country_uk, + "ireland" to R.string.country_ireland, + "poland" to R.string.country_poland, + "hungary" to R.string.country_hungary, + "germany" to R.string.country_germany, + "italy" to R.string.country_italy, + "romania" to R.string.country_romania, + "scandinavia" to R.string.country_scandinavia, + "ukraine" to R.string.country_ukraine, + "india" to R.string.country_india, + "thailand" to R.string.country_thailand + ).forEach { (key, stringRes) -> + DropdownMenuItem( + text = { Text(stringResource(stringRes)) }, + onClick = { + setValue(PREFERRED_REGION, key) + regionDropdownExpanded = false + }, + ) + } + } } } } + + Spacer(modifier = Modifier.height(16.dp)) - Text( - stringResource(R.string.settings_api_keys), - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - - OutlinedTextField( - dhlApiKey, - { setValue(DHL_API_KEY, it) }, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).fillMaxWidth(), - label = { Text(stringResource(R.string.service_dhl)) }, - singleLine = true, - visualTransformation = PasswordVisualTransformation(), - ) - - Text( - AnnotatedString.fromHtml( - stringResource(R.string.dhl_api_key_flavor_text), - linkStyles = - TextLinkStyles( - style = - SpanStyle( + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 12.dp) + ) { + Icon( + painterResource(R.drawable.ic_vpnkey), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + Text( + text = stringResource(R.string.settings_api_keys), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(start = 8.dp) + ) + } + + OutlinedTextField( + value = dhlApiKey, + onValueChange = { setValue(DHL_API_KEY, it) }, + label = { Text(stringResource(R.string.settings_dhl_api_key_label)) }, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp) + ) + + Text( + AnnotatedString.fromHtml( + stringResource(R.string.dhl_api_key_flavor_text), + linkStyles = TextLinkStyles( + style = SpanStyle( textDecoration = TextDecoration.Underline, color = MaterialTheme.colorScheme.primary, ) + ), ), - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), - ) - - Text( - stringResource(R.string.settings_experimental), - modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 2.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - - Row( - modifier = - Modifier.clickable { setValue(DEMO_MODE, demoMode.not()) } - .padding(16.dp, 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column(modifier = Modifier.fillMaxWidth(0.8f)) { - Text(stringResource(R.string.demo_mode)) - Text( - stringResource(R.string.demo_mode_detail), - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 8.dp), ) } - Switch(checked = demoMode, onCheckedChange = { setValue(DEMO_MODE, it) }) } + + Spacer(modifier = Modifier.height(16.dp)) - if (BuildConfig.DEBUG) - FilledTonalButton( - onClick = { - context.sendNotification( - Parcel(0xf100f, "Cool stuff", "", null, Service.EXAMPLE), - Status.OutForDelivery, - ParcelHistoryItem("The courier has picked up the package", LocalDateTime.now(), ""), + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 12.dp) + ) { + Icon( + painterResource(R.drawable.ic_science), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary ) - }, - modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth(), - ) { - Text("Send test notification") + Text( + text = stringResource(R.string.settings_experimental), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(start = 8.dp) + ) + } + + Row( + modifier = Modifier + .clickable { setValue(DEMO_MODE, demoMode.not()) } + .padding(vertical = 8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.fillMaxWidth(0.8f)) { + Text(stringResource(R.string.demo_mode)) + Text( + stringResource(R.string.demo_mode_detail), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Switch(checked = demoMode, onCheckedChange = { setValue(DEMO_MODE, it) }) + } } - - LogcatButton(modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth()) - - FilledTonalButton( - onClick = { aboutDialogOpen = true }, - modifier = Modifier.padding(16.dp, 12.dp).fillMaxWidth(), - ) { - Icon(Icons.Filled.Info, contentDescription = stringResource(R.string.about_app)) - Text( - text = " ${stringResource(R.string.about_app)}", - modifier = Modifier.padding(start = 8.dp), - ) } - - Text( - "Parcel ${BuildConfig.VERSION_NAME}", - modifier = Modifier.padding(16.dp, 8.dp), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + Spacer(modifier = Modifier.height(24.dp)) } if (aboutDialogOpen) { From f2be7483aab9d1b847cf5ae965c6fe88475252a9 Mon Sep 17 00:00:00 2001 From: Zan1456 <62830223+Zan1456@users.noreply.github.com> Date: Tue, 22 Jul 2025 09:49:27 +0200 Subject: [PATCH 18/18] Redesign --- .../dev/itsvic/parceltracker/MainActivity.kt | 35 +++-- .../ui/components/AboutDialog.kt | 43 ++++-- .../FloatingCollapsibleActionBar.kt | 28 +--- .../parceltracker/ui/components/ParcelCard.kt | 140 ++++++++++++++++++ .../ui/views/AddEditParcelView.kt | 28 ++-- .../itsvic/parceltracker/ui/views/HomeView.kt | 6 +- .../parceltracker/ui/views/ParcelView.kt | 107 ++++--------- .../parceltracker/ui/views/SettingsView.kt | 63 ++++---- app/src/main/res/values-hu/strings.xml | 1 + 9 files changed, 282 insertions(+), 169 deletions(-) create mode 100644 app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelCard.kt diff --git a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt index 438aea3..5f0efee 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt @@ -63,7 +63,6 @@ import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import dev.itsvic.parceltracker.ui.components.BottomNavBar import dev.itsvic.parceltracker.ui.components.EditParcelDialog import dev.itsvic.parceltracker.ui.views.AddEditParcelView -import dev.itsvic.parceltracker.ui.views.AdaptiveParcelApp import dev.itsvic.parceltracker.ui.views.HomeView import dev.itsvic.parceltracker.ui.views.ParcelView import dev.itsvic.parceltracker.ui.views.SettingsView @@ -159,7 +158,7 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat } } - val animDuration = 300 + val animDuration = 400 val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route ?: "HomePage" @@ -286,7 +285,7 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat } }, settingsContent = { - SettingsView(onBackPressed = { currentTabletNavItem = TabletNavigationItem.HOME }) + SettingsView() }, addParcelContent = { AddEditParcelView( @@ -354,16 +353,30 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat enterTransition = { slideIntoContainer( towards = AnimatedContentTransitionScope.SlideDirection.Start, - animationSpec = tween(animDuration), - initialOffset = { it / 4 }) + fadeIn(tween(animDuration)) + animationSpec = tween(animDuration, easing = androidx.compose.animation.core.FastOutSlowInEasing), + initialOffset = { it / 3 }) + fadeIn(tween(animDuration / 2, delayMillis = animDuration / 4)) }, - exitTransition = { fadeOut(tween(animDuration)) + scaleOut(tween(500), 0.9f) }, - popEnterTransition = { fadeIn(tween(animDuration)) + scaleIn(tween(500), 0.9f) }, - popExitTransition = { + exitTransition = { + fadeOut(tween(animDuration / 2)) + + scaleOut(tween(animDuration, easing = androidx.compose.animation.core.FastOutSlowInEasing), 0.95f) + slideOutOfContainer( towards = AnimatedContentTransitionScope.SlideDirection.Start, - animationSpec = tween(animDuration), - targetOffset = { -it / 4 }) + fadeOut(tween(animDuration)) + animationSpec = tween(animDuration, easing = androidx.compose.animation.core.FastOutSlowInEasing), + targetOffset = { -it / 6 }) + }, + popEnterTransition = { + fadeIn(tween(animDuration / 2, delayMillis = animDuration / 4)) + + scaleIn(tween(animDuration, easing = androidx.compose.animation.core.FastOutSlowInEasing), 0.95f) + + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.End, + animationSpec = tween(animDuration, easing = androidx.compose.animation.core.FastOutSlowInEasing), + initialOffset = { -it / 6 }) + }, + popExitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.End, + animationSpec = tween(animDuration, easing = androidx.compose.animation.core.FastOutSlowInEasing), + targetOffset = { it / 3 }) + fadeOut(tween(animDuration / 2)) }, modifier = Modifier.padding(innerPadding) ) { @@ -376,7 +389,7 @@ fun ParcelAppNavigation(parcelToOpen: Int, windowSizeClass: androidx.compose.mat ) } - composable { SettingsView(onBackPressed = { navController.popBackStack() }) } + composable { SettingsView() } composable { backStackEntry -> val route: ParcelPage = backStackEntry.toRoute() diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/AboutDialog.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/AboutDialog.kt index deb0d9b..5e65b7f 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/AboutDialog.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/AboutDialog.kt @@ -4,11 +4,14 @@ package dev.itsvic.parceltracker.ui.components import android.content.Context import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button @@ -16,6 +19,9 @@ import androidx.compose.material3.Card import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -37,19 +43,32 @@ fun AboutDialog(onDismissRequest: () -> Unit) { Dialog(onDismissRequest = onDismissRequest) { Card(modifier = Modifier.fillMaxWidth().padding(16.dp), shape = RoundedCornerShape(16.dp)) { Column(modifier = Modifier.padding(24.dp)) { - Text( - text = "Parcel", - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - ) - Text( - text = BuildConfig.VERSION_NAME, - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, + Row( modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - ) + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.icon_foreground), + contentDescription = null, + modifier = Modifier.size(80.dp), + tint = Color.Unspecified + ) + Spacer(Modifier.width(16.dp)) + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Parcel", + style = MaterialTheme.typography.titleLarge, + ) + Text( + text = BuildConfig.VERSION_NAME, + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } Spacer(Modifier.height(24.dp)) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/FloatingCollapsibleActionBar.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/FloatingCollapsibleActionBar.kt index e0a289f..41eccd4 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/components/FloatingCollapsibleActionBar.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/FloatingCollapsibleActionBar.kt @@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.KeyboardArrowDown @@ -58,7 +57,6 @@ fun FloatingCollapsibleActionBar( onEdit: () -> Unit, onArchive: () -> Unit, onDelete: () -> Unit, - onBackPressed: (() -> Unit)? = null, modifier: Modifier = Modifier, ) { var isExpanded by remember { mutableStateOf(false) } @@ -77,19 +75,16 @@ fun FloatingCollapsibleActionBar( contentAlignment = Alignment.BottomCenter ) { Card( - modifier = Modifier - .fillMaxWidth() - .shadow( - elevation = 8.dp, - shape = RoundedCornerShape(16.dp) - ), + modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 8.dp), colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) + containerColor = MaterialTheme.colorScheme.surface + ) ) { - Column { + Column( + modifier = Modifier.fillMaxWidth() + ) { Row( modifier = Modifier .fillMaxWidth() @@ -122,14 +117,7 @@ fun FloatingCollapsibleActionBar( Column( modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) { - if (onBackPressed != null) { - ActionButton( - icon = { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.go_back)) }, - text = stringResource(R.string.go_back), - onClick = onBackPressed - ) - Spacer(modifier = Modifier.height(8.dp)) - } + ActionButton( icon = { Icon(Icons.Filled.Edit, contentDescription = stringResource(R.string.edit)) }, text = stringResource(R.string.edit), diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelCard.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelCard.kt new file mode 100644 index 0000000..5278e91 --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/components/ParcelCard.kt @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +package dev.itsvic.parceltracker.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.itsvic.parceltracker.R +import dev.itsvic.parceltracker.api.Service +import dev.itsvic.parceltracker.api.Status +import dev.itsvic.parceltracker.api.getDeliveryServiceName +import dev.itsvic.parceltracker.db.Parcel +import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme + +@Composable +fun ParcelCard(parcel: Parcel, status: Status?, onClick: () -> Unit) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + .clickable(onClick = onClick), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (status != null) { + Box( + modifier = Modifier + .size(48.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource( + when (status) { + Status.Preadvice -> R.drawable.outline_other_admission_24 + Status.LockerboxAcceptedParcel -> R.drawable.outline_deployed_code_update_24 + Status.PickedUpByCourier -> R.drawable.outline_deployed_code_account_24 + Status.InTransit -> R.drawable.outline_local_shipping_24 + Status.InWarehouse -> R.drawable.outline_warehouse_24 + Status.Customs -> R.drawable.outline_search_24 + Status.OutForDelivery -> R.drawable.outline_delivery_truck_speed_24 + Status.DeliveryFailure -> R.drawable.outline_error_24 + Status.PickupTimeEndingSoon -> R.drawable.outline_notifications_active_24 + Status.AwaitingPickup -> R.drawable.outline_pin_drop_24 + Status.Delivered, + Status.PickedUp -> R.drawable.outline_check_24 + Status.DeliveredToNeighbor -> R.drawable.outline_holiday_village_24 + Status.DeliveredToASafePlace -> R.drawable.outline_roofing_24 + Status.DroppedAtCustomerService -> R.drawable.outline_support_agent_24 + Status.ReturningToSender -> R.drawable.outline_arrow_top_left_24 + Status.ReturnedToSender -> R.drawable.outline_arrow_top_left_24 + Status.Delayed -> R.drawable.outline_deployed_code_history_24 + Status.Damaged -> R.drawable.outline_deployed_code_alert_24 + Status.Destroyed -> R.drawable.outline_destruction_24 + else -> R.drawable.outline_question_mark_24 + } + ), + contentDescription = stringResource(status.nameResource), + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(24.dp) + ) + } + } + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = parcel.humanName, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface + ) + + Text( + text = "${stringResource(getDeliveryServiceName(parcel.service)!!)}: ${parcel.parcelId}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + if (status != null) { + Text( + text = stringResource(status.nameResource), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Medium + ) + } + } + } + } +} + +@Composable +@PreviewLightDark +fun ParcelCardPreview() { + ParcelTrackerTheme { + Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.background)) { + ParcelCard( + Parcel(0, "My precious package", "EXMPL0001", null, Service.EXAMPLE), + status = Status.InTransit, + onClick = {}, + ) + } + } +} diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt index 5fd6ef6..a6ba415 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/AddEditParcelView.kt @@ -36,7 +36,7 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource @@ -76,7 +76,7 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( Scaffold( topBar = { - TopAppBar( + LargeTopAppBar( title = { Text(stringResource(if (isEdit) R.string.edit_parcel else R.string.add_a_parcel)) }, @@ -85,24 +85,18 @@ fun AddEditParcelView(parcel: Parcel?, onBackPressed: () -> Unit, onCompleted: ( }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), ) { innerPadding -> - Box( + Column( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)) + .padding(innerPadding) + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), ) { - Column( - modifier = Modifier - .padding(innerPadding) - .fillMaxWidth() - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - AddEditParcelContent( - parcel = parcel, - onCompleted = onCompleted, - isDialog = false - ) - } + AddEditParcelContent( + parcel = parcel, + onCompleted = onCompleted, + isDialog = false + ) } } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt index 92a5a6b..5cbeff5 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/HomeView.kt @@ -21,7 +21,7 @@ import dev.itsvic.parceltracker.api.Status import dev.itsvic.parceltracker.db.Parcel import dev.itsvic.parceltracker.db.ParcelStatus import dev.itsvic.parceltracker.db.ParcelWithStatus -import dev.itsvic.parceltracker.ui.components.ParcelRow +import dev.itsvic.parceltracker.ui.components.ParcelCard import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import java.time.Instant @@ -49,12 +49,12 @@ fun HomeView( item { Text( stringResource(R.string.no_parcels_flavor), - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 24.dp), ) } items(parcels.reversed()) { parcel -> - ParcelRow(parcel.parcel, parcel.status?.status) { onNavigateToParcel(parcel.parcel) } + ParcelCard(parcel.parcel, parcel.status?.status) { onNavigateToParcel(parcel.parcel) } } } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt index 6b18c64..2209e6b 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt @@ -17,15 +17,20 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.ui.res.painterResource +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -45,6 +50,7 @@ import dev.itsvic.parceltracker.ui.components.ParcelHistoryItemRow import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import java.time.LocalDateTime +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ParcelView( parcel: Parcel, @@ -61,86 +67,36 @@ fun ParcelView( ) { Box { Scaffold( - bottomBar = {}, - ) { innerPadding -> - LazyColumn( - modifier = Modifier.padding(innerPadding).padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - item { - Card( - modifier = Modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), - shape = RoundedCornerShape(16.dp) - ) { - Column(modifier = Modifier.padding(20.dp)) { - Row( - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, - modifier = Modifier.padding(bottom = 16.dp) - ) { - Box( - modifier = Modifier - .size(40.dp) - .background( - MaterialTheme.colorScheme.primaryContainer, - CircleShape - ), - contentAlignment = androidx.compose.ui.Alignment.Center - ) { + topBar = { + TopAppBar( + title = { + Text( + text = humanName, + style = MaterialTheme.typography.titleLarge + ) + }, + navigationIcon = { + if (showBackButton) { + IconButton(onClick = onBackPressed) { Icon( - painter = painterResource(R.drawable.outline_local_shipping_24), - contentDescription = null, - tint = MaterialTheme.colorScheme.onPrimaryContainer, - modifier = Modifier.size(20.dp) - ) - } - Spacer(modifier = Modifier.size(12.dp)) - Text( - text = stringResource(R.string.parcel_details_title, humanName), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface - ) - } - getDeliveryServiceName(service)?.let { - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.delivery_service), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Text( - text = stringResource(it), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface - ) - } - HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - } - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.tracking_number), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - SelectionContainer { - Text( - text = parcel.id, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.go_back) ) } } } - } - } + ) + }, + bottomBar = {}, + ) { innerPadding -> + LazyColumn( + modifier = Modifier + .padding(innerPadding) + .padding(16.dp) + .padding(bottom = 45.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + if (parcel.properties.isNotEmpty()) { item { Card( @@ -347,7 +303,6 @@ fun ParcelView( onEdit = onEdit, onArchive = onArchive, onDelete = onDelete, - onBackPressed = onBackPressed, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt index fe5174c..347f90f 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/SettingsView.kt @@ -10,20 +10,17 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField @@ -35,13 +32,13 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -64,23 +61,16 @@ import dev.itsvic.parceltracker.DHL_API_KEY import dev.itsvic.parceltracker.PREFERRED_REGION import dev.itsvic.parceltracker.R import dev.itsvic.parceltracker.UNMETERED_ONLY -import dev.itsvic.parceltracker.api.ParcelHistoryItem -import dev.itsvic.parceltracker.api.Service -import dev.itsvic.parceltracker.api.Status import dev.itsvic.parceltracker.dataStore -import dev.itsvic.parceltracker.db.Parcel import dev.itsvic.parceltracker.enqueueNotificationWorker -import dev.itsvic.parceltracker.sendNotification import dev.itsvic.parceltracker.ui.components.AboutDialog -import dev.itsvic.parceltracker.ui.components.LogcatButton import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme -import java.time.LocalDateTime import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingsView(onBackPressed: () -> Unit) { +fun SettingsView() { val context = LocalContext.current val demoMode by context.dataStore.data.map { it[DEMO_MODE] == true }.collectAsState(false) val unmeteredOnly by @@ -128,7 +118,9 @@ fun SettingsView(onBackPressed: () -> Unit) { Spacer(modifier = Modifier.height(8.dp)) Card( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .clickable { aboutDialogOpen = true }, elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { Column( @@ -136,23 +128,34 @@ fun SettingsView(onBackPressed: () -> Unit) { horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.app_name), - style = MaterialTheme.typography.headlineSmall, - fontWeight = FontWeight.Bold - ) - Text( - text = stringResource(R.string.settings_version_label, BuildConfig.VERSION_NAME), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.height(12.dp)) - FilledTonalButton( - onClick = { aboutDialogOpen = true }, - modifier = Modifier.fillMaxWidth() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically ) { - Text(stringResource(R.string.about_app)) + Icon( + painter = painterResource(R.drawable.icon_foreground), + contentDescription = null, + modifier = Modifier.size(100.dp), + tint = Color.Unspecified + ) + Spacer(modifier = Modifier.width(16.dp)) + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold + ) + Text( + text = stringResource(R.string.settings_version_label, BuildConfig.VERSION_NAME), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } + Spacer(modifier = Modifier.height(8.dp)) } } @@ -446,5 +449,5 @@ fun SettingsView(onBackPressed: () -> Unit) { @Composable @PreviewLightDark private fun SettingsViewPreview() { - ParcelTrackerTheme { SettingsView(onBackPressed = {}) } + ParcelTrackerTheme { SettingsView() } } diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 6f22fa1..de12071 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -125,6 +125,7 @@ Verzió %s Teszt értesítés küldése DHL API kulcs + A DHL megköveteli, hogy a felhasználóktól API-kulcsot kérjünk. Ezt ingyenesen megszerezheted a <a href=\"https://developer.dhl.com\">DHL\'s API Developer Portal</a> és regisztrálhatsz a \"Shipment Tracking - Unified\" API-ra. Teszt csomag A futár felvette a csomagot Műveletek