Skip to content
Merged
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 @@ -185,26 +185,26 @@ constructor(private val preferences: SharedPreferences, private val locale: Loca

private fun getDeserializedCameraPosition(serializedValue: String): CameraPosition? =
runCatching {
if (serializedValue.isEmpty()) return null
val parts = serializedValue.split(",")
val lat = parts[0].trim().toDouble()
val long = parts[1].trim().toDouble()
val zoomLevel = parts[2].trim().toFloatOrNull()
val south = parts[3].trim().toDoubleOrNull()
val west = parts[4].trim().toDoubleOrNull()
val north = parts[5].trim().toDoubleOrNull()
val east = parts[6].trim().toDoubleOrNull()

var bounds: Bounds? = null
if (south != null && west != null && north != null && east != null) {
bounds = Bounds(south, west, north, east)
}

return CameraPosition(Coordinates(lat, long), zoomLevel, bounds)
}
.getOrElse { exception ->
Timber.e(exception)
// Prevent app from crashing if we are unable to parse the camera position
null
if (serializedValue.isEmpty()) return null
val parts = serializedValue.split(",")
val lat = parts[0].trim().toDouble()
val long = parts[1].trim().toDouble()
val zoomLevel = parts[2].trim().toFloatOrNull()
val south = parts[3].trim().toDoubleOrNull()
val west = parts[4].trim().toDoubleOrNull()
val north = parts[5].trim().toDoubleOrNull()
val east = parts[6].trim().toDoubleOrNull()

var bounds: Bounds? = null
if (south != null && west != null && north != null && east != null) {
bounds = Bounds(south, west, north, east)
}

return CameraPosition(Coordinates(lat, long), zoomLevel, bounds)
}
.getOrElse { exception ->
Timber.e(exception)
// Prevent app from crashing if we are unable to parse the camera position
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ import org.groundplatform.android.data.local.room.fields.TileSetEntityState
AutoMigration(from = 121, to = 122),
AutoMigration(from = 122, to = 123),
AutoMigration(from = 123, to = 124),
AutoMigration(from = 127, to = 128)
AutoMigration(from = 127, to = 128),
],
)
@TypeConverters(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ import org.groundplatform.android.data.local.room.relations.SurveyEntityAndRelat
import org.groundplatform.android.data.local.room.relations.TaskEntityAndRelations
import org.groundplatform.android.data.remote.firebase.protobuf.toModel
import org.groundplatform.android.data.remote.firebase.protobuf.toProto
import org.groundplatform.domain.model.imagery.OfflineArea
import org.groundplatform.android.proto.Survey as SurveyProto
import org.groundplatform.android.proto.Survey.DataSharingTerms
import org.groundplatform.domain.model.Survey
import org.groundplatform.domain.model.User
import org.groundplatform.domain.model.geometry.Coordinates
import org.groundplatform.domain.model.geometry.Geometry
import org.groundplatform.domain.model.geometry.Point
import org.groundplatform.domain.model.imagery.OfflineArea
import org.groundplatform.domain.model.job.Job
import org.groundplatform.domain.model.job.Job.DataCollectionStrategy
import org.groundplatform.domain.model.job.Style
Expand Down Expand Up @@ -263,14 +263,10 @@ fun MultipleChoice.toLocalDataStoreObject(taskId: String): MultipleChoiceEntity

private fun OfflineAreaEntityState.toModelObject() =
when (this) {
OfflineAreaEntityState.PENDING ->
OfflineArea.State.PENDING
OfflineAreaEntityState.IN_PROGRESS ->
OfflineArea.State.IN_PROGRESS
OfflineAreaEntityState.DOWNLOADED ->
OfflineArea.State.DOWNLOADED
OfflineAreaEntityState.FAILED ->
OfflineArea.State.FAILED
OfflineAreaEntityState.PENDING -> OfflineArea.State.PENDING
OfflineAreaEntityState.IN_PROGRESS -> OfflineArea.State.IN_PROGRESS
OfflineAreaEntityState.DOWNLOADED -> OfflineArea.State.DOWNLOADED
OfflineAreaEntityState.FAILED -> OfflineArea.State.FAILED
else -> throw IllegalArgumentException("Unknown area state: $this")
}

Expand Down Expand Up @@ -516,7 +512,7 @@ fun ExpressionEntity.toModelObject(): Expression =
expressionType = expressionType.toExpressionType(),
taskId = taskId,
optionIds = optionIds?.split(',')?.toSet() ?: setOf(),
otherSelected = otherSelected
otherSelected = otherSelected,
)

