Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions app/src/main/java/com/indiana/zwl/presentation/AboutScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.indiana.zwl.BuildConfig
import com.indiana.zwl.R
import com.indiana.zwl.domain.model.WaterDataState
import com.indiana.zwl.presentation.theme.DarkForestBackground
import com.indiana.zwl.presentation.theme.ForestGreenAccent
import com.indiana.zwl.presentation.theme.ForestGreenText
import com.indiana.zwl.shared.data.water.WaterAttribution

private const val BDL_PORTAL_URL = "https://www.bdl.lasy.gov.pl/portal/"
private const val IBL_FIRE_URL = "https://bazapozarow.ibles.pl/"
Expand All @@ -67,6 +69,7 @@ private const val INSTAGRAM_URL = "https://instagram.com/legalny.bushcraft"
@Composable
fun AboutScreen(
onClose: () -> Unit,
waterDataState: WaterDataState? = null,
modifier: Modifier = Modifier
) {
val isLandscape =
Expand Down Expand Up @@ -205,6 +208,50 @@ fun AboutScreen(
}
}

Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
shape = RoundedCornerShape(12.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.Public,
contentDescription = null,
tint = ForestGreenAccent,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Dane źródeł wody (OSM)",
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
}

Text(
text = "Punkty wody pitnej, źródeł, studni i napełniania butelek pochodzą z OpenStreetMap i odświeżają się automatycznie.",
fontSize = 13.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
lineHeight = 19.sp
)

DataRow(label = "Wersja danych", value = waterVersionLabel(waterDataState?.dataVersion))
DataRow(label = "Data pobrania", value = waterFetchedAtLabel(waterDataState?.lastCheckedAt))
DataRow(label = "Liczba punktów", value = waterDataState?.count?.toString() ?: "—")

Text(
text = WaterAttribution.OSM,
fontSize = 11.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}

Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
Expand Down Expand Up @@ -377,3 +424,38 @@ private fun SourceLinkRow(label: String, url: String) {
)
}
}

@Composable
private fun DataRow(label: String, value: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
fontSize = 13.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = value,
fontSize = 13.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface
)
}
}

private fun waterVersionLabel(version: Long?): String {
if (version == null || version <= 0L) return "—"
val year = version / 10000
val month = (version / 100) % 100
val day = version % 100
return String.format(java.util.Locale.US, "%04d-%02d-%02d", year, month, day)
}

