diff --git a/ground/build.gradle b/ground/build.gradle index 648192e903..c00e60b116 100644 --- a/ground/build.gradle +++ b/ground/build.gradle @@ -349,7 +349,7 @@ dependencies { api("com.google.protobuf:protobuf-kotlin-lite:4.26.1") // Pulls protodefs from the specified commit in the ground-platform repo. - groundProtoJar "com.github.google:ground-platform:3e9162a@jar" + groundProtoJar "com.github.google:ground-platform:581946f@jar" } protobuf { diff --git a/ground/src/main/java/com/google/android/ground/model/submission/PhotoTaskData.kt b/ground/src/main/java/com/google/android/ground/model/submission/PhotoTaskData.kt index 1946595e1c..ff6998d446 100644 --- a/ground/src/main/java/com/google/android/ground/model/submission/PhotoTaskData.kt +++ b/ground/src/main/java/com/google/android/ground/model/submission/PhotoTaskData.kt @@ -21,24 +21,10 @@ import kotlinx.serialization.Serializable /** Represents a single photo associated with a given [LocationOfInterest] and [Job]. */ @Serializable -class PhotoTaskData : TaskData { - val surveyId: String - val path: String - val filename: String - get() = path.split("/").last() +class PhotoTaskData(val remoteFilename: String) : TaskData { + override fun getDetailsText(): String = remoteFilename - constructor(path: String, surveyId: String) { - val filePattern = Regex("^[a-zA-Z0-9._ -]+\\.(png|jpg)$") - val filename = path.split("/").last() - require(filename.matches(filePattern)) { "Invalid photo file name" } + override fun isEmpty(): Boolean = remoteFilename.isEmpty() - this.path = path - this.surveyId = surveyId - } - - override fun getDetailsText(): String = path - - override fun isEmpty(): Boolean = path.isEmpty() - - override fun toString(): String = path + override fun toString(): String = remoteFilename } diff --git a/ground/src/main/java/com/google/android/ground/persistence/local/room/converter/ValueJsonConverter.kt b/ground/src/main/java/com/google/android/ground/persistence/local/room/converter/ValueJsonConverter.kt index 7dbb1ed264..24b71efe1c 100644 --- a/ground/src/main/java/com/google/android/ground/persistence/local/room/converter/ValueJsonConverter.kt +++ b/ground/src/main/java/com/google/android/ground/persistence/local/room/converter/ValueJsonConverter.kt @@ -30,7 +30,6 @@ import com.google.android.ground.model.submission.TextTaskData import com.google.android.ground.model.submission.TimeTaskData import com.google.android.ground.model.task.Task import com.google.android.ground.persistence.remote.DataStoreException -import com.google.android.ground.persistence.remote.firebase.FirebaseStorageManager import com.google.android.ground.persistence.remote.firebase.schema.CaptureLocationResultConverter.ACCURACY_KEY import com.google.android.ground.persistence.remote.firebase.schema.CaptureLocationResultConverter.ALTITUDE_KEY import com.google.android.ground.persistence.remote.firebase.schema.CaptureLocationResultConverter.GEOMETRY_KEY @@ -56,8 +55,7 @@ internal object ValueJsonConverter { is NumberTaskData -> taskData.value is DateTaskData -> dateToIsoString(taskData.date) is TimeTaskData -> dateToIsoString(taskData.time) - is PhotoTaskData -> - FirebaseStorageManager.getRemoteMediaPath(taskData.surveyId, taskData.filename) + is PhotoTaskData -> taskData.remoteFilename is DrawAreaTaskData -> GeometryWrapperTypeConverter.toString(taskData.geometry) is DropPinTaskData -> GeometryWrapperTypeConverter.toString(taskData.geometry) is CaptureLocationTaskData -> @@ -89,11 +87,14 @@ internal object ValueJsonConverter { return null } return when (task.type) { - Task.Type.TEXT, - Task.Type.PHOTO -> { + Task.Type.TEXT -> { DataStoreException.checkType(String::class.java, obj) TextTaskData.fromString(obj as String) } + Task.Type.PHOTO -> { + DataStoreException.checkType(String::class.java, obj) + PhotoTaskData(obj as String) + } Task.Type.MULTIPLE_CHOICE -> { DataStoreException.checkType(JSONArray::class.java, obj) MultipleChoiceTaskData.fromList(task.multipleChoice, toList(obj as JSONArray)) diff --git a/ground/src/main/java/com/google/android/ground/persistence/remote/firebase/protobuf/ModelToProtoExt.kt b/ground/src/main/java/com/google/android/ground/persistence/remote/firebase/protobuf/ModelToProtoExt.kt index a409b1e6ba..6e7f225e7d 100644 --- a/ground/src/main/java/com/google/android/ground/persistence/remote/firebase/protobuf/ModelToProtoExt.kt +++ b/ground/src/main/java/com/google/android/ground/persistence/remote/firebase/protobuf/ModelToProtoExt.kt @@ -167,7 +167,8 @@ private fun ValueDelta.toMessage() = taskData { // TODO: Add timestamp } Task.Type.PHOTO -> takePhotoResult = takePhotoResult { - photoPath = (newTaskData as PhotoTaskData).path + val data = newTaskData as PhotoTaskData + photoPath = data.remoteFilename } Task.Type.UNKNOWN -> error("Unknown task type") } @@ -176,6 +177,7 @@ private fun ValueDelta.toMessage() = taskData { private fun createAuditInfoMessage(user: User, timestamp: Date) = auditInfo { userId = user.id displayName = user.displayName + emailAddress = user.email photoUrl = user.photoUrl ?: photoUrl clientTimestamp = timestamp.toMessage() serverTimestamp = timestamp.toMessage() diff --git a/ground/src/main/java/com/google/android/ground/persistence/sync/LocalMutationSyncWorker.kt b/ground/src/main/java/com/google/android/ground/persistence/sync/LocalMutationSyncWorker.kt index 55bf3cb74d..2bc24ed7fd 100644 --- a/ground/src/main/java/com/google/android/ground/persistence/sync/LocalMutationSyncWorker.kt +++ b/ground/src/main/java/com/google/android/ground/persistence/sync/LocalMutationSyncWorker.kt @@ -121,6 +121,7 @@ constructor( } catch (t: Throwable) { // Mark all mutations as having failed since the remote datastore only commits when all // mutations have succeeded. + Timber.d(t, "Local mutation sync failed") mutationRepository.markAsFailed(mutations, t) false } diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/photo/PhotoTaskViewModel.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/photo/PhotoTaskViewModel.kt index 9ff665b17e..256e17451d 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/photo/PhotoTaskViewModel.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/photo/PhotoTaskViewModel.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import com.google.android.ground.model.submission.PhotoTaskData import com.google.android.ground.model.submission.isNotNullOrEmpty +import com.google.android.ground.persistence.remote.firebase.FirebaseStorageManager import com.google.android.ground.repository.UserMediaRepository import com.google.android.ground.ui.datacollection.tasks.AbstractTaskViewModel import com.google.android.ground.ui.util.BitmapUtil @@ -61,7 +62,8 @@ constructor( val bitmap = bitmapUtil.fromUri(uri) val file = userMediaRepository.savePhoto(bitmap, currentTask) userMediaRepository.addImageToGallery(file.absolutePath, file.name) - setValue(PhotoTaskData(file.absolutePath, surveyId)) + val remoteFilename = FirebaseStorageManager.getRemoteMediaPath(surveyId, file.absolutePath) + setValue(PhotoTaskData(remoteFilename)) } catch (e: IOException) { Timber.e(e, "Error getting photo selected from storage") } diff --git a/ground/src/test/java/com/google/android/ground/persistence/remote/firebase/protobuf/ModelToProtoExtKtTest.kt b/ground/src/test/java/com/google/android/ground/persistence/remote/firebase/protobuf/ModelToProtoExtKtTest.kt index db9500475b..2871e4eb5f 100644 --- a/ground/src/test/java/com/google/android/ground/persistence/remote/firebase/protobuf/ModelToProtoExtKtTest.kt +++ b/ground/src/test/java/com/google/android/ground/persistence/remote/firebase/protobuf/ModelToProtoExtKtTest.kt @@ -68,14 +68,16 @@ class ModelToProtoExtKtTest { ownerId = "userId" created = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L } } lastModified = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L } @@ -118,14 +120,16 @@ class ModelToProtoExtKtTest { ownerId = "userId" created = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L } } lastModified = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L } @@ -175,14 +179,16 @@ class ModelToProtoExtKtTest { ownerId = "userId" created = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L } } lastModified = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L } @@ -233,14 +239,16 @@ class ModelToProtoExtKtTest { ownerId = "userId" created = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L } } lastModified = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L } @@ -291,7 +299,8 @@ class ModelToProtoExtKtTest { ownerId = "userId" lastModified = auditInfo { userId = "userId" - displayName = "User" + displayName = user.displayName + emailAddress = user.email photoUrl = "" clientTimestamp = timestamp { seconds = 987654321L } serverTimestamp = timestamp { seconds = 987654321L }