fun DraftSubmissionEntity.toModelObject(survey: Survey): DraftSubmission? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ interface BaseDao<E> {
}

/**
* Conservative SQLite variable limit. The actual limit is ~999, but 900 ensures
* compatibility across SQLite versions. Use when chunking IN clauses.
* Conservative SQLite variable limit. The actual limit is ~999, but 900 ensures compatibility
* across SQLite versions. Use when chunking IN clauses.
*/
const val MAX_SQL_VARIABLES = 900

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,10 @@ class RoomLocationOfInterestStore @Inject internal constructor() : LocalLocation
}

override suspend fun insertOrUpdateAll(lois: List<LocationOfInterest>) {
val entities =
lois.map {
require(!it.geometry.isEmpty()) { "Cannot save LOI ${it.id} with empty geometry" }
it.toLocalDataStoreObject()
}
val entities = lois.map {
require(!it.geometry.isEmpty()) { "Cannot save LOI ${it.id} with empty geometry" }
it.toLocalDataStoreObject()
}
locationOfInterestDao.upsertAll(entities)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,17 @@ internal constructor(
override suspend fun subscribeToSurveyUpdates(surveyId: String) {
if (USE_EMULATORS) return
Timber.d("Subscribing to FCM topic $surveyId")
Firebase.messaging
.subscribeToTopic(surveyId)
.addOnFailureListener { Timber.w(it, "Failed to subscribe to FCM topic $surveyId") }
Firebase.messaging.subscribeToTopic(surveyId).addOnFailureListener {
Timber.w(it, "Failed to subscribe to FCM topic $surveyId")
}
}

override suspend fun unsubscribeFromSurveyUpdates(surveyId: String) {
if (USE_EMULATORS) return
Timber.d("Unsubscribing from FCM topic $surveyId")
Firebase.messaging
.unsubscribeFromTopic(surveyId)
.addOnFailureListener { Timber.w(it, "Failed to unsubscribe from FCM topic $surveyId") }
Firebase.messaging.unsubscribeFromTopic(surveyId).addOnFailureListener {
Timber.w(it, "Failed to unsubscribe from FCM topic $surveyId")
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ private fun FirestoreMapEntry.toMessageField(
private fun FirestoreKey.toMessageFieldNumber() =
toIntOrNull() ?: throw IllegalArgumentException("Non-numeric document key $this")

private fun FirestoreMap.toMessageMap(mapValueType: KClass<*>): MessageMap =
map { (key: FirestoreValue, value: FirestoreValue) -> key to value.toMessageValue(mapValueType) }
.toMap()
@Suppress("ExpressionBodySyntax")
private fun FirestoreMap.toMessageMap(mapValueType: KClass<*>): MessageMap {
return map { (key, value) -> key to value.toMessageValue(mapValueType) }.toMap()
}

@Suppress("UNCHECKED_CAST")
private fun FirestoreValue.toMessageValue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ internal object ConditionConverter {
expressionType = ExpressionType.ANY_OF_SELECTED,
taskId = multipleChoice.taskId,
optionIds = multipleChoice.optionIdsList.toSet(),
otherSelected = multipleChoice.otherSelected
otherSelected = multipleChoice.otherSelected,
)
)
return Condition(MatchType.MATCH_ANY, expressions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,14 @@ constructor(
mutations: List<Mutation>
): MutationRepositoryInterface.MutationResult =
try {
markAsInProgress(mutations)
uploadMutations(mutations)
finalizeDeletions(mutations)
val (hasMediaToUpload, hasNoMedia) =
mutations.partition { it is SubmissionMutation && it.getPhotoData().isNotEmpty() }
if (hasNoMedia.isNotEmpty()) markAsComplete(hasNoMedia)
if (hasMediaToUpload.isNotEmpty()) markForMediaUpload(hasMediaToUpload)
MutationRepositoryInterface.MutationResult.Success(hasMediaToUpload.isNotEmpty())
markAsInProgress(mutations)
uploadMutations(mutations)
finalizeDeletions(mutations)
val (hasMediaToUpload, hasNoMedia) =
mutations.partition { it is SubmissionMutation && it.getPhotoData().isNotEmpty() }
if (hasNoMedia.isNotEmpty()) markAsComplete(hasNoMedia)
if (hasMediaToUpload.isNotEmpty()) markForMediaUpload(hasMediaToUpload)
MutationRepositoryInterface.MutationResult.Success(hasMediaToUpload.isNotEmpty())
} catch (t: Throwable) {
// Mark all mutations as having failed since the remote datastore only commits when all
// mutations have succeeded.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.mapNotNull
import org.groundplatform.android.data.local.stores.LocalOfflineAreaStore
import org.groundplatform.android.data.uuid.OfflineUuidGenerator
import org.groundplatform.domain.model.imagery.LocalTileSource
import org.groundplatform.domain.model.imagery.OfflineArea
import org.groundplatform.domain.model.imagery.TileSource
import org.groundplatform.android.system.GeocodingManager
import org.groundplatform.android.ui.map.gms.mog.MogClient
import org.groundplatform.android.ui.map.gms.mog.MogTileDownloader
Expand All @@ -36,6 +33,9 @@ import org.groundplatform.android.ui.map.gms.mog.maxZoom
import org.groundplatform.android.ui.util.FileUtil
import org.groundplatform.android.util.deleteIfEmpty
import org.groundplatform.android.util.rangeOf
import org.groundplatform.domain.model.imagery.LocalTileSource
import org.groundplatform.domain.model.imagery.OfflineArea
import org.groundplatform.domain.model.imagery.TileSource
import org.groundplatform.domain.model.map.Bounds
import org.groundplatform.domain.model.util.ByteCount
import org.groundplatform.domain.repository.OfflineAreaRepositoryInterface
Expand All @@ -52,7 +52,7 @@ constructor(
private val geocodingManager: GeocodingManager,
private val mogClient: MogClient,
private val offlineUuidGenerator: OfflineUuidGenerator,
): OfflineAreaRepositoryInterface {
) : OfflineAreaRepositoryInterface {

private suspend fun addOfflineArea(bounds: Bounds, zoomRange: IntRange) {
val areaName = geocodingManager.getAreaName(bounds.shrink(AREA_NAME_SENSITIVITY))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ constructor(
private val networkManager: NetworkManager,
private val remoteDataStore: RemoteDataStore,
private val localValueStore: LocalValueStore,
): TermsOfServiceRepositoryInterface {
) : TermsOfServiceRepositoryInterface {

override var isTermsOfServiceAccepted: Boolean by localValueStore::isTermsOfServiceAccepted

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ constructor(
if (ownerId == user.id) return true

// Check if user is a survey organizer
val isOrganizer =
runCatching { surveyRepository.activeSurvey?.getRole(user.email) == Role.SURVEY_ORGANIZER }
.getOrElse { false }
val isOrganizer = runCatching {
surveyRepository.activeSurvey?.getRole(user.email) == Role.SURVEY_ORGANIZER
}
.getOrElse { false }

return isOrganizer
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ fun DataCollectionScreen(
uiState = uiState,
onCloseClicked = { viewModel.onCloseClicked() },
onLoiReportAction = { viewModel.onLoiReportAction(it) },
) {
readyState ->
) { readyState ->
val tasks = readyState.tasks
if (tasks.isNotEmpty()) {
val position = readyState.position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import java.math.RoundingMode
import java.text.DecimalFormat
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
Expand All @@ -51,8 +53,6 @@ import org.groundplatform.android.util.setComposableContent
import org.groundplatform.android.util.toDmsFormat
import org.groundplatform.domain.model.map.CameraPosition
import org.jetbrains.annotations.MustBeInvokedByOverriders
import java.math.RoundingMode
import java.text.DecimalFormat

abstract class AbstractTaskMapFragment<TVM : AbstractTaskViewModel> :
AbstractMapContainerFragment() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import java.text.SimpleDateFormat
import java.util.Date
import org.groundplatform.android.R
import org.groundplatform.android.ui.common.ExcludeFromJacocoGeneratedReport
import org.groundplatform.android.ui.datacollection.TaskPosition
Expand All @@ -44,8 +46,6 @@ import org.groundplatform.android.ui.datacollection.tasks.TaskScreen
import org.groundplatform.domain.model.submission.DateTimeTaskData
import org.groundplatform.ui.theme.AppTheme
import org.groundplatform.ui.theme.sizes
import java.text.SimpleDateFormat
import java.util.Date

const val DATE_PICKER_TEST_TAG: String = "date picker test tag"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fun CaptureLocationTaskScreen(
clazz = CaptureLocationTaskMapFragment::class.java,
arguments = bundleOf(Pair(DataCollectionFragment.TASK_ID, viewModel.task.id)),
)
}
},
) {
val taskActionButtonsStates by viewModel.taskActionButtonStates.collectAsStateWithLifecycle()
val showAccuracyCard by viewModel.showAccuracyCard.collectAsStateWithLifecycle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ import androidx.compose.ui.unit.dp
import ground_android.core.ui.generated.resources.Res
import ground_android.core.ui.generated.resources.other
import org.groundplatform.android.common.Constants
import org.jetbrains.compose.resources.stringResource
import org.groundplatform.android.ui.common.ExcludeFromJacocoGeneratedReport
import org.groundplatform.domain.model.task.MultipleChoice
import org.groundplatform.domain.model.task.Option
import org.groundplatform.ui.theme.AppTheme
import org.jetbrains.compose.resources.stringResource

const val MULTIPLE_CHOICE_ITEM_TEST_TAG = "multiple choice item test tag"
const val OTHER_INPUT_TEXT_TEST_TAG = "other input test tag"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import android.os.Build
import android.os.Build.VERSION_CODES
import androidx.core.net.toUri
import androidx.lifecycle.viewModelScope
import java.io.File
import javax.inject.Inject
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
Expand All @@ -41,8 +43,6 @@ import org.groundplatform.domain.model.submission.isNotNullOrEmpty
import org.groundplatform.domain.model.task.PhotoTaskData
import org.groundplatform.domain.repository.UserMediaRepositoryInterface
import timber.log.Timber
import java.io.File
import javax.inject.Inject

class PhotoTaskViewModel
@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ constructor(
/** Whether the instructions dialog has been shown or not. */
internal var instructionsDialogShown: Boolean by localValueStore::dropPinInstructionsShown



override fun initialize(
job: Job,
task: Task,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import org.groundplatform.android.R
import org.groundplatform.android.ui.common.ExcludeFromJacocoGeneratedReport
import org.groundplatform.android.ui.datacollection.TaskPosition
Expand All @@ -44,9 +47,6 @@ import org.groundplatform.android.ui.datacollection.tasks.TaskScreen
import org.groundplatform.domain.model.submission.DateTimeTaskData
import org.groundplatform.ui.theme.AppTheme
import org.groundplatform.ui.theme.sizes
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date

const val TIME_PICKER_TEST_TAG: String = "time picker test tag"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,17 @@ internal constructor(
viewModelScope.launch { kickLocalMutationSyncWorkers() }
}

val drawerState: StateFlow<HomeDrawerState?> =
flow { emit(userRepository.getAuthenticatedUser()) }
.combine(surveyRepository.activeSurveyFlow) { user, survey ->
HomeDrawerState(
user = user,
survey = survey,
appVersion = org.groundplatform.android.BuildConfig.VERSION_NAME,
)
}
.stateIn(viewModelScope, SharingStarted.Lazily, null)
val drawerState: StateFlow<HomeDrawerState?> = flow {
emit(userRepository.getAuthenticatedUser())
}
.combine(surveyRepository.activeSurveyFlow) { user, survey ->
HomeDrawerState(
user = user,
survey = survey,
appVersion = org.groundplatform.android.BuildConfig.VERSION_NAME,
)
}
.stateIn(viewModelScope, SharingStarted.Lazily, null)

/**
* Enqueue data and photo upload workers for all pending mutations when home screen is first
Expand Down
Loading
Loading