diff --git a/.circleci/config.yml b/.circleci/config.yml index 8b971513ad2..e195b2e49f1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -365,13 +365,17 @@ workflows: - compile filters: branches: - ignore: master + ignore: + - master + - next - test_smoke_instrumented: requires: - compile filters: branches: - only: master + only: + - master + - next nightly: triggers: @@ -381,6 +385,7 @@ workflows: branches: only: - master + - next jobs: - compile - test_instrumented: diff --git a/README.md b/README.md index 8d14e524145..0b36ae17392 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Please note that the `master` branch reflects ongoing development and is not pro ## Release cycle Releases can be requested by any community member and generally happen every 2 months. -Before release we perform a "code freeze" (we stop merging pull requests) and then carry out regression testing. If any problems are found, the release is blocked until we can merge fixes. Once the process is complete, [@lognaturel](https://github.com/lognaturel) pushes the releases to the Play Store following [these instructions](#creating-signed-releases-for-google-play-store). The code is "unfrozen" after a short grace period to make hot fixing easier. +Before release we perform a "code freeze" (we stop merging pull requests). A new beta will then be released to the Play Store by [@lognaturel](https://github.com/lognaturel) and that will be used for regression testing by [@getodk/testers](https://github.com/orgs/getodk/teams/testers). If any problems are found, the release is blocked until we can merge fixes. Regression testing should continue on the original beta build (rather than a new one with fixes) unless problems block the rest of testing. Once the process is complete, [@lognaturel](https://github.com/lognaturel) pushes the releases to the Play Store following [these instructions](#creating-signed-releases-for-google-play-store). The code is "unfrozen" after a short grace period to make hot fixing easier. At the beginning of each release cycle, [@grzesiek2010](https://github.com/grzesiek2010) updates all dependencies that have compatible upgrades available and ensures that the build targets the latest SDK. diff --git a/analytics/build.gradle b/analytics/build.gradle index 803957aa825..c8cc8bfa378 100644 --- a/analytics/build.gradle +++ b/analytics/build.gradle @@ -29,9 +29,7 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } + namespace 'org.odk.collect.analytics' } diff --git a/androidshared/build.gradle b/androidshared/build.gradle index e420bcb0a0c..fe03e18cf2c 100644 --- a/androidshared/build.gradle +++ b/androidshared/build.gradle @@ -36,10 +36,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests { includeAndroidResources = true diff --git a/androidshared/src/androidTest/java/org/odk/collect/androidshared/bitmap/ImageCompressorTest.kt b/androidshared/src/androidTest/java/org/odk/collect/androidshared/bitmap/ImageCompressorTest.kt index 866a171d49f..72fee68da20 100644 --- a/androidshared/src/androidTest/java/org/odk/collect/androidshared/bitmap/ImageCompressorTest.kt +++ b/androidshared/src/androidTest/java/org/odk/collect/androidshared/bitmap/ImageCompressorTest.kt @@ -156,7 +156,7 @@ class ImageCompressorTest { // unsupported exif tags ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH to "5", - ExifInterface.TAG_DNG_VERSION to "100", + ExifInterface.TAG_DNG_VERSION to "100" ) saveTestBitmap(3000, 4000, attributes) diff --git a/androidshared/src/androidTest/java/org/odk/collect/androidshared/bitmap/ImageFileUtilsTest.kt b/androidshared/src/androidTest/java/org/odk/collect/androidshared/bitmap/ImageFileUtilsTest.kt index 749f5bea83a..b7d97c1cc31 100644 --- a/androidshared/src/androidTest/java/org/odk/collect/androidshared/bitmap/ImageFileUtilsTest.kt +++ b/androidshared/src/androidTest/java/org/odk/collect/androidshared/bitmap/ImageFileUtilsTest.kt @@ -180,7 +180,8 @@ class ImageFileUtilsTest { assertEquals( ExifInterface.ORIENTATION_UNDEFINED, exifData.getAttributeInt( - ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_UNDEFINED ) ) } diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/bitmap/ImageFileUtils.kt b/androidshared/src/main/java/org/odk/collect/androidshared/bitmap/ImageFileUtils.kt index fec44ca3478..ad1b8d18310 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/bitmap/ImageFileUtils.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/bitmap/ImageFileUtils.kt @@ -26,9 +26,11 @@ object ImageFileUtils { @JvmStatic fun saveBitmapToFile(bitmap: Bitmap?, path: String) { val compressFormat = - if (path.lowercase(Locale.getDefault()).endsWith(".png")) + if (path.lowercase(Locale.getDefault()).endsWith(".png")) { CompressFormat.PNG - else CompressFormat.JPEG + } else { + CompressFormat.JPEG + } try { if (bitmap != null) { FileOutputStream(path).use { out -> bitmap.compress(compressFormat, IMAGE_COMPRESS_QUALITY, out) } @@ -101,7 +103,9 @@ object ImageFileUtils { if (bitmap != null) { bitmap = Bitmap.createScaledBitmap( bitmap, - newWidth.toInt(), newHeight.toInt(), false + newWidth.toInt(), + newHeight.toInt(), + false ) } } else { @@ -122,7 +126,11 @@ object ImageFileUtils { if (bitmap != null) { Timber.i( "Screen is %dx%d. Image has been scaled down by %f to %dx%d", - screenHeight, screenWidth, scale, bitmap.height, bitmap.width + screenHeight, + screenWidth, + scale, + bitmap.height, + bitmap.width ) } return bitmap diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/ui/PrefUtils.kt b/androidshared/src/main/java/org/odk/collect/androidshared/ui/PrefUtils.kt index 70359014f8a..1bbc52ebe5e 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/ui/PrefUtils.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/ui/PrefUtils.kt @@ -13,7 +13,7 @@ object PrefUtils { title: String, labelIds: IntArray, values: Array, - settings: Settings, + settings: Settings ): ListPreference { val labels: Array = labelIds.map { context.getString(it) }.toTypedArray() return createListPref(context, key, title, labels, values, settings) @@ -48,7 +48,7 @@ object PrefUtils { title: String, labels: Array, values: Array, - settings: Settings, + settings: Settings ): ListPreference { ensurePrefHasValidValue(key, values, settings) return ListPreference(context).also { @@ -65,7 +65,7 @@ object PrefUtils { private fun ensurePrefHasValidValue( key: String, validValues: Array, - settings: Settings, + settings: Settings ) { val value = settings.getString(key) if (validValues.indexOf(value) < 0) { diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeButton.kt b/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeButton.kt index 743a014da86..8e6898cbd88 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeButton.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeButton.kt @@ -9,7 +9,8 @@ class MultiClickSafeButton : MaterialButton { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super( - context, attrs + context, + attrs ) override fun performClick(): Boolean { diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeImageButton.kt b/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeImageButton.kt index dee75c71808..109c7355cf4 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeImageButton.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeImageButton.kt @@ -9,7 +9,8 @@ class MultiClickSafeImageButton : AppCompatImageButton { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super( - context, attrs + context, + attrs ) override fun performClick(): Boolean { diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeTextInputEditText.kt b/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeTextInputEditText.kt index 3a46eb40210..a1c68f1f8f1 100644 --- a/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeTextInputEditText.kt +++ b/androidshared/src/main/java/org/odk/collect/androidshared/ui/multiclicksafe/MultiClickSafeTextInputEditText.kt @@ -9,7 +9,8 @@ class MultiClickSafeTextInputEditText : TextInputEditText { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super( - context, attrs + context, + attrs ) override fun performClick(): Boolean { diff --git a/androidshared/src/test/java/org/odk/collect/androidshared/ui/ColorPickerDialogTest.kt b/androidshared/src/test/java/org/odk/collect/androidshared/ui/ColorPickerDialogTest.kt index 71bc0bba19a..23327a3c15c 100644 --- a/androidshared/src/test/java/org/odk/collect/androidshared/ui/ColorPickerDialogTest.kt +++ b/androidshared/src/test/java/org/odk/collect/androidshared/ui/ColorPickerDialogTest.kt @@ -10,8 +10,8 @@ import androidx.test.espresso.action.ViewActions.pressBack import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.ext.junit.runners.AndroidJUnit4 import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.equalToIgnoringCase +import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.nullValue import org.junit.Rule import org.junit.Test diff --git a/androidtest/src/main/java/org/odk/collect/androidtest/FakeLifecycleOwner.kt b/androidtest/src/main/java/org/odk/collect/androidtest/FakeLifecycleOwner.kt index 79747af8fca..e4a216793e4 100644 --- a/androidtest/src/main/java/org/odk/collect/androidtest/FakeLifecycleOwner.kt +++ b/androidtest/src/main/java/org/odk/collect/androidtest/FakeLifecycleOwner.kt @@ -5,18 +5,15 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry class FakeLifecycleOwner : LifecycleOwner { - - private val lifecycle: LifecycleRegistry by lazy { + private val lifecycleRegistry: LifecycleRegistry by lazy { LifecycleRegistry(this).also { it.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) } } + override val lifecycle: LifecycleRegistry = lifecycleRegistry + fun destroy() { lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) } - - override fun getLifecycle(): Lifecycle { - return lifecycle - } } diff --git a/androidtest/src/main/java/org/odk/collect/androidtest/LiveDataTestUtils.kt b/androidtest/src/main/java/org/odk/collect/androidtest/LiveDataTestUtils.kt index 9fc9df1e710..52979d0db24 100644 --- a/androidtest/src/main/java/org/odk/collect/androidtest/LiveDataTestUtils.kt +++ b/androidtest/src/main/java/org/odk/collect/androidtest/LiveDataTestUtils.kt @@ -23,7 +23,7 @@ fun LiveData.getOrAwaitValue( var data: T? = null val latch = CountDownLatch(1) val observer = object : Observer { - override fun onChanged(o: T?) { + override fun onChanged(o: T) { data = o latch.countDown() this@getOrAwaitValue.removeObserver(this) diff --git a/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt b/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt index b63b3465758..10ef02fc661 100644 --- a/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt +++ b/async/src/main/java/org/odk/collect/async/CoroutineAndWorkManagerScheduler.kt @@ -31,7 +31,7 @@ class CoroutineAndWorkManagerScheduler(foregroundContext: CoroutineContext, back .setInputData(workManagerInputData) .build() - workManager.beginUniqueWork(tag, ExistingWorkPolicy.KEEP, workRequest).enqueue() + workManager.beginUniqueWork(tag, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest).enqueue() } override fun networkDeferred(tag: String, spec: TaskSpec, repeatPeriod: Long, inputData: Map) { diff --git a/audioclips/src/main/java/org/odk/collect/audioclips/AudioClipViewModel.kt b/audioclips/src/main/java/org/odk/collect/audioclips/AudioClipViewModel.kt index a549887addd..a3e9422e641 100644 --- a/audioclips/src/main/java/org/odk/collect/audioclips/AudioClipViewModel.kt +++ b/audioclips/src/main/java/org/odk/collect/audioclips/AudioClipViewModel.kt @@ -3,9 +3,9 @@ package org.odk.collect.audioclips import android.media.MediaPlayer import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.map import org.odk.collect.async.Cancellable import org.odk.collect.async.Scheduler import java.io.File @@ -57,7 +57,7 @@ class AudioClipViewModel(private val mediaPlayerFactory: Supplier, } fun isPlaying(clipID: String): LiveData { - return Transformations.map(currentlyPlaying) { value -> + return currentlyPlaying.map { value -> if (isCurrentPlayingClip(clipID, value)) { !value!!.isPaused } else { diff --git a/audioclips/src/test/java/org/odk/collect/audioclips/AudioClipViewModelTest.kt b/audioclips/src/test/java/org/odk/collect/audioclips/AudioClipViewModelTest.kt index 0e60a3752e8..638b5d642df 100644 --- a/audioclips/src/test/java/org/odk/collect/audioclips/AudioClipViewModelTest.kt +++ b/audioclips/src/test/java/org/odk/collect/audioclips/AudioClipViewModelTest.kt @@ -8,13 +8,13 @@ import org.junit.Rule import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers -import org.mockito.Mockito.`when` import org.mockito.Mockito.doThrow import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.odk.collect.androidtest.getOrAwaitValue import org.odk.collect.testshared.FakeScheduler import java.io.File diff --git a/audiorecorder/build.gradle b/audiorecorder/build.gradle index b78c7c0f1b9..8d9d465252a 100644 --- a/audiorecorder/build.gradle +++ b/audiorecorder/build.gradle @@ -28,10 +28,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests.includeAndroidResources = true } diff --git a/build.gradle b/build.gradle index 9b63a936e16..0884afdeef8 100644 --- a/build.gradle +++ b/build.gradle @@ -16,12 +16,12 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.4.0' - classpath 'com.google.gms:google-services:4.3.14' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21" - classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0" - classpath "com.github.ben-manes:gradle-versions-plugin:0.44.0" + classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.google.gms:google-services:4.3.15' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10" + classpath "org.jlleitschuh.gradle:ktlint-gradle:11.3.1" + classpath "com.github.ben-manes:gradle-versions-plugin:0.46.0" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3" classpath "com.google.android.gms:oss-licenses-plugin:0.10.6" } @@ -64,6 +64,7 @@ allprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { freeCompilerArgs = ['-Xjvm-default=all'] + jvmTarget = '1.8' } } } diff --git a/buildSrc/src/main/java/dependencies/Dependencies.kt b/buildSrc/src/main/java/dependencies/Dependencies.kt index 9a117b68f99..5def5c71b8b 100644 --- a/buildSrc/src/main/java/dependencies/Dependencies.kt +++ b/buildSrc/src/main/java/dependencies/Dependencies.kt @@ -1,20 +1,20 @@ package dependencies object Dependencies { - const val desugar = "com.android.tools:desugar_jdk_libs:1.1.5" + const val desugar = "com.android.tools:desugar_jdk_libs:2.0.2" const val androidx_startup = "androidx.startup:startup-runtime:1.1.1" - const val androidx_annotations = "androidx.annotation:annotation:1.5.0" + const val androidx_annotations = "androidx.annotation:annotation:1.6.0" const val androidx_lifecycle_runtime_ktx = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}" const val androidx_viewpager2= "androidx.viewpager2:viewpager2:1.0.0" const val androidx_lifecycle_livedata_ktx = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}" const val androidx_lifecycle_viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}" const val androidx_core_ktx = "androidx.core:core-ktx:1.9.0" - const val androidx_browser = "androidx.browser:browser:1.4.0" - const val androidx_recyclerview = "androidx.recyclerview:recyclerview:1.2.1" + const val androidx_browser = "androidx.browser:browser:1.5.0" + const val androidx_recyclerview = "androidx.recyclerview:recyclerview:1.3.0" const val androidx_fragment = "androidx.fragment:fragment:${Versions.androidx_fragment}" const val androidx_navigation_fragment_ktx = "androidx.navigation:navigation-fragment-ktx:2.5.3" const val androidx_navigation_ui = "androidx.navigation:navigation-ui-ktx:2.5.3" - const val androidx_appcompat = "androidx.appcompat:appcompat:1.5.1" + const val androidx_appcompat = "androidx.appcompat:appcompat:1.6.1" const val androidx_work_runtime = "androidx.work:work-runtime:${Versions.work}" const val androidx_cardview = "androidx.cardview:cardview:1.0.0" const val androidx_exinterface = "androidx.exifinterface:exifinterface:1.3.6" @@ -23,10 +23,10 @@ object Dependencies { const val androidx_fragment_ktx = "androidx.fragment:fragment-ktx:${Versions.androidx_fragment}" const val android_material = "com.google.android.material:material:1.7.0" const val android_flexbox = "com.google.android.flexbox:flexbox:3.0.0" - const val google_api_client_android = "com.google.api-client:google-api-client-android:2.1.1" - const val google_api_services_drive = "com.google.apis:google-api-services-drive:v3-rev20221023-2.0.0" - const val google_api_services_sheets = "com.google.apis:google-api-services-sheets:v4-rev20220927-2.0.0" - const val play_services_auth = "com.google.android.gms:play-services-auth:20.4.0" + const val google_api_client_android = "com.google.api-client:google-api-client-android:2.2.0" + const val google_api_services_drive = "com.google.apis:google-api-services-drive:v3-rev20230212-2.0.0" + const val google_api_services_sheets = "com.google.apis:google-api-services-sheets:v4-rev20230227-2.0.0" + const val play_services_auth = "com.google.android.gms:play-services-auth:20.4.1" const val play_services_maps = "com.google.android.gms:play-services-maps:18.1.0" const val play_services_location = "com.google.android.gms:play-services-location:20.0.0" // Check if map screens still work when upgrading const val play_services_oss_licenses = "com.google.android.gms:play-services-oss-licenses:17.0.0" @@ -35,7 +35,7 @@ object Dependencies { const val guava = "com.google.guava:guava:31.1-android" const val squareup_okhttp = "com.squareup.okhttp3:okhttp:${Versions.okhttp3}" const val squareup_okhttp_tls = "com.squareup.okhttp3:okhttp-tls:${Versions.okhttp3}" - const val burgstaller_okhttp_digest = "io.github.rburgst:okhttp-digest:3.0" + const val burgstaller_okhttp_digest = "io.github.rburgst:okhttp-digest:3.0.1" const val persian_joda_time = "com.github.mohamadian:persianjodatime:1.2" const val myanmar_calendar = "com.github.chanmratekoko:myanmar-calendar:1.0.6.RC3" const val bikram_sambat = "bikramsambat:bikram-sambat:1.1.0" @@ -57,17 +57,17 @@ object Dependencies { const val glide_compiler = "com.github.bumptech.glide:compiler:${Versions.glide}" const val caverock_androidsvg = "com.caverock:androidsvg-aar:1.4" const val mp4parser_muxer = "org.mp4parser:muxer:1.9.41" // Check if https://github.com/getodk/collect/issues/5323 no longer takes place before upgrading - const val kotlin_stdlib = "org.jetbrains.kotlin:kotlin-stdlib:1.7.22" - const val gson = "com.google.code.gson:gson:2.10" + const val kotlin_stdlib = "org.jetbrains.kotlin:kotlin-stdlib:1.8.10" + const val gson = "com.google.code.gson:gson:2.10.1" const val firebase_analytics = "com.google.firebase:firebase-analytics:21.2.0" - const val firebase_crashlytics = "com.google.firebase:firebase-crashlytics:18.3.2" + const val firebase_crashlytics = "com.google.firebase:firebase-crashlytics:18.3.5" const val fastlane_screengrab = "tools.fastlane:screengrab:2.1.1" const val leakcanary = "com.squareup.leakcanary:leakcanary-android:2.10" const val timber = "com.jakewharton.timber:timber:5.0.1" const val slf4j_api = "org.slf4j:slf4j-api:2.0.6" const val slf4j_timber = "com.arcao:slf4j-timber:3.1@aar" const val emoji_java = "com.vdurmont:emoji-java:5.1.1" - const val json_schema_validator = "com.networknt:json-schema-validator:1.0.75" + const val json_schema_validator = "com.networknt:json-schema-validator:1.0.78" const val splashscreen = "androidx.core:core-splashscreen:1.0.0" const val camerax_core = "androidx.camera:camera-core:${Versions.camerax}" const val camerax_view = "androidx.camera:camera-view:${Versions.camerax}" @@ -81,14 +81,14 @@ object Dependencies { const val mockito_inline = "org.mockito:mockito-inline:${Versions.mockito}" const val mockito_kotlin = "org.mockito.kotlin:mockito-kotlin:4.1.0" const val androidx_fragment_testing = "androidx.fragment:fragment-testing:${Versions.androidx_fragment}" - const val androidx_arch_core_testing = "androidx.arch.core:core-testing:2.1.0" + const val androidx_arch_core_testing = "androidx.arch.core:core-testing:2.2.0" const val androidx_work_testing = "androidx.work:work-testing:${Versions.work}" const val androidx_test_core_ktx = "androidx.test:core-ktx:1.5.0" const val androidx_test_rules = "androidx.test:rules:1.5.0" const val androidx_test_espresso_contrib = "androidx.test.espresso:espresso-contrib:${Versions.espresso}" const val androidx_test_espresso_core = "androidx.test.espresso:espresso-core:${Versions.espresso}" const val androidx_test_espresso_intents = "androidx.test.espresso:espresso-intents:${Versions.espresso}" - const val androidx_test_ext_junit = "androidx.test.ext:junit:1.1.4" + const val androidx_test_ext_junit = "androidx.test.ext:junit:1.1.5" const val okhttp3_mockwebserver = "com.squareup.okhttp3:mockwebserver:${Versions.okhttp3}" const val hamcrest = "org.hamcrest:hamcrest:2.2" const val robolectric = "org.robolectric:robolectric:${Versions.robolectric}" diff --git a/buildSrc/src/main/java/dependencies/Versions.kt b/buildSrc/src/main/java/dependencies/Versions.kt index bd411e3923d..b3bed2c169c 100644 --- a/buildSrc/src/main/java/dependencies/Versions.kt +++ b/buildSrc/src/main/java/dependencies/Versions.kt @@ -3,16 +3,16 @@ package dependencies object Versions { const val android_compile_sdk = 33 const val android_min_sdk = 21 - const val android_target_sdk = 31 + const val android_target_sdk = 33 const val androidx_fragment = "1.5.5" - const val dagger = "2.44.2" - const val espresso = "3.5.0" - const val glide = "4.14.2" - const val mockito = "4.10.0" + const val dagger = "2.45" + const val espresso = "3.5.1" + const val glide = "4.15.1" + const val mockito = "5.2.0" const val okhttp3 = "4.10.0" const val robolectric = "4.9" - const val work = "2.7.1" - const val lifecycle = "2.5.1" - const val camerax = "1.2.0" + const val work = "2.8.0" + const val lifecycle = "2.6.0" + const val camerax = "1.2.1" } diff --git a/collect_app/build.gradle b/collect_app/build.gradle index a295ab8fcc8..faafcb2f15e 100644 --- a/collect_app/build.gradle +++ b/collect_app/build.gradle @@ -186,10 +186,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - testOptions { unitTests { includeAndroidResources = true @@ -341,6 +337,7 @@ dependencies { // Android Architecture Components: implementation Dependencies.androidx_lifecycle_runtime_ktx + implementation Dependencies.androidx_lifecycle_livedata_ktx implementation Dependencies.androidx_viewpager2 // Dagger: diff --git a/collect_app/src/androidTest/assets/forms/two-question-audit.xml b/collect_app/src/androidTest/assets/forms/two-question-audit.xml new file mode 100644 index 00000000000..4e6a57c5ecf --- /dev/null +++ b/collect_app/src/androidTest/assets/forms/two-question-audit.xml @@ -0,0 +1,28 @@ + + + + Two Question + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/external/InstanceEditActionTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/external/InstanceEditActionTest.kt index 49749242cf7..0cd74ec249d 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/external/InstanceEditActionTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/external/InstanceEditActionTest.kt @@ -13,7 +13,6 @@ import org.odk.collect.android.R import org.odk.collect.android.external.InstancesContract import org.odk.collect.android.support.ContentProviderUtils import org.odk.collect.android.support.pages.AppClosedPage -import org.odk.collect.android.support.pages.FormEntryPage import org.odk.collect.android.support.pages.FormHierarchyPage import org.odk.collect.android.support.pages.OkDialog import org.odk.collect.android.support.rules.CollectTestRule @@ -109,7 +108,7 @@ class InstanceEditActionTest { .build() val intent = Intent(Intent.ACTION_EDIT).also { it.data = uriWithoutProjectId } - rule.launch(intent, FormEntryPage("One Question")) + rule.launch(intent, FormHierarchyPage("One Question")) } @Test diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/external/InstanceUploadActionTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/external/InstanceUploadActionTest.kt index f1028f9e758..88b04f31f1a 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/external/InstanceUploadActionTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/external/InstanceUploadActionTest.kt @@ -24,7 +24,6 @@ class InstanceUploadActionTest { @Test fun whenInstanceDoesNotExist_showsError() { - val instanceIds = longArrayOf(11) instanceUploadAction(instanceIds) diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ContextMenuTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ContextMenuTest.java index 3153379f2e2..ed64181d968 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ContextMenuTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ContextMenuTest.java @@ -4,13 +4,13 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.odk.collect.android.R; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; public class ContextMenuTest { private static final String STRING_WIDGETS_TEST_FORM = "string_widgets_in_field_list.xml"; - public FormActivityTestRule activityTestRule = new FormActivityTestRule(STRING_WIDGETS_TEST_FORM, "fl"); + public BlankFormTestRule activityTestRule = new BlankFormTestRule(STRING_WIDGETS_TEST_FORM, "fl"); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/DeletingRepeatGroupsTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/DeletingRepeatGroupsTest.java index 6bde4950c93..daf2d2bada7 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/DeletingRepeatGroupsTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/DeletingRepeatGroupsTest.java @@ -11,14 +11,14 @@ import org.odk.collect.android.support.pages.FormEndPage; import org.odk.collect.android.support.pages.FormEntryPage; import org.odk.collect.android.support.pages.FormHierarchyPage; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.testshared.RecyclerViewMatcher; public class DeletingRepeatGroupsTest { private static final String TEST_FORM = "repeat_groups.xml"; - private final FormActivityTestRule activityTestRule = new FormActivityTestRule(TEST_FORM, "repeatGroups"); + private final BlankFormTestRule activityTestRule = new BlankFormTestRule(TEST_FORM, "repeatGroups"); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/DynamicPreLoadedDataSelects.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/DynamicPreLoadedDataSelects.java index d9418481c6c..b65a776f498 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/DynamicPreLoadedDataSelects.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/DynamicPreLoadedDataSelects.java @@ -3,7 +3,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import java.util.Collections; @@ -17,7 +17,7 @@ public class DynamicPreLoadedDataSelects { private static final String EXTERNAL_CSV_SEARCH_FORM = "external-csv-search.xml"; - public FormActivityTestRule rule = new FormActivityTestRule(EXTERNAL_CSV_SEARCH_FORM, "external-csv-search", Collections.singletonList("external-csv-search-produce.csv")); + public BlankFormTestRule rule = new BlankFormTestRule(EXTERNAL_CSV_SEARCH_FORM, "external-csv-search", Collections.singletonList("external-csv-search-produce.csv")); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/EntityFormTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/EntityFormTest.kt index 227f60597f9..120248393bb 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/EntityFormTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/EntityFormTest.kt @@ -23,7 +23,7 @@ class EntityFormTest { rule.startAtMainMenu() .copyForm("one-question-entity.xml") .startBlankForm("One Question Entity") - .fillOutAndSave(FormEntryPage.QuestionAndAnswer("Name", "Logan Roy")) + .fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Logan Roy")) .openEntityBrowser() .clickOnDataset("people") .assertEntity("full_name: Logan Roy") diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ExternalDataFileNotFoundTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ExternalDataFileNotFoundTest.java index 75549c6cc1f..794477f297f 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ExternalDataFileNotFoundTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ExternalDataFileNotFoundTest.java @@ -6,13 +6,13 @@ import org.odk.collect.android.R; import org.odk.collect.android.storage.StoragePathProvider; import org.odk.collect.android.storage.StorageSubdirectory; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; public class ExternalDataFileNotFoundTest { private static final String EXTERNAL_DATA_QUESTIONS = "external_data_questions.xml"; - public FormActivityTestRule activityTestRule = new FormActivityTestRule(EXTERNAL_DATA_QUESTIONS, "externalDataQuestions"); + public BlankFormTestRule activityTestRule = new BlankFormTestRule(EXTERNAL_DATA_QUESTIONS, "externalDataQuestions"); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FieldListUpdateTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FieldListUpdateTest.java index df0c9c3e972..0fab9a35ad6 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FieldListUpdateTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FieldListUpdateTest.java @@ -64,7 +64,7 @@ import org.odk.collect.android.preferences.GuidanceHint; import org.odk.collect.android.storage.StoragePathProvider; import org.odk.collect.android.support.pages.FormEntryPage; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.androidtest.RecordedIntentsRule; import org.odk.collect.settings.keys.ProjectKeys; @@ -80,7 +80,7 @@ public class FieldListUpdateTest { private static final String FIELD_LIST_TEST_FORM = "fieldlist-updates.xml"; - public FormActivityTestRule rule = new FormActivityTestRule( + public BlankFormTestRule rule = new BlankFormTestRule( FIELD_LIST_TEST_FORM, "fieldlist-updates", Collections.singletonList("fruits.csv") diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FormLanguageTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FormLanguageTest.java index da4459dfc78..94de554f406 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FormLanguageTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FormLanguageTest.java @@ -7,10 +7,10 @@ import org.junit.rules.RuleChain; import org.junit.runner.RunWith; import org.odk.collect.android.R; -import org.odk.collect.android.support.rules.CollectTestRule; -import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.android.support.pages.MainMenuPage; import org.odk.collect.android.support.pages.SaveOrIgnoreDialog; +import org.odk.collect.android.support.rules.CollectTestRule; +import org.odk.collect.android.support.rules.TestRuleChain; @RunWith(AndroidJUnit4.class) public class FormLanguageTest { @@ -25,11 +25,12 @@ public void canSwitchLanguagesInForm() { rule.startAtMainMenu() .copyForm("one-question-translation.xml") .startBlankForm("One Question") - .assertQuestion("what is your age") + .answerQuestion("what is your age", "64") .clickOptionsIcon() .clickOnString(R.string.change_language) .clickOnText("French (fr)") - .assertQuestion("quel âge as-tu"); + .assertQuestion("quel âge as-tu") + .assertText("64"); // Check answer hasn't been cleared/changed } @Test diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/GuidanceHintFormTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/GuidanceHintFormTest.java index 1b661b8ae4d..30fdd2603ed 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/GuidanceHintFormTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/GuidanceHintFormTest.java @@ -17,7 +17,7 @@ import org.odk.collect.android.R; import org.odk.collect.android.TestSettingsProvider; import org.odk.collect.android.preferences.GuidanceHint; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.settings.keys.ProjectKeys; @@ -32,7 +32,7 @@ public static void beforeAll() { Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy()); } - public FormActivityTestRule activityTestRule = new FormActivityTestRule(GUIDANCE_SAMPLE_FORM, "Guidance Form Sample"); + public BlankFormTestRule activityTestRule = new BlankFormTestRule(GUIDANCE_SAMPLE_FORM, "Guidance Form Sample"); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/IntentGroupTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/IntentGroupTest.java index 98ec789beed..4e660b2050e 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/IntentGroupTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/IntentGroupTest.java @@ -57,7 +57,7 @@ import org.odk.collect.android.BuildConfig; import org.odk.collect.android.R; import org.odk.collect.android.application.Collect; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.androidtest.RecordedIntentsRule; @@ -70,7 +70,7 @@ public class IntentGroupTest { private static final String INTENT_GROUP_FORM = "intent-group.xml"; - public FormActivityTestRule rule = new FormActivityTestRule(INTENT_GROUP_FORM, "intent-group"); + public BlankFormTestRule rule = new BlankFormTestRule(INTENT_GROUP_FORM, "intent-group"); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/LikertTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/LikertTest.java index 7a335ba9502..0f04339c03f 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/LikertTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/LikertTest.java @@ -18,7 +18,7 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.odk.collect.android.R; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.ResetStateRule; import org.odk.collect.android.support.rules.TestRuleChain; @@ -27,7 +27,7 @@ public class LikertTest { private static final String LIKERT_TEST_FORM = "likert_test.xml"; - public FormActivityTestRule activityTestRule = new FormActivityTestRule(LIKERT_TEST_FORM, "All widgets likert icon", Collections.singletonList("famous.jpg")); + public BlankFormTestRule activityTestRule = new BlankFormTestRule(LIKERT_TEST_FORM, "All widgets likert icon", Collections.singletonList("famous.jpg")); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/QuickSaveTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/QuickSaveTest.java index d0cc92755ef..0d0ed67755d 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/QuickSaveTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/QuickSaveTest.java @@ -80,7 +80,7 @@ public void whenEditingAFinalizedForm_withViolatedConstraintsOnCurrentScreen_cli rule.startAtMainMenu() .copyForm("two-question-required.xml") .startBlankForm("Two Question Required") - .fillOutAndSave( + .fillOutAndFinalize( new QuestionAndAnswer("What is your name?", "Reuben"), new QuestionAndAnswer("What is your age?", "32", true) ) @@ -107,7 +107,7 @@ public void whenEditingAFinalizedForm_withViolatedConstraintsOnAnotherScreen_cli rule.startAtMainMenu() .copyForm("two-question-required.xml") .startBlankForm("Two Question Required") - .fillOutAndSave( + .fillOutAndFinalize( new QuestionAndAnswer("What is your name?", "Reuben"), new QuestionAndAnswer("What is your age?", "32", true) ) diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/QuittingFormTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/QuittingFormTest.java index 4fa3bf57c33..6625ecb6f62 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/QuittingFormTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/QuittingFormTest.java @@ -115,7 +115,7 @@ public void whenEditingAFinalizedForm_withViolatedConstraintsOnCurrentScreen_pre rule.startAtMainMenu() .copyForm("two-question-required.xml") .startBlankForm("Two Question Required") - .fillOutAndSave( + .fillOutAndFinalize( new QuestionAndAnswer("What is your name?", "Reuben"), new QuestionAndAnswer("What is your age?", "32", true) ) @@ -143,7 +143,7 @@ public void whenEditingAFinalizedForm_withViolatedConstraintsOnAnotherScreen_pre rule.startAtMainMenu() .copyForm("two-question-required.xml") .startBlankForm("Two Question Required") - .fillOutAndSave( + .fillOutAndFinalize( new QuestionAndAnswer("What is your name?", "Reuben"), new QuestionAndAnswer("What is your age?", "32", true) ) diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/RankingWidgetWithCSVTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/RankingWidgetWithCSVTest.java index 4ebd1c39494..4363f9c057b 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/RankingWidgetWithCSVTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/RankingWidgetWithCSVTest.java @@ -3,7 +3,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.android.support.pages.FormEntryPage; @@ -13,7 +13,7 @@ public class RankingWidgetWithCSVTest { private static final String TEST_FORM = "ranking_widget.xml"; - public FormActivityTestRule activityTestRule = new FormActivityTestRule(TEST_FORM, "ranking_widget", Collections.singletonList("fruits.csv")); + public BlankFormTestRule activityTestRule = new BlankFormTestRule(TEST_FORM, "ranking_widget", Collections.singletonList("fruits.csv")); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SaveIncompleteTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SaveIncompleteTest.kt index ac296c3dba8..6a6fe5098fa 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SaveIncompleteTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SaveIncompleteTest.kt @@ -51,7 +51,7 @@ class SaveIncompleteTest { rule.startAtMainMenu() .copyForm("two-question-save-incomplete-required.xml") .startBlankForm("Two Question Save Incomplete Required") - .fillOutAndSave( + .fillOutAndFinalize( QuestionAndAnswer("What is your name?", "Dez"), QuestionAndAnswer("[saveIncomplete] What is your age?", "56", true) ) @@ -76,7 +76,7 @@ class SaveIncompleteTest { rule.startAtMainMenu() .copyForm("two-question-save-incomplete-required.xml") .startBlankForm("Two Question Save Incomplete Required") - .fillOutAndSave( + .fillOutAndFinalize( QuestionAndAnswer("What is your name?", "Dez"), QuestionAndAnswer("[saveIncomplete] What is your age?", "56", true) ) diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SavePointTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SavePointTest.kt new file mode 100644 index 00000000000..5c4f9428d16 --- /dev/null +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SavePointTest.kt @@ -0,0 +1,241 @@ +package org.odk.collect.android.feature.formentry + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith +import org.odk.collect.android.support.CollectHelpers +import org.odk.collect.android.support.StorageUtils +import org.odk.collect.android.support.pages.AppClosedPage +import org.odk.collect.android.support.pages.FormEntryPage +import org.odk.collect.android.support.pages.FormHierarchyPage +import org.odk.collect.android.support.pages.SaveOrIgnoreDialog +import org.odk.collect.android.support.rules.FormEntryActivityTestRule +import org.odk.collect.android.support.rules.TestRuleChain + +@RunWith(AndroidJUnit4::class) +class SavePointTest { + + private val rule = FormEntryActivityTestRule() + + @get:Rule + val ruleChain: RuleChain = TestRuleChain.chain().around(rule) + + @Test + fun savePointIsCreatedWhenMovingForwardInForm() { + // Create save point + rule.setUpProjectAndCopyForm("two-question-audit.xml") + .fillNewForm("two-question-audit.xml", "Two Question") + .answerQuestion("What is your name?", "Alexei") + .swipeToNextQuestion("What is your age?") + .answerQuestion("What is your age?", "46") + .let { simulateBatteryDeath() } + + // Start blank form and check save point is loaded + rule.fillNewForm("two-question-audit.xml", FormHierarchyPage("Two Question")) + .assertText("Alexei") + .assertTextDoesNotExist("46") + .pressBack(FormEntryPage("Two Question")) + .assertQuestion("What is your name?") + .pressBack(SaveOrIgnoreDialog("Two Question", AppClosedPage())) + .clickSaveChanges() + + // Check audit log + val auditLog = StorageUtils.getAuditLogForFirstInstance() + assertThat(auditLog.size, equalTo(7)) + + assertThat(auditLog[0].get("event"), equalTo("form start")) + assertThat(auditLog[1].get("event"), equalTo("question")) + // Second question event not logged - possibly a problem + + assertThat(auditLog[2].get("event"), equalTo("form resume")) + assertThat(auditLog[3].get("event"), equalTo("jump")) + assertThat(auditLog[4].get("event"), equalTo("question")) + assertThat(auditLog[5].get("event"), equalTo("form save")) + assertThat(auditLog[6].get("event"), equalTo("form exit")) + } + + @Test + fun whenEditing_savePointIsCreatedWhenMovingForwardInForm() { + // Create instance + rule.setUpProjectAndCopyForm("two-question-audit.xml") + .fillNewForm("two-question-audit.xml", "Two Question") + .fillOutAndSave( + AppClosedPage(), + FormEntryPage.QuestionAndAnswer("What is your name?", "Pasquale"), + FormEntryPage.QuestionAndAnswer("What is your age?", "52") + ) + + // Create save point + rule.editForm("two-question-audit.xml", "Two Question") + .clickGoToStart() + .answerQuestion("What is your name?", "Alexei") + .swipeToNextQuestion("What is your age?") + .answerQuestion("What is your age?", "46") + .let { simulateBatteryDeath() } + + // Edit instance and check save point is loaded + rule.editForm("two-question-audit.xml", "Two Question") + .assertText("Alexei") + .assertText("52") + .assertTextDoesNotExist("46") + .pressBack(FormEntryPage("Two Question")) + .assertQuestion("What is your name?") + .pressBack(SaveOrIgnoreDialog("Two Question", AppClosedPage())) + .clickSaveChanges() + + // Check audit log + val auditLog = StorageUtils.getAuditLogForFirstInstance() + assertThat(auditLog.size, equalTo(13)) + + assertThat(auditLog[5].get("event"), equalTo("form resume")) + assertThat(auditLog[6].get("event"), equalTo("jump")) + assertThat(auditLog[7].get("event"), equalTo("question")) + // Second question event not logged - possibly a problem + + assertThat(auditLog[8].get("event"), equalTo("form resume")) + assertThat(auditLog[9].get("event"), equalTo("jump")) + assertThat(auditLog[10].get("event"), equalTo("question")) + assertThat(auditLog[11].get("event"), equalTo("form save")) + assertThat(auditLog[12].get("event"), equalTo("form exit")) + } + + @Test + fun savePointIsCreatedWhenLeavingTheApp() { + // Create save point + rule.setUpProjectAndCopyForm("two-question-audit.xml") + .fillNewForm("two-question-audit.xml", "Two Question") + .answerQuestion("What is your name?", "Alexei") + .let { simulateProcessRestore() } + + // Start blank form and check save point is loaded + rule.fillNewForm("two-question-audit.xml", FormHierarchyPage("Two Question")) + .assertText("Alexei") + .pressBack(FormEntryPage("Two Question")) + .assertQuestion("What is your name?") + .pressBack(SaveOrIgnoreDialog("Two Question", AppClosedPage())) + .clickSaveChanges() + + // Check audit log + val auditLog = StorageUtils.getAuditLogForFirstInstance() + assertThat(auditLog.size, equalTo(6)) + + assertThat(auditLog[0].get("event"), equalTo("form start")) + // Question event not logged - possibly a problem + + assertThat(auditLog[1].get("event"), equalTo("form resume")) + assertThat(auditLog[2].get("event"), equalTo("jump")) + assertThat(auditLog[3].get("event"), equalTo("question")) + assertThat(auditLog[4].get("event"), equalTo("form save")) + assertThat(auditLog[5].get("event"), equalTo("form exit")) + } + + @Test + fun whenEditing_savePointIsCreatedWhenLeavingTheApp() { + // Create instance + rule.setUpProjectAndCopyForm("two-question-audit.xml") + .fillNewForm("two-question-audit.xml", "Two Question") + .fillOutAndSave( + AppClosedPage(), + FormEntryPage.QuestionAndAnswer("What is your name?", "Pasquale"), + FormEntryPage.QuestionAndAnswer("What is your age?", "52") + ) + + // Create save point + rule.editForm("two-question-audit.xml", "Two Question") + .clickGoToStart() + .answerQuestion("What is your name?", "Alexei") + .let { simulateProcessRestore() } + + // Edit instance and check save point is loaded + rule.editForm("two-question-audit.xml", "Two Question") + .assertText("Alexei") + .assertText("52") + .pressBack(FormEntryPage("Two Question")) + .assertQuestion("What is your name?") + .pressBack(SaveOrIgnoreDialog("Two Question", AppClosedPage())) + .clickSaveChanges() + + // Check audit log + val auditLog = StorageUtils.getAuditLogForFirstInstance() + assertThat(auditLog.size, equalTo(12)) + + assertThat(auditLog[5].get("event"), equalTo("form resume")) + assertThat(auditLog[6].get("event"), equalTo("jump")) + // Question event not logged - possibly a problem + + assertThat(auditLog[7].get("event"), equalTo("form resume")) + assertThat(auditLog[8].get("event"), equalTo("jump")) + assertThat(auditLog[9].get("event"), equalTo("question")) + assertThat(auditLog[10].get("event"), equalTo("form save")) + assertThat(auditLog[11].get("event"), equalTo("form exit")) + } + + @Test + fun blankFormSavePointIsNotUsedWhenEditingInstance() { + // Create instance + rule.setUpProjectAndCopyForm("two-question-audit.xml") + .fillNewForm("two-question-audit.xml", "Two Question") + .fillOutAndSave( + AppClosedPage(), + FormEntryPage.QuestionAndAnswer("What is your name?", "Pasquale"), + FormEntryPage.QuestionAndAnswer("What is your age?", "52") + ) + + // Create save point for blank form + rule.fillNewForm("two-question-audit.xml", "Two Question") + .answerQuestion("What is your name?", "Alexei") + .let { simulateProcessRestore() } + + // Check editing instance doesn't load save point + rule.editForm("two-question-audit.xml", "Two Question") + .assertText("Pasquale") + .assertText("52") + .assertTextDoesNotExist("Alexei") + } + + @Test + fun editedInstanceSavePointIsNotUsedWhenFillingBlankFormOfTheSameForm() { + // Create instance + rule.setUpProjectAndCopyForm("two-question-audit.xml") + .fillNewForm("two-question-audit.xml", "Two Question") + .fillOutAndSave( + AppClosedPage(), + FormEntryPage.QuestionAndAnswer("What is your name?", "Pasquale"), + FormEntryPage.QuestionAndAnswer("What is your age?", "52") + ) + + // Create save point for instance + rule.editForm("two-question-audit.xml", "Two Question") + .clickGoToStart() + .answerQuestion("What is your name?", "Alexei") + .let { simulateProcessRestore() } + + // Check starting blank form does not load save point + rule.fillNewForm("two-question-audit.xml", "Two Question") + } + + /** + * Simulates a case where the process is killed without lifecycle clean up (like a phone + * being battery dying). + */ + private fun simulateBatteryDeath(): FormEntryActivityTestRule { + CollectHelpers.simulateProcessRestart() + return rule + } + + /** + * Simulate a "process restore" case where an app in the background is killed by Android + * to reclaim memory, change permissions etc + */ + private fun simulateProcessRestore(): FormEntryActivityTestRule { + rule.saveInstanceStateForActivity() + .destroyActivity() + + CollectHelpers.simulateProcessRestart() + return rule + } +} diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/audit/AuditTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/audit/AuditTest.kt index a1aa8dbc1a3..3dc1f26167a 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/audit/AuditTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/audit/AuditTest.kt @@ -26,13 +26,13 @@ class AuditTest { rule.startAtMainMenu() .copyForm("one-question-audit.xml") .startBlankForm("One Question Audit") - .fillOutAndSave( + .fillOutAndFinalize( FormEntryPage.QuestionAndAnswer("what is your age", "31") ) .clickEditSavedForm(1) .clickOnForm("One Question Audit") .clickGoToStart() - .fillOutAndSave( + .fillOutAndFinalize( FormEntryPage.QuestionAndAnswer("what is your age", "32") ) diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/backgroundlocation/LocationTrackingAuditTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/backgroundlocation/LocationTrackingAuditTest.java index 476750c0839..6bad94e1c31 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/backgroundlocation/LocationTrackingAuditTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/backgroundlocation/LocationTrackingAuditTest.java @@ -13,7 +13,7 @@ import org.odk.collect.android.support.FakeLocationClient; import org.odk.collect.android.support.StorageUtils; import org.odk.collect.android.support.TestDependencies; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.location.LocationClient; import org.odk.collect.testshared.FakeLocation; @@ -25,7 +25,7 @@ public class LocationTrackingAuditTest { private final FakeLocationClient locationClient = new FakeLocationClient(); - public FormActivityTestRule rule = new FormActivityTestRule("location-audit.xml", "Audit with Location"); + public BlankFormTestRule rule = new BlankFormTestRule("location-audit.xml", "Audit with Location"); @Rule public RuleChain copyFormChain = TestRuleChain.chain(new TestDependencies() { diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/backgroundlocation/SetGeopointActionTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/backgroundlocation/SetGeopointActionTest.java index f5c3c7fcd5e..f6800b57925 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/backgroundlocation/SetGeopointActionTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/backgroundlocation/SetGeopointActionTest.java @@ -11,13 +11,13 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.odk.collect.android.R; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; public class SetGeopointActionTest { private static final String SETGEOPOINT_ACTION_FORM = "setgeopoint-action.xml"; - public FormActivityTestRule rule = new FormActivityTestRule(SETGEOPOINT_ACTION_FORM, "setgeopoint-action-instance-load"); + public BlankFormTestRule rule = new BlankFormTestRule(SETGEOPOINT_ACTION_FORM, "setgeopoint-action-instance-load"); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/SendFinalizedFormTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/SendFinalizedFormTest.java index 61bb191eff0..933ebdde43f 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/SendFinalizedFormTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/instancemanagement/SendFinalizedFormTest.java @@ -7,6 +7,7 @@ import org.junit.rules.RuleChain; import org.junit.runner.RunWith; import org.odk.collect.android.R; +import org.odk.collect.android.support.CollectHelpers; import org.odk.collect.android.support.TestDependencies; import org.odk.collect.android.support.pages.MainMenuPage; import org.odk.collect.android.support.pages.OkDialog; @@ -15,6 +16,7 @@ import org.odk.collect.android.support.rules.CollectTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.androidtest.RecordedIntentsRule; +import org.odk.collect.projects.Project; @RunWith(AndroidJUnit4.class) public class SendFinalizedFormTest { @@ -101,12 +103,20 @@ public void whenDeleteAfterSendIsEnabled_deletesFilledForm() { @Test public void whenGoogleUsedAsServer_sendsSubmissionToSheet() { - testDependencies.googleAccountPicker.setDeviceAccount("dani@davey.com"); - testDependencies.googleApi.setAccount("dani@davey.com"); + CollectHelpers.addGDProject( + new Project.New( + "GD Project", + "G", + "#3e9fcc" + ), + "dani@davey.com", + testDependencies + ); rule.startAtMainMenu() - .setGoogleAccount("dani@davey.com") - .copyForm("one-question-google.xml") + .openProjectSettingsDialog() + .selectProject("GD Project") + .copyForm("one-question-google.xml", null, false, "GD Project") .startBlankForm("One Question Google") .answerQuestion("what is your age", "47") .swipeToEndScreen() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/AddNewProjectTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/AddNewProjectTest.kt index a2faf394a18..064b2f5ad13 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/AddNewProjectTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/AddNewProjectTest.kt @@ -35,22 +35,6 @@ class AddNewProjectTest { .assertInactiveProject("Demo project", "demo.getodk.org") } - @Test - fun addingGdriveProjectManually_addsNewProject() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) - - rule.startAtMainMenu() - .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) - - .openProjectSettingsDialog() - .assertCurrentProject(googleAccount, "$googleAccount / Google Drive") - .assertInactiveProject("Demo project", "demo.getodk.org") - } - @Test fun addingProjectFromQrCode_addsNewProject() { val page = rule.startAtMainMenu() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationBannerTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationTest.kt similarity index 59% rename from collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationBannerTest.kt rename to collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationTest.kt index 9f8017abc77..617ed7c8b04 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationBannerTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/GoogleDriveDeprecationTest.kt @@ -9,15 +9,16 @@ import org.junit.Test import org.junit.rules.RuleChain import org.odk.collect.android.R import org.odk.collect.android.activities.WebViewActivity +import org.odk.collect.android.support.CollectHelpers import org.odk.collect.android.support.TestDependencies import org.odk.collect.android.support.pages.MainMenuPage -import org.odk.collect.android.support.pages.ProjectSettingsPage import org.odk.collect.android.support.rules.CollectTestRule import org.odk.collect.android.support.rules.TestRuleChain import org.odk.collect.androidtest.RecordedIntentsRule +import org.odk.collect.projects.Project -class GoogleDriveDeprecationBannerTest { - val rule = CollectTestRule() +class GoogleDriveDeprecationTest { + private val rule = CollectTestRule() private val testDependencies = TestDependencies() @get:Rule @@ -25,6 +26,18 @@ class GoogleDriveDeprecationBannerTest { .around(RecordedIntentsRule()) .around(rule) + private val gdProject1 = Project.New( + "GD Project 1", + "G", + "#3e9fcc" + ) + + private val gdProject2 = Project.New( + "GD Project 2", + "G", + "#3e9fcc" + ) + @Test fun bannerIsNotVisibleInNonGoogleDriveProjects() { rule @@ -34,48 +47,21 @@ class GoogleDriveDeprecationBannerTest { @Test fun bannerIsVisibleInGoogleDriveProjects() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .assertText(R.string.google_drive_deprecation_message) } - @Test - fun bannerDisappearsAfterSwitchingFromGoogleDriveProjectToOdkServer() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) - - rule.startAtMainMenu() - .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) - .assertText(R.string.google_drive_deprecation_message) - .openProjectSettingsDialog() - .clickSettings() - .clickServerSettings() - .clickOnServerType() - .clickOnString(R.string.server_platform_odk) - .pressBack(ProjectSettingsPage()) - .pressBack(MainMenuPage()) - .assertTextDoesNotExist(R.string.google_drive_deprecation_message) - } - @Test fun forumThreadIsOpenedAfterClickingLearnMore() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .clickOnString(R.string.learn_more_button_text) intended( @@ -88,14 +74,11 @@ class GoogleDriveDeprecationBannerTest { @Test fun dismissButtonIsVisibleOnlyAfterClickingLearnMore() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .assertTextDoesNotExist(R.string.dismiss_button_text) .clickOnString(R.string.learn_more_button_text) .pressBack(MainMenuPage()) @@ -104,14 +87,11 @@ class GoogleDriveDeprecationBannerTest { @Test fun afterClickingDismissTheBannerDisappears() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .clickOnString(R.string.learn_more_button_text) .pressBack(MainMenuPage()) .clickOnString(R.string.dismiss_button_text) @@ -122,22 +102,44 @@ class GoogleDriveDeprecationBannerTest { @Test fun dismissingTheBannerInOneProjectDoesNotAffectOtherProjects() { - val googleAccount = "steph@curry.basket" - testDependencies.googleAccountPicker.setDeviceAccount(googleAccount) + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) + CollectHelpers.addGDProject(gdProject2, "john@curry.basket", testDependencies) rule.startAtMainMenu() .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount) + .selectProject(gdProject1.name) .clickOnString(R.string.learn_more_button_text) .pressBack(MainMenuPage()) .clickOnString(R.string.dismiss_button_text) .assertTextDoesNotExist(R.string.google_drive_deprecation_message) .openProjectSettingsDialog() - .clickAddProject() - .switchToManualMode() - .openGooglePickerAndSelect(googleAccount, true) + .selectProject(gdProject2.name) .assertText(R.string.google_drive_deprecation_message) } + + @Test + fun additionalWarningShouldNotBeDisplayedWhenRemovingNonGDProject() { + rule + .startAtMainMenu() + .openProjectSettingsDialog() + .clickSettings() + .clickProjectManagement() + .clickOnDeleteProject() + .assertTextDoesNotExist(R.string.delete_google_drive_project_confirm_message) + } + + @Test + fun additionalWarningShouldBeDisplayedWhenRemovingGDProject() { + CollectHelpers.addGDProject(gdProject1, "steph@curry.basket", testDependencies) + + rule + .startAtMainMenu() + .openProjectSettingsDialog() + .selectProject(gdProject1.name) + .openProjectSettingsDialog() + .clickSettings() + .clickProjectManagement() + .clickOnDeleteProject() + .assertText(R.string.delete_google_drive_project_confirm_message) + } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/SwitchProjectTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/SwitchProjectTest.kt index daee994b633..c36ad48cc8a 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/SwitchProjectTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/projects/SwitchProjectTest.kt @@ -78,7 +78,7 @@ class SwitchProjectTest { // Fill form .startBlankForm("One Question Entity") - .fillOutAndSave(FormEntryPage.QuestionAndAnswer("Name", "Alice")) + .fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Alice")) .clickEditSavedForm(1) .assertText("One Question Entity") .pressBack(MainMenuPage()) diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/FormManagementSettingsTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/FormManagementSettingsTest.kt index b8d640b2fe1..9a1a80f8338 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/FormManagementSettingsTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/FormManagementSettingsTest.kt @@ -78,22 +78,4 @@ class FormManagementSettingsTest { assertThat(deferredTasks[0].tag, `is`(previouslyDownloadedTag)) assertThat(deferredTasks[0].repeatPeriod, `is`(1000L * 60 * 60)) } - - @Test - fun whenGoogleDriveUsingAsServer_disablesPrefsAndOnlyAllowsManualUpdates() { - testDependencies.googleAccountPicker.setDeviceAccount("steph@curry.basket") - - MainMenuPage().assertOnPage() - .enablePreviouslyDownloadedOnlyUpdates() // Enabled a different mode before setting up Google - .setGoogleAccount("steph@curry.basket") - .openProjectSettingsDialog() - .clickSettings() - .clickFormManagement() - .assertDisabled(R.string.form_update_mode_title) - .assertDisabled(R.string.form_update_frequency_title) - .assertDisabled(R.string.automatic_download) - .assertText(R.string.manual) - - assertThat(testDependencies.scheduler.deferredTasks.size, `is`(0)) - } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ServerSettingsTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ServerSettingsTest.java index f8433cbcbf4..3f33352ee19 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ServerSettingsTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/settings/ServerSettingsTest.java @@ -1,8 +1,5 @@ package org.odk.collect.android.feature.settings; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; @@ -10,11 +7,9 @@ import org.junit.rules.RuleChain; import org.junit.runner.RunWith; import org.odk.collect.android.R; -import org.odk.collect.android.gdrive.sheets.DriveHelper; import org.odk.collect.android.support.TestDependencies; import org.odk.collect.android.support.pages.MainMenuPage; import org.odk.collect.android.support.pages.ProjectSettingsPage; -import org.odk.collect.android.support.pages.ServerSettingsPage; import org.odk.collect.android.support.rules.CollectTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.androidtest.RecordedIntentsRule; @@ -61,32 +56,13 @@ public void whenUsingODKServer_canAddCredentialsForServer() { .clickOKOnDialog(new MainMenuPage()); } - /** - * This test could definitely be extended to cover form download/submit with the creation - * of a stub - * {@link DriveHelper} and - * {@link org.odk.collect.android.gdrive.GoogleAccountsManager} - */ @Test - public void selectingGoogleAccount_showsGoogleAccountSettings() { + public void selectingServerTypeIsDisabled() { new MainMenuPage().assertOnPage() .openProjectSettingsDialog() .clickSettings() .clickServerSettings() .clickOnServerType() - .clickOnButtonInDialog(R.string.server_platform_google_sheets, new ServerSettingsPage()) - .assertText(R.string.selected_google_account_text) - .assertText(R.string.google_sheets_url); - } - - @Test - public void selectingGoogleAccount_disablesAutomaticUpdates() { - MainMenuPage mainMenu = new MainMenuPage().assertOnPage() - .enablePreviouslyDownloadedOnlyUpdates(); - assertThat(testDependencies.scheduler.getDeferredTasks().size(), is(1)); - - testDependencies.googleAccountPicker.setDeviceAccount("steph@curry.basket"); - mainMenu.setGoogleAccount("steph@curry.basket"); - assertThat(testDependencies.scheduler.getDeferredTasks().size(), is(0)); + .assertTextDoesNotExist(R.string.cancel); } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/smoke/AllWidgetsFormTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/smoke/AllWidgetsFormTest.java index 54d0bbea1da..b6fd688a89f 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/smoke/AllWidgetsFormTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/smoke/AllWidgetsFormTest.java @@ -18,7 +18,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; -import org.odk.collect.android.support.rules.FormActivityTestRule; +import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; import tools.fastlane.screengrab.Screengrab; @@ -37,7 +37,7 @@ public class AllWidgetsFormTest { @ClassRule public static final LocaleTestRule LOCALE_TEST_RULE = new LocaleTestRule(); - public FormActivityTestRule activityTestRule = new FormActivityTestRule("all-widgets.xml", "All widgets"); + public BlankFormTestRule activityTestRule = new BlankFormTestRule("all-widgets.xml", "All widgets"); @Rule public RuleChain copyFormChain = TestRuleChain.chain() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/regression/FillBlankFormTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/regression/FillBlankFormTest.java index 27729a2a858..2224da58c0c 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/regression/FillBlankFormTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/regression/FillBlankFormTest.java @@ -25,15 +25,16 @@ import org.odk.collect.android.storage.StorageSubdirectory; import org.odk.collect.android.support.ActivityHelpers; import org.odk.collect.android.support.pages.AddNewRepeatDialog; -import org.odk.collect.android.support.rules.CollectTestRule; -import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.android.support.pages.BlankFormSearchPage; import org.odk.collect.android.support.pages.ExitFormDialog; import org.odk.collect.android.support.pages.FillBlankFormPage; import org.odk.collect.android.support.pages.FormEndPage; import org.odk.collect.android.support.pages.FormEntryPage; +import org.odk.collect.android.support.pages.FormHierarchyPage; import org.odk.collect.android.support.pages.MainMenuPage; import org.odk.collect.android.support.pages.ProjectSettingsPage; +import org.odk.collect.android.support.rules.CollectTestRule; +import org.odk.collect.android.support.rules.TestRuleChain; import java.util.ArrayList; import java.util.List; @@ -689,9 +690,9 @@ public void hierachyView_shouldNotChangeAfterScreenRotation() { .clickGoToArrow() .clickGoUpIcon() .checkIfElementInHierarchyMatchesToText("Group Name", 0) - .rotateToLandscape(new FormEntryPage("Repeat Group")) + .rotateToLandscape(new FormHierarchyPage("Repeat Group")) .checkIfElementInHierarchyMatchesToText("Group Name", 0) - .rotateToPortrait(new FormEntryPage("Repeat Group")) + .rotateToPortrait(new FormHierarchyPage("Repeat Group")) .checkIfElementInHierarchyMatchesToText("Group Name", 0); } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt index b82fef518f3..0592fd4f50a 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/CollectHelpers.kt @@ -1,10 +1,14 @@ package org.odk.collect.android.support +import android.app.Application import androidx.test.core.app.ApplicationProvider import org.odk.collect.android.application.Collect +import org.odk.collect.android.injection.DaggerUtils import org.odk.collect.android.injection.config.AppDependencyComponent import org.odk.collect.android.injection.config.AppDependencyModule import org.odk.collect.android.injection.config.DaggerAppDependencyComponent +import org.odk.collect.projects.Project +import org.odk.collect.settings.keys.ProjectKeys object CollectHelpers { fun overrideAppDependencyModule(appDependencyModule: AppDependencyModule): AppDependencyComponent { @@ -16,4 +20,31 @@ object CollectHelpers { application.component = testComponent return testComponent } + + fun simulateProcessRestart(appDependencyModule: AppDependencyModule? = null) { + val newComponent = + overrideAppDependencyModule(appDependencyModule ?: AppDependencyModule()) + + // Reinitialize any application state with new deps/state + newComponent.applicationInitializer().initialize() + } + + @JvmStatic + fun addGDProject(gdProject: Project.New, accountName: String, testDependencies: TestDependencies) { + testDependencies.googleAccountPicker.setDeviceAccount(accountName) + testDependencies.googleApi.setAccount(accountName) + + val project = DaggerUtils + .getComponent(ApplicationProvider.getApplicationContext()) + .projectsRepository() + .save(gdProject) + + DaggerUtils + .getComponent(ApplicationProvider.getApplicationContext()) + .settingsProvider().getUnprotectedSettings(project.uuid) + .also { + it.save(ProjectKeys.KEY_PROTOCOL, ProjectKeys.PROTOCOL_GOOGLE_SHEETS) + it.save(ProjectKeys.KEY_SELECTED_GOOGLE_ACCOUNT, accountName) + } + } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeClickableMapFragment.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeClickableMapFragment.kt index bb530d730ed..31df3597381 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeClickableMapFragment.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/FakeClickableMapFragment.kt @@ -15,7 +15,7 @@ class FakeClickableMapFragment : Fragment(), MapFragment { override fun init( readyListener: MapFragment.ReadyListener?, - errorListener: MapFragment.ErrorListener?, + errorListener: MapFragment.ErrorListener? ) { readyListener?.onReady(this) } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/AppClosedPage.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/AppClosedPage.kt index 25b6269eb5b..30259bb3d80 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/AppClosedPage.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/AppClosedPage.kt @@ -1,5 +1,10 @@ package org.odk.collect.android.support.pages +import android.app.Activity +import androidx.test.espresso.core.internal.deps.guava.collect.Iterables +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry +import androidx.test.runner.lifecycle.Stage import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat @@ -9,4 +14,22 @@ class AppClosedPage : Page() { assertThat(currentActivity, equalTo(null)) return this } + + private val currentActivity: Activity? + get() { + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + val activity = arrayOfNulls(1) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val activities = + ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage( + Stage.RESUMED + ) + if (!activities.isEmpty()) { + activity[0] = Iterables.getOnlyElement(activities) as Activity + } else { + activity[0] = null + } + } + return activity[0] + } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/FormEntryPage.java b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/FormEntryPage.java index a0e4f08275a..8ff5e217774 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/FormEntryPage.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/FormEntryPage.java @@ -47,6 +47,11 @@ public FormEntryPage assertOnPage() { }); assertToolbarTitle(formName); + + // Check we are not on the Form Hierarchy page + assertTextDoesNotExist(R.string.jump_to_beginning); + assertTextDoesNotExist(R.string.jump_to_end); + return this; } @@ -66,7 +71,13 @@ public FormEntryPage fillOut(QuestionAndAnswer... questionsAndAnswers) { return page; } - public MainMenuPage fillOutAndSave(QuestionAndAnswer... questionsAndAnswers) { + public > D fillOutAndSave(D destination, QuestionAndAnswer... questionsAndAnswers) { + return fillOut(questionsAndAnswers) + .pressBack(new SaveOrIgnoreDialog<>(formName, destination)) + .clickSaveChanges(); + } + + public MainMenuPage fillOutAndFinalize(QuestionAndAnswer... questionsAndAnswers) { return fillOut(questionsAndAnswers) .swipeToEndScreen() .clickSaveAndExit(); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/MainMenuPage.java b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/MainMenuPage.java index e46231032ad..bb7e879154f 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/MainMenuPage.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/MainMenuPage.java @@ -4,8 +4,6 @@ import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.intent.Intents.intending; -import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; import static androidx.test.espresso.matcher.ViewMatchers.isClickable; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; @@ -14,11 +12,6 @@ import static org.hamcrest.core.AllOf.allOf; import static org.hamcrest.core.StringContains.containsString; -import android.accounts.AccountManager; -import android.app.Activity; -import android.app.Instrumentation; -import android.content.Intent; - import org.odk.collect.android.R; import org.odk.collect.android.support.WaitFor; @@ -170,22 +163,6 @@ public MainMenuPage enableAutoSend() { .pressBack(new MainMenuPage()); } - public MainMenuPage setGoogleAccount(String account) { - Intent data = new Intent(); - data.putExtra(AccountManager.KEY_ACCOUNT_NAME, account); - Instrumentation.ActivityResult activityResult = new Instrumentation.ActivityResult(Activity.RESULT_OK, data); - intending(hasAction("com.google.android.gms.common.account.CHOOSE_ACCOUNT")).respondWith(activityResult); - - return openProjectSettingsDialog() - .clickSettings() - .clickServerSettings() - .clickOnServerType() - .clickOnString(R.string.server_platform_google_sheets) - .clickOnString(R.string.selected_google_account_text) - .pressBack(new ProjectSettingsPage()) - .pressBack(new MainMenuPage()); - } - public MainMenuPage addAndSwitchToProject(String serverUrl) { return openProjectSettingsDialog() .clickAddProject() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ManualProjectCreatorDialogPage.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ManualProjectCreatorDialogPage.kt index aeebbcd63f7..d3896b8ed94 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ManualProjectCreatorDialogPage.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ManualProjectCreatorDialogPage.kt @@ -1,14 +1,7 @@ package org.odk.collect.android.support.pages -import android.accounts.AccountManager -import android.app.Activity -import android.app.Instrumentation -import android.content.Intent import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.scrollTo -import androidx.test.espresso.intent.Intents.intending -import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction import androidx.test.espresso.matcher.ViewMatchers.withText import org.odk.collect.android.R @@ -33,21 +26,6 @@ class ManualProjectCreatorDialogPage : Page() { return this } - fun openGooglePickerAndSelect(googleAccount: String, duplicate: Boolean = false): MainMenuPage { - val data = Intent() - data.putExtra(AccountManager.KEY_ACCOUNT_NAME, googleAccount) - val activityResult = Instrumentation.ActivityResult(Activity.RESULT_OK, data) - intending(hasAction("com.google.android.gms.common.account.CHOOSE_ACCOUNT")).respondWith(activityResult) - - onView(withText(R.string.gdrive_configure)).perform(scrollTo(), click()) - - if (duplicate) { - addDuplicateProject() - } - - return MainMenuPage().assertOnPage() - } - fun addProject(): MainMenuPage { onView(withText(R.string.add)).perform(click()) return MainMenuPage().assertOnPage() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/Page.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/Page.kt index 2c4642db7ad..8fd4c57268a 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/Page.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/Page.kt @@ -1,6 +1,5 @@ package org.odk.collect.android.support.pages -import android.app.Activity import android.content.pm.ActivityInfo import androidx.recyclerview.widget.RecyclerView import androidx.test.core.app.ApplicationProvider @@ -16,7 +15,6 @@ import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions -import androidx.test.espresso.core.internal.deps.guava.collect.Iterables import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.hasDescendant @@ -30,9 +28,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withHint import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry -import androidx.test.runner.lifecycle.Stage import org.hamcrest.CoreMatchers.not import org.hamcrest.Matchers.allOf import org.hamcrest.core.StringContains.containsString @@ -434,20 +429,5 @@ abstract class Page> { private fun rotateToPortrait(): ViewAction { return RotateAction(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) } - - val currentActivity: Activity? - get() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync() - val activity = arrayOfNulls(1) - InstrumentationRegistry.getInstrumentation().runOnMainSync { - val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED) - if (!activities.isEmpty()) { - activity[0] = Iterables.getOnlyElement(activities) as Activity - } else { - activity[0] = null - } - } - return activity[0] - } } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ProjectManagementPage.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ProjectManagementPage.kt index 073c18b100d..cd656318808 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ProjectManagementPage.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/ProjectManagementPage.kt @@ -19,6 +19,11 @@ class ProjectManagementPage : Page() { return QRCodePage().assertOnPage() } + fun clickOnDeleteProject(): ProjectManagementPage { + scrollToRecyclerViewItemAndClickText(R.string.delete_project) + return this + } + fun deleteProject(): MainMenuPage { scrollToRecyclerViewItemAndClickText(R.string.delete_project) clickOnString(R.string.delete_project_yes) diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormActivityTestRule.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/BlankFormTestRule.kt similarity index 73% rename from collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormActivityTestRule.kt rename to collect_app/src/androidTest/java/org/odk/collect/android/support/rules/BlankFormTestRule.kt index a079bcb243e..a23986763f3 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormActivityTestRule.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/BlankFormTestRule.kt @@ -3,37 +3,42 @@ package org.odk.collect.android.support.rules import android.app.Activity import android.app.Application import android.content.Intent +import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider -import org.odk.collect.android.activities.FormEntryActivity -import org.odk.collect.android.external.FormsContract +import org.junit.rules.ExternalResource +import org.odk.collect.android.formmanagement.FormNavigator import org.odk.collect.android.injection.DaggerUtils import org.odk.collect.android.storage.StorageSubdirectory import org.odk.collect.android.support.StorageUtils import org.odk.collect.android.support.pages.FormEntryPage -import org.odk.collect.androidtest.ActivityScenarioLauncherRule import org.odk.collect.projects.Project import org.odk.collect.projects.Project.Companion.DEMO_PROJECT +import timber.log.Timber import java.io.IOException -class FormActivityTestRule @JvmOverloads constructor( +class BlankFormTestRule @JvmOverloads constructor( private val formFilename: String, private val formName: String, private val mediaFilePaths: List? = null -) : ActivityScenarioLauncherRule() { +) : ExternalResource() { - private lateinit var formEntryPage: FormEntryPage + private lateinit var scenario: ActivityScenario override fun before() { - super.before() - setUpProjectAndCopyForm() - launch(activityIntent) - formEntryPage = FormEntryPage(formName) - formEntryPage.assertOnPage() + scenario = ActivityScenario.launch(activityIntent) + } + + override fun after() { + try { + scenario.close() + } catch (e: Throwable) { + Timber.e(Error("Error closing ActivityScenario: $e")) + } } fun startInFormEntry(): FormEntryPage { - return formEntryPage + return FormEntryPage(formName).assertOnPage() } private fun setUpProjectAndCopyForm() { @@ -58,8 +63,7 @@ class FormActivityTestRule @JvmOverloads constructor( .getOneByPath(formPath) val projectId = DaggerUtils.getComponent(application).currentProjectProvider() .getCurrentProject().uuid - val intent = Intent(application, FormEntryActivity::class.java) - intent.data = FormsContract.getUri(projectId, form!!.dbId) - return intent + + return FormNavigator.newInstanceIntent(application, projectId, form!!.dbId) } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/CollectTestRule.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/CollectTestRule.kt index 00a4406644d..815825d7adb 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/CollectTestRule.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/CollectTestRule.kt @@ -15,7 +15,7 @@ import org.odk.collect.androidtest.ActivityScenarioLauncherRule import java.util.function.Consumer class CollectTestRule @JvmOverloads constructor( - private val useDemoProject: Boolean = true, + private val useDemoProject: Boolean = true ) : ActivityScenarioLauncherRule() { override fun before() { @@ -58,7 +58,7 @@ class CollectTestRule @JvmOverloads constructor( fun > launchForResult( intent: Intent, destination: T, - actions: Consumer, + actions: Consumer ): Instrumentation.ActivityResult { val scenario = launchForResult(intent) destination.assertOnPage() diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormEntryActivityTestRule.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormEntryActivityTestRule.kt new file mode 100644 index 00000000000..197e6242b97 --- /dev/null +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormEntryActivityTestRule.kt @@ -0,0 +1,121 @@ +package org.odk.collect.android.support.rules + +import android.app.Activity +import android.app.Application +import android.content.Intent +import android.os.Bundle +import android.os.PersistableBundle +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import org.junit.rules.ExternalResource +import org.odk.collect.android.formmanagement.FormNavigator +import org.odk.collect.android.injection.DaggerUtils +import org.odk.collect.android.storage.StorageSubdirectory +import org.odk.collect.android.support.ActivityHelpers +import org.odk.collect.android.support.StorageUtils +import org.odk.collect.android.support.pages.FormEntryPage +import org.odk.collect.android.support.pages.FormHierarchyPage +import org.odk.collect.android.support.pages.Page +import org.odk.collect.projects.Project +import timber.log.Timber +import java.io.IOException + +class FormEntryActivityTestRule : ExternalResource() { + + private lateinit var intent: Intent + private lateinit var scenario: ActivityScenario + + override fun after() { + try { + scenario.close() + } catch (e: Throwable) { + Timber.e(Error("Error closing ActivityScenario: $e")) + } + } + + fun setUpProjectAndCopyForm(formFilename: String): FormEntryActivityTestRule { + try { + // Set up demo project + val component = + DaggerUtils.getComponent(ApplicationProvider.getApplicationContext()) + component.projectsRepository().save(Project.DEMO_PROJECT) + component.currentProjectProvider().setCurrentProject(Project.DEMO_PROJECT_ID) + StorageUtils.copyFormToDemoProject(formFilename, null, true) + } catch (e: IOException) { + throw RuntimeException(e) + } + + return this + } + + fun > fillNewForm(formFilename: String, destination: D): D { + intent = createNewFormIntent(formFilename) + scenario = ActivityScenario.launch(intent) + return destination.assertOnPage() + } + + fun fillNewForm(formFilename: String, formName: String): FormEntryPage { + return fillNewForm(formFilename, FormEntryPage(formName)) + } + + fun editForm(formFilename: String, instanceName: String): FormHierarchyPage { + intent = createEditFormIntent(formFilename) + scenario = ActivityScenario.launch(intent) + return FormHierarchyPage(instanceName).assertOnPage() + } + + fun saveInstanceStateForActivity(): FormEntryActivityTestRule { + scenario.onActivity { + it.onSaveInstanceState(Bundle(), PersistableBundle()) + } + + return this + } + + fun destroyActivity(): FormEntryActivityTestRule { + lateinit var scenarioActivity: Activity + scenario.onActivity { + scenarioActivity = it + } + + if (ActivityHelpers.getActivity() != scenarioActivity) { + throw IllegalStateException("Can't destroy backstack!") + } + + scenario.moveToState(Lifecycle.State.DESTROYED) + return this + } + + private fun createNewFormIntent(formFilename: String): Intent { + val application = ApplicationProvider.getApplicationContext() + val formPath = DaggerUtils.getComponent(application).storagePathProvider() + .getOdkDirPath(StorageSubdirectory.FORMS) + "/" + formFilename + val form = DaggerUtils.getComponent(application).formsRepositoryProvider().get() + .getOneByPath(formPath) + val projectId = DaggerUtils.getComponent(application).currentProjectProvider() + .getCurrentProject().uuid + + return FormNavigator.newInstanceIntent(application, projectId, form!!.dbId) + } + + private fun createEditFormIntent(formFilename: String): Intent { + val application = ApplicationProvider.getApplicationContext() + val formPath = DaggerUtils.getComponent(application).storagePathProvider() + .getOdkDirPath(StorageSubdirectory.FORMS) + "/" + formFilename + val form = DaggerUtils.getComponent(application).formsRepositoryProvider().get() + .getOneByPath(formPath) + val instance = DaggerUtils.getComponent(application).instancesRepositoryProvider().get() + .getAllByFormId(form!!.formId).first() + val projectId = DaggerUtils.getComponent(application).currentProjectProvider() + .getCurrentProject().uuid + + return FormNavigator.editInstanceIntent( + application, + projectId, + instance.dbId, + false, + instance.status + ) + } +} diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/ResetStateRule.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/ResetStateRule.kt index 1a246d9dc58..5d39a6ec05d 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/ResetStateRule.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/ResetStateRule.kt @@ -21,7 +21,7 @@ import java.io.IOException private class ResetStateStatement( private val base: Statement, - private val appDependencyModule: AppDependencyModule? = null, + private val appDependencyModule: AppDependencyModule? = null ) : Statement() { override fun evaluate() { @@ -32,12 +32,7 @@ private class ResetStateStatement( clearDisk(oldComponent) clearAppState(application) setTestState() - - val newComponent = - CollectHelpers.overrideAppDependencyModule(appDependencyModule ?: AppDependencyModule()) - - // Reinitialize any application state with new deps/state - newComponent.applicationInitializer().initialize() + CollectHelpers.simulateProcessRestart(appDependencyModule) base.evaluate() } @@ -68,7 +63,7 @@ private class ResetStateStatement( } class ResetStateRule @JvmOverloads constructor( - private val appDependencyModule: AppDependencyModule? = null, + private val appDependencyModule: AppDependencyModule? = null ) : TestRule { override fun apply(base: Statement, description: Description): Statement = diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryActivity.java index 3feef793fac..3c38c3fbd25 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryActivity.java @@ -1125,17 +1125,12 @@ public void deleteGroup() { */ @Override public Object onRetainCustomNonConfigurationInstance() { - FormController formController = getFormController(); // if a form is loading, pass the loader task if (formLoaderTask != null && formLoaderTask.getStatus() != AsyncTask.Status.FINISHED) { return formLoaderTask; } - // mFormEntryController is static so we don't need to pass it. - if (formController != null && formController.currentPromptIsQuestion()) { - formEntryViewModel.updateAnswersForScreen(getAnswers(), false); - } return null; } @@ -1835,7 +1830,6 @@ public void onClick(DialogInterface dialog, int i) { switch (i) { case BUTTON_POSITIVE: // yes clearAnswer(qw); - formEntryViewModel.updateAnswersForScreen(getAnswers(), false); break; } } @@ -1877,9 +1871,7 @@ private void createLanguageDialog() { getFormController().setLanguage(languages[whichButton]); dialog.dismiss(); - if (getFormController().currentPromptIsQuestion()) { - formEntryViewModel.updateAnswersForScreen(getAnswers(), false); - } + formEntryViewModel.updateAnswersForScreen(getAnswers(), false); onScreenRefresh(); }) .setTitle(getString(R.string.change_language)) diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt index 3a2e1896484..fea6519fef2 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt +++ b/collect_app/src/main/java/org/odk/collect/android/activities/FormEntryViewModelFactory.kt @@ -39,13 +39,13 @@ class FormEntryViewModelFactory( private val settingsProvider: SettingsProvider, private val permissionsChecker: PermissionsChecker, private val fusedLocationClient: LocationClient, - private val permissionsProvider: PermissionsProvider, + private val permissionsProvider: PermissionsProvider ) : AbstractSavedStateViewModelFactory(owner, null) { override fun create( key: String, modelClass: Class, - handle: SavedStateHandle, + handle: SavedStateHandle ): T { return when (modelClass) { FormEntryViewModel::class.java -> FormEntryViewModel( diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/viewmodels/CurrentProjectViewModel.kt b/collect_app/src/main/java/org/odk/collect/android/activities/viewmodels/CurrentProjectViewModel.kt index e0aaf752f7e..0a00014bab4 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/viewmodels/CurrentProjectViewModel.kt +++ b/collect_app/src/main/java/org/odk/collect/android/activities/viewmodels/CurrentProjectViewModel.kt @@ -12,7 +12,7 @@ import org.odk.collect.projects.Project class CurrentProjectViewModel( private val currentProjectProvider: CurrentProjectProvider, - private val analyticsInitializer: AnalyticsInitializer, + private val analyticsInitializer: AnalyticsInitializer ) : ViewModel() { private val _currentProject by lazy { MutableNonNullLiveData(currentProjectProvider.getCurrentProject()) } @@ -42,7 +42,7 @@ class CurrentProjectViewModel( open class Factory( private val currentProjectProvider: CurrentProjectProvider, - private val analyticsInitializer: AnalyticsInitializer, + private val analyticsInitializer: AnalyticsInitializer ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { diff --git a/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegate.kt b/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegate.kt index 106f711769a..a7ae00898b3 100644 --- a/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegate.kt +++ b/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegate.kt @@ -11,6 +11,7 @@ import org.odk.collect.android.analytics.AnalyticsEvents import org.odk.collect.projects.Project.Saved import org.odk.collect.qrcode.QRCodeDecoder import org.odk.collect.settings.ODKAppSettingsImporter +import org.odk.collect.settings.importing.SettingsImportingResult import java.io.FileNotFoundException import java.io.InputStream @@ -32,15 +33,18 @@ class QRCodeActivityResultDelegate( } try { val response = qrCodeDecoder.decode(imageStream) - if (settingsImporter.fromJSON(response, project)) { - log(AnalyticsEvents.RECONFIGURE_PROJECT) - showToast(R.string.successfully_imported_settings) - ActivityUtils.startActivityAndCloseAllOthers( - activity, - MainMenuActivity::class.java - ) - } else { - showToast(R.string.invalid_qrcode) + + when (settingsImporter.fromJSON(response, project)) { + SettingsImportingResult.SUCCESS -> { + log(AnalyticsEvents.RECONFIGURE_PROJECT) + showToast(R.string.successfully_imported_settings) + ActivityUtils.startActivityAndCloseAllOthers( + activity, + MainMenuActivity::class.java + ) + } + SettingsImportingResult.INVALID_SETTINGS -> showToast(R.string.invalid_qrcode) + SettingsImportingResult.GD_PROJECT -> showToast(R.string.settings_with_gd_protocol) } } catch (e: QRCodeDecoder.QRCodeInvalidException) { showToast(R.string.invalid_qrcode) diff --git a/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeScannerFragment.kt b/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeScannerFragment.kt index 4188c1adcb7..311031eeab3 100644 --- a/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeScannerFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/configure/qr/QRCodeScannerFragment.kt @@ -15,6 +15,7 @@ import org.odk.collect.android.storage.StoragePathProvider import org.odk.collect.androidshared.ui.ToastUtils.showLongToast import org.odk.collect.androidshared.utils.CompressionUtils import org.odk.collect.settings.ODKAppSettingsImporter +import org.odk.collect.settings.importing.SettingsImportingResult import java.io.File import java.io.IOException import java.util.zip.DataFormatException @@ -40,27 +41,29 @@ class QRCodeScannerFragment : BarCodeScannerFragment() { override fun handleScanningResult(result: BarcodeResult) { val oldProjectName = currentProjectProvider.getCurrentProject().name - val importSuccess = settingsImporter.fromJSON( + val settingsImportingResult = settingsImporter.fromJSON( CompressionUtils.decompress(result.text), currentProjectProvider.getCurrentProject() ) - if (importSuccess) { - Analytics.log(AnalyticsEvents.RECONFIGURE_PROJECT) + when (settingsImportingResult) { + SettingsImportingResult.SUCCESS -> { + Analytics.log(AnalyticsEvents.RECONFIGURE_PROJECT) - val newProjectName = currentProjectProvider.getCurrentProject().name - if (newProjectName != oldProjectName) { - File(storagePathProvider.getProjectRootDirPath() + File.separator + oldProjectName).delete() - File(storagePathProvider.getProjectRootDirPath() + File.separator + newProjectName).createNewFile() - } + val newProjectName = currentProjectProvider.getCurrentProject().name + if (newProjectName != oldProjectName) { + File(storagePathProvider.getProjectRootDirPath() + File.separator + oldProjectName).delete() + File(storagePathProvider.getProjectRootDirPath() + File.separator + newProjectName).createNewFile() + } - showLongToast(requireContext(), getString(R.string.successfully_imported_settings)) - ActivityUtils.startActivityAndCloseAllOthers( - requireActivity(), - MainMenuActivity::class.java - ) - } else { - showLongToast(requireContext(), getString(R.string.invalid_qrcode)) + showLongToast(requireContext(), getString(R.string.successfully_imported_settings)) + ActivityUtils.startActivityAndCloseAllOthers( + requireActivity(), + MainMenuActivity::class.java + ) + } + SettingsImportingResult.INVALID_SETTINGS -> showLongToast(requireContext(), getString(R.string.invalid_qrcode)) + SettingsImportingResult.GD_PROJECT -> showLongToast(requireContext(), getString(R.string.settings_with_gd_protocol)) } } diff --git a/collect_app/src/main/java/org/odk/collect/android/configure/qr/ShowQRCodeFragment.kt b/collect_app/src/main/java/org/odk/collect/android/configure/qr/ShowQRCodeFragment.kt index 12bdab06f55..459790a17fa 100644 --- a/collect_app/src/main/java/org/odk/collect/android/configure/qr/ShowQRCodeFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/configure/qr/ShowQRCodeFragment.kt @@ -137,7 +137,8 @@ class ShowQRCodeFragment : Fragment() { } override fun onChildViewRemoved(view: View, view1: View) {} - }) + } + ) } .show() } diff --git a/collect_app/src/main/java/org/odk/collect/android/database/DatabaseObjectMapper.kt b/collect_app/src/main/java/org/odk/collect/android/database/DatabaseObjectMapper.kt index 95a8c9bec23..b3cc87b1bb8 100644 --- a/collect_app/src/main/java/org/odk/collect/android/database/DatabaseObjectMapper.kt +++ b/collect_app/src/main/java/org/odk/collect/android/database/DatabaseObjectMapper.kt @@ -189,9 +189,13 @@ object DatabaseObjectMapper { .status(cursor.getString(statusColumnIndex)) .lastStatusChangeDate(cursor.getLong(lastStatusChangeDateColumnIndex)) .deletedDate( - if (cursor.isNull(deletedDateColumnIndex)) null else cursor.getLong( - deletedDateColumnIndex - ) + if (cursor.isNull(deletedDateColumnIndex)) { + null + } else { + cursor.getLong( + deletedDateColumnIndex + ) + } ) .geometryType(cursor.getString(geometryTypeColumnIndex)) .geometry(cursor.getString(geometryColumnIndex)) diff --git a/collect_app/src/main/java/org/odk/collect/android/draw/DrawView.kt b/collect_app/src/main/java/org/odk/collect/android/draw/DrawView.kt index 5d28f7e9c02..34f7b252ad1 100644 --- a/collect_app/src/main/java/org/odk/collect/android/draw/DrawView.kt +++ b/collect_app/src/main/java/org/odk/collect/android/draw/DrawView.kt @@ -121,8 +121,10 @@ class DrawView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private fun touchMove(x: Float, y: Float) { currentPath.quadTo(valueX, valueY, (x + valueX) / 2, (y + valueY) / 2) offscreenPath.quadTo( - valueX - bitmapLeft, valueY - bitmapTop, - (x + valueX) / 2 - bitmapLeft, (y + valueY) / 2 - bitmapTop + valueX - bitmapLeft, + valueY - bitmapTop, + (x + valueX) / 2 - bitmapLeft, + (y + valueY) / 2 - bitmapTop ) valueX = x valueY = y diff --git a/collect_app/src/main/java/org/odk/collect/android/external/FormUriActivity.kt b/collect_app/src/main/java/org/odk/collect/android/external/FormUriActivity.kt index 5879af19fab..67bdc6a86a6 100644 --- a/collect_app/src/main/java/org/odk/collect/android/external/FormUriActivity.kt +++ b/collect_app/src/main/java/org/odk/collect/android/external/FormUriActivity.kt @@ -57,7 +57,7 @@ class FormUriActivity : ComponentActivity() { it.action = intent.action it.data = uri intent.extras?.let { sourceExtras -> it.putExtras(sourceExtras) } - }, + } ) } } else { diff --git a/collect_app/src/main/java/org/odk/collect/android/formentry/QuitFormDialog.kt b/collect_app/src/main/java/org/odk/collect/android/formentry/QuitFormDialog.kt index 916a98d0ce5..62e71e5cfb1 100644 --- a/collect_app/src/main/java/org/odk/collect/android/formentry/QuitFormDialog.kt +++ b/collect_app/src/main/java/org/odk/collect/android/formentry/QuitFormDialog.kt @@ -30,7 +30,7 @@ object QuitFormDialog { formEntryViewModel: FormEntryViewModel, settingsProvider: SettingsProvider, currentProjectProvider: CurrentProjectProvider, - onSaveChangesClicked: Runnable?, + onSaveChangesClicked: Runnable? ): AlertDialog { return create( activity, @@ -50,7 +50,7 @@ object QuitFormDialog { formEntryViewModel: FormEntryViewModel, settingsProvider: SettingsProvider, currentProjectProvider: CurrentProjectProvider, - onSaveChangesClicked: Runnable?, + onSaveChangesClicked: Runnable? ): AlertDialog { val title: String = if (formSaveViewModel.formName == null) activity.resources.getString(R.string.no_form_loaded) else formSaveViewModel.getFormName() diff --git a/collect_app/src/main/java/org/odk/collect/android/formentry/audit/AuditUtils.kt b/collect_app/src/main/java/org/odk/collect/android/formentry/audit/AuditUtils.kt index d62bad4bb30..f63b5bbddc8 100644 --- a/collect_app/src/main/java/org/odk/collect/android/formentry/audit/AuditUtils.kt +++ b/collect_app/src/main/java/org/odk/collect/android/formentry/audit/AuditUtils.kt @@ -18,9 +18,11 @@ object AuditUtils { try { for (question in formController.getQuestionPrompts()) { val answer = - if (question.answerValue != null) + if (question.answerValue != null) { question.answerValue!!.displayText - else null + } else { + null + } auditEventLogger.logEvent( AuditEvent.AuditEventType.QUESTION, diff --git a/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListActivity.kt b/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListActivity.kt index 27962a652b5..44a9eaf991d 100644 --- a/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListActivity.kt +++ b/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListActivity.kt @@ -11,11 +11,10 @@ import android.widget.TextView import androidx.activity.viewModels import androidx.recyclerview.widget.RecyclerView import org.odk.collect.android.R -import org.odk.collect.android.activities.FormEntryActivity import org.odk.collect.android.activities.FormMapActivity +import org.odk.collect.android.formmanagement.FormNavigator import org.odk.collect.android.injection.DaggerUtils import org.odk.collect.android.preferences.dialogs.ServerAuthDialogFragment -import org.odk.collect.android.utilities.ApplicationConstants import org.odk.collect.androidshared.network.NetworkStateProvider import org.odk.collect.androidshared.ui.DialogFragmentUtils import org.odk.collect.androidshared.ui.SnackbarUtils @@ -75,16 +74,7 @@ class BlankFormListActivity : LocalizedActivity(), OnFormItemClickListener { setResult(RESULT_OK, Intent().setData(formUri)) } else { // caller wants to view/edit a form, so launch formentryactivity - Intent(this, FormEntryActivity::class.java).apply { - action = Intent.ACTION_EDIT - data = formUri - putExtra( - ApplicationConstants.BundleKeys.FORM_MODE, - ApplicationConstants.FormModes.EDIT_SAVED - ) - - startActivity(this) - } + startActivity(FormNavigator.newInstanceIntent(this, formUri)) } finish() } diff --git a/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListViewModel.kt b/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListViewModel.kt index f4778f48f5c..9a7e034378c 100644 --- a/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListViewModel.kt +++ b/collect_app/src/main/java/org/odk/collect/android/formlists/blankformlist/BlankFormListViewModel.kt @@ -1,13 +1,12 @@ package org.odk.collect.android.formlists.blankformlist import android.app.Application -import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer -import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.map import org.odk.collect.android.formmanagement.FormsUpdater import org.odk.collect.android.formmanagement.matchexactly.SyncStatusAppState import org.odk.collect.android.preferences.utilities.FormUpdateMode @@ -24,8 +23,6 @@ import org.odk.collect.forms.FormsRepository import org.odk.collect.forms.instances.InstancesRepository import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.shared.settings.Settings -import org.odk.collect.shared.strings.Md5.getMd5Hash -import java.io.ByteArrayInputStream class BlankFormListViewModel( private val formsRepository: FormsRepository, @@ -50,13 +47,11 @@ class BlankFormListViewModel( private val isFormLoadingRunning = MutableNonNullLiveData(false) private val isSyncingWithStorageRunning = MutableNonNullLiveData(false) - val isLoading: LiveData = Transformations.map( - LiveDataUtils.zip3( - isFormLoadingRunning, - isSyncingWithStorageRunning, - syncRepository.isSyncing(projectId), - ) - ) { (one, two, three) -> one || two || three } + val isLoading: LiveData = LiveDataUtils.zip3( + isFormLoadingRunning, + isSyncingWithStorageRunning, + syncRepository.isSyncing(projectId) + ).map { (one, two, three) -> one || two || three } var sortingOrder: Int = generalSettings.getInt("formChooserListSortingOrder") get() { return generalSettings.getInt("formChooserListSortingOrder") } @@ -151,7 +146,6 @@ class BlankFormListViewModel( } fun syncWithServer(): LiveData { - logManualSyncWithServer() val result = MutableLiveData() scheduler.immediate( { formsUpdater.matchFormsWithServer(projectId) }, @@ -171,17 +165,13 @@ class BlankFormListViewModel( } fun isOutOfSyncWithServer(): LiveData { - return Transformations.map( - syncRepository.getSyncError(projectId) - ) { obj: FormSourceException? -> + return syncRepository.getSyncError(projectId).map { obj: FormSourceException? -> obj != null } } fun isAuthenticationRequired(): LiveData { - return Transformations.map( - syncRepository.getSyncError(projectId) - ) { error: FormSourceException? -> + return syncRepository.getSyncError(projectId).map { error: FormSourceException? -> if (error != null) { error is AuthRequired } else { @@ -190,12 +180,6 @@ class BlankFormListViewModel( } } - private fun logManualSyncWithServer() { - val uri = Uri.parse(generalSettings.getString(ProjectKeys.KEY_SERVER_URL)) - val host = if (uri.host != null) uri.host else "" - val urlHash = getMd5Hash(ByteArrayInputStream(host!!.toByteArray())) ?: "" - } - private fun sortAndFilter() { _formsToDisplay.value = when (sortingOrder) { 0 -> _allForms.value.sortedBy { it.formName.lowercase() } diff --git a/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormNavigator.kt b/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormNavigator.kt index ec0261c4817..f1a0bee472e 100644 --- a/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormNavigator.kt +++ b/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormNavigator.kt @@ -1,7 +1,8 @@ package org.odk.collect.android.formmanagement -import android.app.Activity +import android.content.Context import android.content.Intent +import android.net.Uri import org.odk.collect.android.activities.FormEntryActivity import org.odk.collect.android.external.FormsContract import org.odk.collect.android.external.InstancesContract @@ -18,16 +19,45 @@ class FormNavigator( private val instancesRepositoryProvider: () -> InstancesRepository ) { - fun editInstance(activity: Activity, instanceId: Long) { - val uri = InstancesContract.getUri(projectId, instanceId) - activity.startActivity( - Intent(activity, FormEntryActivity::class.java).also { + fun editInstance(context: Context, instanceId: Long) { + val editingDisabled = !settingsProvider.getProtectedSettings().getBoolean(KEY_EDIT_SAVED) + val status = instancesRepositoryProvider().get(instanceId)?.status + + context.startActivity( + editInstanceIntent(context, projectId, instanceId, editingDisabled, status) + ) + } + + fun newInstance(context: Context, formId: Long) { + context.startActivity( + newInstanceIntent(context, projectId, formId) + ) + } + + companion object { + fun newInstanceIntent(context: Context, uri: Uri?): Intent { + return Intent(context, FormEntryActivity::class.java).also { it.action = Intent.ACTION_EDIT it.data = uri + } + } + + fun newInstanceIntent(context: Context, projectId: String, formId: Long): Intent { + return newInstanceIntent(context, FormsContract.getUri(projectId, formId)) + } - val editingDisabled = - !settingsProvider.getProtectedSettings().getBoolean(KEY_EDIT_SAVED) - val status = instancesRepositoryProvider().get(instanceId)?.status + fun editInstanceIntent( + context: Context, + projectId: String, + instanceId: Long, + editingDisabled: Boolean, + status: String? + ): Intent { + val uri = InstancesContract.getUri(projectId, instanceId) + + return Intent(context, FormEntryActivity::class.java).also { + it.action = Intent.ACTION_EDIT + it.data = uri if (editingDisabled || status == Instance.STATUS_SUBMITTED || @@ -36,15 +66,6 @@ class FormNavigator( it.putExtra(FORM_MODE, VIEW_SENT) } } - ) - } - - fun newInstance(activity: Activity, formId: Long) { - activity.startActivity( - Intent(activity, FormEntryActivity::class.java).also { - it.action = Intent.ACTION_EDIT - it.data = FormsContract.getUri(projectId, formId) - } - ) + } } } diff --git a/collect_app/src/main/java/org/odk/collect/android/formmanagement/formmap/FormMapViewModel.kt b/collect_app/src/main/java/org/odk/collect/android/formmanagement/formmap/FormMapViewModel.kt index e8a58f4c37e..6e4b806c08e 100644 --- a/collect_app/src/main/java/org/odk/collect/android/formmanagement/formmap/FormMapViewModel.kt +++ b/collect_app/src/main/java/org/odk/collect/android/formmanagement/formmap/FormMapViewModel.kt @@ -100,7 +100,7 @@ class FormMapViewModel( private fun createItem( instance: Instance, latitude: Double, - longitude: Double, + longitude: Double ): MappableSelectItem { val instanceLastStatusChangeDate = InstanceProvider.getDisplaySubtext( resources, diff --git a/collect_app/src/main/java/org/odk/collect/android/fragments/dialogs/NumberPickerDialog.kt b/collect_app/src/main/java/org/odk/collect/android/fragments/dialogs/NumberPickerDialog.kt index 623ebcd5ad7..a64b7c9f8f2 100644 --- a/collect_app/src/main/java/org/odk/collect/android/fragments/dialogs/NumberPickerDialog.kt +++ b/collect_app/src/main/java/org/odk/collect/android/fragments/dialogs/NumberPickerDialog.kt @@ -55,7 +55,8 @@ class NumberPickerDialog : DialogFragment() { .setView(view) .setPositiveButton(R.string.ok) { _, _ -> listener?.onNumberPickerValueSelected( - requireArguments().getInt(WIDGET_ID), numberPicker.value + requireArguments().getInt(WIDGET_ID), + numberPicker.value ) } .setNegativeButton(R.string.cancel) { _, _ -> } diff --git a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectGeoDependencyModule.kt b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectGeoDependencyModule.kt index e2f6b823dfb..ba7d252f7e4 100644 --- a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectGeoDependencyModule.kt +++ b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectGeoDependencyModule.kt @@ -20,7 +20,7 @@ class CollectGeoDependencyModule( private val mapFragmentFactory: MapFragmentFactory, private val locationClient: LocationClient, private val scheduler: Scheduler, - private val permissionChecker: PermissionsChecker, + private val permissionChecker: PermissionsChecker ) : GeoDependencyModule() { override fun providesReferenceLayerSettingsNavigator(): ReferenceLayerSettingsNavigator { diff --git a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt index 1d4666fe727..08dfe459ae9 100644 --- a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt +++ b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt @@ -11,7 +11,7 @@ import org.odk.collect.settings.keys.ProjectKeys class CollectOsmDroidDependencyModule( private val referenceLayerRepository: ReferenceLayerRepository, private val locationClient: LocationClient, - private val settingsProvider: SettingsProvider, + private val settingsProvider: SettingsProvider ) : OsmDroidDependencyModule() { override fun providesReferenceLayerRepository(): ReferenceLayerRepository { return referenceLayerRepository diff --git a/collect_app/src/main/java/org/odk/collect/android/notifications/builders/FormUpdatesDownloadedNotificationBuilder.kt b/collect_app/src/main/java/org/odk/collect/android/notifications/builders/FormUpdatesDownloadedNotificationBuilder.kt index 6e1d19750e2..a9f1476fd49 100644 --- a/collect_app/src/main/java/org/odk/collect/android/notifications/builders/FormUpdatesDownloadedNotificationBuilder.kt +++ b/collect_app/src/main/java/org/odk/collect/android/notifications/builders/FormUpdatesDownloadedNotificationBuilder.kt @@ -39,16 +39,22 @@ object FormUpdatesDownloadedNotificationBuilder { ) val title = - if (allFormsDownloadedSuccessfully) application.getLocalizedString(R.string.forms_download_succeeded) - else application.getLocalizedString(R.string.forms_download_failed) + if (allFormsDownloadedSuccessfully) { + application.getLocalizedString(R.string.forms_download_succeeded) + } else { + application.getLocalizedString(R.string.forms_download_failed) + } val message = - if (allFormsDownloadedSuccessfully) application.getLocalizedString(R.string.all_downloads_succeeded) - else application.getLocalizedString( - R.string.some_downloads_failed, - FormsDownloadResultInterpreter.getNumberOfFailures(result), - result.size - ) + if (allFormsDownloadedSuccessfully) { + application.getLocalizedString(R.string.all_downloads_succeeded) + } else { + application.getLocalizedString( + R.string.some_downloads_failed, + FormsDownloadResultInterpreter.getNumberOfFailures(result), + result.size + ) + } return NotificationCompat.Builder( application, diff --git a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt index 81a7a07f9d4..67e5700c767 100644 --- a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt @@ -100,8 +100,11 @@ class MapsPreferencesFragment : BaseProjectPreferencesFragment() { */ private fun initBasemapSourcePref() { basemapSourcePref = PrefUtils.createListPref( - requireContext(), KEY_BASEMAP_SOURCE, getString(R.string.basemap_source), - MapConfiguratorProvider.getLabelIds(), MapConfiguratorProvider.getIds(), + requireContext(), + KEY_BASEMAP_SOURCE, + getString(R.string.basemap_source), + MapConfiguratorProvider.getLabelIds(), + MapConfiguratorProvider.getIds(), settingsProvider.getUnprotectedSettings() ) basemapSourcePref.setIconSpaceReserved(false) diff --git a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectDisplayPreferencesFragment.kt b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectDisplayPreferencesFragment.kt index 8ca015127a1..e2827f31ad1 100644 --- a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectDisplayPreferencesFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectDisplayPreferencesFragment.kt @@ -53,7 +53,8 @@ class ProjectDisplayPreferencesFragment : projectsRepository.save(Project.Saved(uuid, name, icon, color)) findPreference(PROJECT_COLOR_KEY)!!.summaryProvider = ProjectDetailsSummaryProvider( - PROJECT_COLOR_KEY, currentProjectProvider + PROJECT_COLOR_KEY, + currentProjectProvider ) } ) @@ -66,15 +67,18 @@ class ProjectDisplayPreferencesFragment : findPreference(PROJECT_NAME_KEY)!!.summaryProvider = ProjectDetailsSummaryProvider( - PROJECT_NAME_KEY, currentProjectProvider + PROJECT_NAME_KEY, + currentProjectProvider ) findPreference(PROJECT_ICON_KEY)!!.summaryProvider = ProjectDetailsSummaryProvider( - PROJECT_ICON_KEY, currentProjectProvider + PROJECT_ICON_KEY, + currentProjectProvider ) findPreference(PROJECT_COLOR_KEY)!!.summaryProvider = ProjectDetailsSummaryProvider( - PROJECT_COLOR_KEY, currentProjectProvider + PROJECT_COLOR_KEY, + currentProjectProvider ) findPreference(PROJECT_NAME_KEY)!!.onPreferenceChangeListener = this findPreference(PROJECT_ICON_KEY)!!.onPreferenceChangeListener = this @@ -105,7 +109,9 @@ class ProjectDisplayPreferencesFragment : currentProjectProvider.getCurrentProject().color ) ), - 0, summary.length, 0 + 0, + summary.length, + 0 ) summary } @@ -168,7 +174,10 @@ class ProjectDisplayPreferencesFragment : projectsRepository.save( Project.Saved( - uuid, newValue.toString(), icon, color + uuid, + newValue.toString(), + icon, + color ) ) } @@ -177,7 +186,10 @@ class ProjectDisplayPreferencesFragment : projectsRepository.save( Project.Saved( - uuid, name, newValue.toString(), color + uuid, + name, + newValue.toString(), + color ) ) } diff --git a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectManagementPreferencesFragment.kt b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectManagementPreferencesFragment.kt index ad04c520ae5..7f1f7aabce2 100644 --- a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectManagementPreferencesFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/ProjectManagementPreferencesFragment.kt @@ -20,6 +20,7 @@ import org.odk.collect.android.projects.DeleteProjectResult import org.odk.collect.android.projects.ProjectDeleter import org.odk.collect.androidshared.ui.ToastUtils import org.odk.collect.androidshared.ui.multiclicksafe.MultiClickGuard +import org.odk.collect.settings.keys.ProjectKeys import javax.inject.Inject class ProjectManagementPreferencesFragment : @@ -65,12 +66,17 @@ class ProjectManagementPreferencesFragment : val pref = Intent(activity, QRCodeTabsActivity::class.java) startActivity(pref) } - DELETE_PROJECT_KEY -> MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.delete_project) - .setMessage(R.string.delete_project_confirm_message) - .setNegativeButton(R.string.delete_project_no) { _: DialogInterface?, _: Int -> } - .setPositiveButton(R.string.delete_project_yes) { _: DialogInterface?, _: Int -> deleteProject() } - .show() + DELETE_PROJECT_KEY -> { + val isGDProject = ProjectKeys.PROTOCOL_GOOGLE_SHEETS == settingsProvider.getUnprotectedSettings().getString(ProjectKeys.KEY_PROTOCOL) + val message = if (isGDProject) R.string.delete_google_drive_project_confirm_message else R.string.delete_project_confirm_message + + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.delete_project) + .setMessage(message) + .setNegativeButton(R.string.delete_project_no) { _: DialogInterface?, _: Int -> } + .setPositiveButton(R.string.delete_project_yes) { _: DialogInterface?, _: Int -> deleteProject() } + .show() + } } return true } diff --git a/collect_app/src/main/java/org/odk/collect/android/projects/ManualProjectCreatorDialog.kt b/collect_app/src/main/java/org/odk/collect/android/projects/ManualProjectCreatorDialog.kt index e5abccfe4fc..997a28ad381 100644 --- a/collect_app/src/main/java/org/odk/collect/android/projects/ManualProjectCreatorDialog.kt +++ b/collect_app/src/main/java/org/odk/collect/android/projects/ManualProjectCreatorDialog.kt @@ -1,15 +1,10 @@ package org.odk.collect.android.projects -import android.accounts.AccountManager -import android.app.Activity import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.activity.result.ActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.Toolbar import androidx.core.widget.doOnTextChanged import org.odk.collect.analytics.Analytics @@ -19,18 +14,14 @@ import org.odk.collect.android.activities.MainMenuActivity import org.odk.collect.android.analytics.AnalyticsEvents import org.odk.collect.android.configure.qr.AppConfigurationGenerator import org.odk.collect.android.databinding.ManualProjectCreatorDialogLayoutBinding -import org.odk.collect.android.gdrive.GoogleAccountsManager import org.odk.collect.android.injection.DaggerUtils import org.odk.collect.android.projects.DuplicateProjectConfirmationKeys.MATCHING_PROJECT import org.odk.collect.android.projects.DuplicateProjectConfirmationKeys.SETTINGS_JSON import org.odk.collect.android.utilities.SoftKeyboardController -import org.odk.collect.androidshared.system.IntentLauncher import org.odk.collect.androidshared.ui.DialogFragmentUtils import org.odk.collect.androidshared.ui.ToastUtils import org.odk.collect.androidshared.utils.Validator import org.odk.collect.material.MaterialFullScreenDialogFragment -import org.odk.collect.permissions.PermissionListener -import org.odk.collect.permissions.PermissionsProvider import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.SettingsProvider import javax.inject.Inject @@ -51,55 +42,16 @@ class ManualProjectCreatorDialog : @Inject lateinit var currentProjectProvider: CurrentProjectProvider - @Inject - lateinit var permissionsProvider: PermissionsProvider - - @Inject - lateinit var googleAccountsManager: GoogleAccountsManager - @Inject lateinit var projectsRepository: ProjectsRepository @Inject lateinit var settingsProvider: SettingsProvider - @Inject - lateinit var intentLauncher: IntentLauncher - lateinit var settingsConnectionMatcher: SettingsConnectionMatcher private lateinit var binding: ManualProjectCreatorDialogLayoutBinding - val googleAccountResultLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - val resultData = result.data - - if (result.resultCode == Activity.RESULT_OK && resultData != null && resultData.extras != null) { - val accountName = resultData.getStringExtra(AccountManager.KEY_ACCOUNT_NAME) - googleAccountsManager.selectAccount(accountName) - - val settingsJson = - appConfigurationGenerator.getAppConfigurationAsJsonWithGoogleDriveDetails( - accountName - ) - - settingsConnectionMatcher.getProjectWithMatchingConnection(settingsJson) - ?.let { uuid -> - val confirmationArgs = Bundle() - confirmationArgs.putString(SETTINGS_JSON, settingsJson) - confirmationArgs.putString(MATCHING_PROJECT, uuid) - DialogFragmentUtils.showIfNotShowing( - DuplicateProjectConfirmationDialog::class.java, - confirmationArgs, - childFragmentManager - ) - } ?: run { - Analytics.log(AnalyticsEvents.GOOGLE_ACCOUNT_PROJECT) - createProject(settingsJson) - } - } - } - override fun onAttach(context: Context) { super.onAttach(context) DaggerUtils.getComponent(context).inject(this) @@ -134,10 +86,6 @@ class ManualProjectCreatorDialog : binding.addButton.setOnClickListener { handleAddingNewProject() } - - binding.gdrive.setOnClickListener { - configureGoogleAccount() - } } override fun onCloseClicked() { @@ -182,26 +130,6 @@ class ManualProjectCreatorDialog : } } - private fun configureGoogleAccount() { - permissionsProvider.requestGetAccountsPermission( - requireActivity(), - object : PermissionListener { - override fun granted() { - val intent: Intent = googleAccountsManager.accountChooserIntent - intentLauncher.launchForResult(googleAccountResultLauncher, intent) { - ToastUtils.showShortToast( - requireContext(), - getString( - R.string.activity_not_found, - getString(R.string.choose_account) - ) - ) - } - } - } - ) - } - override fun createProject(settingsJson: String) { projectCreator.createNewProject(settingsJson) ActivityUtils.startActivityAndCloseAllOthers(activity, MainMenuActivity::class.java) diff --git a/collect_app/src/main/java/org/odk/collect/android/projects/ProjectCreator.kt b/collect_app/src/main/java/org/odk/collect/android/projects/ProjectCreator.kt index b0b16c1ee70..2f967e13ce3 100644 --- a/collect_app/src/main/java/org/odk/collect/android/projects/ProjectCreator.kt +++ b/collect_app/src/main/java/org/odk/collect/android/projects/ProjectCreator.kt @@ -4,6 +4,7 @@ import org.odk.collect.projects.Project import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.ODKAppSettingsImporter import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.importing.SettingsImportingResult class ProjectCreator( private val projectsRepository: ProjectsRepository, @@ -12,18 +13,18 @@ class ProjectCreator( private val settingsProvider: SettingsProvider ) { - fun createNewProject(settingsJson: String): Boolean { + fun createNewProject(settingsJson: String): SettingsImportingResult { val savedProject = projectsRepository.save(Project.New("", "", "")) - val settingsImportedSuccessfully = settingsImporter.fromJSON(settingsJson, savedProject) + val settingsImportingResult = settingsImporter.fromJSON(settingsJson, savedProject) - return if (settingsImportedSuccessfully) { + return if (settingsImportingResult == SettingsImportingResult.SUCCESS) { currentProjectProvider.setCurrentProject(savedProject.uuid) - true + settingsImportingResult } else { settingsProvider.getUnprotectedSettings(savedProject.uuid).clear() settingsProvider.getProtectedSettings(savedProject.uuid).clear() projectsRepository.delete(savedProject.uuid) - false + settingsImportingResult } } } diff --git a/collect_app/src/main/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialog.kt b/collect_app/src/main/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialog.kt index 8f442972e9c..97f2c20c251 100644 --- a/collect_app/src/main/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialog.kt +++ b/collect_app/src/main/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialog.kt @@ -35,6 +35,7 @@ import org.odk.collect.projects.ProjectsRepository import org.odk.collect.qrcode.QRCodeDecoder import org.odk.collect.settings.ODKAppSettingsImporter import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.importing.SettingsImportingResult import timber.log.Timber import javax.inject.Inject @@ -274,21 +275,21 @@ class QrCodeProjectCreatorDialog : } override fun createProject(settingsJson: String) { - val projectCreatedSuccessfully = projectCreator.createNewProject(settingsJson) - - if (projectCreatedSuccessfully) { - Analytics.log(AnalyticsEvents.QR_CREATE_PROJECT) - - ActivityUtils.startActivityAndCloseAllOthers(activity, MainMenuActivity::class.java) - ToastUtils.showLongToast( - requireContext(), - getString( - R.string.switched_project, - currentProjectProvider.getCurrentProject().name + when (projectCreator.createNewProject(settingsJson)) { + SettingsImportingResult.SUCCESS -> { + Analytics.log(AnalyticsEvents.QR_CREATE_PROJECT) + + ActivityUtils.startActivityAndCloseAllOthers(activity, MainMenuActivity::class.java) + ToastUtils.showLongToast( + requireContext(), + getString( + R.string.switched_project, + currentProjectProvider.getCurrentProject().name + ) ) - ) - } else { - ToastUtils.showLongToast(requireContext(), getString(R.string.invalid_qrcode)) + } + SettingsImportingResult.INVALID_SETTINGS -> ToastUtils.showLongToast(requireContext(), getString(R.string.invalid_qrcode)) + SettingsImportingResult.GD_PROJECT -> ToastUtils.showLongToast(requireContext(), getString(R.string.settings_with_gd_protocol)) } } diff --git a/collect_app/src/main/java/org/odk/collect/android/utilities/Appearances.kt b/collect_app/src/main/java/org/odk/collect/android/utilities/Appearances.kt index 6055298d586..27c9c6c3ab5 100644 --- a/collect_app/src/main/java/org/odk/collect/android/utilities/Appearances.kt +++ b/collect_app/src/main/java/org/odk/collect/android/utilities/Appearances.kt @@ -34,17 +34,21 @@ object Appearances { const val YEAR = "year" // Select one/multiple appearances - @Deprecated("") const val COMPACT = "compact" + @Deprecated("") + const val COMPACT = "compact" - @Deprecated("") const val COMPACT_N = "compact-" + @Deprecated("") + const val COMPACT_N = "compact-" const val MINIMAL = "minimal" const val COLUMNS = "columns" const val COLUMNS_N = "columns-" const val COLUMNS_PACK = "columns-pack" - @Deprecated("") const val QUICKCOMPACT = "quickcompact" + @Deprecated("") + const val QUICKCOMPACT = "quickcompact" - @Deprecated("") const val SEARCH = "search" + @Deprecated("") + const val SEARCH = "search" const val AUTOCOMPLETE = "autocomplete" const val LIST_NO_LABEL = "list-nolabel" const val LIST = "list" @@ -60,7 +64,8 @@ object Appearances { const val ANNOTATE = "annotate" const val DRAW = "draw" - @Deprecated("") const val SELFIE = "selfie" + @Deprecated("") + const val SELFIE = "selfie" const val NEW_FRONT = "new-front" const val NEW = "new" const val FRONT = "front" @@ -124,9 +129,13 @@ object Appearances { val substringFromNumColumns = appearance.substring(idx + columnsAppearance.length) numColumns = substringFromNumColumns.substring( 0, - if (substringFromNumColumns.contains(" ")) substringFromNumColumns.indexOf( - ' ' - ) else substringFromNumColumns.length + if (substringFromNumColumns.contains(" ")) { + substringFromNumColumns.indexOf( + ' ' + ) + } else { + substringFromNumColumns.length + } ).toInt() if (numColumns < 1) { numColumns = 1 diff --git a/collect_app/src/main/java/org/odk/collect/android/utilities/ControllableLifecyleOwner.kt b/collect_app/src/main/java/org/odk/collect/android/utilities/ControllableLifecyleOwner.kt index 1a54aa800f8..8fd0130d91d 100644 --- a/collect_app/src/main/java/org/odk/collect/android/utilities/ControllableLifecyleOwner.kt +++ b/collect_app/src/main/java/org/odk/collect/android/utilities/ControllableLifecyleOwner.kt @@ -9,15 +9,13 @@ class ControllableLifecyleOwner : LifecycleOwner { this.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) } + override val lifecycle: LifecycleRegistry = lifecycleRegistry + fun start() { - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) } fun destroy() { - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) - } - - override fun getLifecycle(): Lifecycle { - return lifecycleRegistry + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) } } diff --git a/collect_app/src/main/java/org/odk/collect/android/utilities/MediaUtils.kt b/collect_app/src/main/java/org/odk/collect/android/utilities/MediaUtils.kt index a930de5010d..be31cda9916 100644 --- a/collect_app/src/main/java/org/odk/collect/android/utilities/MediaUtils.kt +++ b/collect_app/src/main/java/org/odk/collect/android/utilities/MediaUtils.kt @@ -76,8 +76,11 @@ class MediaUtils(private val intentLauncher: IntentLauncher, private val content } private fun getMimeType(file: File, expectedMimeType: String?) = - if (expectedMimeType == null || expectedMimeType.isEmpty()) FileUtils.getMimeType(file) - else expectedMimeType + if (expectedMimeType == null || expectedMimeType.isEmpty()) { + FileUtils.getMimeType(file) + } else { + expectedMimeType + } fun pickFile(activity: Activity, mimeType: String, requestCode: Int) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/items/SelectOneFromMapDialogFragment.kt b/collect_app/src/main/java/org/odk/collect/android/widgets/items/SelectOneFromMapDialogFragment.kt index cdfd2552d14..cc8957e4a88 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/items/SelectOneFromMapDialogFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/items/SelectOneFromMapDialogFragment.kt @@ -70,7 +70,7 @@ class SelectOneFromMapDialogFragment(private val viewModelFactory: ViewModelProv override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?, + savedInstanceState: Bundle? ): View { val binding = SelectOneFromMapDialogLayoutBinding.inflate(inflater) return binding.root @@ -107,7 +107,7 @@ internal class SelectChoicesMapData( private val resources: Resources, scheduler: Scheduler, prompt: FormEntryPrompt, - private val selectedIndex: Int?, + private val selectedIndex: Int? ) : SelectionMapData { private val mapTitle = MutableLiveData(prompt.longText) @@ -132,7 +132,7 @@ internal class SelectChoicesMapData( private fun loadItemsFromChoices( selectChoices: MutableList, - prompt: FormEntryPrompt, + prompt: FormEntryPrompt ): List { return selectChoices.foldIndexed(emptyList()) { index, list, selectChoice -> val geometry = selectChoice.getChild("geometry") diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/ActivityGeoDataRequester.kt b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/ActivityGeoDataRequester.kt index 9375ca56975..a743cd8a226 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/ActivityGeoDataRequester.kt +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/ActivityGeoDataRequester.kt @@ -20,13 +20,13 @@ import java.lang.Boolean.parseBoolean class ActivityGeoDataRequester( private val permissionsProvider: PermissionsProvider, - private val activity: Activity, + private val activity: Activity ) : GeoDataRequester { override fun requestGeoPoint( prompt: FormEntryPrompt, answerText: String?, - waitingForDataRegistry: WaitingForDataRegistry, + waitingForDataRegistry: WaitingForDataRegistry ) { permissionsProvider.requestEnabledLocationPermissions( activity, @@ -39,7 +39,7 @@ class ActivityGeoDataRequester( if (parsedGeometry.isNotEmpty()) { it.putParcelable( GeoPointMapActivity.EXTRA_LOCATION, - parsedGeometry[0], + parsedGeometry[0] ) } @@ -48,18 +48,18 @@ class ActivityGeoDataRequester( val unacceptableAccuracyThreshold = FormEntryPromptUtils.getBodyAttribute( prompt, - "unacceptableAccuracyThreshold", + "unacceptableAccuracyThreshold" ) it.putFloat( GeoPointActivity.EXTRA_ACCURACY_THRESHOLD, - accuracyThreshold?.toFloatOrNull() ?: DEFAULT_ACCURACY_THRESHOLD, + accuracyThreshold?.toFloatOrNull() ?: DEFAULT_ACCURACY_THRESHOLD ) it.putFloat( GeoPointActivity.EXTRA_UNACCEPTABLE_ACCURACY_THRESHOLD, unacceptableAccuracyThreshold?.toFloatOrNull() - ?: DEFAULT_UNACCEPTABLE_ACCURACY_THRESHOLD, + ?: DEFAULT_UNACCEPTABLE_ACCURACY_THRESHOLD ) it.putBoolean(EXTRA_RETAIN_MOCK_ACCURACY, getAllowMockAccuracy(prompt)) @@ -69,24 +69,24 @@ class ActivityGeoDataRequester( val intent = Intent( activity, - if (isMapsAppearance(prompt)) GeoPointMapActivity::class.java else GeoPointActivity::class.java, + if (isMapsAppearance(prompt)) GeoPointMapActivity::class.java else GeoPointActivity::class.java ).also { it.putExtras(bundle) } activity.startActivityForResult( intent, - ApplicationConstants.RequestCodes.LOCATION_CAPTURE, + ApplicationConstants.RequestCodes.LOCATION_CAPTURE ) } - }, + } ) } override fun requestGeoShape( prompt: FormEntryPrompt, answerText: String?, - waitingForDataRegistry: WaitingForDataRegistry, + waitingForDataRegistry: WaitingForDataRegistry ) { permissionsProvider.requestEnabledLocationPermissions( activity, @@ -101,7 +101,7 @@ class ActivityGeoDataRequester( ) it.putExtra( GeoPolyActivity.OUTPUT_MODE_KEY, - GeoPolyActivity.OutputMode.GEOSHAPE, + GeoPolyActivity.OutputMode.GEOSHAPE ) it.putExtra(EXTRA_READ_ONLY, prompt.isReadOnly) it.putExtra(EXTRA_RETAIN_MOCK_ACCURACY, getAllowMockAccuracy(prompt)) @@ -109,17 +109,17 @@ class ActivityGeoDataRequester( activity.startActivityForResult( intent, - ApplicationConstants.RequestCodes.GEOSHAPE_CAPTURE, + ApplicationConstants.RequestCodes.GEOSHAPE_CAPTURE ) } - }, + } ) } override fun requestGeoTrace( prompt: FormEntryPrompt, answerText: String?, - waitingForDataRegistry: WaitingForDataRegistry, + waitingForDataRegistry: WaitingForDataRegistry ) { permissionsProvider.requestEnabledLocationPermissions( activity, @@ -134,7 +134,7 @@ class ActivityGeoDataRequester( ) it.putExtra( GeoPolyActivity.OUTPUT_MODE_KEY, - GeoPolyActivity.OutputMode.GEOTRACE, + GeoPolyActivity.OutputMode.GEOTRACE ) it.putExtra(EXTRA_READ_ONLY, prompt.isReadOnly) it.putExtra(EXTRA_RETAIN_MOCK_ACCURACY, getAllowMockAccuracy(prompt)) @@ -142,10 +142,10 @@ class ActivityGeoDataRequester( activity.startActivityForResult( intent, - ApplicationConstants.RequestCodes.GEOTRACE_CAPTURE, + ApplicationConstants.RequestCodes.GEOTRACE_CAPTURE ) } - }, + } ) } diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/FileRequester.kt b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/FileRequester.kt index 756510f534c..62a5723b3ba 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/FileRequester.kt +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/FileRequester.kt @@ -25,15 +25,20 @@ class FileRequesterImpl( val intent = externalAppIntentProvider.getIntentToRunExternalApp(formController, formEntryPrompt) val intentWithoutDefaultCategory = externalAppIntentProvider.getIntentToRunExternalAppWithoutDefaultCategory( - formController, formEntryPrompt, + formController, + formEntryPrompt, activity.packageManager ) intentLauncher.launchForResult( - activity, intent, requestCode + activity, + intent, + requestCode ) { intentLauncher.launchForResult( - activity, intentWithoutDefaultCategory, requestCode + activity, + intentWithoutDefaultCategory, + requestCode ) { showLongToast(activity, getErrorMessage(formEntryPrompt, activity)) } diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/GeoWidgetUtils.kt b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/GeoWidgetUtils.kt index 2f7bd1949e6..4a7eca5e5ca 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/GeoWidgetUtils.kt +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/GeoWidgetUtils.kt @@ -83,7 +83,7 @@ object GeoWidgetUtils { fun convertCoordinatesIntoDegreeFormat( context: Context, coordinate: Double, - type: String, + type: String ): String { val coordinateDegrees = Location.convert(abs(coordinate), Location.FORMAT_SECONDS) val coordinateSplit = coordinateDegrees.split(":").toTypedArray() diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/StringRequester.kt b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/StringRequester.kt index 5179fcdcd12..b0e0737ab3d 100644 --- a/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/StringRequester.kt +++ b/collect_app/src/main/java/org/odk/collect/android/widgets/utilities/StringRequester.kt @@ -41,7 +41,8 @@ class StringRequesterImpl( if (intent != null && Intent.ACTION_SENDTO == intent.action) { intentLauncher.launch(activity, intent) { intentLauncher.launch( - activity, intentWithoutDefaultCategory + activity, + intentWithoutDefaultCategory ) { onError(getErrorMessage(formEntryPrompt, activity)) } @@ -49,7 +50,9 @@ class StringRequesterImpl( } else { intentLauncher.launchForResult(activity, intent, requestCode) { intentLauncher.launchForResult( - activity, intentWithoutDefaultCategory, requestCode + activity, + intentWithoutDefaultCategory, + requestCode ) { onError(getErrorMessage(formEntryPrompt, activity)) } diff --git a/collect_app/src/main/res/layout/manual_project_creator_dialog_layout.xml b/collect_app/src/main/res/layout/manual_project_creator_dialog_layout.xml index a7a53be2fc8..ce1370e5b6c 100644 --- a/collect_app/src/main/res/layout/manual_project_creator_dialog_layout.xml +++ b/collect_app/src/main/res/layout/manual_project_creator_dialog_layout.xml @@ -96,7 +96,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/margin_standard" - app:layout_constraintBottom_toTopOf="@+id/gdrive" app:layout_constraintTop_toBottomOf="@+id/password"> - - - - - - - diff --git a/collect_app/src/main/res/xml/server_preferences.xml b/collect_app/src/main/res/xml/server_preferences.xml index a91a3b261b5..1dfbf741061 100644 --- a/collect_app/src/main/res/xml/server_preferences.xml +++ b/collect_app/src/main/res/xml/server_preferences.xml @@ -8,5 +8,6 @@ android:entryValues="@array/protocol_entry_values" android:key="protocol" android:title="@string/type" + android:enabled="false" app:iconSpaceReserved="false" /> \ No newline at end of file diff --git a/collect_app/src/test/java/org/odk/collect/android/activities/MainMenuActivityTest.kt b/collect_app/src/test/java/org/odk/collect/android/activities/MainMenuActivityTest.kt index 5d849b4ad1f..c6a974faa41 100644 --- a/collect_app/src/test/java/org/odk/collect/android/activities/MainMenuActivityTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/activities/MainMenuActivityTest.kt @@ -11,8 +11,8 @@ import androidx.lifecycle.ViewModel import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.CoreMatchers.`is` import org.hamcrest.MatcherAssert.assertThat import org.junit.Before import org.junit.Rule @@ -66,7 +66,7 @@ class MainMenuActivityTest { application: Application, settingsProvider: SettingsProvider, instancesAppState: InstancesAppState, - scheduler: Scheduler, + scheduler: Scheduler ): MainMenuViewModel.Factory { return object : MainMenuViewModel.Factory( versionInformation, @@ -85,7 +85,7 @@ class MainMenuActivityTest { currentProjectProvider: CurrentProjectProvider, analyticsInitializer: AnalyticsInitializer, storagePathProvider: StoragePathProvider, - projectsRepository: ProjectsRepository, + projectsRepository: ProjectsRepository ): CurrentProjectViewModel.Factory { return object : CurrentProjectViewModel.Factory( currentProjectProvider, diff --git a/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegateTest.kt b/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegateTest.kt index ceb01c46e65..66b29c687f8 100644 --- a/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegateTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeActivityResultDelegateTest.kt @@ -20,6 +20,7 @@ import org.odk.collect.android.support.CollectHelpers import org.odk.collect.projects.Project.Saved import org.odk.collect.qrcode.QRCodeDecoder import org.odk.collect.settings.ODKAppSettingsImporter +import org.odk.collect.settings.importing.SettingsImportingResult import org.robolectric.Robolectric import org.robolectric.Shadows import org.robolectric.shadows.ShadowToast @@ -63,7 +64,7 @@ class QRCodeActivityResultDelegateTest { val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) val data = intentWithData("file://qr", "qr") fakeQRDecoder.register("qr", "data") - whenever(settingsImporter.fromJSON("data", project)).thenReturn(true) + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.SUCCESS) delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) } @@ -77,10 +78,21 @@ class QRCodeActivityResultDelegateTest { val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) val data = intentWithData("file://qr", "qr") fakeQRDecoder.register("qr", "data") - whenever(settingsImporter.fromJSON("data", project)).thenReturn(false) + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) } + @Test + fun forSelectPhotoWithGoogleDriveProtocol_whenImporting_showsInvalidToast() { + val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) + val data = intentWithData("file://qr", "qr") + fakeQRDecoder.register("qr", "data") + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.GD_PROJECT) + delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) + + assertThat(ShadowToast.getTextOfLatestToast(), equalTo(context.getString(R.string.settings_with_gd_protocol))) + } + @Test fun forSelectPhoto_whenQRCodeDecodeFailsWithInvalid_showsInvalidToast() { importSettingsFromQrCode_withInvalidQrCode() @@ -91,7 +103,7 @@ class QRCodeActivityResultDelegateTest { val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) val data = intentWithData("file://qr", "qr") fakeQRDecoder.failsWith(QRCodeDecoder.QRCodeInvalidException()) - whenever(settingsImporter.fromJSON("data", project)).thenReturn(false) + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) } @@ -105,7 +117,7 @@ class QRCodeActivityResultDelegateTest { val delegate = QRCodeActivityResultDelegate(context, settingsImporter, fakeQRDecoder, project) val data = intentWithData("file://qr", "qr") fakeQRDecoder.failsWith(QRCodeDecoder.QRCodeNotFoundException()) - whenever(settingsImporter.fromJSON("data", project)).thenReturn(false) + whenever(settingsImporter.fromJSON("data", project)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) delegate.onActivityResult(QRCodeMenuDelegate.SELECT_PHOTO, Activity.RESULT_OK, data) } @@ -129,7 +141,8 @@ class QRCodeActivityResultDelegateTest { val inputStream = ByteArrayInputStream(streamContents.toByteArray()) Shadows.shadowOf(ApplicationProvider.getApplicationContext().contentResolver) .registerInputStream( - Uri.parse("file://qr"), inputStream + Uri.parse("file://qr"), + inputStream ) val data = Intent() data.data = Uri.parse(uri) diff --git a/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeMenuDelegateTest.kt b/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeMenuDelegateTest.kt index 1dab8a32fdd..4589473d1be 100644 --- a/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeMenuDelegateTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/configure/qr/QRCodeMenuDelegateTest.kt @@ -46,8 +46,13 @@ class QRCodeMenuDelegateTest { private fun setupMenuDelegate() { menuDelegate = QRCodeMenuDelegate( - activity, intentLauncher, qrCodeGenerator, - appConfigurationGenerator, fileProvider, getSettingsProvider(), fakeScheduler + activity, + intentLauncher, + qrCodeGenerator, + appConfigurationGenerator, + fileProvider, + getSettingsProvider(), + fakeScheduler ) } @@ -75,7 +80,8 @@ class QRCodeMenuDelegateTest { menuDelegate.onOptionsItemSelected(RoboMenuItem(R.id.menu_item_scan_sd_card)) assertThat( - Shadows.shadowOf(activity).nextStartedActivityForResult, nullValue() + Shadows.shadowOf(activity).nextStartedActivityForResult, + nullValue() ) assertThat(ShadowToast.getLatestToast(), notNullValue()) } diff --git a/collect_app/src/test/java/org/odk/collect/android/database/DatabaseConnectionTest.kt b/collect_app/src/test/java/org/odk/collect/android/database/DatabaseConnectionTest.kt index 0c048aee40e..ba3895c8c03 100644 --- a/collect_app/src/test/java/org/odk/collect/android/database/DatabaseConnectionTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/database/DatabaseConnectionTest.kt @@ -13,8 +13,8 @@ import java.io.File @RunWith(AndroidJUnit4::class) class DatabaseConnectionTest { - @Test // https://github.com/getodk/collect/issues/5042 + @Test fun `database file should be recreated if removed between operations`() { val dbDir = createTempDir() val formsDbPath = dbDir.absolutePath + File.separator + "forms.db" diff --git a/collect_app/src/test/java/org/odk/collect/android/formentry/AudioVideoImageTextLabelVisibilityTest.kt b/collect_app/src/test/java/org/odk/collect/android/formentry/AudioVideoImageTextLabelVisibilityTest.kt index 10f71ebb9de..e77f3200cf5 100644 --- a/collect_app/src/test/java/org/odk/collect/android/formentry/AudioVideoImageTextLabelVisibilityTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/formentry/AudioVideoImageTextLabelVisibilityTest.kt @@ -51,7 +51,9 @@ class AudioVideoImageTextLabelVisibilityTest( WidgetTestActivity::class.java ) audioHelper = AudioHelper( - activity, activity.viewLifecycle, mock(), + activity, + activity.viewLifecycle, + mock(), { mock() } diff --git a/collect_app/src/test/java/org/odk/collect/android/formentry/QuitFormDialogTest.kt b/collect_app/src/test/java/org/odk/collect/android/formentry/QuitFormDialogTest.kt index 30fbca790f9..54997571431 100644 --- a/collect_app/src/test/java/org/odk/collect/android/formentry/QuitFormDialogTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/formentry/QuitFormDialogTest.kt @@ -41,7 +41,7 @@ class QuitFormDialogTest { assertThat(dialog.getButton(DialogInterface.BUTTON_POSITIVE).visibility, equalTo(View.GONE)) assertThat( dialog.getButton(DialogInterface.BUTTON_NEGATIVE).visibility, - equalTo(View.VISIBLE), + equalTo(View.VISIBLE) ) assertThat( dialog.getButton(DialogInterface.BUTTON_NEGATIVE).text, @@ -76,7 +76,7 @@ class QuitFormDialogTest { val dialogTitle = dialog.findViewById(R.id.alertTitle) assertThat( dialogTitle!!.text.toString(), - equalTo(activity.getString(R.string.quit_application, "blah")), + equalTo(activity.getString(R.string.quit_application, "blah")) ) } diff --git a/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuDelegateTest.kt b/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuDelegateTest.kt index 794f4631e10..f494e76a5bd 100644 --- a/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuDelegateTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListMenuDelegateTest.kt @@ -8,8 +8,8 @@ import androidx.core.internal.view.SupportMenu import androidx.fragment.app.FragmentActivity import androidx.lifecycle.MutableLiveData import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.instanceOf +import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.nullValue import org.hamcrest.MatcherAssert.assertThat import org.junit.Before @@ -109,7 +109,8 @@ class BlankFormListMenuDelegateTest { menuDelegate.onOptionsItemSelected(RoboMenuItem(R.id.menu_refresh)) assertThat( - ShadowToast.getTextOfLatestToast(), `is`(activity.getString(R.string.form_update_succeeded)) + ShadowToast.getTextOfLatestToast(), + `is`(activity.getString(R.string.form_update_succeeded)) ) } @@ -131,7 +132,8 @@ class BlankFormListMenuDelegateTest { menuDelegate.onOptionsItemSelected(RoboMenuItem(R.id.menu_refresh)) assertThat( - ShadowToast.getTextOfLatestToast(), `is`(activity.getString(R.string.no_connection)) + ShadowToast.getTextOfLatestToast(), + `is`(activity.getString(R.string.no_connection)) ) verify(viewModel, never()).syncWithServer() } diff --git a/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListViewModelTest.kt b/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListViewModelTest.kt index 7b4d23802df..c6f94cd6b1c 100644 --- a/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListViewModelTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/formlists/blankformlist/BlankFormListViewModelTest.kt @@ -4,8 +4,8 @@ import android.app.Application import androidx.lifecycle.MutableLiveData import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.CoreMatchers.`is` import org.hamcrest.MatcherAssert.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -336,7 +336,7 @@ class BlankFormListViewModelTest { instance(formId = "3", lastStatusChangeDate = 2L), instance(formId = "5", lastStatusChangeDate = 3L), instance(formId = "4", lastStatusChangeDate = 4L), - instance(formId = "2", lastStatusChangeDate = 5L), + instance(formId = "2", lastStatusChangeDate = 5L) ) createViewModel() @@ -383,7 +383,7 @@ class BlankFormListViewModelTest { saveInstances( instance(formId = "1", lastStatusChangeDate = 1L), - instance(formId = "3", lastStatusChangeDate = 2L), + instance(formId = "3", lastStatusChangeDate = 2L) ) createViewModel() @@ -408,7 +408,7 @@ class BlankFormListViewModelTest { saveInstances( instance(formId = "1", lastStatusChangeDate = 1L, version = "1"), instance(formId = "2", lastStatusChangeDate = 2L), - instance(formId = "1", lastStatusChangeDate = 3L, version = "2"), + instance(formId = "1", lastStatusChangeDate = 3L, version = "2") ) createViewModel(shouldHideOldFormVersions = false) diff --git a/collect_app/src/test/java/org/odk/collect/android/formmanagement/FormDownloadExceptionMapperTest.kt b/collect_app/src/test/java/org/odk/collect/android/formmanagement/FormDownloadExceptionMapperTest.kt index a088dd6abbd..57200ecd4e3 100644 --- a/collect_app/src/test/java/org/odk/collect/android/formmanagement/FormDownloadExceptionMapperTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/formmanagement/FormDownloadExceptionMapperTest.kt @@ -24,7 +24,7 @@ class FormDownloadExceptionMapperTest { @Test fun formWithNoHashError_returnsFormWithNoHashErrorMessage() { val expectedString = context.getString( - R.string.form_with_no_hash_error, + R.string.form_with_no_hash_error ) + " " + context.getString(R.string.report_to_project_lead) assertThat( mapper.getMessage(FormDownloadException.FormWithNoHash()), @@ -35,7 +35,7 @@ class FormDownloadExceptionMapperTest { @Test fun formParsingError_returnsFormParsingErrorMessage() { val expectedString = context.getString( - R.string.form_parsing_error, + R.string.form_parsing_error ) + " " + context.getString(R.string.report_to_project_lead) assertThat( mapper.getMessage(FormDownloadException.FormParsingError()), @@ -46,7 +46,7 @@ class FormDownloadExceptionMapperTest { @Test fun formSaveError_returnsFormSaveErrorMessage() { val expectedString = context.getString( - R.string.form_save_disk_error, + R.string.form_save_disk_error ) + " " + context.getString(R.string.report_to_project_lead) assertThat( mapper.getMessage(FormDownloadException.DiskError()), @@ -57,7 +57,7 @@ class FormDownloadExceptionMapperTest { @Test fun formWithInvalidSubmissionError_returnsFormInvalidSubmissionErrorMessage() { val expectedString = context.getString( - R.string.form_with_invalid_submission_error, + R.string.form_with_invalid_submission_error ) + " " + context.getString(R.string.report_to_project_lead) assertThat( mapper.getMessage(FormDownloadException.InvalidSubmission()), diff --git a/collect_app/src/test/java/org/odk/collect/android/formmanagement/FormUpdateDownloaderTest.kt b/collect_app/src/test/java/org/odk/collect/android/formmanagement/FormUpdateDownloaderTest.kt index ff71cd7c047..61deb926234 100644 --- a/collect_app/src/test/java/org/odk/collect/android/formmanagement/FormUpdateDownloaderTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/formmanagement/FormUpdateDownloaderTest.kt @@ -1,8 +1,8 @@ package org.odk.collect.android.formmanagement import androidx.test.espresso.matcher.ViewMatchers.assertThat -import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.`is` import org.junit.Test import org.mockito.Mockito.any import org.mockito.Mockito.doAnswer diff --git a/collect_app/src/test/java/org/odk/collect/android/formmanagement/ServerFormsDetailsFetcherTest.kt b/collect_app/src/test/java/org/odk/collect/android/formmanagement/ServerFormsDetailsFetcherTest.kt index 1f40e238a6e..5a18953f035 100644 --- a/collect_app/src/test/java/org/odk/collect/android/formmanagement/ServerFormsDetailsFetcherTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/formmanagement/ServerFormsDetailsFetcherTest.kt @@ -1,8 +1,8 @@ package org.odk.collect.android.formmanagement import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.nullValue import org.junit.Test import org.mockito.kotlin.doReturn diff --git a/collect_app/src/test/java/org/odk/collect/android/formmanagement/formmap/FormMapViewModelTest.kt b/collect_app/src/test/java/org/odk/collect/android/formmanagement/formmap/FormMapViewModelTest.kt index ca921e516c3..759c1cb284a 100644 --- a/collect_app/src/test/java/org/odk/collect/android/formmanagement/formmap/FormMapViewModelTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/formmanagement/formmap/FormMapViewModelTest.kt @@ -252,7 +252,7 @@ class FormMapViewModelTest { ) ) ), - info = formatDate(R.string.deleted_on_date_at_time, 123L), + info = formatDate(R.string.deleted_on_date_at_time, 123L) ) assertThat(viewModel.getMappableItems().value!![0], equalTo(expectedItem)) } diff --git a/collect_app/src/test/java/org/odk/collect/android/openrosa/CaseInsensitiveEmptyHeadersTest.java b/collect_app/src/test/java/org/odk/collect/android/openrosa/CaseInsensitiveEmptyHeadersTest.java index 0faeb68b71b..6ddfff20c2b 100644 --- a/collect_app/src/test/java/org/odk/collect/android/openrosa/CaseInsensitiveEmptyHeadersTest.java +++ b/collect_app/src/test/java/org/odk/collect/android/openrosa/CaseInsensitiveEmptyHeadersTest.java @@ -8,7 +8,7 @@ public class CaseInsensitiveEmptyHeadersTest { @Test public void testGetHeaders() { - Assert.assertTrue(headers.getHeaders().size() == 0); + Assert.assertEquals(0, headers.getHeaders().size()); } @Test diff --git a/collect_app/src/test/java/org/odk/collect/android/preferences/screens/ProjectDisplayPreferencesFragmentTest.kt b/collect_app/src/test/java/org/odk/collect/android/preferences/screens/ProjectDisplayPreferencesFragmentTest.kt index 67cd93c1789..fa4e0623832 100644 --- a/collect_app/src/test/java/org/odk/collect/android/preferences/screens/ProjectDisplayPreferencesFragmentTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/preferences/screens/ProjectDisplayPreferencesFragmentTest.kt @@ -11,8 +11,8 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` import org.odk.collect.android.R import org.odk.collect.android.application.Collect import org.odk.collect.android.injection.config.AppDependencyModule diff --git a/collect_app/src/test/java/org/odk/collect/android/preferences/source/SettingsStoreTest.kt b/collect_app/src/test/java/org/odk/collect/android/preferences/source/SettingsStoreTest.kt index dcbb4bf5062..442092a0604 100644 --- a/collect_app/src/test/java/org/odk/collect/android/preferences/source/SettingsStoreTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/preferences/source/SettingsStoreTest.kt @@ -4,9 +4,9 @@ import org.hamcrest.CoreMatchers.`is` import org.hamcrest.MatcherAssert.assertThat import org.junit.Before import org.junit.Test -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.odk.collect.shared.settings.Settings class SettingsStoreTest { diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/CurrentProjectProviderTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/CurrentProjectProviderTest.kt index 69de0e3e121..54e37fff770 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/CurrentProjectProviderTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/CurrentProjectProviderTest.kt @@ -1,8 +1,8 @@ package org.odk.collect.android.projects import androidx.test.espresso.matcher.ViewMatchers.assertThat -import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.`is` import org.junit.Test import org.odk.collect.projects.InMemProjectsRepository import org.odk.collect.projects.Project diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/ManualProjectCreatorDialogTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/ManualProjectCreatorDialogTest.kt index b72ceaeef0b..4e003033092 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/ManualProjectCreatorDialogTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/ManualProjectCreatorDialogTest.kt @@ -1,12 +1,9 @@ package org.odk.collect.android.projects -import android.content.Context -import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.pressBack import androidx.test.espresso.action.ViewActions.replaceText -import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers @@ -29,13 +26,11 @@ import org.odk.collect.android.activities.MainMenuActivity import org.odk.collect.android.injection.config.AppDependencyModule import org.odk.collect.android.support.CollectHelpers import org.odk.collect.android.support.Matchers.isPasswordHidden -import org.odk.collect.androidshared.system.IntentLauncher import org.odk.collect.fragmentstest.FragmentScenarioLauncherRule import org.odk.collect.projects.Project import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.ODKAppSettingsImporter import org.odk.collect.settings.SettingsProvider -import org.odk.collect.testshared.ErrorIntentLauncher import org.robolectric.shadows.ShadowToast @RunWith(AndroidJUnit4::class) @@ -163,18 +158,4 @@ class ManualProjectCreatorDialogTest { Intents.release() } } - - @Test - fun `If activity to choose google account is not found the app should not crash`() { - CollectHelpers.overrideAppDependencyModule(object : AppDependencyModule() { - override fun providesIntentLauncher(): IntentLauncher { - return ErrorIntentLauncher() - } - }) - - launcherRule.launch(ManualProjectCreatorDialog::class.java) - onView(withText(R.string.gdrive_configure)).inRoot(isDialog()).perform(scrollTo(), click()) - val context = ApplicationProvider.getApplicationContext() - assertThat(ShadowToast.getTextOfLatestToast(), `is`(context.getString(R.string.activity_not_found, context.getString(R.string.choose_account)))) - } } diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/ProjectCreatorTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/ProjectCreatorTest.kt index 093cdfde606..3202ef38235 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/ProjectCreatorTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/ProjectCreatorTest.kt @@ -12,6 +12,7 @@ import org.odk.collect.projects.Project import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.ODKAppSettingsImporter import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.importing.SettingsImportingResult import org.odk.collect.shared.settings.Settings class ProjectCreatorTest { @@ -55,24 +56,40 @@ class ProjectCreatorTest { } @Test - fun `When importing settings failed createNewProject() should return false`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(false) + fun `When importing settings failed createNewProject() should return 'INVALID_SETTINGS'`() { + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) projectCreator.createNewProject(json) - assertThat(projectCreator.createNewProject(json), `is`(false)) + assertThat(projectCreator.createNewProject(json), `is`(SettingsImportingResult.INVALID_SETTINGS)) } @Test - fun `When importing settings succeeded createNewProject() should return true`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(true) + fun `When importing settings contain GD protocol createNewProject() should return 'GD_PROJECT'`() { + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.GD_PROJECT) projectCreator.createNewProject(json) - assertThat(projectCreator.createNewProject(json), `is`(true)) + assertThat(projectCreator.createNewProject(json), `is`(SettingsImportingResult.GD_PROJECT)) + } + + @Test + fun `When importing settings succeeded createNewProject() should return 'SUCCESS'`() { + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.SUCCESS) + + projectCreator.createNewProject(json) + assertThat(projectCreator.createNewProject(json), `is`(SettingsImportingResult.SUCCESS)) } @Test fun `When importing settings failed should created project be deleted`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(false) + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) + + projectCreator.createNewProject(json) + verify(projectsRepository).delete(savedProject.uuid) + } + + @Test + fun `When importing settings contain GD protocol should created project be deleted`() { + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.GD_PROJECT) projectCreator.createNewProject(json) verify(projectsRepository).delete(savedProject.uuid) @@ -80,7 +97,7 @@ class ProjectCreatorTest { @Test fun `When importing settings failed should prefs be cleared`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(false) + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.INVALID_SETTINGS) projectCreator.createNewProject(json) @@ -90,7 +107,7 @@ class ProjectCreatorTest { @Test fun `New project id should be set`() { - whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(true) + whenever(settingsImporter.fromJSON(json, savedProject)).thenReturn(SettingsImportingResult.SUCCESS) projectCreator.createNewProject(json) verify(currentProjectProvider).setCurrentProject("1") diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/ProjectDeleterTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/ProjectDeleterTest.kt index ff676f6eaed..fec25612199 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/ProjectDeleterTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/ProjectDeleterTest.kt @@ -1,8 +1,8 @@ package org.odk.collect.android.projects import androidx.test.espresso.matcher.ViewMatchers.assertThat -import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.instanceOf +import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.nullValue import org.junit.Before import org.junit.Test diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/ProjectSettingsDialogTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/ProjectSettingsDialogTest.kt index f52b8a6656e..dacb3e62b4e 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/ProjectSettingsDialogTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/ProjectSettingsDialogTest.kt @@ -51,7 +51,7 @@ class ProjectSettingsDialogTest { ) } - val projectsRepository = InMemProjectsRepository(UUIDGenerator(),) + val projectsRepository = InMemProjectsRepository(UUIDGenerator()) @get:Rule val launcherRule = diff --git a/collect_app/src/test/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialogTest.kt b/collect_app/src/test/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialogTest.kt index 64e6205db11..54da9f219b1 100644 --- a/collect_app/src/test/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialogTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/projects/QrCodeProjectCreatorDialogTest.kt @@ -24,8 +24,8 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.`when` import org.mockito.Mockito.verifyNoInteractions +import org.mockito.Mockito.`when` import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.odk.collect.android.R @@ -180,4 +180,41 @@ class QrCodeProjectCreatorDialogTest { ) verifyNoInteractions(projectCreator) } + + @Test + fun `When QR code contains GD protocol a toast should be displayed`() { + val projectCreator = mock() + + CollectHelpers.overrideAppDependencyModule(object : AppDependencyModule() { + override fun providesBarcodeViewDecoder(): BarcodeViewDecoder { + val barcodeResult = mock { + `when`(it.text).thenReturn( + CompressionUtils.compress( + "{\n" + + " \"general\": {\n" + + " \"protocol\" : \"google_sheets\"" + + " },\n" + + " \"admin\": {\n" + + " }\n" + + "}" + ) + ) + } + + return mock { + `when`(it.waitForBarcode(any())).thenReturn(MutableLiveData(barcodeResult)) + } + } + }) + + launcherRule.launch(QrCodeProjectCreatorDialog::class.java) + assertThat( + ShadowToast.getTextOfLatestToast(), + `is`( + ApplicationProvider.getApplicationContext() + .getString(R.string.settings_with_gd_protocol) + ) + ) + verifyNoInteractions(projectCreator) + } } diff --git a/collect_app/src/test/java/org/odk/collect/android/storage/StoragePathProviderTest.kt b/collect_app/src/test/java/org/odk/collect/android/storage/StoragePathProviderTest.kt index bf5176f71f7..9b36cd0ac4f 100644 --- a/collect_app/src/test/java/org/odk/collect/android/storage/StoragePathProviderTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/storage/StoragePathProviderTest.kt @@ -5,8 +5,8 @@ import org.hamcrest.CoreMatchers.`is` import org.junit.After import org.junit.Before import org.junit.Test -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` import org.odk.collect.android.projects.CurrentProjectProvider import org.odk.collect.projects.Project import org.odk.collect.projects.ProjectsRepository diff --git a/collect_app/src/test/java/org/odk/collect/android/widgets/items/SelectOneFromMapDialogFragmentTest.kt b/collect_app/src/test/java/org/odk/collect/android/widgets/items/SelectOneFromMapDialogFragmentTest.kt index 347d113ea84..1b5d445c6c9 100644 --- a/collect_app/src/test/java/org/odk/collect/android/widgets/items/SelectOneFromMapDialogFragmentTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/widgets/items/SelectOneFromMapDialogFragmentTest.kt @@ -193,7 +193,8 @@ class SelectOneFromMapDialogFragmentTest { "A", emptyList(), IconifiedText( - R.drawable.ic_save, application.getString(R.string.select_item) + R.drawable.ic_save, + application.getString(R.string.select_item) ) ), MappableSelectItem.WithAction( diff --git a/collect_app/src/test/java/org/odk/collect/android/widgets/items/SelectOneFromMapWidgetTest.kt b/collect_app/src/test/java/org/odk/collect/android/widgets/items/SelectOneFromMapWidgetTest.kt index 775a0bcc10d..87ac5786b71 100644 --- a/collect_app/src/test/java/org/odk/collect/android/widgets/items/SelectOneFromMapWidgetTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/widgets/items/SelectOneFromMapWidgetTest.kt @@ -96,7 +96,7 @@ class SelectOneFromMapWidgetTest { SelectOneFromMapDialogFragment(object : ViewModelProvider.Factory { override fun create( modelClass: Class, - extras: CreationExtras, + extras: CreationExtras ): T { return formEntryViewModel as T } @@ -271,7 +271,7 @@ class SelectOneFromMapWidgetTest { SelectOneFromMapDialogFragment(object : ViewModelProvider.Factory { override fun create( modelClass: Class, - extras: CreationExtras, + extras: CreationExtras ): T { return formEntryViewModel as T } diff --git a/collect_app/src/test/java/org/odk/collect/android/widgets/support/NoOpMapFragment.kt b/collect_app/src/test/java/org/odk/collect/android/widgets/support/NoOpMapFragment.kt index 3d3f643e13d..a09636c0e6b 100644 --- a/collect_app/src/test/java/org/odk/collect/android/widgets/support/NoOpMapFragment.kt +++ b/collect_app/src/test/java/org/odk/collect/android/widgets/support/NoOpMapFragment.kt @@ -10,7 +10,7 @@ class NoOpMapFragment : Fragment(), MapFragment { override fun init( readyListener: MapFragment.ReadyListener?, - errorListener: MapFragment.ErrorListener?, + errorListener: MapFragment.ErrorListener? ) { } @@ -34,7 +34,7 @@ class NoOpMapFragment : Fragment(), MapFragment { override fun zoomToBoundingBox( points: MutableIterable?, scaleFactor: Double, - animate: Boolean, + animate: Boolean ) { } diff --git a/collect_app/src/test/java/org/odk/collect/android/widgets/utilities/ExternalAppRecordingRequesterTest.kt b/collect_app/src/test/java/org/odk/collect/android/widgets/utilities/ExternalAppRecordingRequesterTest.kt index 3c6b90d2eb0..b3578101a7c 100644 --- a/collect_app/src/test/java/org/odk/collect/android/widgets/utilities/ExternalAppRecordingRequesterTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/widgets/utilities/ExternalAppRecordingRequesterTest.kt @@ -4,8 +4,8 @@ import android.app.Activity import android.provider.MediaStore import androidx.test.espresso.matcher.ViewMatchers.assertThat import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.nullValue import org.junit.Before import org.junit.Test diff --git a/config/quality.gradle b/config/quality.gradle index f03be338729..97c524a4a9c 100644 --- a/config/quality.gradle +++ b/config/quality.gradle @@ -5,7 +5,7 @@ def reportsDir = "${project.buildDir}/reports" apply plugin: 'checkstyle' -checkstyle.toolVersion = '10.5.0' +checkstyle.toolVersion = '10.9.0' tasks.register("checkstyle", Checkstyle) { configFile file("$configDir/checkstyle.xml") @@ -24,7 +24,7 @@ tasks.register("checkstyle", Checkstyle) { apply plugin: 'pmd' pmd { - toolVersion = '6.52.0' + toolVersion = '6.55.0' } tasks.register("pmd", Pmd) { @@ -37,12 +37,12 @@ tasks.register("pmd", Pmd) { exclude '**/gen/**' reports { - xml.enabled = false - html.enabled = true xml { + enabled false setDestination new File("$reportsDir/pmd/pmd.xml") } html { + enabled true setDestination new File("$reportsDir/pmd/pmd.html") } } @@ -50,4 +50,8 @@ tasks.register("pmd", Pmd) { //------------------------ktlint------------------------// -apply plugin: "org.jlleitschuh.gradle.ktlint" \ No newline at end of file +apply plugin: "org.jlleitschuh.gradle.ktlint" + +ktlint { + disabledRules.set(["no-blank-lines-in-chained-method-calls"]) +} \ No newline at end of file diff --git a/crash-handler/build.gradle.kts b/crash-handler/build.gradle.kts index 7422f54d5cf..31fab61cb97 100644 --- a/crash-handler/build.gradle.kts +++ b/crash-handler/build.gradle.kts @@ -32,10 +32,6 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = "1.8" - } - testOptions { unitTests { isIncludeAndroidResources = true diff --git a/crash-handler/src/main/java/org/odk/collect/crashhandler/CrashHandler.kt b/crash-handler/src/main/java/org/odk/collect/crashhandler/CrashHandler.kt index c6cc2eb418e..b5eb8e3fe58 100644 --- a/crash-handler/src/main/java/org/odk/collect/crashhandler/CrashHandler.kt +++ b/crash-handler/src/main/java/org/odk/collect/crashhandler/CrashHandler.kt @@ -106,7 +106,7 @@ class CrashHandler(private val processKiller: Runnable = Runnable { exitProcess( private fun wrapUncaughtExceptionHandler( crashHandler: CrashHandler, - context: Context, + context: Context ) { if (originalHandler != null) { throw IllegalStateException("install() should not be called multiple times without uninstall()!") diff --git a/entities/build.gradle.kts b/entities/build.gradle.kts index 5fe59605694..5e850180feb 100644 --- a/entities/build.gradle.kts +++ b/entities/build.gradle.kts @@ -33,10 +33,6 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = "1.8" - } - testOptions { unitTests { isIncludeAndroidResources = true diff --git a/entities/src/main/java/org/odk/collect/entities/DatasetsFragment.kt b/entities/src/main/java/org/odk/collect/entities/DatasetsFragment.kt index a4801fb6b29..ec468b81e8c 100644 --- a/entities/src/main/java/org/odk/collect/entities/DatasetsFragment.kt +++ b/entities/src/main/java/org/odk/collect/entities/DatasetsFragment.kt @@ -24,7 +24,7 @@ class DatasetsFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?, + savedInstanceState: Bundle? ): View { return ListLayoutBinding.inflate(inflater, container, false).root } diff --git a/entities/src/main/java/org/odk/collect/entities/EntitiesFragment.kt b/entities/src/main/java/org/odk/collect/entities/EntitiesFragment.kt index a9b29175db1..b05e37cd405 100644 --- a/entities/src/main/java/org/odk/collect/entities/EntitiesFragment.kt +++ b/entities/src/main/java/org/odk/collect/entities/EntitiesFragment.kt @@ -24,7 +24,7 @@ class EntitiesFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?, + savedInstanceState: Bundle? ): View { return ListLayoutBinding.inflate(inflater, container, false).root } diff --git a/errors/build.gradle b/errors/build.gradle index f08c05e0fe7..dc497674753 100644 --- a/errors/build.gradle +++ b/errors/build.gradle @@ -30,10 +30,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests { includeAndroidResources = true diff --git a/externalapp/build.gradle b/externalapp/build.gradle index bf5b4e5de1a..a26baba029a 100644 --- a/externalapp/build.gradle +++ b/externalapp/build.gradle @@ -31,10 +31,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests { includeAndroidResources = true diff --git a/fragmentstest/build.gradle b/fragmentstest/build.gradle index 57dd6fa67f6..87de150b1b9 100644 --- a/fragmentstest/build.gradle +++ b/fragmentstest/build.gradle @@ -28,9 +28,7 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } + namespace 'org.odk.collect.fragmentstest' } diff --git a/geo/build.gradle b/geo/build.gradle index d8d3ab46b4c..f0b605aab5f 100644 --- a/geo/build.gradle +++ b/geo/build.gradle @@ -32,10 +32,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests { includeAndroidResources = true @@ -53,6 +49,7 @@ dependencies { implementation Dependencies.kotlin_stdlib implementation Dependencies.androidx_appcompat + implementation Dependencies.androidx_lifecycle_livedata_ktx implementation Dependencies.android_material implementation Dependencies.timber implementation Dependencies.play_services_location diff --git a/geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointViewModel.kt b/geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointViewModel.kt index 3dee2280403..2ddf43f19d7 100644 --- a/geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointViewModel.kt +++ b/geo/src/main/java/org/odk/collect/geo/geopoint/GeoPointViewModel.kt @@ -2,9 +2,9 @@ package org.odk.collect.geo.geopoint import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.map import org.odk.collect.analytics.Analytics.Companion.log import org.odk.collect.androidshared.livedata.MutableNonNullLiveData import org.odk.collect.androidshared.livedata.NonNullLiveData @@ -56,7 +56,7 @@ internal class LocationTrackerGeoPointViewModel( private val trackerLocation = MutableLiveData(null) override val acceptedLocation: MutableLiveData = MutableLiveData(null) - override val currentAccuracy = Transformations.map(trackerLocation) { + override val currentAccuracy = trackerLocation.map { if (it != null) { when { it.accuracy > unacceptableAccuracyThreshold -> GeoPointAccuracy.Unacceptable(it.accuracy) diff --git a/geo/src/main/java/org/odk/collect/geo/selection/MappableSelectItem.kt b/geo/src/main/java/org/odk/collect/geo/selection/MappableSelectItem.kt index 33fe26e95cf..cc57a33f6e3 100644 --- a/geo/src/main/java/org/odk/collect/geo/selection/MappableSelectItem.kt +++ b/geo/src/main/java/org/odk/collect/geo/selection/MappableSelectItem.kt @@ -24,7 +24,7 @@ sealed interface MappableSelectItem { val info: String, override val selected: Boolean = false, override val color: String? = null, - override val symbol: String? = null, + override val symbol: String? = null ) : MappableSelectItem { constructor( @@ -38,7 +38,7 @@ sealed interface MappableSelectItem { info: String, selected: Boolean = false, color: String? = null, - symbol: String? = null, + symbol: String? = null ) : this( id, listOf(MapPoint(latitude, longitude)), @@ -63,7 +63,7 @@ sealed interface MappableSelectItem { val action: IconifiedText, override val selected: Boolean = false, override val color: String? = null, - override val symbol: String? = null, + override val symbol: String? = null ) : MappableSelectItem { constructor( @@ -77,7 +77,7 @@ sealed interface MappableSelectItem { action: IconifiedText, selected: Boolean = false, color: String? = null, - symbol: String? = null, + symbol: String? = null ) : this( id, listOf(MapPoint(latitude, longitude)), diff --git a/geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt b/geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt index 5832406d4b7..e018db9804f 100644 --- a/geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt +++ b/geo/src/main/java/org/odk/collect/geo/selection/SelectionMapFragment.kt @@ -44,7 +44,7 @@ class SelectionMapFragment( val skipSummary: Boolean = false, val zoomToFitItems: Boolean = true, val showNewItemButton: Boolean = true, - val onBackPressedDispatcher: (() -> OnBackPressedDispatcher)? = null, + val onBackPressedDispatcher: (() -> OnBackPressedDispatcher)? = null ) : Fragment() { @Inject @@ -117,7 +117,7 @@ class SelectionMapFragment( override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?, + savedInstanceState: Bundle? ): View { return SelectionMapLayoutBinding.inflate(inflater).root } diff --git a/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointActivityTest.kt b/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointActivityTest.kt index 1564d3ca08c..127c48ce12a 100644 --- a/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointActivityTest.kt +++ b/geo/src/test/java/org/odk/collect/geo/geopoint/GeoPointActivityTest.kt @@ -66,7 +66,7 @@ class GeoPointActivityTest { val intent = Intent(getApplicationContext(), GeoPointActivity::class.java) launcherRule.launch(intent) - verify(viewModel).start(retainMockAccuracy = false,) + verify(viewModel).start(retainMockAccuracy = false) } @Test @@ -112,7 +112,7 @@ class GeoPointActivityTest { intent.putExtra(EXTRA_RETAIN_MOCK_ACCURACY, true) launcherRule.launch(intent) - verify(viewModel).start(retainMockAccuracy = true,) + verify(viewModel).start(retainMockAccuracy = true) } @Test @@ -121,7 +121,7 @@ class GeoPointActivityTest { intent.putExtra(GeoPointActivity.EXTRA_ACCURACY_THRESHOLD, 5.0f) launcherRule.launch(intent) - verify(viewModel).start(retainMockAccuracy = false, accuracyThreshold = 5.0f,) + verify(viewModel).start(retainMockAccuracy = false, accuracyThreshold = 5.0f) } @Test diff --git a/geo/src/test/java/org/odk/collect/geo/geopoint/LocationTrackerGeoPointViewModelTest.kt b/geo/src/test/java/org/odk/collect/geo/geopoint/LocationTrackerGeoPointViewModelTest.kt index ee993d5dc32..794e5f75e12 100644 --- a/geo/src/test/java/org/odk/collect/geo/geopoint/LocationTrackerGeoPointViewModelTest.kt +++ b/geo/src/test/java/org/odk/collect/geo/geopoint/LocationTrackerGeoPointViewModelTest.kt @@ -35,7 +35,7 @@ class LocationTrackerGeoPointViewModelTest { @Test fun `start() starts LocationTracker with with retain mock accuracy value when set`() { val viewModel = createViewModel() - viewModel.start(retainMockAccuracy = true,) + viewModel.start(retainMockAccuracy = true) verify(locationTracker).start(true, 1000L) } @@ -43,7 +43,7 @@ class LocationTrackerGeoPointViewModelTest { @Test fun `acceptedLocation is null when no location`() { val viewModel = createViewModel() - viewModel.start(accuracyThreshold = 0.0f,) + viewModel.start(accuracyThreshold = 0.0f) val location = viewModel.acceptedLocation whenever(locationTracker.getCurrentLocation()).thenReturn(null) @@ -54,7 +54,7 @@ class LocationTrackerGeoPointViewModelTest { @Test fun `acceptedLocation is null when accuracy is higher than threshold value`() { val viewModel = createViewModel() - viewModel.start(accuracyThreshold = 1.0f,) + viewModel.start(accuracyThreshold = 1.0f) val location = viewModel.acceptedLocation whenever(locationTracker.getCurrentLocation()).thenReturn(Location(0.0, 0.0, 0.0, 1.1f)) @@ -65,7 +65,7 @@ class LocationTrackerGeoPointViewModelTest { @Test fun `acceptedLocation is tracker location when accuracy is equal to threshold value`() { val viewModel = createViewModel() - viewModel.start(accuracyThreshold = 1.0f,) + viewModel.start(accuracyThreshold = 1.0f) val location = viewModel.acceptedLocation val locationTrackerLocation = Location(0.0, 0.0, 0.0, 1.0f) @@ -77,7 +77,7 @@ class LocationTrackerGeoPointViewModelTest { @Test fun `acceptedLocation is tracker location when accuracy is lower than threshold value`() { val viewModel = createViewModel() - viewModel.start(accuracyThreshold = 1.0f,) + viewModel.start(accuracyThreshold = 1.0f) val location = viewModel.acceptedLocation val locationTrackerLocation = Location(0.0, 0.0, 0.0, 0.9f) @@ -89,7 +89,7 @@ class LocationTrackerGeoPointViewModelTest { @Test fun `acceptedLocation does not update after it has met the threshold`() { val viewModel = createViewModel() - viewModel.start(accuracyThreshold = 1.0f,) + viewModel.start(accuracyThreshold = 1.0f) val location = viewModel.acceptedLocation val locationTrackerLocation = Location(0.0, 0.0, 0.0, 1.0f) @@ -183,7 +183,7 @@ class LocationTrackerGeoPointViewModelTest { @Test fun `forceLocation() sets acceptedLocation to location tracker location regardless of threshold`() { val viewModel = createViewModel() - viewModel.start(accuracyThreshold = 1.0f,) + viewModel.start(accuracyThreshold = 1.0f) val location = viewModel.acceptedLocation val locationTrackerLocation = Location(0.0, 0.0, 0.0, 2.5f) diff --git a/geo/src/test/java/org/odk/collect/geo/selection/SelectionMapFragmentTest.kt b/geo/src/test/java/org/odk/collect/geo/selection/SelectionMapFragmentTest.kt index 399a4005b18..46b5f7e3c8d 100644 --- a/geo/src/test/java/org/odk/collect/geo/selection/SelectionMapFragmentTest.kt +++ b/geo/src/test/java/org/odk/collect/geo/selection/SelectionMapFragmentTest.kt @@ -450,7 +450,7 @@ class SelectionMapFragmentTest { fun `clicking on item always selects correct item`() { val items = listOf( Fixtures.actionMappableSelectItem().copy(id = 0, points = listOf(MapPoint(40.0, 0.0), MapPoint(41.0, 0.0))), - Fixtures.actionMappableSelectItem().copy(id = 1, points = listOf(MapPoint(45.0, 0.0))), + Fixtures.actionMappableSelectItem().copy(id = 1, points = listOf(MapPoint(45.0, 0.0))) ) whenever(data.getMappableItems()).thenReturn(MutableLiveData(items)) @@ -477,7 +477,7 @@ class SelectionMapFragmentTest { largeIcon = android.R.drawable.ic_lock_idle_alarm, symbol = "B", color = "#000000" - ), + ) ) whenever(data.getMappableItems()).thenReturn(MutableLiveData(items)) @@ -513,7 +513,7 @@ class SelectionMapFragmentTest { largeIcon = android.R.drawable.ic_lock_idle_alarm, symbol = "B", color = "#000000" - ), + ) ) whenever(data.getMappableItems()).thenReturn(MutableLiveData(items)) @@ -538,7 +538,7 @@ class SelectionMapFragmentTest { fun `clicking on item sets item on summary sheet`() { val items = listOf( Fixtures.actionMappableSelectItem().copy(id = 0, name = "Blah1"), - Fixtures.actionMappableSelectItem().copy(id = 1, name = "Blah2"), + Fixtures.actionMappableSelectItem().copy(id = 1, name = "Blah2") ) whenever(data.getMappableItems()).thenReturn(MutableLiveData(items)) @@ -554,7 +554,7 @@ class SelectionMapFragmentTest { fun `clicking on item returns item ID as result when skipSummary is true`() { val items = listOf( Fixtures.actionMappableSelectItem().copy(id = 0), - Fixtures.actionMappableSelectItem().copy(id = 1), + Fixtures.actionMappableSelectItem().copy(id = 1) ) whenever(data.getMappableItems()).thenReturn(MutableLiveData(items)) diff --git a/geo/src/test/java/org/odk/collect/geo/support/FakeMapFragment.kt b/geo/src/test/java/org/odk/collect/geo/support/FakeMapFragment.kt index 53eb7972aa4..094c33b130b 100644 --- a/geo/src/test/java/org/odk/collect/geo/support/FakeMapFragment.kt +++ b/geo/src/test/java/org/odk/collect/geo/support/FakeMapFragment.kt @@ -33,7 +33,7 @@ class FakeMapFragment : Fragment(), MapFragment { override fun init( readyListener: ReadyListener?, - errorListener: MapFragment.ErrorListener?, + errorListener: MapFragment.ErrorListener? ) { this.readyListener = readyListener } @@ -72,7 +72,7 @@ class FakeMapFragment : Fragment(), MapFragment { override fun zoomToBoundingBox( points: Iterable, scaleFactor: Double, - animate: Boolean, + animate: Boolean ) { center = null zoom = 0.0 diff --git a/geo/src/test/java/org/odk/collect/geo/support/Fixtures.kt b/geo/src/test/java/org/odk/collect/geo/support/Fixtures.kt index e4ac47d8419..feaf2f5f639 100644 --- a/geo/src/test/java/org/odk/collect/geo/support/Fixtures.kt +++ b/geo/src/test/java/org/odk/collect/geo/support/Fixtures.kt @@ -13,7 +13,7 @@ object Fixtures { R.drawable.ic_lock_idle_charging, "0", listOf(MappableSelectItem.IconifiedText(R.drawable.ic_lock_idle_charging, "An item")), - MappableSelectItem.IconifiedText(R.drawable.ic_delete, "Action"), + MappableSelectItem.IconifiedText(R.drawable.ic_delete, "Action") ) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 902295f3756..410ce7ee1db 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,5 +4,5 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists android.enableD8.desugaring=true -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip org.gradle.configureondemand=false diff --git a/imageloader/build.gradle b/imageloader/build.gradle index c7fc971ba0d..3bd87443198 100644 --- a/imageloader/build.gradle +++ b/imageloader/build.gradle @@ -29,9 +29,7 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } + namespace 'org.odk.collect.imageloader' } diff --git a/location/build.gradle b/location/build.gradle index ce92288ac9c..51a4306df9b 100644 --- a/location/build.gradle +++ b/location/build.gradle @@ -31,10 +31,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests { includeAndroidResources = true diff --git a/location/src/test/java/org/odk/collect/location/LocationClientProviderTest.kt b/location/src/test/java/org/odk/collect/location/LocationClientProviderTest.kt index afd2fb54edb..f7714e2863b 100644 --- a/location/src/test/java/org/odk/collect/location/LocationClientProviderTest.kt +++ b/location/src/test/java/org/odk/collect/location/LocationClientProviderTest.kt @@ -17,8 +17,8 @@ import android.content.Context import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.instanceOf +import org.hamcrest.Matchers.`is` import org.junit.Test import org.mockito.ArgumentMatchers import org.mockito.Mockito diff --git a/mapbox/build.gradle b/mapbox/build.gradle index e7ca2ca7b28..1d1557113e4 100644 --- a/mapbox/build.gradle +++ b/mapbox/build.gradle @@ -32,10 +32,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - namespace 'org.odk.collect.mapbox' } diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapUtils.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapUtils.kt index 075e5f42bf7..06bf7b4b296 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapUtils.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapUtils.kt @@ -36,7 +36,7 @@ object MapUtils { fun createPointAnnotations( context: Context, pointAnnotationManager: PointAnnotationManager, - markerFeatures: List, + markerFeatures: List ): List { val pointAnnotationOptionsList = markerFeatures.map { PointAnnotationOptions() diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index 8190d37987a..f714d960707 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -142,7 +142,7 @@ class MapboxMapFragment : override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?, + savedInstanceState: Bundle? ): View { mapView = MapView(inflater.context).apply { scalebar.enabled = false @@ -256,7 +256,7 @@ class MapboxMapFragment : override fun zoomToBoundingBox( mapPoints: Iterable?, scaleFactor: Double, - animate: Boolean, + animate: Boolean ) { mapPoints?.let { val points = mapPoints.map { @@ -461,8 +461,10 @@ class MapboxMapFragment : override fun onLocationChanged(location: Location) { lastLocationFix = MapPoint( - location.latitude, location.longitude, - location.altitude, location.accuracy.toDouble() + location.latitude, + location.longitude, + location.altitude, + location.accuracy.toDouble() ) lastLocationProvider = location.provider Timber.i( @@ -504,7 +506,7 @@ class MapboxMapFragment : this.locationPuck = LocationPuck2D( AppCompatResources.getDrawable( requireContext(), - R.drawable.ic_crosshairs, + R.drawable.ic_crosshairs ) ) } diff --git a/maps/build.gradle b/maps/build.gradle index 56ac3e0ae97..35ac770b95c 100644 --- a/maps/build.gradle +++ b/maps/build.gradle @@ -33,10 +33,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests { includeAndroidResources = true diff --git a/maps/src/main/java/org/odk/collect/maps/MapFragmentDelegate.kt b/maps/src/main/java/org/odk/collect/maps/MapFragmentDelegate.kt index 9548ef4fc6d..41e85ef7e0f 100644 --- a/maps/src/main/java/org/odk/collect/maps/MapFragmentDelegate.kt +++ b/maps/src/main/java/org/odk/collect/maps/MapFragmentDelegate.kt @@ -9,7 +9,7 @@ class MapFragmentDelegate( private val mapFragment: MapFragment, configuratorProvider: () -> MapConfigurator, settingsProvider: () -> Settings, - private val onConfigChanged: Consumer, + private val onConfigChanged: Consumer ) : OnSettingChangeListener { private val configurator by lazy { configuratorProvider() } diff --git a/maps/src/main/java/org/odk/collect/maps/markers/MarkerDescription.kt b/maps/src/main/java/org/odk/collect/maps/markers/MarkerDescription.kt index b501430a3cf..d735d5fa320 100644 --- a/maps/src/main/java/org/odk/collect/maps/markers/MarkerDescription.kt +++ b/maps/src/main/java/org/odk/collect/maps/markers/MarkerDescription.kt @@ -6,6 +6,7 @@ import org.odk.collect.maps.MapPoint data class MarkerDescription( val point: MapPoint, val isDraggable: Boolean, - @get:MapFragment.IconAnchor @param:MapFragment.IconAnchor val iconAnchor: String, + @get:MapFragment.IconAnchor @param:MapFragment.IconAnchor + val iconAnchor: String, val iconDescription: MarkerIconDescription ) diff --git a/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconCreator.kt b/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconCreator.kt index 228a0265a7a..316df9a0354 100644 --- a/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconCreator.kt +++ b/maps/src/main/java/org/odk/collect/maps/markers/MarkerIconCreator.kt @@ -46,7 +46,7 @@ object MarkerIconCreator { context: Context, drawableId: Int, color: Int?, - symbol: String?, + symbol: String? ): Bitmap { val drawable = ContextCompat.getDrawable(context, drawableId) if (drawable != null) { diff --git a/osmdroid/build.gradle b/osmdroid/build.gradle index 428aeb84a6c..bdaca965524 100644 --- a/osmdroid/build.gradle +++ b/osmdroid/build.gradle @@ -32,9 +32,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } namespace 'org.odk.collect.osmdroid' } @@ -42,7 +39,11 @@ dependencies { coreLibraryDesugaring Dependencies.desugar implementation project(':shared') - implementation project(':androidshared') + implementation(project(':androidshared')) { + // Without this the build fails with 'Corrupt serialized resolution result'. + // It looks like a Gradle issue. Hopefully we can get rid of it in the future. + exclude group: 'androidx.lifecycle' + } implementation project(':icons') implementation project(':maps') implementation project(':location') diff --git a/permissions/build.gradle b/permissions/build.gradle index b91c48759ca..dfcbde77a1f 100644 --- a/permissions/build.gradle +++ b/permissions/build.gradle @@ -30,10 +30,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests { includeAndroidResources = true diff --git a/permissions/src/main/java/org/odk/collect/permissions/PermissionsDialogCreator.kt b/permissions/src/main/java/org/odk/collect/permissions/PermissionsDialogCreator.kt index d2100b5df08..fa49f4fa4cb 100644 --- a/permissions/src/main/java/org/odk/collect/permissions/PermissionsDialogCreator.kt +++ b/permissions/src/main/java/org/odk/collect/permissions/PermissionsDialogCreator.kt @@ -34,7 +34,8 @@ internal object PermissionsDialogCreatorImpl : PermissionsDialogCreator { activity.getString(R.string.enable_gps) ) { _: DialogInterface?, _: Int -> activity.startActivityForResult( - Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0 + Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), + 0 ) } .setNegativeButton( diff --git a/permissions/src/main/java/org/odk/collect/permissions/PermissionsProvider.kt b/permissions/src/main/java/org/odk/collect/permissions/PermissionsProvider.kt index 6462e0269c3..5d1c992c7c3 100644 --- a/permissions/src/main/java/org/odk/collect/permissions/PermissionsProvider.kt +++ b/permissions/src/main/java/org/odk/collect/permissions/PermissionsProvider.kt @@ -103,7 +103,8 @@ open class PermissionsProvider internal constructor( ) } }, - Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION ) } @@ -151,7 +152,8 @@ open class PermissionsProvider internal constructor( ) } }, - Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO + Manifest.permission.CAMERA, + Manifest.permission.RECORD_AUDIO ) } @@ -247,8 +249,10 @@ open class PermissionsProvider internal constructor( override fun denied() { permissionsDialogCreator.showAdditionalExplanation( - activity, R.string.storage_runtime_permission_denied_title, - R.string.storage_runtime_permission_denied_desc, R.drawable.sd, + activity, + R.string.storage_runtime_permission_denied_title, + R.string.storage_runtime_permission_denied_desc, + R.drawable.sd, listener ) } diff --git a/projects/build.gradle b/projects/build.gradle index 1b2f70ac611..449fa9e66ff 100644 --- a/projects/build.gradle +++ b/projects/build.gradle @@ -32,10 +32,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - buildFeatures { viewBinding true } diff --git a/projects/src/test/java/org/odk/collect/projects/InMemProjectsRepositoryTest.kt b/projects/src/test/java/org/odk/collect/projects/InMemProjectsRepositoryTest.kt index efa97fa2969..d5f2c1b4e80 100644 --- a/projects/src/test/java/org/odk/collect/projects/InMemProjectsRepositoryTest.kt +++ b/projects/src/test/java/org/odk/collect/projects/InMemProjectsRepositoryTest.kt @@ -5,7 +5,7 @@ import java.util.function.Supplier class InMemProjectsRepositoryTest : ProjectsRepositoryTest() { override fun buildSubject(): ProjectsRepository { - return InMemProjectsRepository(UUIDGenerator(),) + return InMemProjectsRepository(UUIDGenerator()) } override fun buildSubject(clock: Supplier): ProjectsRepository { diff --git a/projects/src/test/java/org/odk/collect/projects/ProjectsRepositoryTest.kt b/projects/src/test/java/org/odk/collect/projects/ProjectsRepositoryTest.kt index 81e96601a99..8b121177927 100644 --- a/projects/src/test/java/org/odk/collect/projects/ProjectsRepositoryTest.kt +++ b/projects/src/test/java/org/odk/collect/projects/ProjectsRepositoryTest.kt @@ -7,8 +7,8 @@ import org.hamcrest.Matchers.not import org.hamcrest.Matchers.notNullValue import org.junit.Before import org.junit.Test -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` import java.util.function.Supplier abstract class ProjectsRepositoryTest { diff --git a/projects/src/test/java/org/odk/collect/projects/SharedPreferencesProjectsRepositoryTest.kt b/projects/src/test/java/org/odk/collect/projects/SharedPreferencesProjectsRepositoryTest.kt index 8593bfc3c20..2a58d862aa4 100644 --- a/projects/src/test/java/org/odk/collect/projects/SharedPreferencesProjectsRepositoryTest.kt +++ b/projects/src/test/java/org/odk/collect/projects/SharedPreferencesProjectsRepositoryTest.kt @@ -14,7 +14,7 @@ class SharedPreferencesProjectsRepositoryTest : ProjectsRepositoryTest() { UUIDGenerator(), Gson(), InMemSettings(), - "test", + "test" ) } diff --git a/qr-code/build.gradle.kts b/qr-code/build.gradle.kts index 174beee156c..9f573eff45d 100644 --- a/qr-code/build.gradle.kts +++ b/qr-code/build.gradle.kts @@ -32,10 +32,6 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = "1.8" - } - testOptions { unitTests { isIncludeAndroidResources = true diff --git a/selfie-camera/build.gradle.kts b/selfie-camera/build.gradle.kts index 096850590a6..ca8be652a87 100644 --- a/selfie-camera/build.gradle.kts +++ b/selfie-camera/build.gradle.kts @@ -33,10 +33,6 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = "1.8" - } - testOptions { unitTests { isIncludeAndroidResources = true diff --git a/selfie-camera/src/main/java/org/odk/collect/selfiecamera/CameraXCamera.kt b/selfie-camera/src/main/java/org/odk/collect/selfiecamera/CameraXCamera.kt index fa77c81574e..9d3080747c0 100644 --- a/selfie-camera/src/main/java/org/odk/collect/selfiecamera/CameraXCamera.kt +++ b/selfie-camera/src/main/java/org/odk/collect/selfiecamera/CameraXCamera.kt @@ -54,7 +54,7 @@ internal class CameraXCamera : Camera { override fun takePicture( imagePath: String, onImageSaved: () -> Unit, - onImageSaveError: () -> Unit, + onImageSaveError: () -> Unit ) { Pair(imageCapture, activity).let { (i, a) -> if (i == null || a == null) { diff --git a/selfie-camera/src/test/java/org/odk/collect/selfiecamera/CaptureSelfieActivityTest.kt b/selfie-camera/src/test/java/org/odk/collect/selfiecamera/CaptureSelfieActivityTest.kt index cf0e41db952..7f5f27c112d 100644 --- a/selfie-camera/src/test/java/org/odk/collect/selfiecamera/CaptureSelfieActivityTest.kt +++ b/selfie-camera/src/test/java/org/odk/collect/selfiecamera/CaptureSelfieActivityTest.kt @@ -157,7 +157,7 @@ private class FakeCamera : Camera { override fun takePicture( imagePath: String, onImageSaved: () -> Unit, - onImageSaveError: () -> Unit, + onImageSaveError: () -> Unit ) { if (state.value == Camera.State.UNINITIALIZED) { throw IllegalStateException() diff --git a/settings/build.gradle b/settings/build.gradle index 0dad842a0bf..2a3a449ba62 100644 --- a/settings/build.gradle +++ b/settings/build.gradle @@ -31,9 +31,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } packagingOptions { resources { // These library licenses will be referenced in-app diff --git a/settings/src/main/java/org/odk/collect/settings/ODKAppSettingsImporter.kt b/settings/src/main/java/org/odk/collect/settings/ODKAppSettingsImporter.kt index bf4c3a777b9..16e73b73ed5 100644 --- a/settings/src/main/java/org/odk/collect/settings/ODKAppSettingsImporter.kt +++ b/settings/src/main/java/org/odk/collect/settings/ODKAppSettingsImporter.kt @@ -6,6 +6,7 @@ import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.importing.ProjectDetailsCreatorImpl import org.odk.collect.settings.importing.SettingsChangeHandler import org.odk.collect.settings.importing.SettingsImporter +import org.odk.collect.settings.importing.SettingsImportingResult import org.odk.collect.settings.validation.JsonSchemaSettingsValidator class ODKAppSettingsImporter( @@ -29,11 +30,11 @@ class ODKAppSettingsImporter( ProjectDetailsCreatorImpl(projectColors, generalDefaults) ) - fun fromJSON(json: String, project: Project.Saved): Boolean { + fun fromJSON(json: String, project: Project.Saved): SettingsImportingResult { return try { settingsImporter.fromJSON(json, project, deviceUnsupportedSettings) } catch (e: Throwable) { - false + SettingsImportingResult.INVALID_SETTINGS } } } diff --git a/settings/src/main/java/org/odk/collect/settings/importing/SettingsImporter.kt b/settings/src/main/java/org/odk/collect/settings/importing/SettingsImporter.kt index 4de64b11fc5..07459bafb06 100644 --- a/settings/src/main/java/org/odk/collect/settings/importing/SettingsImporter.kt +++ b/settings/src/main/java/org/odk/collect/settings/importing/SettingsImporter.kt @@ -20,9 +20,9 @@ internal class SettingsImporter( private val projectDetailsCreator: ProjectDetailsCreator ) { - fun fromJSON(json: String, project: Project.Saved, deviceUnsupportedSettings: JSONObject): Boolean { + fun fromJSON(json: String, project: Project.Saved, deviceUnsupportedSettings: JSONObject): SettingsImportingResult { if (!settingsValidator.isValid(json)) { - return false + return SettingsImportingResult.INVALID_SETTINGS } val generalSettings = settingsProvider.getUnprotectedSettings(project.uuid) @@ -33,6 +33,10 @@ internal class SettingsImporter( val jsonObject = JSONObject(json) + if (isGDProject(jsonObject)) { + return SettingsImportingResult.GD_PROJECT + } + // Import unprotected settings importToPrefs(jsonObject, AppConfigurationKeys.GENERAL, generalSettings, deviceUnsupportedSettings) @@ -65,7 +69,13 @@ internal class SettingsImporter( settingsChangedHandler.onSettingsChanged(project.uuid) - return true + return SettingsImportingResult.SUCCESS + } + + private fun isGDProject(jsonObject: JSONObject): Boolean { + val generalSettings = jsonObject.getJSONObject(AppConfigurationKeys.GENERAL) + return generalSettings.has(ProjectKeys.KEY_PROTOCOL) && + generalSettings.get(ProjectKeys.KEY_PROTOCOL) == ProjectKeys.PROTOCOL_GOOGLE_SHEETS } private fun importToPrefs( diff --git a/settings/src/main/java/org/odk/collect/settings/importing/SettingsImportingResult.kt b/settings/src/main/java/org/odk/collect/settings/importing/SettingsImportingResult.kt new file mode 100644 index 00000000000..f8f78519ae7 --- /dev/null +++ b/settings/src/main/java/org/odk/collect/settings/importing/SettingsImportingResult.kt @@ -0,0 +1,7 @@ +package org.odk.collect.settings.importing + +enum class SettingsImportingResult { + SUCCESS, + INVALID_SETTINGS, + GD_PROJECT +} diff --git a/settings/src/test/java/org/odk/collect/settings/ODKAppSettingsImporterTest.kt b/settings/src/test/java/org/odk/collect/settings/ODKAppSettingsImporterTest.kt index f61760b6f10..a95fe4b43e1 100644 --- a/settings/src/test/java/org/odk/collect/settings/ODKAppSettingsImporterTest.kt +++ b/settings/src/test/java/org/odk/collect/settings/ODKAppSettingsImporterTest.kt @@ -10,6 +10,7 @@ import org.mockito.kotlin.whenever import org.odk.collect.projects.InMemProjectsRepository import org.odk.collect.projects.Project import org.odk.collect.settings.importing.SettingsChangeHandler +import org.odk.collect.settings.importing.SettingsImportingResult import org.odk.collect.settings.support.SettingsUtils.assertSettingsEmpty import java.lang.RuntimeException @@ -39,7 +40,7 @@ class ODKAppSettingsImporterTest { "}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(true)) + assertThat(result, equalTo(SettingsImportingResult.SUCCESS)) } @Test @@ -48,7 +49,7 @@ class ODKAppSettingsImporterTest { "{ \"admin\": {}}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(false)) + assertThat(result, equalTo(SettingsImportingResult.INVALID_SETTINGS)) assertSettingsEmpty(settingsProvider.getUnprotectedSettings()) assertSettingsEmpty(settingsProvider.getProtectedSettings()) } @@ -59,7 +60,7 @@ class ODKAppSettingsImporterTest { "{ \"general\": {}}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(false)) + assertThat(result, equalTo(SettingsImportingResult.INVALID_SETTINGS)) assertSettingsEmpty(settingsProvider.getUnprotectedSettings()) assertSettingsEmpty(settingsProvider.getProtectedSettings()) } @@ -70,7 +71,7 @@ class ODKAppSettingsImporterTest { "{\"general\":{*},\"admin\":{}}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(false)) + assertThat(result, equalTo(SettingsImportingResult.INVALID_SETTINGS)) assertSettingsEmpty(settingsProvider.getUnprotectedSettings()) assertSettingsEmpty(settingsProvider.getProtectedSettings()) } @@ -88,6 +89,23 @@ class ODKAppSettingsImporterTest { "}", projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) ) - assertThat(result, equalTo(false)) + assertThat(result, equalTo(SettingsImportingResult.INVALID_SETTINGS)) + } + + @Test + fun `rejects JSON with google_sheets protocol`() { + val result = settingsImporter.fromJSON( + "{\n" + + " \"general\": {\n" + + " \"protocol\" : \"google_sheets\"" + + " },\n" + + " \"admin\": {\n" + + " }\n" + + "}", + projectsRepository.save(Project.New("Flat", "AS", "#ff0000")) + ) + assertThat(result, equalTo(SettingsImportingResult.GD_PROJECT)) + assertSettingsEmpty(settingsProvider.getUnprotectedSettings()) + assertSettingsEmpty(settingsProvider.getProtectedSettings()) } } diff --git a/settings/src/test/java/org/odk/collect/settings/importing/SettingsImporterTest.kt b/settings/src/test/java/org/odk/collect/settings/importing/SettingsImporterTest.kt index 24aef318005..3cbfec59dc9 100644 --- a/settings/src/test/java/org/odk/collect/settings/importing/SettingsImporterTest.kt +++ b/settings/src/test/java/org/odk/collect/settings/importing/SettingsImporterTest.kt @@ -66,9 +66,9 @@ class SettingsImporterTest { } @Test - fun whenJSONSettingsAreInvalid_returnsFalse() { + fun `when JSON settings are invalid returns 'INVALID_SETTINGS'`() { whenever(settingsValidator.isValid(emptySettings())).thenReturn(false) - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(false)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.INVALID_SETTINGS)) } @Test @@ -86,7 +86,7 @@ class SettingsImporterTest { JSONObject().put("key3", 5) ) - assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) assertThat(generalSettings.contains("key3"), `is`(false)) assertThat(adminSettings.contains("key3"), `is`(false)) @@ -120,9 +120,11 @@ class SettingsImporterTest { assertThat( importer.fromJSON( - json.toString(), currentProject, deviceUnsupportedSettings + json.toString(), + currentProject, + deviceUnsupportedSettings ), - `is`(true) + `is`(SettingsImportingResult.SUCCESS) ) assertThat(generalSettings.contains("key3"), `is`(true)) @@ -133,15 +135,18 @@ class SettingsImporterTest { @Test fun `for supported settings that do not exist in json save defaults`() { - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) assertSettings( generalSettings, - "key1", "default", - "key2", true + "key1", + "default", + "key2", + true ) assertSettings( adminSettings, - "key1", 5 + "key1", + 5 ) } @@ -160,15 +165,18 @@ class SettingsImporterTest { JSONObject().put("key1", 6) ) - assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) assertSettings( generalSettings, - "key1", "default", - "key2", true + "key1", + "default", + "key2", + true ) assertSettings( adminSettings, - "key1", 5 + "key1", + 5 ) } @@ -176,22 +184,28 @@ class SettingsImporterTest { fun whenKeysAlreadyExistInPrefs_overridesWithDefaults() { initSettings( generalSettings, - "key1", "existing", - "key2", false + "key1", + "existing", + "key2", + false ) initSettings( adminSettings, - "key1", 0 + "key1", + 0 ) - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) assertSettings( generalSettings, - "key1", "default", - "key2", true + "key1", + "default", + "key2", + true ) assertSettings( adminSettings, - "key1", 5 + "key1", + 5 ) } @@ -213,7 +227,7 @@ class SettingsImporterTest { projectsRepository, projectDetailsCreator ) - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) } @Test // Migrations might use old keys that are "unknown" to the app @@ -239,7 +253,7 @@ class SettingsImporterTest { projectsRepository, projectDetailsCreator ) - assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(json.toString(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) } @Test @@ -254,7 +268,7 @@ class SettingsImporterTest { projectsRepository, projectDetailsCreator ) - assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(true)) + assertThat(importer.fromJSON(emptySettings(), currentProject, JSONObject()), `is`(SettingsImportingResult.SUCCESS)) verify(settingsChangeHandler).onSettingsChanged("1") verifyNoMoreInteractions(settingsChangeHandler) } @@ -315,25 +329,14 @@ class SettingsImporterTest { } @Test - fun `when protocol is Google Drive and project name not set, project name falls back to Google account`() { - val generalJson = JSONObject() - .put(ProjectKeys.KEY_PROTOCOL, ProjectKeys.PROTOCOL_GOOGLE_SHEETS) - .put(ProjectKeys.KEY_SELECTED_GOOGLE_ACCOUNT, "foo@bar.baz") + fun `when protocol is Google Drive returns GD_PROJECT`() { + val generalJson = JSONObject().put(ProjectKeys.KEY_PROTOCOL, ProjectKeys.PROTOCOL_GOOGLE_SHEETS) + val settings = JSONObject() .put(AppConfigurationKeys.GENERAL, generalJson) .put(AppConfigurationKeys.ADMIN, JSONObject()) - whenever( - projectDetailsCreator.createProjectFromDetails( - any(), - any(), - any(), - any() - ) - ).thenReturn(Project.New("A", "B", "C")) - - importer.fromJSON(settings.toString(), currentProject, JSONObject()) - verify(projectDetailsCreator).createProjectFromDetails("", "", "", "foo@bar.baz") + assertThat(importer.fromJSON(settings.toString(), currentProject, JSONObject()), `is`(SettingsImportingResult.GD_PROJECT)) } private fun emptySettings(): String { diff --git a/shadows/build.gradle.kts b/shadows/build.gradle.kts index 4786f7670d6..5dd999b1337 100644 --- a/shadows/build.gradle.kts +++ b/shadows/build.gradle.kts @@ -31,10 +31,6 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = "1.8" - } - testOptions { unitTests { isIncludeAndroidResources = true diff --git a/shared/src/main/java/org/odk/collect/shared/strings/StringUtils.kt b/shared/src/main/java/org/odk/collect/shared/strings/StringUtils.kt index f80b87ee0c9..584b483050e 100644 --- a/shared/src/main/java/org/odk/collect/shared/strings/StringUtils.kt +++ b/shared/src/main/java/org/odk/collect/shared/strings/StringUtils.kt @@ -19,10 +19,14 @@ object StringUtils { @JvmStatic fun ellipsizeBeginning(text: String): String { - return if (text.length <= 100) text else "..." + text.substring( - text.length - 97, - text.length - ) + return if (text.length <= 100) { + text + } else { + "..." + text.substring( + text.length - 97, + text.length + ) + } } /** @@ -81,7 +85,9 @@ object StringUtils { } return if (end >= start) { text.subSequence(start, end + 1) - } else text + } else { + text + } } @JvmStatic @@ -91,6 +97,8 @@ object StringUtils { } return if (str.endsWith(remove)) { str.substring(0, str.length - remove.length) - } else str + } else { + str + } } } diff --git a/strings/src/main/res/values-cs/strings.xml b/strings/src/main/res/values-cs/strings.xml index 2f732390250..77290dde19a 100644 --- a/strings/src/main/res/values-cs/strings.xml +++ b/strings/src/main/res/values-cs/strings.xml @@ -200,7 +200,6 @@ Soubor SVG neexistuje! Udělat fotku Vyberte obrázek - Zvolte účet Vybraný soubor není platný obrázek Klepnutím na obrazovku udělejte snímek Přední fotoaparát není v tomto zařízení k dispozici @@ -938,10 +937,6 @@ Ručně zadejte podrobnosti projektu Po přidání projektu jej můžete nakonfigurovat v části Nastavení - - Můj projekt používá Google Drive. - - Konfigurovat Ještě nemáte žádný projekt? diff --git a/strings/src/main/res/values-de/strings.xml b/strings/src/main/res/values-de/strings.xml index 72d66d2c350..34a9ee681ed 100644 --- a/strings/src/main/res/values-de/strings.xml +++ b/strings/src/main/res/values-de/strings.xml @@ -203,7 +203,6 @@ SVG-Datei existiert nicht! Foto machen Bild auswählen - Konto auswählen Ausgewählte Datei ist kein gültiges Bild. Bildschirm berühren um ein Foto aufzunehmen Frontkamera bei diesem Gerät nicht verfügbar @@ -935,10 +934,6 @@ Projektdetails manuell eingeben Nachdem Sie Ihr Projekt hinzugefügt haben, können Sie es in den konfigurieren. - - Mein Projekt benutzt Google Drive. - - Konfigurieren Noch kein eigenes Projekt? diff --git a/strings/src/main/res/values-es/strings.xml b/strings/src/main/res/values-es/strings.xml index 954f156b9f8..e4ee91c071f 100644 --- a/strings/src/main/res/values-es/strings.xml +++ b/strings/src/main/res/values-es/strings.xml @@ -202,7 +202,6 @@ Archivo SVG no existe Tomar la Foto Escoja la Imagen - Elegir cuenta El archivo seleccionado no es una imagen válida Toca la pantalla para tomar una foto Este dispositivo no tiene Cámara frontal disponible @@ -938,10 +937,6 @@ Ingrese manualmente los detalles del proyecto Después de agregar su proyecto, puede configurarlo en Configuración - - Mi proyecto usa Google Drive. - - Configurar ¿Aún no tienes un proyecto? diff --git a/strings/src/main/res/values-fa/strings.xml b/strings/src/main/res/values-fa/strings.xml index be6cf959a6c..bd5cc7d4126 100644 --- a/strings/src/main/res/values-fa/strings.xml +++ b/strings/src/main/res/values-fa/strings.xml @@ -196,7 +196,6 @@ فایل SVG موجود نیست! گرفتن عکس انتخاب تصویر - حساب را انتخاب کنید فایل انتخاب شده تصویر قابل اعتبار ندارد برای گرفتن عکس روی صفحه ضربه بزنید این دستگاه دوربین جلو موجود نیست @@ -879,10 +878,6 @@ جزئیات پروژه را به صورت دستی وارد کنید پس از اینکه پروژه خود را اضافه کردید، می توانید آن را در تنظیمات تنظیم کنید - - پروژه من از Google Drive استفاده می کند. - - تنظیم کردن هنوز پروژه ای ندارید؟ diff --git a/strings/src/main/res/values-fi/strings.xml b/strings/src/main/res/values-fi/strings.xml index f019f6f6f8c..57cea9d7867 100644 --- a/strings/src/main/res/values-fi/strings.xml +++ b/strings/src/main/res/values-fi/strings.xml @@ -203,7 +203,6 @@ SVG-tiedosto puuttuu! Ota kuva Valitse kuva - Valitse tili Valittu tiedosto ei ole kelvollinen kuva Napsauta näyttöä ottaaksesi kuvan Etukamera ei ole käytettävissä tällä laitteella @@ -935,10 +934,6 @@ Syötä projektin yksityiskohdat manuaalisesti Lisättyäsi projektin voit konfiguroida sen Asetuksissa. - - Projektini käyttää Google Drive -palvelua. - - Asetukset Puuttuuko projekti vielä? diff --git a/strings/src/main/res/values-fr/strings.xml b/strings/src/main/res/values-fr/strings.xml index 3c00526c9fa..3c5047c509f 100644 --- a/strings/src/main/res/values-fr/strings.xml +++ b/strings/src/main/res/values-fr/strings.xml @@ -203,7 +203,6 @@ Le fichier SVG n\'existe pas ! Prendre une photo Choisir une Image - Choisir compte Le fichier choisi n\'est pas une image valide Toucher l\'écran pour prendre une photo Caméra frontale non disponible sur cet appareil @@ -938,10 +937,6 @@ Saisir les détails du projet Après avoir ajouté un projet, vous pouvez le configurer dans les Paramètres - - Mon projet utilise Google Drive. - - Configurer Pas encore de projet? diff --git a/strings/src/main/res/values-in/strings.xml b/strings/src/main/res/values-in/strings.xml index 4544d256fac..94f8bd19e20 100644 --- a/strings/src/main/res/values-in/strings.xml +++ b/strings/src/main/res/values-in/strings.xml @@ -194,7 +194,6 @@ Berkas SVG tidak ada. Ambil Foto Pilih Gambar - Pilih akun Berkas terpilih bukan gambar yang valid Sentuh layar untuk ambil gambar Kamera depan tidak tersedia dalam perangkat ini. @@ -892,10 +891,6 @@ Konfigurasi dengan QR kode Masukkan rincian proyek secara manual - - Proyek saya menggunakan Google Drive - - Konfigurasi Belum memliki proyek? diff --git a/strings/src/main/res/values-it/strings.xml b/strings/src/main/res/values-it/strings.xml index 285509f6bb4..948923739e9 100644 --- a/strings/src/main/res/values-it/strings.xml +++ b/strings/src/main/res/values-it/strings.xml @@ -202,7 +202,6 @@ Il file SVG non esiste! Scatta Foto Seleziona Foto - Scegli l\'account Il file selezionato non è una valida immagine Tocca lo schermo per scattare una foto Fotocamera frontale non disponibile in questo dispositivo @@ -937,10 +936,6 @@ Inserisci manualmente i dettagli del progetto Dopo aver aggiunto il progetto, puoi configurarlo in Impostazioni - - Il mio progetto utilizza Google Drive. - - Configurare Non hai ancora un Progetto? diff --git a/strings/src/main/res/values-pt/strings.xml b/strings/src/main/res/values-pt/strings.xml index 5607cccbf91..c34117f955d 100644 --- a/strings/src/main/res/values-pt/strings.xml +++ b/strings/src/main/res/values-pt/strings.xml @@ -194,7 +194,6 @@ O arquivo SVG não existe! Tirar Foto Escolher Imagem - Escolher conta O arquivo selecionado não é uma imagem válida Toque na tela para tirar uma fotografia A câmera da frente não está disponível neste dispositivo @@ -917,10 +916,6 @@ Entrar com os detalhes do projeto manualmente Após adicionar o seu projeto, você pode configurá-lo em Configurações - - Meu projeto usa o Google Drive - - Configurar Não tem ainda um projeto? diff --git a/strings/src/main/res/values-rw/strings.xml b/strings/src/main/res/values-rw/strings.xml index d54fe5257f9..b77af165255 100644 --- a/strings/src/main/res/values-rw/strings.xml +++ b/strings/src/main/res/values-rw/strings.xml @@ -195,7 +195,6 @@ N\'ukomea guhura n\'iki kibazo, wakigeza kuwa gusabye gukusanya amakuru.Ububiko bwa SVG ntabwo buboneka! Fata ifoto Hitamo Ishusho - Hitamo konti Ifoto wahisemo mububiko si ifoto yukuri Kora kuri screen ufate ifoto Ifoto y\'imbere ntabwo iboneka kuri iki gikoresho @@ -862,10 +861,6 @@ Birashoboka ko ari zimwe zashyizwemo mubihe bitandukanye cgw ziturutse ahatanduk Byihuze hifashishijwe kode ya QR Ukoresheje amaboko injiza amakuru kuri project - - Poroje yanjye ikoresha Google Drive - - Hindura Nta poroje ufitemo diff --git a/strings/src/main/res/values-sw/strings.xml b/strings/src/main/res/values-sw/strings.xml index 82f742e16fe..2ef27259b8c 100644 --- a/strings/src/main/res/values-sw/strings.xml +++ b/strings/src/main/res/values-sw/strings.xml @@ -194,7 +194,6 @@ Kabrasha la SVG halipo Chukua picha Chagua picha - Chagua akaunti Faili lillilochaguliwa sio picha sahihi Bofya kioo kuchukua picha Kamera ya mbele haipo katika kifaa hiki @@ -749,8 +748,6 @@ select the wording and where to put the \n for maximum impact in your language.--> - - Mradi wangu unatumia Google Drive. diff --git a/strings/src/main/res/values-tr/strings.xml b/strings/src/main/res/values-tr/strings.xml index 281fb3b8df7..cdb63417f6d 100644 --- a/strings/src/main/res/values-tr/strings.xml +++ b/strings/src/main/res/values-tr/strings.xml @@ -396,10 +396,6 @@ QR kod ile kurulum yap Proje detaylarını manuel ekle - - Projem Google Drive kullanıyor. - - Ayarla Hala projeniz yok mu? diff --git a/strings/src/main/res/values-zh/strings.xml b/strings/src/main/res/values-zh/strings.xml index f67c550ecf5..c03e6d579b3 100644 --- a/strings/src/main/res/values-zh/strings.xml +++ b/strings/src/main/res/values-zh/strings.xml @@ -198,7 +198,6 @@ SVG文件不存在! 照相 选择图片 - 选择帐户 所选文件不是有效图像 点击屏幕拍照 前置摄像头在此设备上不可用 @@ -925,10 +924,6 @@ 手动输入项目详细信息 添加项目后,可以在“设置”中对其进行配置 - - 我的项目使用Google Drive。 - - 配置 还没有项目? diff --git a/strings/src/main/res/values/strings.xml b/strings/src/main/res/values/strings.xml index 8bbfbc9f38b..3678db9e94e 100644 --- a/strings/src/main/res/values/strings.xml +++ b/strings/src/main/res/values/strings.xml @@ -256,7 +256,6 @@ Take Picture Choose Image - Choose account Selected file is not a valid image Tap the screen to take a picture Front camera not available on this device @@ -1051,6 +1050,7 @@ QR code does not contain valid settings QR Code not found in the selected image Current settings are corrupt. From project management settings, reset settings or import working ones. + Google Drive/Sheets projects can no longer be created Switch to %s All blank forms, submissions and settings will be permanently deleted. + This is a Google Drive/Sheets project. Once you delete it, you will not be able to create it again.\n\nAll blank forms, submissions and settings will be permanently deleted. Yes No Project can\'t be deleted @@ -1135,10 +1136,6 @@ Manually enter project details After you add your project, you can configure it in Settings - - My project uses Google Drive. - - Configure Don\'t have a project yet? diff --git a/upgrade/build.gradle b/upgrade/build.gradle index 7da09527243..48cc14f7efa 100644 --- a/upgrade/build.gradle +++ b/upgrade/build.gradle @@ -31,10 +31,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - testOptions { unitTests { includeAndroidResources = true