private fun waterFetchedAtLabel(millis: Long?): String {
if (millis == null || millis <= 0L) return "zestaw wbudowany"
return java.text.SimpleDateFormat("yyyy-MM-dd HH:mm", java.util.Locale.getDefault())
.format(java.util.Date(millis))
}
6 changes: 5 additions & 1 deletion app/src/main/java/com/indiana/zwl/presentation/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ fun MainScreen(
val savedPoints by viewModel.savedPoints.collectAsStateWithLifecycle()
val showSavedPointList by viewModel.showSavedPointList.collectAsStateWithLifecycle()
val selectedSavedPointInfo by viewModel.selectedSavedPointInfo.collectAsStateWithLifecycle()
val waterDataState by viewModel.waterDataState.collectAsStateWithLifecycle()
val savedPointDetailViewModel: SavedPointDetailViewModel = hiltViewModel()
val selectedSavedPointDetails by savedPointDetailViewModel.details.collectAsStateWithLifecycle()
var selectedTab by rememberSaveable { mutableStateOf(0) }
Expand Down Expand Up @@ -459,7 +460,10 @@ fun MainScreen(
)

if (showAbout) {
AboutScreen(onClose = { showAbout = false })
AboutScreen(
waterDataState = waterDataState,
onClose = { showAbout = false }
)
}

if (showSavedPointList) {
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/com/indiana/zwl/presentation/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.indiana.zwl.domain.model.Zone
import com.indiana.zwl.domain.model.Poi
import com.indiana.zwl.domain.model.SavedPoint
import com.indiana.zwl.domain.model.NewSavedPoint
import com.indiana.zwl.domain.model.WaterDataState
import com.indiana.zwl.domain.model.WaterSource
import com.indiana.zwl.shared.data.water.WaterSyncManager
import com.indiana.zwl.domain.usecase.GetFireRiskUseCase
Expand Down Expand Up @@ -288,6 +289,15 @@ class MainViewModel @Inject constructor(
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

private val _waterDataState = MutableStateFlow<WaterDataState?>(null)
val waterDataState: StateFlow<WaterDataState?> = _waterDataState.asStateFlow()

fun refreshWaterDataState() {
viewModelScope.launch {
_waterDataState.value = waterSourceRepository.getDataState()
}
}

private val _showPoiGroups = MutableStateFlow(
PoiUiGroup.entries.associateWith { sharedPrefs.getBoolean("show_poi_${it.key}", true) }
)
Expand Down Expand Up @@ -451,6 +461,7 @@ class MainViewModel @Inject constructor(

init {
loadZonesAndInitializeEngine()
refreshWaterDataState()
}

fun setLocationPermissionGranted(granted: Boolean) {
Expand Down Expand Up @@ -484,6 +495,7 @@ class MainViewModel @Inject constructor(
viewModelScope.launch(ioDispatcher) {
try {
waterSyncManager.ensureBaselineIfEmpty()
refreshWaterDataState()
} catch (e: Exception) {
if (e is CancellationException) throw e
e.printStackTrace()
Expand Down Expand Up @@ -544,6 +556,7 @@ class MainViewModel @Inject constructor(
viewModelScope.launch(ioDispatcher) {
try {
waterSyncManager.refreshIfStale()
refreshWaterDataState()
} catch (e: Exception) {
if (e is CancellationException) throw e
e.printStackTrace()
Expand Down
2 changes: 2 additions & 0 deletions iosApp/iosApp/ViewModels/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ final class MainViewModel: NSObject, ObservableObject {
@Published var bansGeoJson: String = ""
@Published var poisGeoJson: String = ""
@Published var waterGeoJson: String = ""
@Published var waterDataState: WaterDataState?

// Layer toggles (persisted across launches like Android's
// SharedPreferences `zwl_map_settings`; they reset to defaults only on
Expand Down Expand Up @@ -373,6 +374,7 @@ final class MainViewModel: NSObject, ObservableObject {
includeDrinking: showWaterDrinking,
includeSprings: showWaterSprings,
includeWells: showWaterWells)) ?? ""
waterDataState = (try? await app.waterDataState()) ?? nil
guard let zones = try? await app.zonesGeoJson(),
let bans = try? await app.bansGeoJson(),
let pois = try? await app.poisGeoJson() else { return }
Expand Down
41 changes: 41 additions & 0 deletions iosApp/iosApp/Views/AboutView.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import SwiftUI
import shared

/// "O aplikacji" (Android `AboutScreen` parity): legal disclaimer, data
/// sources with tappable links, privacy note and the app version footer.
struct AboutView: View {
let waterDataState: WaterDataState?
@Environment(\.dismiss) private var dismiss

private let bdlPortalUrl = URL(string: "https://www.bdl.lasy.gov.pl/portal/")!
Expand Down Expand Up @@ -37,6 +39,19 @@ struct AboutView: View {
sectionHeader("Źródła danych rządowych", systemImage: "globe")
}

Section {
Text("Punkty wody pitnej, źródeł, studni i napełniania butelek pochodzą z OpenStreetMap i odświeżają się automatycznie.")
.font(.system(size: 13))
dataRow("Wersja danych", waterDataState.map { waterVersionLabel($0.dataVersion) } ?? "—")
dataRow("Data pobrania", waterDataState.map { waterFetchedAtLabel($0.lastCheckedAt) } ?? "zestaw wbudowany")
dataRow("Liczba punktów", waterDataState.map { String($0.count) } ?? "—")
Text(WaterAttribution.shared.OSM)
.font(.system(size: 11))
.foregroundColor(.secondary)
} header: {
sectionHeader("Dane źródeł wody (OSM)", systemImage: "drop")
}

Section {
Text("Twoja lokalizacja służy wyłącznie do sprawdzenia stref i jest przetwarzana lokalnie na urządzeniu. Aby pokazać zagrożenie pożarowe, aplikacja wysyła anonimowe zapytanie z Twoimi współrzędnymi do publicznego API BDL — bez tworzenia profili i bez zapisu historii lokalizacji. Aplikacja nie zawiera reklam ani systemów analitycznych.")
.font(.system(size: 13))
Expand Down Expand Up @@ -102,6 +117,32 @@ struct AboutView: View {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "—"
}

private func dataRow(_ label: String, _ value: String) -> some View {
HStack {
Text(label)
.font(.system(size: 13))
.foregroundColor(.secondary)
Spacer()
Text(value)
.font(.system(size: 13, weight: .medium))
}
}

private func waterVersionLabel(_ version: Int64) -> String {
guard version > 0 else { return "—" }
let year = version / 10000
let month = (version / 100) % 100
let day = version % 100
return String(format: "%04d-%02d-%02d", year, month, day)
}

private func waterFetchedAtLabel(_ millis: Int64) -> String {
guard millis > 0 else { return "zestaw wbudowany" }
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm"
return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(millis) / 1000.0))
}

private func sectionHeader(_ title: String, systemImage: String) -> some View {
Label(title, systemImage: systemImage)
.font(.system(size: 13, weight: .bold))
Expand Down
2 changes: 1 addition & 1 deletion iosApp/iosApp/Views/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ struct MainView: View {
OfflineAreasView(viewModel: viewModel)
}
.fullScreenCover(isPresented: $showAbout) {
AboutView()
AboutView(waterDataState: viewModel.waterDataState)
}
.alert("Obszar za duży", isPresented: Binding(
get: { viewModel.downloadBlockedMessage != nil },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.indiana.zwl.domain.model.NewSavedPoint
import com.indiana.zwl.domain.model.Poi
import com.indiana.zwl.domain.model.SavedPoint
import com.indiana.zwl.domain.model.SoilCover
import com.indiana.zwl.domain.model.WaterDataState
import com.indiana.zwl.domain.model.WaterSource
import com.indiana.zwl.domain.model.Zone
import com.indiana.zwl.domain.repository.ForestBanRepository
Expand Down Expand Up @@ -246,6 +247,8 @@ class ForestApp(

fun cachedWaterSources(): List<WaterSource> = cachedWaterSources

suspend fun waterDataState(): WaterDataState? = waterSourceRepository.getDataState()

suspend fun syncZones(): Boolean = withContext(Dispatchers.Default) {
try {
val responseStr = arcgisApi.getZanocujWLesieZones()
Expand Down
Loading