Skip to content

Commit

Permalink
Merge pull request #119 from Semper-Viventem/support-network-silent-mode
Browse files Browse the repository at this point in the history
Support Offline Mode
  • Loading branch information
Semper-Viventem authored Feb 21, 2024
2 parents 6ea62ad + 33f781b commit e077beb
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 179 deletions.
7 changes: 5 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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\"")
}
Expand Down Expand Up @@ -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\"")
}
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/f/cking/software/data/repo/SettingsRepository.kt
Original file line number Diff line number Diff line change
@@ -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()
}
Expand Down Expand Up @@ -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<Boolean> {
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"
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/f/cking/software/ui/UiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -445,6 +456,7 @@ object DeviceDetailsScreen {
modifier: Modifier,
viewModel: DeviceDetailsViewModel,
isLoading: (isLoading: Boolean) -> Unit,
mapIsReadyToUse: () -> Unit,
) {

val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -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) }
)
}
Expand Down
144 changes: 144 additions & 0 deletions app/src/main/java/f/cking/software/ui/map/ComposableMap.kt
Original file line number Diff line number Diff line change
@@ -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 -> {}
}
}
}
}

39 changes: 39 additions & 0 deletions app/src/main/java/f/cking/software/ui/map/MapViewModel.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading

0 comments on commit e077beb

Please sign in to comment.