Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
Expand All @@ -44,8 +41,8 @@ import org.groundplatform.android.ui.common.MapConfig
import org.groundplatform.android.ui.components.MapFloatingActionButton
import org.groundplatform.android.ui.home.mapcontainer.HomeScreenMapContainerViewModel
import org.groundplatform.android.ui.map.MapFragment
import org.groundplatform.android.ui.offlineareas.selector.model.BottomTextState
import org.groundplatform.android.ui.offlineareas.selector.model.UiState
import org.groundplatform.android.ui.offlineareas.selector.model.OfflineAreaSelectorEvent
import org.groundplatform.android.ui.offlineareas.selector.model.OfflineAreaSelectorState
import org.groundplatform.android.util.renderComposableDialog
import org.groundplatform.android.util.setComposableContent

Expand Down Expand Up @@ -91,65 +88,37 @@ class OfflineAreaSelectorFragment : AbstractMapContainerFragment() {
}
binding.downloadButton.setOnClickListener { viewModel.onDownloadClick() }
binding.cancelButton.setOnClickListener { viewModel.onCancelClick() }
setupDownloadProgressDialog()
setupObservers()
}

private fun setupObservers() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.isDownloadProgressVisible.observe(viewLifecycleOwner) {
showDownloadProgressDialog(it)
}
viewModel.isFailure.observe(viewLifecycleOwner) {
if (it) {
Toast.makeText(context, R.string.offline_area_download_error, Toast.LENGTH_LONG).show()
}
}
}
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { viewModel.uiState.collect { updateUi(it) } }

lifecycleScope.launch {
viewModel.navigate.collect {
when (it) {
is UiState.OfflineAreaBackToHomeScreen -> {
findNavController()
.navigate(OfflineAreaSelectorFragmentDirections.offlineAreaBackToHomescreen())
}
is UiState.Up -> {
findNavController().navigateUp()
}
}
}
}

lifecycleScope.launch {
viewModel.networkUnavailableEvent.collect {
popups.ErrorPopup().show(R.string.connect_to_download_message)
}
}

