diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c619983..51be9ed 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,14 +24,16 @@ android { minSdk = 29 targetSdk = 34 - versionCode = 1708449099 - versionName = "0.22.9-beta" + versionCode = 1708536347 + versionName = "0.23.0-beta" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "REPORT_ISSUE_URL", "\"https://github.com/Semper-Viventem/MetaRadar/issues\"") buildConfigField("String", "GITHUB_URL", "\"https://github.com/Semper-Viventem/MetaRadar\"") buildConfigField("String", "STORE_PAGE_URL", "\"Not specified\"") + buildConfigField("String", "MAP_LICENSE_URL", "\"https://www.openstreetmap.org/copyright\"") + buildConfigField("Boolean", "OFFLINE_MODE_DEFAULT_STATE", "false") buildConfigField("String", "DISTRIBUTION", "\"Not specified\"") } @@ -98,6 +100,7 @@ android { isDefault = false dimension = "distribution" + buildConfigField("Boolean", "OFFLINE_MODE_DEFAULT_STATE", "true") buildConfigField("Boolean", "STORE_RATING_IS_APPLICABLE", "false") buildConfigField("String", "DISTRIBUTION", "\"F-Droid\"") } diff --git a/app/src/main/java/f/cking/software/data/repo/SettingsRepository.kt b/app/src/main/java/f/cking/software/data/repo/SettingsRepository.kt index aa363ec..36f6984 100644 --- a/app/src/main/java/f/cking/software/data/repo/SettingsRepository.kt +++ b/app/src/main/java/f/cking/software/data/repo/SettingsRepository.kt @@ -1,12 +1,17 @@ package f.cking.software.data.repo import android.content.SharedPreferences +import f.cking.software.BuildConfig import f.cking.software.TheAppConfig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow class SettingsRepository( private val sharedPreferences: SharedPreferences, ) { + private val silentModeState = MutableStateFlow(getSilentMode()) + fun setGarbagingTime(time: Long) { sharedPreferences.edit().putLong(KEY_GARBAGING_TIME, time).apply() } @@ -63,6 +68,19 @@ class SettingsRepository( sharedPreferences.edit().putLong(KEY_ENJOY_THE_APP_STARTING_POINT, value).apply() } + fun setSilentMode(enabled: Boolean) { + sharedPreferences.edit().putBoolean(KEY_SILENT_NETWORK_MODE, enabled).apply() + silentModeState.tryEmit(getSilentMode()) + } + + fun getSilentMode(): Boolean { + return sharedPreferences.getBoolean(KEY_SILENT_NETWORK_MODE, BuildConfig.OFFLINE_MODE_DEFAULT_STATE) + } + + fun observeSilentMode(): Flow { + return silentModeState + } + companion object { private const val KEY_GARBAGING_TIME = "key_garbaging_time" private const val KEY_USE_GPS_ONLY = "key_use_gps_location_only" @@ -71,6 +89,7 @@ class SettingsRepository( private const val KEY_FIRST_APP_LAUNCH_TIME = "key_first_app_launch_time" private const val KEY_ENJOY_THE_APP_ANSWERED = "key_enjoy_the_app_answered_v1" private const val KEY_ENJOY_THE_APP_STARTING_POINT = "key_enjoy_the_app_starting_point" + private const val KEY_SILENT_NETWORK_MODE = "silent_network_mode" const val NO_APP_LAUNCH_TIME = -1L const val NO_ENJOY_THE_APP_STARTING_POINT = -1L diff --git a/app/src/main/java/f/cking/software/ui/UiModule.kt b/app/src/main/java/f/cking/software/ui/UiModule.kt index b990daa..cb41ef1 100644 --- a/app/src/main/java/f/cking/software/ui/UiModule.kt +++ b/app/src/main/java/f/cking/software/ui/UiModule.kt @@ -4,6 +4,7 @@ import f.cking.software.ui.devicedetails.DeviceDetailsViewModel import f.cking.software.ui.devicelist.DeviceListViewModel import f.cking.software.ui.journal.JournalViewModel import f.cking.software.ui.main.MainViewModel +import f.cking.software.ui.map.MapViewModel import f.cking.software.ui.profiledetails.ProfileDetailsViewModel import f.cking.software.ui.profileslist.ProfilesListViewModel import f.cking.software.ui.selectdevice.SelectDeviceViewModel @@ -27,5 +28,6 @@ object UiModule { viewModel { SelectDeviceViewModel(get(), get()) } viewModel { DeviceDetailsViewModel(address = it[0], get(), get(), get(), get(), get(), get(), get(), get()) } viewModel { JournalViewModel(get(), get(), get(), get(), get()) } + viewModel { MapViewModel(get(), get(), get()) } } } \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt b/app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt index 5e95a33..74e0241 100644 --- a/app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt +++ b/app/src/main/java/f/cking/software/ui/devicedetails/DeviceDetailsScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.material3.SuggestionChip import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -58,8 +59,8 @@ import f.cking.software.domain.model.LocationModel import f.cking.software.dpToPx import f.cking.software.frameRate import f.cking.software.ui.AsyncBatchProcessor +import f.cking.software.ui.map.MapView import f.cking.software.ui.tagdialog.TagDialog -import f.cking.software.utils.graphic.MapView import f.cking.software.utils.graphic.RoundedBox import f.cking.software.utils.graphic.SystemNavbarSpacer import f.cking.software.utils.graphic.TagChip @@ -389,17 +390,27 @@ object DeviceDetailsScreen { @Composable private fun LocationHistory(modifier: Modifier = Modifier, deviceData: DeviceData, viewModel: DeviceDetailsViewModel) { RoundedBox(modifier = modifier, internalPaddings = 0.dp) { - Box(modifier = Modifier - .fillMaxWidth() - .weight(1f)) { + val mapIsReady = remember { mutableStateOf(false) } + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { Map( Modifier.fillMaxSize(), viewModel = viewModel, - isLoading = { viewModel.markersInLoadingState = it } + isLoading = { viewModel.markersInLoadingState = it }, + mapIsReadyToUse = { + mapIsReady.value = true + } ) - MapOverlay(viewModel = viewModel) + if (mapIsReady.value) { + MapOverlay(viewModel = viewModel) + } + } + if (mapIsReady.value) { + HistoryPeriod(deviceData = deviceData, viewModel = viewModel) } - HistoryPeriod(deviceData = deviceData, viewModel = viewModel) } } @@ -445,6 +456,7 @@ object DeviceDetailsScreen { modifier: Modifier, viewModel: DeviceDetailsViewModel, isLoading: (isLoading: Boolean) -> Unit, + mapIsReadyToUse: () -> Unit, ) { val scope = rememberCoroutineScope() @@ -484,7 +496,10 @@ object DeviceDetailsScreen { MapView( modifier = modifier, - onLoad = { map -> initMapState(map) }, + onLoad = { map -> + initMapState(map) + mapIsReadyToUse.invoke() + }, onUpdate = { map -> refreshMap(map, viewModel, batchProcessor) } ) } diff --git a/app/src/main/java/f/cking/software/ui/map/ComposableMap.kt b/app/src/main/java/f/cking/software/ui/map/ComposableMap.kt new file mode 100644 index 0000000..3c45348 --- /dev/null +++ b/app/src/main/java/f/cking/software/ui/map/ComposableMap.kt @@ -0,0 +1,144 @@ +package f.cking.software.ui.map + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import f.cking.software.R +import f.cking.software.utils.graphic.RoundedBox +import f.cking.software.utils.graphic.Switcher +import org.koin.androidx.compose.koinViewModel +import org.osmdroid.views.MapView + + +/** + * A composable Google Map. + * @author Arnau Mora + * @since 20211230 + * @param modifier Modifiers to apply to the map. + * @param onLoad This will get called once the map has been loaded. + */ +@Composable +fun MapView( + modifier: Modifier = Modifier, + onLoad: ((map: MapView) -> Unit)? = null, + onUpdate: ((map: MapView) -> Unit)? = null, +) { + val viewModel: MapViewModel = koinViewModel() + + if (viewModel.silentModeEnabled) { + SilentModeDisclaimer(modifier = modifier, viewModel = viewModel) + } else { + OSMMap(modifier = modifier, viewModel = viewModel, onLoad = onLoad, onUpdate = onUpdate) + } +} + +@Composable +fun SilentModeDisclaimer(modifier: Modifier, viewModel: MapViewModel) { + Box(modifier, contentAlignment = Alignment.Center) { + Image( + modifier = Modifier.fillMaxSize().alpha(0.5f), + painter = painterResource(id = R.drawable.map_placeholder), + contentDescription = null, + contentScale = ContentScale.Crop, + ) + RoundedBox(internalPaddings = 0.dp) { + Text(modifier = Modifier.padding(16.dp), text = stringResource(id = R.string.silent_mode_is_enabled_disclaimer)) + Spacer(modifier = Modifier.height(8.dp)) + Switcher( + value = viewModel.silentModeEnabled, + title = stringResource(id = R.string.silent_mode_title), + subtitle = stringResource(id = R.string.silent_mode_subtitle), + onClick = { + viewModel.changeSilentModeState() + } + ) + } + } +} + +@Composable +fun OSMMap( + modifier: Modifier = Modifier, + viewModel: MapViewModel, + onLoad: ((map: MapView) -> Unit)? = null, + onUpdate: ((map: MapView) -> Unit)? = null, +) { + val mapViewState = rememberMapViewWithLifecycle() + Box(modifier = modifier) { + AndroidView( + factory = { mapViewState.apply { onLoad?.invoke(this) } }, + update = { mapView -> onUpdate?.invoke(mapView) }, + ) + Text( + text = stringResource(R.string.osm_copyright), + modifier = Modifier + .padding(start = 4.dp) + .align(Alignment.BottomStart) + .alpha(0.9f) + .clickable { viewModel.openOSMLicense() }, + color = Color.DarkGray, + fontWeight = FontWeight.Bold, + fontSize = 12.sp, + ) + } +} + +@Composable +fun rememberMapViewWithLifecycle(): MapView { + val context = LocalContext.current + val mapView = remember { + MapView(context).apply { + id = R.id.layout_map + clipToOutline = true + } + } + + // Makes MapView follow the lifecycle of this composable + val lifecycleObserver = rememberMapLifecycleObserver(mapView) + val lifecycle = LocalLifecycleOwner.current.lifecycle + DisposableEffect(lifecycle) { + lifecycle.addObserver(lifecycleObserver) + onDispose { + lifecycle.removeObserver(lifecycleObserver) + } + } + + return mapView +} + +@Composable +fun rememberMapLifecycleObserver(mapView: MapView): LifecycleEventObserver { + return remember(mapView) { + LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> mapView.onResume() + Lifecycle.Event.ON_PAUSE -> mapView.onPause() + else -> {} + } + } + } +} + diff --git a/app/src/main/java/f/cking/software/ui/map/MapViewModel.kt b/app/src/main/java/f/cking/software/ui/map/MapViewModel.kt new file mode 100644 index 0000000..bb1be30 --- /dev/null +++ b/app/src/main/java/f/cking/software/ui/map/MapViewModel.kt @@ -0,0 +1,39 @@ +package f.cking.software.ui.map + +import android.widget.Toast +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import f.cking.software.BuildConfig +import f.cking.software.R +import f.cking.software.data.helpers.ActivityProvider +import f.cking.software.data.helpers.IntentHelper +import f.cking.software.data.repo.SettingsRepository +import kotlinx.coroutines.launch + +class MapViewModel( + private val settingsRepository: SettingsRepository, + private val intentHelper: IntentHelper, + private val activityProvider: ActivityProvider, +) : ViewModel() { + + var silentModeEnabled by mutableStateOf(settingsRepository.getSilentMode()) + + init { + viewModelScope.launch { + settingsRepository.observeSilentMode() + .collect { silentModeEnabled = it } + } + } + + fun openOSMLicense() { + intentHelper.openUrl(BuildConfig.MAP_LICENSE_URL) + } + + fun changeSilentModeState() { + settingsRepository.setSilentMode(!settingsRepository.getSilentMode()) + Toast.makeText(activityProvider.requireActivity(), R.string.silent_mode_is_off_toast, Toast.LENGTH_LONG).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/ui/selectlocation/SelectLocationScreen.kt b/app/src/main/java/f/cking/software/ui/selectlocation/SelectLocationScreen.kt index b288ec6..f666c76 100644 --- a/app/src/main/java/f/cking/software/ui/selectlocation/SelectLocationScreen.kt +++ b/app/src/main/java/f/cking/software/ui/selectlocation/SelectLocationScreen.kt @@ -43,7 +43,7 @@ import f.cking.software.TheAppConfig import f.cking.software.data.helpers.LocationProvider import f.cking.software.domain.model.LocationModel import f.cking.software.ui.devicedetails.MapConfig -import f.cking.software.utils.graphic.MapView +import f.cking.software.ui.map.MapView import f.cking.software.utils.graphic.RoundedBox import f.cking.software.utils.graphic.SystemNavbarSpacer import kotlinx.coroutines.flow.filterNotNull @@ -121,33 +121,36 @@ object SelectLocationScreen { initialLocationModel = initialLocationModel, onMapReady = { map.value = it } ) - Box( - modifier = Modifier - .size(width = 20.dp, height = 10.dp) - .blur(2.dp), - contentAlignment = Alignment.Center, - ) { + + if (map.value != null) { Box( modifier = Modifier - .size(width = 10.dp, height = 5.dp) - .background(color = Color.DarkGray, shape = AbsoluteCutCornerShape(10.dp)) - ) - } - Column( - modifier = Modifier - .height(120.dp) - .width(60.dp) - ) { - val painter = painterResource(R.drawable.ic_location) - Image( + .size(width = 20.dp, height = 10.dp) + .blur(2.dp), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .size(width = 10.dp, height = 5.dp) + .background(color = Color.DarkGray, shape = AbsoluteCutCornerShape(10.dp)) + ) + } + Column( modifier = Modifier - .fillMaxWidth() - .height(60.dp), - contentScale = ContentScale.FillWidth, - painter = painter, - contentDescription = null, - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary) - ) + .height(120.dp) + .width(60.dp) + ) { + val painter = painterResource(R.drawable.ic_location) + Image( + modifier = Modifier + .fillMaxWidth() + .height(60.dp), + contentScale = ContentScale.FillWidth, + painter = painter, + contentDescription = null, + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary) + ) + } } } } diff --git a/app/src/main/java/f/cking/software/ui/settings/SettingsScreen.kt b/app/src/main/java/f/cking/software/ui/settings/SettingsScreen.kt index 14a6f4b..22e1f94 100644 --- a/app/src/main/java/f/cking/software/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/f/cking/software/ui/settings/SettingsScreen.kt @@ -2,10 +2,8 @@ package f.cking.software.ui.settings import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable 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 @@ -16,7 +14,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -37,6 +34,7 @@ import f.cking.software.dateTimeStringFormat import f.cking.software.utils.graphic.BottomNavigationSpacer import f.cking.software.utils.graphic.FABSpacer import f.cking.software.utils.graphic.RoundedBox +import f.cking.software.utils.graphic.Switcher import f.cking.software.utils.graphic.ThemedDialog import org.koin.androidx.compose.koinViewModel @@ -52,15 +50,11 @@ object SettingsScreen { .verticalScroll(rememberScrollState()) ) { Spacer(modifier = Modifier.height(16.dp)) - ProjectGithub(viewModel = viewModel) + ProjectInformationBlock(viewModel = viewModel) Spacer(modifier = Modifier.height(8.dp)) - ReportIssue(viewModel = viewModel) + AppSettings(viewModel = viewModel) Spacer(modifier = Modifier.height(8.dp)) - ClearDatabaseBlock(viewModel = viewModel) - Spacer(modifier = Modifier.height(8.dp)) - BackupDatabaseBlock(viewModel = viewModel) - Spacer(modifier = Modifier.height(8.dp)) - RunOnStartup(viewModel = viewModel) + DatabaseBlock(viewModel = viewModel) Spacer(modifier = Modifier.height(8.dp)) LocationBlock(viewModel = viewModel) Spacer(modifier = Modifier.height(8.dp)) @@ -89,15 +83,6 @@ object SettingsScreen { } } - @Composable - private fun ClearDatabaseBlock(viewModel: SettingsViewModel) { - RoundedBox { - ClearGarbageButton(viewModel) - Spacer(modifier = Modifier.height(8.dp)) - ClearLocationsButton(viewModel) - } - } - @Composable private fun LocationBlock(viewModel: SettingsViewModel) { RoundedBox(internalPaddings = 0.dp) { @@ -109,11 +94,18 @@ object SettingsScreen { } @Composable - private fun BackupDatabaseBlock(viewModel: SettingsViewModel) { + private fun DatabaseBlock(viewModel: SettingsViewModel) { RoundedBox { + Text(text = stringResource(id = R.string.database_block_title), fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.height(4.dp)) + BackupDB(viewModel = viewModel) Spacer(modifier = Modifier.height(8.dp)) RestoreDB(viewModel = viewModel) + Spacer(modifier = Modifier.height(8.dp)) + ClearGarbageButton(viewModel) + Spacer(modifier = Modifier.height(8.dp)) + ClearLocationsButton(viewModel) } } @@ -264,8 +256,16 @@ object SettingsScreen { } @Composable - private fun RunOnStartup(viewModel: SettingsViewModel) { + private fun AppSettings(viewModel: SettingsViewModel) { RoundedBox(internalPaddings = 0.dp) { + Text(modifier = Modifier.padding(16.dp), text = stringResource(id = R.string.app_settings_title), fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.height(4.dp)) + Switcher( + value = viewModel.silentModeEnabled, + title = stringResource(R.string.silent_mode_title), + subtitle = stringResource(id = R.string.silent_mode_subtitle), + onClick = { viewModel.changeSilentMode() } + ) Switcher( value = viewModel.runOnStartup, title = stringResource(R.string.launch_on_system_startup_title), @@ -276,8 +276,14 @@ object SettingsScreen { } @Composable - private fun ReportIssue(viewModel: SettingsViewModel) { + private fun ProjectInformationBlock(viewModel: SettingsViewModel) { RoundedBox { + Text(text = stringResource(R.string.project_github_title, stringResource(id = R.string.app_name)), fontWeight = FontWeight.SemiBold) + Spacer(modifier = Modifier.height(4.dp)) + Button(modifier = Modifier.fillMaxWidth(), onClick = { viewModel.onGithubClick() }) { + Text(text = stringResource(R.string.open_github), color = MaterialTheme.colorScheme.onPrimary) + } + Spacer(modifier = Modifier.height(8.dp)) Text(text = stringResource(R.string.report_issue_title), fontWeight = FontWeight.SemiBold) Spacer(modifier = Modifier.height(4.dp)) Button(modifier = Modifier.fillMaxWidth(), onClick = { viewModel.opReportIssueClick() }) { @@ -298,50 +304,4 @@ object SettingsScreen { Text(text = stringResource(R.string.app_info_distribution, BuildConfig.DISTRIBUTION)) } } - - @Composable - private fun ProjectGithub(viewModel: SettingsViewModel) { - RoundedBox { - Text(text = stringResource(R.string.project_github_title, stringResource(id = R.string.app_name)), fontWeight = FontWeight.SemiBold) - Spacer(modifier = Modifier.height(4.dp)) - Button(modifier = Modifier.fillMaxWidth(), onClick = { viewModel.onGithubClick() }) { - Text(text = stringResource(R.string.open_github), color = MaterialTheme.colorScheme.onPrimary) - } - } - } - - @Composable - private fun Switcher( - value: Boolean, - title: String, - subtitle: String?, - onClick: () -> Unit, - ) { - Box(modifier = Modifier - .fillMaxWidth() - .clickable { onClick.invoke() } - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Column( - modifier = Modifier.weight(1f), - ) { - Text(text = title) - subtitle?.let { - Spacer(modifier = Modifier.height(4.dp)) - Text(text = it, fontWeight = FontWeight.Light, fontSize = 12.sp) - } - } - Spacer(modifier = Modifier.width(4.dp)) - Switch( - checked = value, - onCheckedChange = { onClick.invoke() } - ) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/f/cking/software/ui/settings/SettingsViewModel.kt b/app/src/main/java/f/cking/software/ui/settings/SettingsViewModel.kt index ff08b59..73c5ccf 100644 --- a/app/src/main/java/f/cking/software/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/f/cking/software/ui/settings/SettingsViewModel.kt @@ -49,9 +49,11 @@ class SettingsViewModel( var useGpsLocationOnly: Boolean by mutableStateOf(settingsRepository.getUseGpsLocationOnly()) var locationData: LocationProvider.LocationHandle? by mutableStateOf(null) var runOnStartup: Boolean by mutableStateOf(settingsRepository.getRunOnStartup()) + var silentModeEnabled: Boolean by mutableStateOf(settingsRepository.getSilentMode()) init { observeLocationData() + observeSilentMode() } fun onRemoveGarbageClick() { @@ -132,6 +134,10 @@ class SettingsViewModel( runOnStartup = newValue } + fun changeSilentMode() { + settingsRepository.setSilentMode(!settingsRepository.getSilentMode()) + } + fun opReportIssueClick() { intentHelper.openUrl(BuildConfig.REPORT_ISSUE_URL) } @@ -149,6 +155,13 @@ class SettingsViewModel( } } + private fun observeSilentMode() { + viewModelScope.launch { + settingsRepository.observeSilentMode() + .collect { silentModeEnabled = it } + } + } + private fun restoreFrom(uri: Uri) { viewModelScope.launch { backupDbInProgress = true diff --git a/app/src/main/java/f/cking/software/utils/graphic/ComposeFunctions.kt b/app/src/main/java/f/cking/software/utils/graphic/ComposeFunctions.kt index ff1ea8e..e8017f8 100644 --- a/app/src/main/java/f/cking/software/utils/graphic/ComposeFunctions.kt +++ b/app/src/main/java/f/cking/software/utils/graphic/ComposeFunctions.kt @@ -27,11 +27,11 @@ import androidx.compose.material3.AssistChipDefaults import androidx.compose.material3.ColorScheme import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -45,7 +45,6 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -54,10 +53,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.DialogProperties -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver import com.google.accompanist.flowlayout.FlowRow import com.vanpra.composematerialdialogs.MaterialDialog import com.vanpra.composematerialdialogs.MaterialDialogButtons @@ -73,11 +69,9 @@ import com.vanpra.composematerialdialogs.rememberMaterialDialogState import f.cking.software.R import f.cking.software.domain.model.DeviceData import f.cking.software.dpToPx -import f.cking.software.openUrl import f.cking.software.pxToDp import f.cking.software.toHexString import f.cking.software.ui.GlobalUiState -import org.osmdroid.views.MapView import java.time.LocalDate import java.time.LocalTime import kotlin.math.abs @@ -342,74 +336,6 @@ fun DeviceListItem( } } -@Composable -fun rememberMapViewWithLifecycle(): MapView { - val context = LocalContext.current - val mapView = remember { - MapView(context).apply { - id = R.id.layout_map - clipToOutline = true - } - } - - // Makes MapView follow the lifecycle of this composable - val lifecycleObserver = rememberMapLifecycleObserver(mapView) - val lifecycle = LocalLifecycleOwner.current.lifecycle - DisposableEffect(lifecycle) { - lifecycle.addObserver(lifecycleObserver) - onDispose { - lifecycle.removeObserver(lifecycleObserver) - } - } - - return mapView -} - -@Composable -fun rememberMapLifecycleObserver(mapView: MapView): LifecycleEventObserver = - remember(mapView) { - LifecycleEventObserver { _, event -> - when (event) { - Lifecycle.Event.ON_RESUME -> mapView.onResume() - Lifecycle.Event.ON_PAUSE -> mapView.onPause() - else -> {} - } - } - } - -/** - * A composable Google Map. - * @author Arnau Mora - * @since 20211230 - * @param modifier Modifiers to apply to the map. - * @param onLoad This will get called once the map has been loaded. - */ -@Composable -fun MapView( - modifier: Modifier = Modifier, - onLoad: ((map: MapView) -> Unit)? = null, - onUpdate: ((map: MapView) -> Unit)? = null, -) { - val mapViewState = rememberMapViewWithLifecycle() - val context = LocalContext.current - Box(modifier = modifier) { - AndroidView( - { mapViewState.apply { onLoad?.invoke(this) } }, - ) { mapView -> onUpdate?.invoke(mapView) } - Text( - text = stringResource(R.string.osm_copyright), - modifier = Modifier - .padding(start = 4.dp) - .align(Alignment.BottomStart) - .alpha(0.9f) - .clickable { context.openUrl("https://www.openstreetmap.org/copyright") }, - color = Color.DarkGray, - fontWeight = FontWeight.Bold, - fontSize = 12.sp, - ) - } -} - @Composable fun Divider() { Box(modifier = Modifier.padding(horizontal = 16.dp)) { @@ -566,4 +492,40 @@ fun SystemNavbarSpacer() { fun ColorScheme.surfaceEvaluated(evaluation: Dp = 3.dp): Color { return this.surfaceColorAtElevation(evaluation) +} + +@Composable +fun Switcher( + modifier: Modifier = Modifier, + value: Boolean, + title: String, + subtitle: String?, + onClick: () -> Unit, +) { + Box(modifier = modifier + .fillMaxWidth() + .clickable { onClick.invoke() } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier.weight(1f), + ) { + Text(text = title) + subtitle?.let { + Spacer(modifier = Modifier.height(4.dp)) + Text(text = it, fontWeight = FontWeight.Light, fontSize = 12.sp) + } + } + Spacer(modifier = Modifier.width(4.dp)) + Switch( + checked = value, + onCheckedChange = { onClick.invoke() } + ) + } + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/map_placeholder.webp b/app/src/main/res/drawable/map_placeholder.webp new file mode 100644 index 0000000..da115bd Binary files /dev/null and b/app/src/main/res/drawable/map_placeholder.webp differ diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 6eb631a..77ca31c 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -90,7 +90,7 @@ Szűrő kihagyása, ha a hely nem érhető el (egyéb esetben az alapérték hamis) Dátumból Időből - ℅s szemét eszköz törölve + %s szemét eszköz törölve Add meg a szükséges engedélyeket manuálisan Borítás figyelmen kívül hagyása Aktív diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b935cc2..3c7214f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -86,6 +86,11 @@ %s is an open-source project. You can find the source on GitHub. Feel free to ask your questions, contribute, and star the repository! Go to GitHub A secret cat photo + Offline mode + Turn off any network communications in the app. It might be helpful to save traffic. + Database actions + App settings + Offline mode is turned off. You can change it in the settings. Search manufacturer @@ -216,6 +221,7 @@ Add tag Delete tag \"%s\"? © OpenStreetMap + The map is not available in the offline mode. To see the map disable the offline mode first. %s dBm %s m Disclaimer