diff --git a/build.gradle b/build.gradle index 8fd0480b70..4732550734 100644 --- a/build.gradle +++ b/build.gradle @@ -58,9 +58,9 @@ plugins { id "com.github.ben-manes.versions" version "0.36.0" id "org.jetbrains.kotlin.android" version "$kotlinVersion" apply false id "org.jetbrains.kotlin.plugin.serialization" version "$kotlinVersion" - id "com.ncorti.ktfmt.gradle" version "0.11.0" + id "com.ncorti.ktfmt.gradle" version "0.17.0" id "com.google.dagger.hilt.android" version "$hiltVersion" apply false - id "io.gitlab.arturbosch.detekt" version "1.23.0" + id "io.gitlab.arturbosch.detekt" version "1.23.5" } allprojects { @@ -85,7 +85,7 @@ subprojects { } detekt { - toolVersion = "1.23.0" + toolVersion = "1.23.5" source = files("ground/src", "sharedTest/src") allRules = true diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index f3eb5c20c6..0ac87eef03 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -527,7 +527,7 @@ style: CanBeNonNullable: active: true CascadingCallWrapping: - active: true + active: false includeElvis: true ClassOrdering: active: true @@ -674,8 +674,6 @@ style: active: true OptionalUnit: active: true - OptionalWhenBraces: - active: false PreferToOverPairSyntax: active: true ProtectedMemberInFinalClass: diff --git a/ground/src/main/java/com/google/android/ground/Config.kt b/ground/src/main/java/com/google/android/ground/Config.kt index 26ad279bf2..fe7f8a57c6 100644 --- a/ground/src/main/java/com/google/android/ground/Config.kt +++ b/ground/src/main/java/com/google/android/ground/Config.kt @@ -57,6 +57,7 @@ object Config { // TODO(#1730): Make sub-paths configurable and stop hardcoding here. const val DEFAULT_MOG_MIN_ZOOM = 8 const val DEFAULT_MOG_MAX_ZOOM = 14 + fun getMogSources(path: String = "/offline-imagery/default/") = listOf( MogSource( diff --git a/ground/src/main/java/com/google/android/ground/MainViewModel.kt b/ground/src/main/java/com/google/android/ground/MainViewModel.kt index 438438ef5a..27da61223e 100644 --- a/ground/src/main/java/com/google/android/ground/MainViewModel.kt +++ b/ground/src/main/java/com/google/android/ground/MainViewModel.kt @@ -126,7 +126,7 @@ constructor( } private fun getDirectionAfterSignIn(): NavDirections = - if (surveyRepository.activeSurvey != null) { + if (surveyRepository.selectedSurveyId != null) { HomeScreenFragmentDirections.showHomeScreen() } else { SurveySelectorFragmentDirections.showSurveySelectorScreen(true) diff --git a/ground/src/main/java/com/google/android/ground/domain/usecases/survey/ActivateSurveyUseCase.kt b/ground/src/main/java/com/google/android/ground/domain/usecases/survey/ActivateSurveyUseCase.kt index f5235f5072..b3a108448a 100644 --- a/ground/src/main/java/com/google/android/ground/domain/usecases/survey/ActivateSurveyUseCase.kt +++ b/ground/src/main/java/com/google/android/ground/domain/usecases/survey/ActivateSurveyUseCase.kt @@ -39,7 +39,8 @@ constructor( } surveyRepository.getOfflineSurvey(surveyId) - ?: makeSurveyAvailableOffline(surveyId) ?: error("Survey $surveyId not found in remote db") + ?: makeSurveyAvailableOffline(surveyId) + ?: error("Survey $surveyId not found in remote db") surveyRepository.selectedSurveyId = surveyId } diff --git a/ground/src/main/java/com/google/android/ground/model/AuditInfo.kt b/ground/src/main/java/com/google/android/ground/model/AuditInfo.kt index 4c410b13fc..25734d6b08 100644 --- a/ground/src/main/java/com/google/android/ground/model/AuditInfo.kt +++ b/ground/src/main/java/com/google/android/ground/model/AuditInfo.kt @@ -21,11 +21,11 @@ import java.util.Date * User details and timestamp for creation or modification of a model object. * * @property user the user initiating the related action. This can never be null, since users must - * always be logged in to make changes. + * always be logged in to make changes. * @property clientTimestamp the time at which the user action was initiated, according to the - * user's device. Defaults to the current time if unspecified. + * user's device. Defaults to the current time if unspecified. * @property serverTimestamp the time at which the server received the requested change according to - * the server's internal clock, or empty if the updated server time was not yet received. + * the server's internal clock, or empty if the updated server time was not yet received. */ data class AuditInfo( val user: User, diff --git a/ground/src/main/java/com/google/android/ground/model/submission/Submission.kt b/ground/src/main/java/com/google/android/ground/model/submission/Submission.kt index 02be43901e..ff69ea4225 100644 --- a/ground/src/main/java/com/google/android/ground/model/submission/Submission.kt +++ b/ground/src/main/java/com/google/android/ground/model/submission/Submission.kt @@ -24,7 +24,7 @@ import com.google.android.ground.model.locationofinterest.LocationOfInterest * * @property created the user and time audit info pertaining to the creation of this submission. * @property lastModified the user and time audit info pertaining to the last modification of this - * submission. + * submission. */ data class Submission @JvmOverloads diff --git a/ground/src/main/java/com/google/android/ground/model/submission/SubmissionData.kt b/ground/src/main/java/com/google/android/ground/model/submission/SubmissionData.kt index 07b3e74db9..5dab702c4d 100644 --- a/ground/src/main/java/com/google/android/ground/model/submission/SubmissionData.kt +++ b/ground/src/main/java/com/google/android/ground/model/submission/SubmissionData.kt @@ -19,7 +19,7 @@ package com.google.android.ground.model.submission * An immutable map of task ids to submitted data values. * * @property data A map from task id to values. This map is mutable and therefore should never be - * exposed outside this class. + * exposed outside this class. */ // TODO: Merge into Submission? data class SubmissionData(private val data: Map = mapOf()) { diff --git a/ground/src/main/java/com/google/android/ground/persistence/local/LocalDataStoreModule.kt b/ground/src/main/java/com/google/android/ground/persistence/local/LocalDataStoreModule.kt index 734bc6930d..68eb382b71 100644 --- a/ground/src/main/java/com/google/android/ground/persistence/local/LocalDataStoreModule.kt +++ b/ground/src/main/java/com/google/android/ground/persistence/local/LocalDataStoreModule.kt @@ -39,8 +39,11 @@ abstract class LocalDataStoreModule { @Binds @Singleton abstract fun offlineAreaStore(store: RoomOfflineAreaStore): LocalOfflineAreaStore + @Binds @Singleton abstract fun submissionStore(store: RoomSubmissionStore): LocalSubmissionStore + @Binds @Singleton abstract fun surveyStore(store: RoomSurveyStore): LocalSurveyStore + @Binds @Singleton abstract fun userStore(store: RoomUserStore): LocalUserStore companion object { diff --git a/ground/src/main/java/com/google/android/ground/persistence/local/room/LocalDatabase.kt b/ground/src/main/java/com/google/android/ground/persistence/local/room/LocalDatabase.kt index cbed5da363..7d9ca82bc1 100644 --- a/ground/src/main/java/com/google/android/ground/persistence/local/room/LocalDatabase.kt +++ b/ground/src/main/java/com/google/android/ground/persistence/local/room/LocalDatabase.kt @@ -108,16 +108,27 @@ import com.google.android.ground.persistence.local.room.fields.TileSetEntityStat ) abstract class LocalDatabase : RoomDatabase() { abstract fun locationOfInterestDao(): LocationOfInterestDao + abstract fun locationOfInterestMutationDao(): LocationOfInterestMutationDao + abstract fun taskDao(): TaskDao + abstract fun jobDao(): JobDao + abstract fun multipleChoiceDao(): MultipleChoiceDao + abstract fun optionDao(): OptionDao + abstract fun surveyDao(): SurveyDao + abstract fun tileSourceDao(): TileSourceDao + abstract fun submissionDao(): SubmissionDao + abstract fun submissionMutationDao(): SubmissionMutationDao + abstract fun offlineAreaDao(): OfflineAreaDao + abstract fun userDao(): UserDao abstract fun conditionDao(): ConditionDao abstract fun expressionDao(): ExpressionDao diff --git a/ground/src/main/java/com/google/android/ground/persistence/local/room/stores/RoomSubmissionStore.kt b/ground/src/main/java/com/google/android/ground/persistence/local/room/stores/RoomSubmissionStore.kt index 0381bb9f0e..e763d010bd 100644 --- a/ground/src/main/java/com/google/android/ground/persistence/local/room/stores/RoomSubmissionStore.kt +++ b/ground/src/main/java/com/google/android/ground/persistence/local/room/stores/RoomSubmissionStore.kt @@ -80,8 +80,7 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore ): List = submissionDao .findByLocationOfInterestId(locationOfInterest.id, jobId, EntityState.DEFAULT) - ?.mapNotNull { logOnFailure { it.toModelObject(locationOfInterest) } } - ?: listOf() + ?.mapNotNull { logOnFailure { it.toModelObject(locationOfInterest) } } ?: listOf() override suspend fun merge(model: Submission) { submissionMutationDao @@ -100,7 +99,7 @@ class RoomSubmissionStore @Inject internal constructor() : LocalSubmissionStore * Applies mutation to submission in database or creates a new one. * * @return A Completable that emits an error if mutation type is "UPDATE" but entity does not - * exist, or if type is "CREATE" and entity already exists. + * exist, or if type is "CREATE" and entity already exists. */ override suspend fun apply(mutation: SubmissionMutation) { when (mutation.type) { diff --git a/ground/src/main/java/com/google/android/ground/repository/LocationOfInterestRepository.kt b/ground/src/main/java/com/google/android/ground/repository/LocationOfInterestRepository.kt index 195e247b86..168e1cc161 100644 --- a/ground/src/main/java/com/google/android/ground/repository/LocationOfInterestRepository.kt +++ b/ground/src/main/java/com/google/android/ground/repository/LocationOfInterestRepository.kt @@ -70,8 +70,7 @@ constructor( suspend fun getOfflineLoi(surveyId: String, locationOfInterest: String): LocationOfInterest = localSurveyStore.getSurveyById(surveyId)?.let { localLoiStore.getLocationOfInterest(it, locationOfInterest) - } - ?: throw NotFoundException("Location of interest not found $locationOfInterest") + } ?: throw NotFoundException("Location of interest not found $locationOfInterest") fun createLocationOfInterest( geometry: Geometry, diff --git a/ground/src/main/java/com/google/android/ground/repository/TermsOfServiceRepository.kt b/ground/src/main/java/com/google/android/ground/repository/TermsOfServiceRepository.kt index 48033ccc20..704972f775 100644 --- a/ground/src/main/java/com/google/android/ground/repository/TermsOfServiceRepository.kt +++ b/ground/src/main/java/com/google/android/ground/repository/TermsOfServiceRepository.kt @@ -40,7 +40,7 @@ constructor( /** * @return [TermsOfService] from remote data store. Otherwise null if the request times out or - * network is unavailable. + * network is unavailable. */ suspend fun getTermsOfService(): TermsOfService? { // TODO(#1691): Maybe parse the exception and display to the user. diff --git a/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogClient.kt b/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogClient.kt index cb5c18798a..78cb0d382f 100644 --- a/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogClient.kt +++ b/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogClient.kt @@ -19,7 +19,6 @@ package com.google.android.ground.ui.map.gms.mog import android.util.LruCache import com.google.android.ground.persistence.remote.RemoteStorageManager import com.google.android.ground.ui.map.Bounds -import java.io.FileNotFoundException import java.io.InputStream import kotlinx.coroutines.Deferred import kotlinx.coroutines.async @@ -50,10 +49,10 @@ class MogClient(val collection: MogCollection, val remoteStorageManager: RemoteS * specified [tileBounds] and [zoomRange]s. * * @param tileBounds the bounds used to constrain which tiles are retrieved. Only requests for - * tiles within or overlapping these bounds are returned. + * tiles within or overlapping these bounds are returned. * @param zoomRange the min. and max. zoom levels for which tile requests should be returned. - * Defaults to all available zoom levels in the collection ([MogSource.minZoom] to - * [MogSource.maxZoom]). + * Defaults to all available zoom levels in the collection ([MogSource.minZoom] to + * [MogSource.maxZoom]). */ suspend fun buildTilesRequests( tileBounds: Bounds, @@ -187,11 +186,11 @@ class MogClient(val collection: MogCollection, val remoteStorageManager: RemoteS private suspend fun MogPathOrUrl.toUrl(): MogUrl? = if (startsWith("/")) { - nullIfNotFound { remoteStorageManager.getDownloadUrl(this).toString() } + nullIfError { remoteStorageManager.getDownloadUrl(this).toString() } } else this private fun MogUrl.readMetadata(mogBounds: TileCoordinates): MogMetadata? = - nullIfNotFound { UrlInputStream(this) }?.use { this.readMogMetadataAndClose(mogBounds, it) } + nullIfError { UrlInputStream(this) }?.use { this.readMogMetadataAndClose(mogBounds, it) } /** Reads the metadata from the specified input stream. */ private fun MogUrl.readMogMetadataAndClose( @@ -208,9 +207,10 @@ class MogClient(val collection: MogCollection, val remoteStorageManager: RemoteS } } -private inline fun nullIfNotFound(fn: () -> T) = +private inline fun nullIfError(fn: () -> T) = try { fn() - } catch (_: FileNotFoundException) { + } catch (e: Exception) { + Timber.e(e) null } diff --git a/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogImageMetadata.kt b/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogImageMetadata.kt index 2febf295eb..d3af392fac 100644 --- a/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogImageMetadata.kt +++ b/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogImageMetadata.kt @@ -70,6 +70,7 @@ data class MogImageMetadata( // against accidental usage by throwing exception if called. @Suppress("detekt:ExceptionRaisedInUnexpectedLocation") override fun equals(other: Any?) = throw UnsupportedOperationException() + @Suppress("detekt:ExceptionRaisedInUnexpectedLocation") override fun hashCode() = throw UnsupportedOperationException() @@ -86,8 +87,7 @@ data class MogImageMetadata( tiffTagToValue[TiffTag.ImageLength] as Int, (tiffTagToValue[TiffTag.JPEGTables] as? List<*>) ?.map { (it as Int).toByte() } - ?.toByteArray() - ?: byteArrayOf(), + ?.toByteArray() ?: byteArrayOf(), (tiffTagToValue[TiffTag.GdalNodata] as? String)?.toIntOrNull() ) } diff --git a/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogTileMetadata.kt b/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogTileMetadata.kt index 42359e3b8e..ae0014da33 100644 --- a/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogTileMetadata.kt +++ b/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogTileMetadata.kt @@ -29,6 +29,7 @@ data class MogTileMetadata( // against accidental usage by throwing exception if called. @Suppress("detekt:ExceptionRaisedInUnexpectedLocation") override fun equals(other: Any?) = throw UnsupportedOperationException() + @Suppress("detekt:ExceptionRaisedInUnexpectedLocation") override fun hashCode() = throw UnsupportedOperationException() } diff --git a/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogTilesRequest.kt b/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogTilesRequest.kt index 392a85071b..1f65b42cee 100644 --- a/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogTilesRequest.kt +++ b/ground/src/main/java/com/google/android/ground/ui/map/gms/mog/MogTilesRequest.kt @@ -21,6 +21,7 @@ package com.google.android.ground.ui.map.gms.mog open class MogTilesRequest(val sourceUrl: String, val tiles: List) { val totalBytes: Int get() = tiles.sumOf { it.byteRange.count() } + val byteRange: LongRange get() = LongRange(tiles.first().byteRange.first, tiles.last().byteRange.last) @@ -29,9 +30,7 @@ open class MogTilesRequest(val sourceUrl: String, val tiles: List