viewModel.downloadButtonEnabled.observe(viewLifecycleOwner) {
binding.downloadButton.isEnabled = it
binding.downloadButton.isClickable = it
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.bottomTextState.collect {
binding.bottomText.text =
launch {
viewModel.uiEvent.collect {
when (it) {
is BottomTextState.AreaSize ->
resources.getString(R.string.selected_offline_area_size, it.size)
BottomTextState.AreaTooLarge ->
resources.getString(R.string.selected_offline_area_too_large)
BottomTextState.Loading ->
resources.getString(
R.string.selected_offline_area_size,
resources.getString(R.string.offline_area_size_loading_symbol),
)
BottomTextState.NetworkError ->
resources.getString(R.string.connect_to_download_message)
BottomTextState.NoImageryAvailable ->
resources.getString(R.string.no_imagery_available_for_area)
null -> ""
is OfflineAreaSelectorEvent.NavigateOfflineAreaBackToHomeScreen -> {
findNavController()
.navigate(OfflineAreaSelectorFragmentDirections.offlineAreaBackToHomescreen())
}

is OfflineAreaSelectorEvent.NavigateUp -> {
findNavController().navigateUp()
}

OfflineAreaSelectorEvent.NetworkUnavailable -> {
popups.ErrorPopup().show(R.string.connect_to_download_message)
}

OfflineAreaSelectorEvent.DownloadError -> {
Toast.makeText(context, R.string.offline_area_download_error, Toast.LENGTH_LONG)
.show()
}
}
}
}
}
}
Expand All @@ -173,20 +142,45 @@ class OfflineAreaSelectorFragment : AbstractMapContainerFragment() {

override fun getMapViewModel(): BaseMapViewModel = viewModel

private fun showDownloadProgressDialog(isVisible: Boolean) {
renderComposableDialog {
val openAlertDialog = remember { mutableStateOf(isVisible) }
val progress = viewModel.downloadProgress.observeAsState(0f)
when {
openAlertDialog.value -> {
DownloadProgressDialog(
progress = progress.value,
onDismiss = {
openAlertDialog.value = false
viewModel.stopDownloading()
},
private fun updateUi(state: OfflineAreaSelectorState) {
binding.bottomText.text =
when (state.bottomTextState) {
is OfflineAreaSelectorState.BottomTextState.AreaSize ->
resources.getString(R.string.selected_offline_area_size, state.bottomTextState.size)

OfflineAreaSelectorState.BottomTextState.AreaTooLarge ->
resources.getString(R.string.selected_offline_area_too_large)

OfflineAreaSelectorState.BottomTextState.Loading ->
resources.getString(
R.string.selected_offline_area_size,
resources.getString(R.string.offline_area_size_loading_symbol),
)
}

OfflineAreaSelectorState.BottomTextState.NetworkError ->
resources.getString(R.string.connect_to_download_message)

OfflineAreaSelectorState.BottomTextState.NoImageryAvailable ->
resources.getString(R.string.no_imagery_available_for_area)

null -> ""
}

with(binding.downloadButton) {
isEnabled = state.isDownloadButtonEnabled()
isClickable = state.isDownloadButtonEnabled()
}
}

private fun setupDownloadProgressDialog() {
renderComposableDialog {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val downloadState = state.downloadState
if (downloadState is OfflineAreaSelectorState.DownloadState.InProgress) {
DownloadProgressDialog(
progress = downloadState.progress,
onDismiss = { viewModel.stopDownloading() },
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package org.groundplatform.android.ui.offlineareas.selector

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
Expand All @@ -38,9 +37,8 @@ import org.groundplatform.android.system.NetworkManager
import org.groundplatform.android.system.PermissionsManager
import org.groundplatform.android.system.SettingsManager
import org.groundplatform.android.ui.common.BaseMapViewModel
import org.groundplatform.android.ui.common.SharedViewModel
import org.groundplatform.android.ui.offlineareas.selector.model.BottomTextState
import org.groundplatform.android.ui.offlineareas.selector.model.UiState
import org.groundplatform.android.ui.offlineareas.selector.model.OfflineAreaSelectorEvent
import org.groundplatform.android.ui.offlineareas.selector.model.OfflineAreaSelectorState
import org.groundplatform.android.util.toMb
import org.groundplatform.android.util.toMbString
import org.groundplatform.domain.model.map.Bounds
Expand All @@ -51,7 +49,6 @@ private const val MIN_DOWNLOAD_ZOOM_LEVEL = 9
private const val MAX_AREA_DOWNLOAD_SIZE_MB = 50

/** States and behaviors of Map UI used to select areas for download and viewing offline. */
@SharedViewModel
class OfflineAreaSelectorViewModel
@Inject
internal constructor(
Expand All @@ -78,26 +75,18 @@ internal constructor(
val remoteTileSource: TileSource = RemoteMogTileSource

private var viewport: Bounds? = null
val isDownloadProgressVisible = MutableLiveData(false)
val downloadProgress = MutableLiveData(0f)

private val _bottomTextState = MutableStateFlow<BottomTextState?>(null)
val bottomTextState: StateFlow<BottomTextState?> = _bottomTextState
private val _uiState = MutableStateFlow(OfflineAreaSelectorState())
val uiState: StateFlow<OfflineAreaSelectorState> = _uiState

val downloadButtonEnabled = MutableLiveData(false)
val isFailure = MutableLiveData(false)

private val _navigate = MutableSharedFlow<UiState>(replay = 0)
val navigate = _navigate.asSharedFlow()

private val _networkUnavailableEvent = MutableSharedFlow<Unit>()
val networkUnavailableEvent = _networkUnavailableEvent.asSharedFlow()
private val _uiEvent = MutableSharedFlow<OfflineAreaSelectorEvent>(replay = 0)
val uiEvent = _uiEvent.asSharedFlow()

var downloadJob: Job? = null

fun onDownloadClick() {
if (!networkManager.isNetworkConnected()) {
viewModelScope.launch { _networkUnavailableEvent.emit(Unit) }
viewModelScope.launch { _uiEvent.emit(OfflineAreaSelectorEvent.NetworkUnavailable) }
return
}

Expand All @@ -106,22 +95,24 @@ internal constructor(
return
}

isDownloadProgressVisible.value = true
downloadProgress.value = 0f
_uiState.value =
_uiState.value.copy(downloadState = OfflineAreaSelectorState.DownloadState.InProgress(0f))
downloadJob =
viewModelScope.launch(ioDispatcher) {
offlineAreaRepository
.downloadTiles(viewport!!)
.catch {
isFailure.postValue(true)
isDownloadProgressVisible.postValue(false)
_uiState.value =
_uiState.value.copy(downloadState = OfflineAreaSelectorState.DownloadState.Idle)
_uiEvent.emit(OfflineAreaSelectorEvent.DownloadError)
Timber.d("Download Stopped by $it ")
}
.collect { (bytesDownloaded, totalBytes) ->
updateDownloadProgress(bytesDownloaded, totalBytes)
}
isDownloadProgressVisible.postValue(false)
_navigate.emit(UiState.OfflineAreaBackToHomeScreen)
_uiState.value =
_uiState.value.copy(downloadState = OfflineAreaSelectorState.DownloadState.Idle)
_uiEvent.emit(OfflineAreaSelectorEvent.NavigateOfflineAreaBackToHomeScreen)
}
}

Expand All @@ -132,22 +123,25 @@ internal constructor(
} else {
0f
}
downloadProgress.postValue(progressValue)
_uiState.value =
_uiState.value.copy(
downloadState = OfflineAreaSelectorState.DownloadState.InProgress(progressValue)
)
}

fun onCancelClick() {
viewModelScope.launch { _navigate.emit(UiState.Up) }
viewModelScope.launch { _uiEvent.emit(OfflineAreaSelectorEvent.NavigateUp) }
}

fun stopDownloading() {
downloadJob?.cancel()
downloadJob = null
isDownloadProgressVisible.postValue(false)
_uiState.value =
_uiState.value.copy(downloadState = OfflineAreaSelectorState.DownloadState.Idle)
}

override fun onMapDragged() {
downloadButtonEnabled.postValue(false)
_bottomTextState.value = null
_uiState.value = _uiState.value.copy(bottomTextState = null)
super.onMapDragged()
}

Expand Down Expand Up @@ -178,7 +172,8 @@ internal constructor(
onUnavailableAreaSelected()
return
}
_bottomTextState.value = BottomTextState.Loading
_uiState.value =
_uiState.value.copy(bottomTextState = OfflineAreaSelectorState.BottomTextState.Loading)

offlineAreaRepository
.estimateSizeOnDisk(bounds)
Expand All @@ -197,22 +192,26 @@ internal constructor(
}

private fun onUpdateDownloadSizeError() {
_bottomTextState.value = BottomTextState.NetworkError
downloadButtonEnabled.postValue(false)
_uiState.value =
_uiState.value.copy(bottomTextState = OfflineAreaSelectorState.BottomTextState.NetworkError)
}

private fun onUnavailableAreaSelected() {
_bottomTextState.value = BottomTextState.NoImageryAvailable
downloadButtonEnabled.postValue(false)
_uiState.value =
_uiState.value.copy(
bottomTextState = OfflineAreaSelectorState.BottomTextState.NoImageryAvailable
)
}

private fun onDownloadableAreaSelected(sizeInMb: Float) {
_bottomTextState.value = BottomTextState.AreaSize(sizeInMb.toMbString())
downloadButtonEnabled.postValue(true)
_uiState.value =
_uiState.value.copy(
bottomTextState = OfflineAreaSelectorState.BottomTextState.AreaSize(sizeInMb.toMbString())
)
}

private fun onLargeAreaSelected() {
_bottomTextState.value = BottomTextState.AreaTooLarge
downloadButtonEnabled.postValue(false)
_uiState.value =
_uiState.value.copy(bottomTextState = OfflineAreaSelectorState.BottomTextState.AreaTooLarge)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
*/
package org.groundplatform.android.ui.offlineareas.selector.model

sealed class UiState {
sealed class OfflineAreaSelectorEvent {

data object OfflineAreaBackToHomeScreen : UiState()
data object NavigateOfflineAreaBackToHomeScreen : OfflineAreaSelectorEvent()

data object Up : UiState()
data object NavigateUp : OfflineAreaSelectorEvent()

data object NetworkUnavailable : OfflineAreaSelectorEvent()

data object DownloadError : OfflineAreaSelectorEvent()
}
Loading
Loading