diff --git a/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialogFragment.kt b/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialog.kt similarity index 80% rename from ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialogFragment.kt rename to ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialog.kt index 151adff0d4..08bf43cde4 100644 --- a/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialogFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialog.kt @@ -18,9 +18,9 @@ package com.google.android.ground.ui.offlineareas.selector import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.AlertDialog @@ -32,38 +32,35 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.google.android.ground.R @Composable fun DownloadProgressDialog(viewModel: OfflineAreaSelectorViewModel, onDismiss: () -> Unit) { - val progress by viewModel.downloadProgress.observeAsState(0) - val maxProgress by viewModel.downloadProgressMax.observeAsState(100) + val progress by viewModel.downloadProgress.observeAsState(0f) AlertDialog( containerColor = MaterialTheme.colorScheme.surface, onDismissRequest = {}, title = { Text( - stringResource(R.string.offline_map_imagery_download_progress_dialog_title, progress), + stringResource( + R.string.offline_map_imagery_download_progress_dialog_title, + (progress * 100).toInt(), + ), color = MaterialTheme.colorScheme.onSurface, ) }, text = { Column { val animatedProgress by - animateFloatAsState( - targetValue = progress.toFloat() / maxProgress, - animationSpec = tween(durationMillis = 300), - ) + animateFloatAsState(targetValue = progress, animationSpec = tween(durationMillis = 300)) LinearProgressIndicator( - modifier = - Modifier.background( - shape = RoundedCornerShape(4.dp), - color = MaterialTheme.colorScheme.primary, - ), + modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(8.dp)).testTag("progressBar"), progress = { animatedProgress }, color = MaterialTheme.colorScheme.primary, trackColor = MaterialTheme.colorScheme.surfaceVariant, diff --git a/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorViewModel.kt b/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorViewModel.kt index 8765d285c8..75b652fcad 100644 --- a/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorViewModel.kt +++ b/ground/src/main/java/com/google/android/ground/ui/offlineareas/selector/OfflineAreaSelectorViewModel.kt @@ -74,8 +74,7 @@ internal constructor( private val offlineAreaSizeLoadingSymbol = resources.getString(R.string.offline_area_size_loading_symbol) val isDownloadProgressVisible = MutableLiveData(false) - val downloadProgressMax = MutableLiveData(0) - val downloadProgress = MutableLiveData(0) + val downloadProgress = MutableLiveData(0f) val bottomText = MutableLiveData(null) val downloadButtonEnabled = MutableLiveData(false) @@ -94,13 +93,16 @@ internal constructor( } isDownloadProgressVisible.value = true - downloadProgress.value = 0 + downloadProgress.value = 0f viewModelScope.launch(ioDispatcher) { offlineAreaRepository.downloadTiles(viewport!!).collect { (bytesDownloaded, totalBytes) -> - // Set total bytes / max value on first iteration. - if (downloadProgressMax.value != totalBytes) downloadProgressMax.postValue(totalBytes) - // Add number of bytes downloaded to progress. - downloadProgress.postValue(bytesDownloaded) + val progressValue = + if (totalBytes > 0) { + (bytesDownloaded.toFloat() / totalBytes.toFloat()).coerceIn(0f, 1f) + } else { + 0f + } + downloadProgress.postValue(progressValue) } isDownloadProgressVisible.postValue(false) navigator.navigate(OfflineAreaSelectorFragmentDirections.offlineAreaBackToHomescreen()) diff --git a/ground/src/test/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialogTest.kt b/ground/src/test/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialogTest.kt new file mode 100644 index 0000000000..399727480f --- /dev/null +++ b/ground/src/test/java/com/google/android/ground/ui/offlineareas/selector/DownloadProgressDialogTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.ground.ui.offlineareas.selector + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import com.google.android.ground.BaseHiltTest +import com.google.android.ground.R +import dagger.hilt.android.testing.HiltAndroidTest +import javax.inject.Inject +import kotlin.test.Test +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@HiltAndroidTest +@RunWith(RobolectricTestRunner::class) +class DownloadProgressDialogTest : BaseHiltTest() { + + @get:Rule override val composeTestRule = createAndroidComposeRule() + + @Inject lateinit var viewModel: OfflineAreaSelectorViewModel + + @Test + fun downloadProgressDialog_DisplaysTitleCorrectly() { + composeTestRule.setContent { DownloadProgressDialog(viewModel) {} } + + composeTestRule + .onNodeWithText( + composeTestRule.activity.getString( + R.string.offline_map_imagery_download_progress_dialog_title, + 0, + ) + ) + .assertIsDisplayed() + } + + @Test + fun downloadProgressDialog_DisplaysCorrectMessage() { + composeTestRule.setContent { DownloadProgressDialog(viewModel) {} } + + composeTestRule + .onNodeWithText( + composeTestRule.activity.getString( + R.string.offline_map_imagery_download_progress_dialog_message + ) + ) + .assertIsDisplayed() + } + + @Test + fun downloadProgressDialog_CallsOnDismissOnDismissButtonClick() { + var isDismissed = false + + composeTestRule.setContent { DownloadProgressDialog(viewModel) { isDismissed = true } } + + composeTestRule + .onNodeWithText(composeTestRule.activity.getString(R.string.cancel)) + .performClick() + + assertTrue(isDismissed) + } + + @Test + fun downloadProgressDialog_DisplaysCorrectTitleForProgress() { + viewModel.downloadProgress.value = 0.5f + + composeTestRule.setContent { DownloadProgressDialog(viewModel) {} } + + composeTestRule + .onNodeWithText( + composeTestRule.activity.getString( + R.string.offline_map_imagery_download_progress_dialog_title, + 50, + ) + ) + .assertIsDisplayed() + + viewModel.downloadProgress.value = 1f + + composeTestRule + .onNodeWithText( + composeTestRule.activity.getString( + R.string.offline_map_imagery_download_progress_dialog_title, + 100, + ) + ) + .assertIsDisplayed() + } +}