Skip to content
Open
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
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