Skip to content

Commit

Permalink
Merge branch 'master' into sufy/2300/labeled-lois
Browse files Browse the repository at this point in the history
  • Loading branch information
sufyanAbbasi committed Mar 6, 2024
2 parents be162c9 + d021127 commit 35adbef
Show file tree
Hide file tree
Showing 27 changed files with 550 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.model.submission

/** Represents a single instance of data being collected by the user. */
data class DraftSubmission(
val id: String,
val jobId: String,
val loiId: String?,
val surveyId: String,
val deltas: List<ValueDelta>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ abstract class LocalDataStoreModule {
@Binds @Singleton abstract fun userStore(store: RoomUserStore): LocalUserStore

companion object {
@Provides
fun draftSubmissionDao(localDatabase: LocalDatabase): DraftSubmissionDao {
return localDatabase.draftSubmissionDao()
}

@Provides
fun locationOfInterestDao(localDatabase: LocalDatabase): LocationOfInterestDao {
return localDatabase.locationOfInterestDao()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ class LocalValueStore @Inject constructor(private val preferences: SharedPrefere
preferences.edit().putBoolean(DRAW_AREA_INSTRUCTIONS_SHOWN, value).apply()
}

var draftSubmissionId: String?
get() = allowThreadDiskReads { preferences.getString(DRAFT_SUBMISSION_ID, null) }
set(value) = allowThreadDiskReads {
preferences.edit().putString(DRAFT_SUBMISSION_ID, value).apply()
}

/** Removes all values stored in the local store. */
fun clear() = allowThreadDiskWrites { preferences.edit().clear().apply() }

Expand Down Expand Up @@ -128,5 +134,6 @@ class LocalValueStore @Inject constructor(private val preferences: SharedPrefere
const val LOCATION_LOCK_ENABLED = "location_lock_enabled"
const val OFFLINE_MAP_IMAGERY = "offline_map_imagery"
const val DRAW_AREA_INSTRUCTIONS_SHOWN = "draw_area_instructions_shown"
const val DRAFT_SUBMISSION_ID = "draft_submission_id"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.google.android.ground.persistence.local.room.converter.JsonObjectType
import com.google.android.ground.persistence.local.room.converter.LoiPropertiesMapConverter
import com.google.android.ground.persistence.local.room.converter.StyleTypeConverter
import com.google.android.ground.persistence.local.room.dao.ConditionDao
import com.google.android.ground.persistence.local.room.dao.DraftSubmissionDao
import com.google.android.ground.persistence.local.room.dao.ExpressionDao
import com.google.android.ground.persistence.local.room.dao.JobDao
import com.google.android.ground.persistence.local.room.dao.LocationOfInterestDao
Expand All @@ -39,6 +40,7 @@ import com.google.android.ground.persistence.local.room.dao.TaskDao
import com.google.android.ground.persistence.local.room.dao.TileSourceDao
import com.google.android.ground.persistence.local.room.dao.UserDao
import com.google.android.ground.persistence.local.room.entity.ConditionEntity
import com.google.android.ground.persistence.local.room.entity.DraftSubmissionEntity
import com.google.android.ground.persistence.local.room.entity.ExpressionEntity
import com.google.android.ground.persistence.local.room.entity.JobEntity
import com.google.android.ground.persistence.local.room.entity.LocationOfInterestEntity
Expand Down Expand Up @@ -72,6 +74,7 @@ import com.google.android.ground.persistence.local.room.fields.TileSetEntityStat
@Database(
entities =
[
DraftSubmissionEntity::class,
LocationOfInterestEntity::class,
LocationOfInterestMutationEntity::class,
TaskEntity::class,
Expand Down Expand Up @@ -107,6 +110,8 @@ import com.google.android.ground.persistence.local.room.fields.TileSetEntityStat
LoiPropertiesMapConverter::class,
)
abstract class LocalDatabase : RoomDatabase() {
abstract fun draftSubmissionDao(): DraftSubmissionDao

abstract fun locationOfInterestDao(): LocationOfInterestDao

abstract fun locationOfInterestMutationDao(): LocationOfInterestMutationDao
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.google.android.ground.model.job.Style
import com.google.android.ground.model.locationofinterest.LocationOfInterest
import com.google.android.ground.model.mutation.LocationOfInterestMutation
import com.google.android.ground.model.mutation.SubmissionMutation
import com.google.android.ground.model.submission.DraftSubmission
import com.google.android.ground.model.submission.Submission
import com.google.android.ground.model.submission.SubmissionData
import com.google.android.ground.model.task.Condition
Expand Down Expand Up @@ -488,3 +489,27 @@ fun ExpressionEntity.toModelObject(): Expression =
taskId = taskId,
optionIds = optionIds?.split(',')?.toSet() ?: setOf(),
)

@Throws(LocalDataConsistencyException::class)
fun DraftSubmissionEntity.toModelObject(survey: Survey): DraftSubmission {
val job =
survey.getJob(jobId)
?: throw LocalDataConsistencyException("Unknown jobId in submission mutation $id")

return DraftSubmission(
id = id,
jobId = jobId,
loiId = loiId,
surveyId = surveyId,
deltas = SubmissionDeltasConverter.fromString(job, deltas),
)
}

fun DraftSubmission.toLocalDataStoreObject() =
DraftSubmissionEntity(
id = id,
jobId = jobId,
loiId = loiId,
surveyId = surveyId,
deltas = SubmissionDeltasConverter.toString(deltas),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.persistence.local.room.dao

import androidx.room.Dao
import androidx.room.Query
import com.google.android.ground.persistence.local.room.entity.DraftSubmissionEntity

/** Data access object for database operations related to [DraftSubmissionDao]. */
@Dao
interface DraftSubmissionDao : BaseDao<DraftSubmissionEntity> {

@Query("SELECT * FROM draft_submission WHERE id = :draftSubmissionId")
suspend fun findById(draftSubmissionId: String): DraftSubmissionEntity?

@Query("DELETE FROM draft_submission") fun delete()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.persistence.local.room.entity

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.google.android.ground.model.submission.DraftSubmission

/** Representation of a [DraftSubmission] in local db. */
@Entity(tableName = "draft_submission", indices = [Index("loi_id", "job_id", "survey_id")])
data class DraftSubmissionEntity(
@ColumnInfo(name = "id") @PrimaryKey val id: String,
@ColumnInfo(name = "job_id") val jobId: String,
@ColumnInfo(name = "loi_id") val loiId: String?,
@ColumnInfo(name = "survey_id") val surveyId: String,
@ColumnInfo(name = "deltas") val deltas: String?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.google.android.ground.model.job.Job
import com.google.android.ground.model.locationofinterest.LocationOfInterest
import com.google.android.ground.model.mutation.Mutation
import com.google.android.ground.model.mutation.SubmissionMutation
import com.google.android.ground.model.submission.DraftSubmission
import com.google.android.ground.model.submission.Submission
import com.google.android.ground.model.submission.SubmissionData
import com.google.android.ground.model.submission.ValueDelta
Expand All @@ -30,6 +31,7 @@ import com.google.android.ground.persistence.local.room.converter.SubmissionData
import com.google.android.ground.persistence.local.room.converter.SubmissionDeltasConverter
import com.google.android.ground.persistence.local.room.converter.toLocalDataStoreObject
import com.google.android.ground.persistence.local.room.converter.toModelObject
import com.google.android.ground.persistence.local.room.dao.DraftSubmissionDao
import com.google.android.ground.persistence.local.room.dao.SubmissionDao
import com.google.android.ground.persistence.local.room.dao.SubmissionMutationDao
import com.google.android.ground.persistence.local.room.dao.insertOrUpdate
Expand All @@ -53,6 +55,7 @@ import timber.log.Timber
/** Manages access to [Submission] objects persisted in local storage. */
@Singleton
class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore {
@Inject lateinit var draftSubmissionDao: DraftSubmissionDao
@Inject lateinit var submissionDao: SubmissionDao
@Inject lateinit var submissionMutationDao: SubmissionMutationDao
@Inject lateinit var userStore: RoomUserStore
Expand All @@ -64,7 +67,7 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore
*/
override suspend fun getSubmission(
locationOfInterest: LocationOfInterest,
submissionId: String
submissionId: String,
): Submission =
submissionDao.findById(submissionId)?.toModelObject(locationOfInterest)
?: throw LocalDataStoreException("Submission not found $submissionId")
Expand All @@ -76,7 +79,7 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore
*/
override suspend fun getSubmissions(
locationOfInterest: LocationOfInterest,
jobId: String
jobId: String,
): List<Submission> =
submissionDao
.findByLocationOfInterestId(locationOfInterest.id, jobId, EntityState.DEFAULT)
Expand All @@ -87,7 +90,7 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore
.findBySubmissionId(
model.id,
MutationEntitySyncStatus.PENDING,
MutationEntitySyncStatus.IN_PROGRESS
MutationEntitySyncStatus.IN_PROGRESS,
)
?.let { mergeSubmission(model.job, model.toLocalDataStoreObject(), it) }
}
Expand Down Expand Up @@ -145,7 +148,7 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore
private suspend fun mergeSubmission(
job: Job,
submission: SubmissionEntity,
mutations: List<SubmissionMutationEntity>
mutations: List<SubmissionMutationEntity>,
) {
if (mutations.isEmpty()) {
submissionDao.insertOrUpdate(submission)
Expand All @@ -160,7 +163,7 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore
job: Job?,
submission: SubmissionEntity,
mutations: List<SubmissionMutationEntity>,
user: User
user: User,
): SubmissionEntity {
val lastMutation = mutations[mutations.size - 1]
val clientTimestamp = lastMutation.clientTimestamp
Expand All @@ -169,14 +172,14 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore

return submission.copy(
data = SubmissionDataConverter.toString(commitMutations(job, submission, mutations)),
lastModified = AuditInfoEntity(UserDetails.fromUser(user), clientTimestamp)
lastModified = AuditInfoEntity(UserDetails.fromUser(user), clientTimestamp),
)
}

private fun commitMutations(
job: Job?,
submission: SubmissionEntity,
mutations: List<SubmissionMutationEntity>
mutations: List<SubmissionMutationEntity>,
): SubmissionData {
val responseMap = SubmissionDataConverter.fromString(job!!, submission.data)
val deltas = mutableListOf<ValueDelta>()
Expand All @@ -194,7 +197,7 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore
override fun getSubmissionMutationsByLoiIdFlow(
survey: Survey,
locationOfInterestId: String,
vararg allowedStates: MutationEntitySyncStatus
vararg allowedStates: MutationEntitySyncStatus,
): Flow<List<SubmissionMutation>> =
submissionMutationDao.findByLoiIdFlow(locationOfInterestId, *allowedStates).map {
list: List<SubmissionMutationEntity> ->
Expand All @@ -220,21 +223,34 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore

override suspend fun findByLocationOfInterestId(
loidId: String,
vararg states: MutationEntitySyncStatus
vararg states: MutationEntitySyncStatus,
): List<SubmissionMutationEntity> =
submissionMutationDao.findByLocationOfInterestId(loidId, *states)

override suspend fun getPendingCreateCount(loiId: String): Int =
submissionMutationDao.getSubmissionMutationCount(
loiId,
MutationEntityType.CREATE,
MutationEntitySyncStatus.PENDING
MutationEntitySyncStatus.PENDING,
)

override suspend fun getPendingDeleteCount(loiId: String): Int =
submissionMutationDao.getSubmissionMutationCount(
loiId,
MutationEntityType.DELETE,
MutationEntitySyncStatus.PENDING
MutationEntitySyncStatus.PENDING,
)

override suspend fun getDraftSubmission(
draftSubmissionId: String,
survey: Survey,
): DraftSubmission? = draftSubmissionDao.findById(draftSubmissionId)?.toModelObject(survey)

override suspend fun saveDraftSubmission(draftSubmission: DraftSubmission) {
draftSubmissionDao.insertOrUpdate(draftSubmission.toLocalDataStoreObject())
}

override suspend fun deleteDraftSubmissions() {
draftSubmissionDao.delete()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.android.ground.persistence.local.stores
import com.google.android.ground.model.Survey
import com.google.android.ground.model.locationofinterest.LocationOfInterest
import com.google.android.ground.model.mutation.SubmissionMutation
import com.google.android.ground.model.submission.DraftSubmission
import com.google.android.ground.model.submission.Submission
import com.google.android.ground.persistence.local.room.entity.SubmissionMutationEntity
import com.google.android.ground.persistence.local.room.fields.MutationEntitySyncStatus
Expand All @@ -30,13 +31,13 @@ interface LocalSubmissionStore : LocalMutationStore<SubmissionMutation, Submissi
*/
suspend fun getSubmissions(
locationOfInterest: LocationOfInterest,
jobId: String
jobId: String,
): List<Submission>

/** Returns the submission with the specified UUID from the local data store, if found. */
suspend fun getSubmission(
locationOfInterest: LocationOfInterest,
submissionId: String
submissionId: String,
): Submission

/** Deletes submission from local database. */
Expand All @@ -49,7 +50,7 @@ interface LocalSubmissionStore : LocalMutationStore<SubmissionMutation, Submissi
fun getSubmissionMutationsByLoiIdFlow(
survey: Survey,
locationOfInterestId: String,
vararg allowedStates: MutationEntitySyncStatus
vararg allowedStates: MutationEntitySyncStatus,
): Flow<List<SubmissionMutation>>

/**
Expand All @@ -60,10 +61,19 @@ interface LocalSubmissionStore : LocalMutationStore<SubmissionMutation, Submissi

suspend fun findByLocationOfInterestId(
loidId: String,
vararg states: MutationEntitySyncStatus
vararg states: MutationEntitySyncStatus,
): List<SubmissionMutationEntity>

suspend fun getPendingCreateCount(loiId: String): Int

suspend fun getPendingDeleteCount(loiId: String): Int

/** Fetches the draft submission for the given UUID from local database. */
suspend fun getDraftSubmission(draftSubmissionId: String, survey: Survey): DraftSubmission?

/** Saves the given draft submission to local database. */
suspend fun saveDraftSubmission(draftSubmission: DraftSubmission)

/** Removes all locally stored draft submissions. */
suspend fun deleteDraftSubmissions()
}
Loading

0 comments on commit 35adbef

Please sign in to comment.