diff --git a/apps/build.gradle b/apps/build.gradle index a0b3f6aaa8..5d6de59820 100644 --- a/apps/build.gradle +++ b/apps/build.gradle @@ -34,6 +34,7 @@ buildscript { classpath Plugins.FIREBASE_CRASHLYTICS if (project.coverageEnabled) { classpath Plugins.JACOCO_ANDROID } classpath Plugins.HILT + classpath Plugins.KSP } } @@ -50,10 +51,40 @@ allprojects { username pspdfMavenUser password pspdfMavenPass } - url 'https://customers.pspdfkit.com/maven/' + url 'https://my.nutrient.io/maven' } maven { url "https://maven.google.com/" } } + + plugins.withType(com.android.build.gradle.BasePlugin) { + android { + packaging { + resources { + pickFirsts += [ + 'META-INF/INDEX.LIST', + 'META-INF/io.netty.versions.properties' + ] + merges += [ + 'META-INF/LICENSE*', + 'META-INF/NOTICE*', + 'META-INF/DEPENDENCIES*' + ] + excludes += [ + 'META-INF/DEPENDENCIES', + 'META-INF/LICENSE', + 'META-INF/LICENSE.txt', + 'META-INF/LICENSE.md', + 'META-INF/NOTICE', + 'META-INF/NOTICE.txt', + 'META-INF/NOTICE.md', + 'META-INF/maven/**', + 'META-INF/*.kotlin_module', + 'META-INF/services/javax.annotation.processing.Processor' + ] + } + } + } + } } task assembleAllApps() { @@ -72,4 +103,3 @@ configurations.all{ } } } - diff --git a/apps/buildSrc/src/main/java/GlobalDependencies.kt b/apps/buildSrc/src/main/java/GlobalDependencies.kt index a10251eb0d..d922ebaa3c 100644 --- a/apps/buildSrc/src/main/java/GlobalDependencies.kt +++ b/apps/buildSrc/src/main/java/GlobalDependencies.kt @@ -8,7 +8,7 @@ object Versions { /* Build/tooling */ const val ANDROID_GRADLE_TOOLS = "8.6.1" - const val BUILD_TOOLS = "34.0.0" + const val BUILD_TOOLS = "35.0.0" /* Testing */ const val JUNIT = "4.13.2" @@ -18,31 +18,31 @@ object Versions { /* Kotlin */ const val KOTLIN = "2.0.21" const val KOTLIN_COROUTINES = "1.9.0" + const val KSP = "2.0.21-1.0.27" /* Google, Play Services */ - const val GOOGLE_SERVICES = "4.4.2" + const val GOOGLE_SERVICES = "4.4.3" /* Others */ - const val APOLLO = "4.1.1" - const val PSPDFKIT = "2024.3.1" + const val APOLLO = "4.3.3" + const val NUTRIENT = "10.7.0" const val PHOTO_VIEW = "2.3.0" const val MOBIUS = "1.2.1" - const val HILT = "2.52" - const val HILT_ANDROIDX = "1.2.0" - const val LIFECYCLE = "2.8.6" - const val FRAGMENT = "1.8.4" - const val WORK_MANAGER = "2.9.1" - const val WORK_TEST = "2.9.1" - const val GLIDE_VERSION = "4.16.0" + const val HILT = "2.57.2" + const val HILT_ANDROIDX = "1.3.0" + const val LIFECYCLE = "2.9.4" + const val FRAGMENT = "1.8.9" + const val WORK_MANAGER = "2.10.5" + const val GLIDE_VERSION = "5.0.5" const val RETROFIT = "2.11.0" const val OKHTTP = "4.12.0" - const val ROOM = "2.6.1" - const val HAMCREST = "2.2" - const val NAVIGATION = "2.8.3" - const val MEDIA3 = "1.6.1" - const val DATASTORE = "1.1.1" - const val LOTTIE = "6.5.2" - const val ENCRYPTED_SHARED_PREFERENCES = "1.0.0" + const val ROOM = "2.7.0" + const val HAMCREST = "3.0" + const val NAVIGATION = "2.9.5" + const val MEDIA3 = "1.8.0" + const val DATASTORE = "1.1.7" + const val LOTTIE = "6.6.6" + const val ENCRYPTED_SHARED_PREFERENCES = "1.1.0" const val JAVA_JWT = "4.5.0" const val GLANCE = "1.1.1" const val LIVEDATA = "1.9.0" @@ -65,28 +65,27 @@ object Libs { const val ANDROIDX_APPCOMPAT = "androidx.appcompat:appcompat:1.7.0" const val ANDROIDX_BROWSER = "androidx.browser:browser:1.8.0" const val ANDROIDX_CARDVIEW = "androidx.cardview:cardview:1.0.0" - const val ANDROIDX_CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:2.1.4" + const val ANDROIDX_CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:2.2.0" const val ANDROIDX_EXIF = "androidx.exifinterface:exifinterface:1.3.7" const val ANDROIDX_FRAGMENT = "androidx.fragment:fragment:${Versions.FRAGMENT}" const val ANDROIDX_FRAGMENT_KTX = "androidx.fragment:fragment-ktx:${Versions.FRAGMENT}" const val ANDROIDX_PALETTE = "androidx.palette:palette:1.0.0" const val ANDROIDX_PERCENT = "androidx.percentlayout:percentlayout:1.0.0" - const val ANDROIDX_RECYCLERVIEW = "androidx.recyclerview:recyclerview:1.3.2" + const val ANDROIDX_RECYCLERVIEW = "androidx.recyclerview:recyclerview:1.4.0" const val ANDROIDX_VECTOR = "androidx.vectordrawable:vectordrawable:1.2.0" const val ANDROIDX_SWIPE_REFRESH_LAYOUT = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" const val ANDROIDX_CORE_TESTING = "androidx.arch.core:core-testing:2.2.0" const val ANDROIDX_WORK_MANAGER = "androidx.work:work-runtime:${Versions.WORK_MANAGER}" const val ANDROIDX_WORK_MANAGER_KTX = "androidx.work:work-runtime-ktx:${Versions.WORK_MANAGER}" - const val ANDROIDX_WORK_TEST = "androidx.work:work-testing:${Versions.WORK_TEST}" - const val ANDROIDX_WEBKIT = "androidx.webkit:webkit:1.9.0" - const val ANDROIDX_DATABINDING_COMPILER = "androidx.databinding:databinding-compiler:${Versions.ANDROID_GRADLE_TOOLS}" // This is bundled with the gradle plugin so we use the same version - const val ANDROIDX_COMPOSE_ACTIVITY = "androidx.activity:activity-compose:1.9.0" + const val ANDROIDX_WORK_TEST = "androidx.work:work-testing:${Versions.WORK_MANAGER}" + const val ANDROIDX_WEBKIT = "androidx.webkit:webkit:1.12.0" + const val ANDROIDX_COMPOSE_ACTIVITY = "androidx.activity:activity-compose:1.10.0" const val DATASTORE = "androidx.datastore:datastore-preferences:${Versions.DATASTORE}" const val ENCRYPTED_SHARED_PREFERENCES = "androidx.security:security-crypto:${Versions.ENCRYPTED_SHARED_PREFERENCES}" const val JAVA_JWT = "com.auth0:java-jwt:${Versions.JAVA_JWT}" /* Firebase */ - const val FIREBASE_BOM = "com.google.firebase:firebase-bom:33.4.0" + const val FIREBASE_BOM = "com.google.firebase:firebase-bom:34.3.0" const val FIREBASE_CRASHLYTICS = "com.google.firebase:firebase-crashlytics" const val FIREBASE_MESSAGING = "com.google.firebase:firebase-messaging" const val FIREBASE_CONFIG = "com.google.firebase:firebase-config" @@ -95,7 +94,7 @@ object Libs { /* Google Dependencies */ const val PLAY_IN_APP_UPDATES = "com.google.android.play:app-update:2.1.0" const val FLEXBOX_LAYOUT = "com.google.android.flexbox:flexbox:3.0.0" - const val MATERIAL_DESIGN = "com.google.android.material:material:1.12.0" + const val MATERIAL_DESIGN = "com.google.android.material:material:1.13.0" /* Mobius */ const val MOBIUS_CORE = "com.spotify.mobius:mobius-core:${Versions.MOBIUS}" @@ -133,7 +132,7 @@ object Libs { const val COMPOSE_VIEW_MODEL = "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.LIFECYCLE}" const val COMPOSE_NAVIGATION = "androidx.navigation:navigation-compose:2.8.9" /* Media and content handling */ - const val PSPDFKIT = "com.pspdfkit:pspdfkit:${Versions.PSPDFKIT}" + const val NUTRIENT = "io.nutrient:nutrient:${Versions.NUTRIENT}" const val MEDIA3 = "androidx.media3:media3-exoplayer:${Versions.MEDIA3}" const val MEDIA3_UI = "androidx.media3:media3-ui:${Versions.MEDIA3}" const val MEDIA3_HLS = "androidx.media3:media3-exoplayer-hls:${Versions.MEDIA3}" @@ -171,7 +170,7 @@ object Libs { const val APACHE_COMMONS_TEXT = "org.apache.commons:commons-text:1.12.0" const val CAMERA_VIEW = "com.otaliastudios:cameraview:2.7.2" - const val PENDO = "sdk.pendo.io:pendoIO:3.6+" + const val PENDO = "sdk.pendo.io:pendoIO:3.7.+" const val ROOM = "androidx.room:room-runtime:${Versions.ROOM}" const val ROOM_COMPILER = "androidx.room:room-compiler:${Versions.ROOM}" @@ -183,7 +182,7 @@ object Libs { const val RRULE = "org.scala-saddle:google-rfc-2445:20110304" // Compose - const val COMPOSE_BOM = "androidx.compose:compose-bom:2024.09.02" + const val COMPOSE_BOM = "androidx.compose:compose-bom:2025.09.01" const val COMPOSE_MATERIAL = "androidx.compose.material:material" const val COMPOSE_MATERIAL_ICONS = "androidx.compose.material:material-icons-core" const val COMPOSE_PREVIEW = "androidx.compose.ui:ui-tooling-preview" @@ -191,11 +190,11 @@ object Libs { const val COMPOSE_UI = "androidx.compose.ui:ui-android" const val COMPOSE_UI_TEST = "androidx.compose.ui:ui-test-junit4" const val COMPOSE_UI_TEST_MANIFEST = "androidx.compose.ui:ui-test-manifest" - const val COMPOSE_MATERIAL_3 = "androidx.compose.material3:material3:1.4.0-alpha12" + const val COMPOSE_MATERIAL_3 = "androidx.compose.material3:material3:1.4.0" const val COMPOSE_ADAPTIVE = "androidx.compose.material3.adaptive:adaptive" const val COMPOSE_MATERIAL3_WINDOW_SIZE = "androidx.compose.material3:material3-window-size-class" - const val COMPOSE_NAVIGATION_HILT = "androidx.hilt:hilt-navigation-compose:1.2.0" - const val COMPOSE_FRAGMENT = "androidx.fragment:fragment-compose:1.8.6" + const val COMPOSE_NAVIGATION_HILT = "androidx.hilt:hilt-navigation-compose:1.3.0" + const val COMPOSE_FRAGMENT = "androidx.fragment:fragment-compose:1.8.9" // Glance const val GLANCE = "androidx.glance:glance:${Versions.GLANCE}" @@ -220,4 +219,5 @@ object Plugins { const val GOOGLE_SERVICES = "com.google.gms:google-services:${Versions.GOOGLE_SERVICES}" const val JACOCO_ANDROID = "com.dicedmelon.gradle:jacoco-android:${Versions.JACOCO_ANDROID}" const val HILT = "com.google.dagger:hilt-android-gradle-plugin:${Versions.HILT}" + const val KSP = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:${Versions.KSP}" } diff --git a/apps/gradle.properties b/apps/gradle.properties index 61364d13a3..01ce297dfc 100644 --- a/apps/gradle.properties +++ b/apps/gradle.properties @@ -3,4 +3,4 @@ android.enableJetifier=true android.nonFinalResIds=false android.nonTransitiveRClass=false android.useAndroidX=true -org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 \ No newline at end of file +org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 \ No newline at end of file diff --git a/apps/parent/build.gradle b/apps/parent/build.gradle index d8c7168f16..1736c5549c 100644 --- a/apps/parent/build.gradle +++ b/apps/parent/build.gradle @@ -19,7 +19,8 @@ plugins { id 'com.android.application' id 'com.google.gms.google-services' id 'org.jetbrains.kotlin.android' - id 'kotlin-kapt' + id 'kotlin-kapt' // Keep kapt for Data Binding + id 'com.google.devtools.ksp' id 'com.google.firebase.crashlytics' id 'dagger.hilt.android.plugin' id 'org.jetbrains.kotlin.plugin.compose' @@ -51,15 +52,23 @@ android { PrivateData.merge(project, "parent") } - packagingOptions { - exclude 'META-INF/maven/com.google.guava/guava/pom.xml' - exclude 'META-INF/maven/com.google.guava/guava/pom.properties' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/NOTICE' - exclude 'META-INF/rxjava.properties' - exclude 'LICENSE.txt' + packaging { + resources { + pickFirsts += [ + 'META-INF/INDEX.LIST', + 'META-INF/io.netty.versions.properties' + ] + excludes += [ + 'META-INF/DEPENDENCIES', + 'META-INF/LICENSE', + 'META-INF/LICENSE.txt', + 'META-INF/NOTICE', + 'META-INF/NOTICE.txt', + 'META-INF/maven/com.google.guava/guava/pom.properties', + 'META-INF/maven/com.google.guava/guava/pom.xml', + 'META-INF/rxjava.properties' + ] + } } @@ -194,14 +203,14 @@ dependencies { /* DI */ implementation Libs.HILT - kapt Libs.HILT_COMPILER + ksp Libs.HILT_COMPILER implementation Libs.HILT_ANDROIDX_WORK - kapt Libs.HILT_ANDROIDX_COMPILER + ksp Libs.HILT_ANDROIDX_COMPILER androidTestImplementation Libs.HILT_TESTING /* ROOM */ implementation Libs.ROOM - kapt Libs.ROOM_COMPILER + ksp Libs.ROOM_COMPILER implementation Libs.ROOM_COROUTINES /* Navigation */ diff --git a/apps/parent/proguard-rules.txt b/apps/parent/proguard-rules.txt index bde42b7512..ab46a57d84 100644 --- a/apps/parent/proguard-rules.txt +++ b/apps/parent/proguard-rules.txt @@ -254,4 +254,19 @@ -dontwarn java.beans.SimpleBeanInfo -keep class androidx.navigation.** { *; } - -keep interface androidx.navigation.** { *; } \ No newline at end of file + -keep interface androidx.navigation.** { *; } + +# Netty and BlockHound integration +-dontwarn reactor.blockhound.integration.BlockHoundIntegration +-dontwarn io.netty.util.internal.Hidden$NettyBlockHoundIntegration +-keep class reactor.blockhound.integration.** { *; } +-keep class io.netty.util.internal.Hidden$NettyBlockHoundIntegration { *; } + +# Additional Netty keep rules for R8 +-dontwarn io.netty.** +-keep class io.netty.** { *; } +-keepclassmembers class io.netty.** { *; } + +# BlockHound related classes +-dontwarn reactor.blockhound.** +-keep class reactor.blockhound.** { *; } diff --git a/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/espresso/TestAppManager.kt b/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/espresso/TestAppManager.kt index 5677669b65..7f637c07c3 100644 --- a/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/espresso/TestAppManager.kt +++ b/apps/parent/src/androidTest/java/com/instructure/parentapp/ui/espresso/TestAppManager.kt @@ -17,6 +17,7 @@ package com.instructure.parentapp.ui.espresso +import androidx.work.DefaultWorkerFactory import androidx.work.WorkerFactory import com.instructure.pandautils.features.reminder.AlarmScheduler import com.instructure.parentapp.util.BaseAppManager @@ -26,7 +27,7 @@ open class TestAppManager : BaseAppManager() { private var workerFactory: WorkerFactory? = null override fun getWorkManagerFactory(): WorkerFactory { - return workerFactory ?: WorkerFactory.getDefaultWorkerFactory() + return workerFactory ?: DefaultWorkerFactory } override fun getScheduler(): AlarmScheduler? { diff --git a/apps/settings.gradle b/apps/settings.gradle index 6be9862e0f..99bd1d48a7 100644 --- a/apps/settings.gradle +++ b/apps/settings.gradle @@ -7,7 +7,7 @@ pluginManagement { } } dependencies { - classpath("com.android.tools:r8:8.2.47") + classpath("com.android.tools:r8:8.13.6") } } } @@ -29,7 +29,6 @@ include ':pandautils' include ':rceditor' include ':recyclerview' include ':pandares' -include ':DocumentScanner' include ':horizon' project(':annotations').projectDir = new File(rootProject.projectDir, '/../libs/annotations') @@ -43,5 +42,4 @@ project(':pandautils').projectDir = new File(rootProject.projectDir, '/../libs/p project(':rceditor').projectDir = new File(rootProject.projectDir, '/../libs/rceditor') project(':recyclerview').projectDir = new File(rootProject.projectDir, '/../libs/recyclerview') project(':pandares').projectDir = new File(rootProject.projectDir, '/../libs/pandares') -project(':DocumentScanner').projectDir = new File(rootProject.projectDir, '/../libs/DocumentScanner') project(':horizon').projectDir = new File(rootProject.projectDir, '/../libs/horizon') diff --git a/apps/student/build.gradle b/apps/student/build.gradle index 9c288dbd49..0f87999743 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -16,7 +16,8 @@ */ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-kapt' // Keep kapt for Data Binding +apply plugin: 'com.google.devtools.ksp' apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'org.jetbrains.kotlin.plugin.compose' @@ -61,15 +62,23 @@ android { } } - packagingOptions { - exclude 'META-INF/maven/com.google.guava/guava/pom.xml' - exclude 'META-INF/maven/com.google.guava/guava/pom.properties' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/NOTICE' - exclude 'META-INF/rxjava.properties' - exclude 'LICENSE.txt' + packaging { + resources { + pickFirsts += [ + 'META-INF/INDEX.LIST', + 'META-INF/io.netty.versions.properties' + ] + excludes += [ + 'META-INF/DEPENDENCIES', + 'META-INF/LICENSE', + 'META-INF/LICENSE.txt', + 'META-INF/NOTICE', + 'META-INF/NOTICE.txt', + 'META-INF/maven/com.google.guava/guava/pom.properties', + 'META-INF/maven/com.google.guava/guava/pom.xml', + 'META-INF/rxjava.properties' + ] + } } lintOptions { @@ -227,7 +236,6 @@ dependencies { implementation project(path: ':annotations') implementation project(path: ':rceditor') implementation project(path: ':interactions') - implementation project(path: ':DocumentScanner') implementation project(path: ':horizon') /* Android Test Dependencies */ @@ -294,15 +302,15 @@ dependencies { implementation Libs.LIVE_DATA implementation Libs.VIEW_MODE_SAVED_STATE implementation Libs.ANDROIDX_FRAGMENT_KTX - kapt Libs.LIFECYCLE_COMPILER + kapt Libs.LIFECYCLE_COMPILER // Keep kapt for lifecycle if it includes Data Binding /* DI */ implementation Libs.HILT - kapt Libs.HILT_COMPILER + ksp Libs.HILT_COMPILER androidTestImplementation Libs.HILT_TESTING - kaptAndroidTestQa Libs.HILT_TESTING_COMPILER + kspAndroidTest Libs.HILT_TESTING_COMPILER implementation Libs.HILT_ANDROIDX_WORK - kapt Libs.HILT_ANDROIDX_COMPILER + ksp Libs.HILT_ANDROIDX_COMPILER androidTestImplementation Libs.UI_AUTOMATOR @@ -314,7 +322,7 @@ dependencies { /* ROOM */ implementation Libs.ROOM - kapt Libs.ROOM_COMPILER + ksp Libs.ROOM_COMPILER implementation Libs.ROOM_COROUTINES testImplementation Libs.HAMCREST diff --git a/apps/student/proguard-rules.txt b/apps/student/proguard-rules.txt index aec6327015..90370642f2 100644 --- a/apps/student/proguard-rules.txt +++ b/apps/student/proguard-rules.txt @@ -269,3 +269,18 @@ -keep class androidx.navigation.** { *; } -keep interface androidx.navigation.** { *; } + +# Netty and BlockHound integration +-dontwarn reactor.blockhound.integration.BlockHoundIntegration +-dontwarn io.netty.util.internal.Hidden$NettyBlockHoundIntegration +-keep class reactor.blockhound.integration.** { *; } +-keep class io.netty.util.internal.Hidden$NettyBlockHoundIntegration { *; } + +# Additional Netty keep rules for R8 +-dontwarn io.netty.** +-keep class io.netty.** { *; } +-keepclassmembers class io.netty.** { *; } + +# BlockHound related classes +-dontwarn reactor.blockhound.** +-keep class reactor.blockhound.** { *; } diff --git a/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt b/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt index fc00cc637e..3b21a05d1d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/espresso/TestAppManager.kt @@ -16,6 +16,7 @@ */ package com.instructure.student.espresso +import androidx.work.DefaultWorkerFactory import androidx.work.WorkerFactory import com.instructure.student.util.BaseAppManager @@ -24,6 +25,6 @@ open class TestAppManager : BaseAppManager() { var workerFactory: WorkerFactory? = null override fun getWorkManagerFactory(): WorkerFactory { - return workerFactory ?: WorkerFactory.getDefaultWorkerFactory() + return workerFactory ?: DefaultWorkerFactory } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/classic/GradesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/classic/GradesE2ETest.kt index 2669a7eb0e..8114133b74 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/classic/GradesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/classic/GradesE2ETest.kt @@ -81,7 +81,8 @@ class GradesE2ETest: StudentComposeTest() { courseGradesPage.assertItemDisplayed(quizMatcher) courseGradesPage.assertGradeNotDisplayed(quizMatcher) - val dueDateInCanvasFormat = getDateInCanvasCalendarFormat(1.days.fromNow.iso8601) + var dueDateInCanvasFormat = getDateInCanvasCalendarFormat(1.days.fromNow.iso8601) + dueDateInCanvasFormat = dueDateInCanvasFormat.replace(" 0", " ") Log.d(ASSERTION_TAG, "Assert that the '${assignment.name}' assignment's due date is tomorrow ('$dueDateInCanvasFormat').") courseGradesPage.assertAssignmentDueDate(assignment.name, dueDateInCanvasFormat) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt index 86ac820578..0620ff5dfa 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/PickerSubmissionUploadInteractionTest.kt @@ -190,37 +190,6 @@ class PickerSubmissionUploadInteractionTest : StudentComposeTest() { pickerSubmissionUploadPage.assertFileDisplayed(mockedFileName) } - @Test - @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) - fun testFab_scanner(){ - val scannerComponent = "com.instructure.student.features.documentscanning.DocumentScanningActivity" - - goToSubmissionPicker() - - Intents.init() - try { - val context = getInstrumentation().targetContext - val dir = context.externalCacheDir - val sampleFile = File(dir, mockedFileName) - val uri = Uri.fromFile(sampleFile) - val resultData = Intent().apply { data = uri } - val scannerResult = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData) - - intending( - IntentMatchers.hasComponent(scannerComponent) - ).respondWith(scannerResult) - - pickerSubmissionUploadPage.chooseScanner() - } finally { - release() - } - - pickerSubmissionUploadPage.waitForSubmitButtonToAppear() - - pickerSubmissionUploadPage.assertFileDisplayed(mockedFileName) - - } - @Test @TestMetaData(Priority.COMMON, FeatureCategory.SUBMISSIONS, TestCategory.INTERACTION) fun testDeleteFile() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/classic/PickerSubmissionUploadPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/classic/PickerSubmissionUploadPage.kt index 97d3136adf..3c96301b25 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/classic/PickerSubmissionUploadPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/classic/PickerSubmissionUploadPage.kt @@ -33,7 +33,6 @@ class PickerSubmissionUploadPage : BasePage(R.id.pickerSubmissionUploadPage) { private val deviceIcon by OnViewWithId(R.id.sourceDeviceIcon) private val cameraIcon by OnViewWithId(R.id.sourceCameraIcon) private val galleryIcon by OnViewWithId(R.id.sourceGalleryIcon) - private val scannerIcon by OnViewWithId(R.id.sourceDocumentScanningIcon) private val deleteButton by OnViewWithId(R.id.deleteButton) fun chooseDevice() { @@ -48,10 +47,6 @@ class PickerSubmissionUploadPage : BasePage(R.id.pickerSubmissionUploadPage) { galleryIcon.click() } - fun chooseScanner() { - scannerIcon.click() - } - fun waitForSubmitButtonToAppear() { waitForViewWithText(R.string.submit) } diff --git a/apps/student/src/main/AndroidManifest.xml b/apps/student/src/main/AndroidManifest.xml index 6cc7ffea16..af986c7a9d 100644 --- a/apps/student/src/main/AndroidManifest.xml +++ b/apps/student/src/main/AndroidManifest.xml @@ -286,12 +286,6 @@ - - diff --git a/apps/student/src/main/java/com/instructure/student/features/documentscanning/BitmapExtensions.kt b/apps/student/src/main/java/com/instructure/student/features/documentscanning/BitmapExtensions.kt deleted file mode 100644 index a4a8cd2a98..0000000000 --- a/apps/student/src/main/java/com/instructure/student/features/documentscanning/BitmapExtensions.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.instructure.student.features.documentscanning - -import android.graphics.* - -fun Bitmap.toGrayscale(): Bitmap { - val width = this.width - val height = this.height - - val grayscaleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(grayscaleBitmap) - val paint = Paint() - val colorMatrix = ColorMatrix() - colorMatrix.setSaturation(0f) - val colorMatrixFilter = ColorMatrixColorFilter(colorMatrix) - paint.colorFilter = colorMatrixFilter - canvas.drawBitmap(this, 0f, 0f, paint) - return grayscaleBitmap -} - -fun Bitmap.toMonochrome(): Bitmap { - val width = this.width - val height = this.height - - val monochromeBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - - val hsv = FloatArray(3) - for (column in 0 until width) { - for (row in 0 until height) { - Color.colorToHSV(this.getPixel(column, row), hsv) - if (hsv[2] > 0.5f) { - monochromeBitmap.setPixel(column, row, Color.WHITE) - } else { - monochromeBitmap.setPixel(column, row, Color.BLACK) - } - } - } - - return monochromeBitmap -} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningActivity.kt b/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningActivity.kt deleted file mode 100644 index 73badd284b..0000000000 --- a/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningActivity.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2022 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.instructure.student.features.documentscanning - -import android.app.Activity -import android.content.Intent -import android.graphics.Bitmap -import android.os.Bundle -import androidx.activity.viewModels -import androidx.core.content.ContextCompat -import androidx.core.net.toUri -import androidx.databinding.DataBindingUtil -import com.instructure.pandautils.binding.viewBinding -import com.instructure.pandautils.utils.ViewStyler -import com.instructure.student.R -import com.instructure.student.databinding.ActivityDocumentScanningBinding -import com.zynksoftware.documentscanner.ScanActivity -import com.zynksoftware.documentscanner.model.DocumentScannerErrorModel -import com.zynksoftware.documentscanner.model.ScannerResults -import dagger.hilt.android.AndroidEntryPoint -import java.io.File -import java.io.FileOutputStream -import java.text.SimpleDateFormat -import java.util.* - -@AndroidEntryPoint -class DocumentScanningActivity : ScanActivity() { - - private lateinit var binding: ActivityDocumentScanningBinding - - private val viewModel: DocumentScanningViewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = DataBindingUtil.setContentView(this, R.layout.activity_document_scanning) - binding.lifecycleOwner = this - binding.viewModel = viewModel - - addFragmentContentLayout() - - setupToolbar() - - viewModel.events.observe(this) { event -> - event.getContentIfNotHandled()?.let { - handleAction(it) - } - } - } - - private fun handleAction(action: DocumentScanningAction) { - when (action) { - is DocumentScanningAction.SaveBitmapAction -> { - val file = File(filesDir, "scanned_${SimpleDateFormat("yyyyMMddkkmmss", Locale.getDefault()).format(Date())}.jpg") - var fileOutputStream: FileOutputStream? = null - try { - fileOutputStream = FileOutputStream(file.absolutePath) - action.bitmap.compress(Bitmap.CompressFormat.JPEG, action.quality, fileOutputStream) - val intent = Intent() - intent.data = file.toUri() - setResult(Activity.RESULT_OK, intent) - finish() - } finally { - fileOutputStream?.run { - flush() - close() - } - } - } - } - } - - override fun onClose() { - setResult(RESULT_CANCELED) - finish() - } - - override fun onError(error: DocumentScannerErrorModel) { - - } - - override fun onSuccess(scannerResults: ScannerResults) { - viewModel.setScannerResults(scannerResults) - } - - private fun setupToolbar() { - binding.toolbar.apply { - setTitle(R.string.documentScanningTitle) - navigationIcon = ContextCompat.getDrawable(this@DocumentScanningActivity, R.drawable.ic_back_arrow) - navigationIcon?.isAutoMirrored = true - ViewStyler.themeToolbarLight(this@DocumentScanningActivity, this) - setNavigationContentDescription(R.string.close) - setNavigationOnClickListener { onClose() } - } - } -} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningViewData.kt b/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningViewData.kt deleted file mode 100644 index 7db75cde27..0000000000 --- a/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningViewData.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.instructure.student.features.documentscanning - -import android.graphics.Bitmap -import androidx.databinding.BaseObservable -import androidx.databinding.Bindable -import com.instructure.student.features.documentscanning.itemviewmodels.FilterItemViewModel - -data class DocumentScanningViewData( - @get:Bindable var selectedBitmap: Bitmap, - val filterItemViewModels: List -) : BaseObservable() - -data class FilterItemViewData( - val bitmap: Bitmap, - val name: String -) - -sealed class DocumentScanningAction { - data class SaveBitmapAction(val bitmap: Bitmap, val quality: Int): DocumentScanningAction() -} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningViewModel.kt b/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningViewModel.kt deleted file mode 100644 index 7a99039541..0000000000 --- a/apps/student/src/main/java/com/instructure/student/features/documentscanning/DocumentScanningViewModel.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2022 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.instructure.student.features.documentscanning - -import android.content.res.Resources -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.instructure.pandautils.R -import com.instructure.pandautils.BR -import com.instructure.student.features.documentscanning.itemviewmodels.FilterItemViewModel -import com.instructure.pandautils.mvvm.Event -import com.instructure.pandautils.mvvm.ViewState -import com.zynksoftware.documentscanner.model.ScannerResults -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class DocumentScanningViewModel @Inject constructor( - private val resources: Resources -) : ViewModel() { - - val state: LiveData - get() = _state - private val _state = MutableLiveData() - - val data: LiveData - get() = _data - private val _data = MutableLiveData() - - val events: LiveData> - get() = _events - private val _events = MutableLiveData>() - - private lateinit var selectedItem: FilterItemViewModel - - fun setScannerResults(results: ScannerResults) { - _state.postValue(ViewState.Loading) - createViewData(results) - } - - private fun createViewData(results: ScannerResults) { - if (results.croppedImageFile != null && results.originalImageFile != null) { - val croppedBitmap = BitmapFactory.decodeFile(results.croppedImageFile!!.path) - val originalBitmap = BitmapFactory.decodeFile(results.originalImageFile!!.path) - val grayscaleBitmap = croppedBitmap.toGrayscale() - val monochromeBitmap = croppedBitmap.toMonochrome() - - //We no longer need these files - results.croppedImageFile?.delete() - results.croppedImageFile?.delete() - results.transformedImageFile?.delete() - - val filters = listOf( - createFilterViewModel(croppedBitmap, true, resources.getString(R.string.filter_name_color)), - createFilterViewModel(grayscaleBitmap, false, resources.getString(R.string.filter_name_grayscale)), - createFilterViewModel(monochromeBitmap, false, resources.getString(R.string.filter_name_monochrome)), - createFilterViewModel(originalBitmap, false, resources.getString(R.string.filter_name_original)) - ) - selectedItem = filters[0] - - val viewData = DocumentScanningViewData( - croppedBitmap, - filters - ) - _data.postValue(viewData) - _state.postValue(ViewState.Success) - } else { - _state.postValue(ViewState.Error()) - } - } - - private fun createFilterViewModel(bitmap: Bitmap, selected: Boolean, name: String): FilterItemViewModel { - return FilterItemViewModel( - FilterItemViewData(bitmap, name), - selected, - this::onFilterSelected - ) - } - - fun onFilterSelected(itemViewModel: FilterItemViewModel) { - selectedItem.apply { - selected = false - notifyPropertyChanged(BR.selected) - } - _data.value?.apply { - selectedBitmap = itemViewModel.data.bitmap - notifyPropertyChanged(BR.selectedBitmap) - } - selectedItem = itemViewModel - } - - fun onSaveClicked() { - _events.postValue(Event(DocumentScanningAction.SaveBitmapAction(selectedItem.data.bitmap, 100))) - } -} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/documentscanning/itemviewmodels/FilterItemViewModel.kt b/apps/student/src/main/java/com/instructure/student/features/documentscanning/itemviewmodels/FilterItemViewModel.kt deleted file mode 100644 index c84895a93e..0000000000 --- a/apps/student/src/main/java/com/instructure/student/features/documentscanning/itemviewmodels/FilterItemViewModel.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2022 - present Instructure, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.instructure.student.features.documentscanning.itemviewmodels - -import androidx.databinding.BaseObservable -import androidx.databinding.Bindable -import com.instructure.pandautils.BR -import com.instructure.student.features.documentscanning.FilterItemViewData -import com.instructure.pandautils.mvvm.ItemViewModel -import com.instructure.student.R - -class FilterItemViewModel( - val data: FilterItemViewData, - @get:Bindable var selected: Boolean, - val onSelect: (FilterItemViewModel) -> Unit -) : ItemViewModel, BaseObservable() { - override val layoutId: Int = R.layout.item_document_scanning_filter - - fun select() { - if (!selected) { - selected = true - notifyPropertyChanged(BR.selected) - onSelect(this) - } - } -} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/files/list/FileListFragment.kt b/apps/student/src/main/java/com/instructure/student/features/files/list/FileListFragment.kt index df6e22f475..c6fd5a2a59 100644 --- a/apps/student/src/main/java/com/instructure/student/features/files/list/FileListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/files/list/FileListFragment.kt @@ -551,8 +551,9 @@ class FileListFragment : ParentFragment(), Bookmarkable, FileUploadDialogParent } } - override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { + override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { workInfoLiveData.observe(viewLifecycleOwner) { + if (it == null) return@observe if (it.state == WorkInfo.State.SUCCEEDED) { updateFileList(true) folder?.let { fileFolder -> diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/annnotation/AnnotationSubmissionViewModel.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/annnotation/AnnotationSubmissionViewModel.kt index 57cb35e018..9d09c607ff 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/annnotation/AnnotationSubmissionViewModel.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/annnotation/AnnotationSubmissionViewModel.kt @@ -59,6 +59,7 @@ class AnnotationSubmissionViewModel @Inject constructor( ViewState.Error(resources.getString(R.string.failedToLoadSubmission)) } } catch (e: Exception) { + e.printStackTrace() _state.value = ViewState.Error(resources.getString(R.string.failedToLoadSubmission)) } } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadEffectHandler.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadEffectHandler.kt index f99647fdac..726b08dbce 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadEffectHandler.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadEffectHandler.kt @@ -18,7 +18,6 @@ package com.instructure.student.mobius.assignmentDetails.submission.picker import android.app.Activity import android.content.Context -import android.content.Intent import android.net.Uri import androidx.core.content.FileProvider import com.instructure.canvasapi2.models.postmodels.FileSubmitObject @@ -32,7 +31,6 @@ import com.instructure.pandautils.utils.getFragmentActivity import com.instructure.pandautils.utils.remove import com.instructure.pandautils.utils.requestPermissions import com.instructure.student.R -import com.instructure.student.features.documentscanning.DocumentScanningActivity import com.instructure.student.mobius.assignmentDetails.isIntentAvailable import com.instructure.student.mobius.assignmentDetails.submission.picker.PickerSubmissionMode.CommentAttachment import com.instructure.student.mobius.assignmentDetails.submission.picker.PickerSubmissionMode.FileSubmission @@ -117,9 +115,6 @@ class PickerSubmissionUploadEffectHandler( PickerSubmissionUploadEffect.LaunchSelectFile -> { launchSelectFile() } - PickerSubmissionUploadEffect.LaunchDocumentScanning -> { - launchDocumentScanning() - } is PickerSubmissionUploadEffect.LoadFileContents -> { loadFile(effect.allowedExtensions, effect.uri, context) } @@ -210,17 +205,6 @@ class PickerSubmissionUploadEffectHandler( } } - private fun launchDocumentScanning() { - // Get camera permission if we need it - if (needsPermissions( - PickerSubmissionUploadEvent.DocumentScanningClicked, - PermissionUtils.CAMERA - ) - ) return - val intent = Intent(context, DocumentScanningActivity::class.java) - (context.getFragmentActivity()).startActivityForResult(intent, REQUEST_DOCUMENT_SCANNING) - } - private fun launchCamera() { // Get camera permission if we need it if (needsPermissions( diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadModels.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadModels.kt index 0a2ca09bfa..20c7544991 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadModels.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadModels.kt @@ -25,7 +25,6 @@ sealed class PickerSubmissionUploadEvent { object CameraClicked : PickerSubmissionUploadEvent() object GalleryClicked : PickerSubmissionUploadEvent() object SelectFileClicked : PickerSubmissionUploadEvent() - object DocumentScanningClicked : PickerSubmissionUploadEvent() data class OnFileSelected(val uri: Uri) : PickerSubmissionUploadEvent() data class OnFileRemoved(val fileIndex: Int) : PickerSubmissionUploadEvent() data class OnFileAdded(val file: FileSubmitObject?) : PickerSubmissionUploadEvent() @@ -35,7 +34,6 @@ sealed class PickerSubmissionUploadEffect { object LaunchCamera : PickerSubmissionUploadEffect() object LaunchGallery : PickerSubmissionUploadEffect() object LaunchSelectFile : PickerSubmissionUploadEffect() - object LaunchDocumentScanning : PickerSubmissionUploadEffect() data class HandleSubmit(val model: PickerSubmissionUploadModel) : PickerSubmissionUploadEffect() data class LoadFileContents(val uri: Uri, val allowedExtensions: List) : PickerSubmissionUploadEffect() diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadUpdate.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadUpdate.kt index 00b68bcedb..c16a600954 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadUpdate.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/PickerSubmissionUploadUpdate.kt @@ -38,7 +38,6 @@ class PickerSubmissionUploadUpdate : PickerSubmissionUploadEvent.CameraClicked -> Next.dispatch(setOf(PickerSubmissionUploadEffect.LaunchCamera)) PickerSubmissionUploadEvent.GalleryClicked -> Next.dispatch(setOf(PickerSubmissionUploadEffect.LaunchGallery)) PickerSubmissionUploadEvent.SelectFileClicked -> Next.dispatch(setOf(PickerSubmissionUploadEffect.LaunchSelectFile)) - PickerSubmissionUploadEvent.DocumentScanningClicked -> Next.dispatch(setOf(PickerSubmissionUploadEffect.LaunchDocumentScanning)) is PickerSubmissionUploadEvent.OnFileSelected -> { Next.next( model.copy(isLoadingFile = true), diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/ui/PickerSubmissionUploadView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/ui/PickerSubmissionUploadView.kt index 7a6d08e6ce..8a5f4cfe74 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/ui/PickerSubmissionUploadView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/picker/ui/PickerSubmissionUploadView.kt @@ -60,7 +60,6 @@ class PickerSubmissionUploadView(inflater: LayoutInflater, parent: ViewGroup, va binding.sourceDevice.setOnClickListener { consumer?.accept(PickerSubmissionUploadEvent.SelectFileClicked) } binding.sourceGallery.setOnClickListener { consumer?.accept(PickerSubmissionUploadEvent.GalleryClicked) } - binding.sourceDocumentScanning.setOnClickListener { consumer?.accept(PickerSubmissionUploadEvent.DocumentScanningClicked) } } override fun onConnect(output: Consumer) { diff --git a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt index 8010db1157..ab26515598 100644 --- a/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt +++ b/apps/student/src/main/java/com/instructure/student/util/BaseAppManager.kt @@ -21,7 +21,6 @@ import android.webkit.WebView import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.ContextCompat import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.instructure.pandautils.utils.filecache.FileCache import com.instructure.canvasapi2.utils.Analytics import com.instructure.canvasapi2.utils.AnalyticsEventConstants import com.instructure.canvasapi2.utils.Logger @@ -32,14 +31,13 @@ import com.instructure.pandautils.utils.AppTheme import com.instructure.pandautils.utils.AppType import com.instructure.pandautils.utils.ColorKeeper import com.instructure.pandautils.utils.ThemePrefs +import com.instructure.pandautils.utils.filecache.FileCache import com.instructure.student.BuildConfig import com.instructure.student.R import com.instructure.student.activity.NavigationActivity -import com.pspdfkit.PSPDFKit -import com.pspdfkit.exceptions.InvalidPSPDFKitLicenseException -import com.pspdfkit.exceptions.PSPDFKitInitializationFailedException -import com.pspdfkit.initialization.InitializationOptions -import com.zynksoftware.documentscanner.ui.DocumentScanner +import com.pspdfkit.Nutrient +import com.pspdfkit.exceptions.InvalidNutrientLicenseException +import com.pspdfkit.exceptions.NutrientInitializationFailedException abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), AnalyticsEventHandling { @@ -61,9 +59,7 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), Analyti // Hold off on initializing this until we emit the user properties. RemoteConfigUtils.initialize() - initPSPDFKit() - - initDocumentScanning() + initNutrient() if (BuildConfig.DEBUG) { FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false) @@ -110,19 +106,15 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager(), Analyti } - private fun initPSPDFKit() { + private fun initNutrient() { try { - PSPDFKit.initialize(this, InitializationOptions(licenseKey = BuildConfig.PSPDFKIT_LICENSE_KEY)) - } catch (e: PSPDFKitInitializationFailedException) { - Logger.e("Current device is not compatible with PSPDFKIT!") - } catch (e: InvalidPSPDFKitLicenseException) { - Logger.e("Invalid or Trial PSPDFKIT License!") + Nutrient.initialize(this, BuildConfig.PSPDFKIT_LICENSE_KEY) + } catch (e: NutrientInitializationFailedException) { + Logger.e("Current device is not compatible with Nutrient!") + } catch (e: InvalidNutrientLicenseException) { + Logger.e("Invalid or Trial Nutrient License!") } } - private fun initDocumentScanning() { - DocumentScanner.init(this) - } - override fun performLogoutOnAuthError() = Unit } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt b/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt index 73fb0b1066..1e34d0599f 100644 --- a/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt +++ b/apps/student/src/main/java/com/instructure/student/util/FileUtils.kt @@ -72,20 +72,14 @@ object FileUtils { // We don't want to allow users to edit for submission viewing pspdfActivityConfiguration = PdfActivityConfiguration.Builder(context) .scrollDirection(PageScrollDirection.HORIZONTAL) - .showThumbnailGrid() .setThumbnailBarMode(ThumbnailBarMode.THUMBNAIL_BAR_MODE_PINNED) - .disableAnnotationEditing() - .disableAnnotationList() - .disableDocumentEditor() .fitMode(PageFitMode.FIT_TO_WIDTH) .build() } else { // Standard behavior pspdfActivityConfiguration = PdfActivityConfiguration.Builder(context) .scrollDirection(PageScrollDirection.HORIZONTAL) - .showThumbnailGrid() .setDocumentInfoViewSeparated(false) - .enableDocumentEditor() .enabledAnnotationTools(annotationCreationList) .editableAnnotationTypes(annotationEditList) .fitMode(PageFitMode.FIT_TO_WIDTH) diff --git a/apps/student/src/main/res/layout/activity_document_scanning.xml b/apps/student/src/main/res/layout/activity_document_scanning.xml deleted file mode 100644 index a8b7347a77..0000000000 --- a/apps/student/src/main/res/layout/activity_document_scanning.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/student/src/main/res/layout/activity_navigation.xml b/apps/student/src/main/res/layout/activity_navigation.xml index d508f631e4..cdbdefd661 100644 --- a/apps/student/src/main/res/layout/activity_navigation.xml +++ b/apps/student/src/main/res/layout/activity_navigation.xml @@ -61,6 +61,7 @@ android:id="@+id/bottomBar" android:layout_width="match_parent" android:layout_height="wrap_content" + android:minHeight="48dp" android:background="@color/backgroundLightestElevated" app:elevation="0dp" app:itemIconTint="@color/textDarkest" diff --git a/apps/student/src/main/res/layout/fragment_picker_submission_upload.xml b/apps/student/src/main/res/layout/fragment_picker_submission_upload.xml index 96be4e68e1..73b14a4553 100644 --- a/apps/student/src/main/res/layout/fragment_picker_submission_upload.xml +++ b/apps/student/src/main/res/layout/fragment_picker_submission_upload.xml @@ -160,36 +160,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle index 94405a5a01..29113262da 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -16,7 +16,8 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-kapt' // Keep kapt for Data Binding +apply plugin: 'com.google.devtools.ksp' apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'org.jetbrains.kotlin.plugin.compose' @@ -32,6 +33,25 @@ android { exclude 'META-INF/rxjava.properties' } + packaging { + resources { + pickFirsts += [ + 'META-INF/INDEX.LIST', + 'META-INF/io.netty.versions.properties' + ] + excludes += [ + 'META-INF/DEPENDENCIES', + 'META-INF/LICENSE', + 'META-INF/LICENSE.txt', + 'META-INF/NOTICE', + 'META-INF/NOTICE.txt', + 'META-INF/maven/com.google.guava/guava/pom.properties', + 'META-INF/maven/com.google.guava/guava/pom.xml', + 'META-INF/rxjava.properties' + ] + } + } + defaultConfig { minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK @@ -262,6 +282,9 @@ dependencies { implementation Libs.ANDROIDX_VECTOR implementation Libs.PLAY_IN_APP_UPDATES + /* Analytics */ + implementation Libs.PENDO + /* Firebase */ implementation platform(Libs.FIREBASE_BOM) { exclude group: 'com.google.firebase', module: 'firebase-analytics' @@ -271,36 +294,28 @@ dependencies { transitive = true } - testImplementation Libs.ANDROIDX_CORE_TESTING + implementation Libs.CAMERA_VIEW - /* AAC */ - implementation Libs.VIEW_MODEL - implementation Libs.LIVE_DATA - implementation Libs.VIEW_MODE_SAVED_STATE - implementation Libs.ANDROIDX_FRAGMENT_KTX - kapt Libs.LIFECYCLE_COMPILER + testImplementation Libs.ANDROIDX_CORE_TESTING /* DI */ implementation Libs.HILT - kapt Libs.HILT_COMPILER + ksp Libs.HILT_COMPILER androidTestImplementation Libs.HILT_TESTING - kaptAndroidTestQa Libs.HILT_TESTING_COMPILER + kspAndroidTest Libs.HILT_TESTING_COMPILER implementation Libs.HILT_ANDROIDX_WORK - kapt Libs.HILT_ANDROIDX_COMPILER - - androidTestImplementation Libs.UI_AUTOMATOR - - /* WorkManager */ - implementation Libs.ANDROIDX_WORK_MANAGER - implementation Libs.ANDROIDX_WORK_MANAGER_KTX + ksp Libs.HILT_ANDROIDX_COMPILER - implementation Libs.PENDO - - implementation Libs.CAMERA_VIEW + /* AAC */ + implementation Libs.VIEW_MODEL + implementation Libs.LIVE_DATA + implementation Libs.VIEW_MODE_SAVED_STATE + implementation Libs.ANDROIDX_FRAGMENT_KTX + kapt Libs.LIFECYCLE_COMPILER // Keep kapt for lifecycle if it includes Data Binding - /* ROOM */ + /* Room */ implementation Libs.ROOM - kapt Libs.ROOM_COMPILER + ksp Libs.ROOM_COMPILER implementation Libs.ROOM_COROUTINES testImplementation Libs.HAMCREST diff --git a/apps/teacher/proguard-rules.txt b/apps/teacher/proguard-rules.txt index 0157f11519..41f4c46430 100644 --- a/apps/teacher/proguard-rules.txt +++ b/apps/teacher/proguard-rules.txt @@ -258,4 +258,19 @@ -dontwarn java.beans.SimpleBeanInfo -keep class androidx.navigation.** { *; } - -keep interface androidx.navigation.** { *; } \ No newline at end of file + -keep interface androidx.navigation.** { *; } + +# Netty and BlockHound integration +-dontwarn reactor.blockhound.integration.BlockHoundIntegration +-dontwarn io.netty.util.internal.Hidden$NettyBlockHoundIntegration +-keep class reactor.blockhound.integration.** { *; } +-keep class io.netty.util.internal.Hidden$NettyBlockHoundIntegration { *; } + +# Additional Netty keep rules for R8 +-dontwarn io.netty.** +-keep class io.netty.** { *; } +-keepclassmembers class io.netty.** { *; } + +# BlockHound related classes +-dontwarn reactor.blockhound.** +-keep class reactor.blockhound.** { *; } diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/espresso/TestAppManager.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/espresso/TestAppManager.kt index fcc640a20e..fb528a505e 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/espresso/TestAppManager.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/espresso/TestAppManager.kt @@ -16,6 +16,7 @@ */ package com.instructure.teacher.espresso +import androidx.work.DefaultWorkerFactory import androidx.work.WorkerFactory import com.instructure.pandautils.features.reminder.AlarmScheduler import com.instructure.teacher.utils.BaseAppManager @@ -25,7 +26,7 @@ open class TestAppManager : BaseAppManager() { var workerFactory: WorkerFactory? = null override fun getWorkManagerFactory(): WorkerFactory { - return workerFactory ?: WorkerFactory.getDefaultWorkerFactory() + return workerFactory ?: DefaultWorkerFactory } override fun getScheduler(): AlarmScheduler? = null diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/SpeedGraderPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/SpeedGraderPage.kt index 45e402a879..366460115e 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/SpeedGraderPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/compose/SpeedGraderPage.kt @@ -16,7 +16,9 @@ package com.instructure.teacher.ui.pages.compose import androidx.annotation.StringRes +import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextContains import androidx.compose.ui.test.hasAnyAncestor @@ -24,7 +26,7 @@ import androidx.compose.ui.test.hasAnySibling import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.ComposeTestRule -import androidx.compose.ui.test.onChildren +import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick @@ -66,7 +68,6 @@ import com.instructure.espresso.swipeToTop import com.instructure.teacher.R import org.hamcrest.Matchers import org.hamcrest.Matchers.allOf -import org.junit.Assert.assertEquals import java.util.Locale /** @@ -252,15 +253,17 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() */ fun selectCommentLibraryResultItem(index: Int? = null) { - val textNodes = composeTestRule - .onNodeWithTag("commentLibraryListColumn") - .onChildren() - .fetchSemanticsNodes() - - val targetText = textNodes[index ?: 0] - .config[androidx.compose.ui.semantics.SemanticsProperties.Text] + val targetText = composeTestRule + .onAllNodesWithTag("commentLibraryItem")[index ?: 0] + .fetchSemanticsNode() + .config[SemanticsProperties.Text] .joinToString("") { it.text } - composeTestRule.onNode(hasText(targetText, substring = true) and !(hasTestTag("ownCommentText"))) + composeTestRule.onNode( + hasText( + targetText, + substring = true + ) and (hasTestTag("ownCommentText").not()) + ) .performScrollTo() .performClick() composeTestRule.waitForIdle() @@ -290,12 +293,9 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() @OptIn(ExperimentalTestApi::class) fun assertCommentLibraryItemCount(expectedCount: Int) { composeTestRule.waitUntilExactlyOneExists(hasTestTagThatContains("commentLibraryListColumn"), timeoutMillis = 5000) - val textNodes = composeTestRule - .onNodeWithTag("commentLibraryListColumn") - .onChildren() - .fetchSemanticsNodes() - - assertEquals(expectedCount, textNodes.size) + composeTestRule + .onAllNodesWithTag("commentLibraryItem") + .assertCountEquals(expectedCount) } /** @@ -359,7 +359,7 @@ class SpeedGraderPage(private val composeTestRule: ComposeTestRule) : BasePage() getStringFromResource( R.string.sg_tab_files_w_counter, fileCount - ).toUpperCase() + ).uppercase() ) filesTab.click() } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentViewHolder.kt b/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentViewHolder.kt index 03c4108c24..c66fcf72b2 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentViewHolder.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/PSPDFKit/AnnotationComments/AnnotationCommentViewHolder.kt @@ -51,7 +51,7 @@ class AnnotationCommentViewHolder(private val binding: AdapterAnnotationCommentB } commentEditIcon.onClick { - val popup = PopupMenu(context, it, Gravity.TOP, 0, com.google.android.material.R.style.Widget_AppCompat_PopupMenu_Overflow) + val popup = PopupMenu(context, it, Gravity.TOP, 0, com.google.android.material.R.style.Widget_Material3_PopupMenu_Overflow) popup.inflate(R.menu.menu_edit_annotation_comment) if(!canEdit) popup.menu.removeItem(R.id.edit) if(!canDelete) popup.menu.removeItem(R.id.delete) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/FileListFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/FileListFragment.kt index eb150acd28..2ecbcaf3df 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/FileListFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/FileListFragment.kt @@ -345,9 +345,9 @@ class FileListFragment : BaseSyncFragment< }) } - override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { + override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { workInfoLiveData.observe(viewLifecycleOwner) { - if (it.state == WorkInfo.State.SUCCEEDED) { + if (it?.state == WorkInfo.State.SUCCEEDED) { presenter.refresh(true) } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/SpeedGraderCommentsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/SpeedGraderCommentsFragment.kt index 22fc3b92de..563dddce6c 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/SpeedGraderCommentsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/SpeedGraderCommentsFragment.kt @@ -303,8 +303,9 @@ class SpeedGraderCommentsFragment : BaseListFragment) { + override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { workInfoLiveData.observe(this) { + if (it == null) return@observe presenter.onFileUploadWorkInfoChanged(it) } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt b/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt index 15296bc223..ebbdb3e8ac 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/utils/BaseAppManager.kt @@ -21,7 +21,6 @@ import android.os.Build import androidx.appcompat.app.AppCompatDelegate import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.instructure.pandautils.utils.filecache.FileCache import com.instructure.canvasapi2.utils.AnalyticsEventConstants import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.Logger @@ -36,14 +35,14 @@ import com.instructure.pandautils.utils.AppType import com.instructure.pandautils.utils.ColorKeeper import com.instructure.pandautils.utils.Const import com.instructure.pandautils.utils.ThemePrefs +import com.instructure.pandautils.utils.filecache.FileCache import com.instructure.teacher.BuildConfig import com.instructure.teacher.R import com.instructure.teacher.activities.InitActivity import com.instructure.teacher.tasks.TeacherLogoutTask -import com.pspdfkit.PSPDFKit -import com.pspdfkit.exceptions.InvalidPSPDFKitLicenseException -import com.pspdfkit.exceptions.PSPDFKitInitializationFailedException -import com.pspdfkit.initialization.InitializationOptions +import com.pspdfkit.Nutrient +import com.pspdfkit.exceptions.InvalidNutrientLicenseException +import com.pspdfkit.exceptions.NutrientInitializationFailedException abstract class BaseAppManager : com.instructure.canvasapi2.AppManager() { @@ -76,11 +75,11 @@ abstract class BaseAppManager : com.instructure.canvasapi2.AppManager() { ColorKeeper.defaultColor = getColorCompat(R.color.textDarkest) try { - PSPDFKit.initialize(this, InitializationOptions(licenseKey = BuildConfig.PSPDFKIT_LICENSE_KEY)) - } catch (e: PSPDFKitInitializationFailedException) { - Logger.e("Current device is not compatible with PSPDFKIT!") - } catch (e: InvalidPSPDFKitLicenseException) { - Logger.e("Invalid or Trial PSPDFKIT License!") + Nutrient.initialize(this, BuildConfig.PSPDFKIT_LICENSE_KEY) + } catch (e: NutrientInitializationFailedException) { + Logger.e("Current device is not compatible with Nutrient!") + } catch (e: InvalidNutrientLicenseException) { + Logger.e("Invalid or Trial Nutrient License!") } MasqueradeHelper.masqueradeLogoutTask = Runnable { diff --git a/apps/teacher/src/main/res/layout/activity_init.xml b/apps/teacher/src/main/res/layout/activity_init.xml index 699d4456ba..46c52d9b83 100644 --- a/apps/teacher/src/main/res/layout/activity_init.xml +++ b/apps/teacher/src/main/res/layout/activity_init.xml @@ -112,6 +112,7 @@ android:id="@+id/bottomBar" android:layout_width="match_parent" android:layout_height="56dp" + android:minHeight="48dp" android:layout_alignParentBottom="true" android:background="@color/backgroundLightestElevated" app:itemIconTint="@color/textDarkest" diff --git a/automation/espresso/build.gradle b/automation/espresso/build.gradle index 8a48e8502a..4b7874ce96 100644 --- a/automation/espresso/build.gradle +++ b/automation/espresso/build.gradle @@ -37,7 +37,7 @@ allprojects { apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' android { @@ -175,9 +175,9 @@ dependencies { /* DI */ implementation Libs.HILT - kapt Libs.HILT_COMPILER + ksp Libs.HILT_COMPILER implementation Libs.HILT_TESTING - kapt Libs.HILT_TESTING_COMPILER + ksp Libs.HILT_TESTING_COMPILER implementation Libs.HILT_ANDROIDX_WORK implementation Libs.COMPOSE_UI_TEST_MANIFEST diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestAppManager.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestAppManager.kt index 9e77a41f1f..f2e876daad 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestAppManager.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/TestAppManager.kt @@ -14,6 +14,7 @@ * limitations under the License. */package com.instructure.canvas.espresso +import androidx.work.DefaultWorkerFactory import android.annotation.SuppressLint import android.content.Context import android.util.Log @@ -27,22 +28,23 @@ import com.instructure.canvasapi2.utils.RemoteConfigUtils open class TestAppManager: AppManager() { + var testDriver: TestDriver? = null + + var workerFactory: WorkerFactory? = null + @SuppressLint("RestrictedApi") override fun onCreate() { super.onCreate() RemoteConfigUtils.initialize() if (workerFactory == null) { - workerFactory = WorkerFactory.getDefaultWorkerFactory() + workerFactory = getWorkManagerFactory() } } - var testDriver: TestDriver? = null - - var workerFactory: WorkerFactory? = null @SuppressLint("RestrictedApi") override fun getWorkManagerFactory(): WorkerFactory { - return workerFactory ?: WorkerFactory.getDefaultWorkerFactory() + return workerFactory ?: DefaultWorkerFactory } override fun performLogoutOnAuthError() = Unit diff --git a/automation/soseedygrpc/build.gradle b/automation/soseedygrpc/build.gradle index d83c2030fb..ba6cdfef85 100644 --- a/automation/soseedygrpc/build.gradle +++ b/automation/soseedygrpc/build.gradle @@ -23,7 +23,7 @@ dependencies { compile project(':dataseedingapi') // https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.grpc%22%20a%3A%22grpc-netty%22 - compile 'io.grpc:grpc-netty:1.61.1' + compile 'io.grpc:grpc-netty:1.75.0' compile Libs.KOTLIN_STD_LIB // https://mvnrepository.com/artifact/io.netty/netty-tcnative-boringssl-static diff --git a/gradle/gradle/wrapper/gradle-wrapper.properties b/gradle/gradle/wrapper/gradle-wrapper.properties index b82aa23a4f..37f853b1c8 100644 --- a/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/libs/DocumentScanner/build.gradle b/libs/DocumentScanner/build.gradle deleted file mode 100644 index 98764308cc..0000000000 --- a/libs/DocumentScanner/build.gradle +++ /dev/null @@ -1,91 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -group="com.zynkware" - -def libraryVersionCode = 5 -def libraryVersionName = "1.0.1" - -repositories { - mavenCentral() - google() - maven { url "https://jitpack.io" } -} - -android { - namespace 'com.zynksoftware.documentscanner' - compileSdkVersion Versions.COMPILE_SDK - buildToolsVersion Versions.BUILD_TOOLS - - defaultConfig { - minSdkVersion 21 - targetSdkVersion Versions.TARGET_SDK - versionCode libraryVersionCode - versionName libraryVersionName - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17 - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - main.res.srcDirs = ['src/main/res'] - main.manifest.srcFile 'src/main/AndroidManifest.xml' - } - - buildFeatures { - viewBinding true - } -} - -repositories { - mavenCentral() - google() - maven { url 'https://jitpack.io' } -} - -dependencies { - implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation Libs.KOTLIN_STD_LIB - - implementation 'androidx.core:core-ktx:1.12.0' - implementation Libs.ANDROIDX_APPCOMPAT - - implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' - - implementation 'com.github.zynkware:Tiny-OpenCV:4.4.0-3' - - implementation "androidx.camera:camera-camera2:1.4.2" - implementation "androidx.camera:camera-lifecycle:1.4.2" - implementation "androidx.camera:camera-view:1.4.2" - - implementation 'androidx.exifinterface:exifinterface:1.4.0' - implementation Libs.KOTLIN_COROUTINES_ANDROID - implementation 'id.zelory:compressor:3.0.1' -} - -task sourceJar(type: Jar) { - from android.sourceSets.main.java.srcDirs - from fileTree(dir: 'src/libs', include: ['*.jar']) -} - -task androidSourcesJar(type: Jar) { - archiveClassifier.set('sources') - from android.sourceSets.main.java.srcDirs -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/AndroidManifest.xml b/libs/DocumentScanner/src/main/AndroidManifest.xml deleted file mode 100644 index 679e8fc75a..0000000000 --- a/libs/DocumentScanner/src/main/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ScanActivity.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ScanActivity.kt deleted file mode 100644 index e388c63ca0..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ScanActivity.kt +++ /dev/null @@ -1,29 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner - -import com.zynksoftware.documentscanner.ui.scan.InternalScanActivity - -abstract class ScanActivity : InternalScanActivity() { - - fun addFragmentContentLayout() { - addFragmentContentLayoutInternal() - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/BitmapExtensions.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/BitmapExtensions.kt deleted file mode 100644 index 8afd1e1f98..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/BitmapExtensions.kt +++ /dev/null @@ -1,51 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.common.extensions - -import android.graphics.Bitmap -import android.graphics.Matrix -import android.graphics.RectF -import org.opencv.android.Utils -import org.opencv.core.CvType -import org.opencv.core.Mat -import org.opencv.core.Scalar - -internal fun Bitmap.rotateBitmap(angle: Int): Bitmap { - val matrix = Matrix() - matrix.postRotate(angle.toFloat()) - return Bitmap.createBitmap(this, 0, 0, this.width, this.height, matrix, true) -} - -internal fun Bitmap.toMat(): Mat { - val mat = Mat(this.height, this.width, CvType.CV_8U, Scalar(4.toDouble())) - val bitmap32 = this.copy(Bitmap.Config.ARGB_8888, true) - Utils.bitmapToMat(bitmap32, mat) - return mat -} - -internal fun Bitmap.scaledBitmap(width: Int, height: Int): Bitmap { - val m = Matrix() - m.setRectToRect( - RectF(0f, 0f, this.width.toFloat(), this.height.toFloat()), - RectF(0f, 0f, width.toFloat(), height.toFloat()), - Matrix.ScaleToFit.CENTER - ) - return Bitmap.createBitmap(this, 0, 0, this.width, this.height, m, true) -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/ImageProxyExtensions.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/ImageProxyExtensions.kt deleted file mode 100644 index f0b6716c1f..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/ImageProxyExtensions.kt +++ /dev/null @@ -1,93 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.common.extensions - -import android.graphics.ImageFormat -import androidx.camera.core.ImageProxy -import org.opencv.core.CvType -import org.opencv.core.Mat -import org.opencv.imgproc.Imgproc - -internal fun ImageProxy.yuvToRgba(): Mat { - val rgbaMat = Mat() - - if (format == ImageFormat.YUV_420_888 - && planes.size == 3) { - - val chromaPixelStride = planes[1].pixelStride - - if (chromaPixelStride == 2) { // Chroma channels are interleaved - val yPlane = planes[0].buffer - val uvPlane1 = planes[1].buffer - val uvPlane2 = planes[2].buffer - - val yMat = Mat((height), width, CvType.CV_8UC1, yPlane) - val uvMat1 = Mat(height / 2, width / 2, CvType.CV_8UC2, uvPlane1) - val uvMat2 = Mat(height / 2, width / 2, CvType.CV_8UC2, uvPlane2) - val addrDiff = uvMat2.dataAddr() - uvMat1.dataAddr() - if (addrDiff > 0) { - Imgproc.cvtColorTwoPlane(yMat, uvMat1, rgbaMat, Imgproc.COLOR_YUV2RGBA_NV12) - } else { - Imgproc.cvtColorTwoPlane(yMat, uvMat2, rgbaMat, Imgproc.COLOR_YUV2RGBA_NV21) - } - } else { // Chroma channels are not interleaved - val yuvBytes = ByteArray(width * (height + height / 2)) - val yPlane = planes[0].buffer - val uPlane = planes[1].buffer - val vPlane = planes[2].buffer - - yPlane.get(yuvBytes, 0, width * height) - - val chromaRowStride = planes[1].rowStride - val chromaRowPadding = chromaRowStride - width / 2 - - var offset = width * height - if (chromaRowPadding == 0) { - // When the row stride of the chroma channels equals their width, we can copy - // the entire channels in one go - uPlane.get(yuvBytes, offset, width * height / 4) - offset += width * height / 4 - vPlane.get(yuvBytes, offset, width * height / 4) - } else { - // When not equal, we need to copy the channels row by row - for (i in 0 until height / 2) { - uPlane.get(yuvBytes, offset, width / 2) - offset += width / 2 - if (i < height / 2 - 1) { - uPlane.position(uPlane.position() + chromaRowPadding) - } - } - for (i in 0 until height / 2) { - vPlane.get(yuvBytes, offset, width / 2) - offset += width / 2 - if (i < height / 2 - 1) { - vPlane.position(vPlane.position() + chromaRowPadding) - } - } - } - - val yuvMat = Mat(height + height / 2, width, CvType.CV_8UC1) - yuvMat.put(0, 0, yuvBytes) - Imgproc.cvtColor(yuvMat, rgbaMat, Imgproc.COLOR_YUV2BGR_NV21, 4) - } - } - - return rgbaMat -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/OpenCvExtensions.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/OpenCvExtensions.kt deleted file mode 100644 index 4d6e7f4779..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/OpenCvExtensions.kt +++ /dev/null @@ -1,42 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.common.extensions - -import android.graphics.Bitmap -import org.opencv.android.Utils -import org.opencv.core.* -import java.util.* - -internal fun Mat.toBitmap(): Bitmap { - val bitmap = Bitmap.createBitmap(this.cols(), this.rows(), Bitmap.Config.ARGB_8888) - Utils.matToBitmap(this, bitmap) - return bitmap -} - -internal fun MatOfPoint2f.scaleRectangle(scale: Double): MatOfPoint2f { - val originalPoints = this.toList() - val resultPoints: MutableList = ArrayList() - for (point in originalPoints) { - resultPoints.add(Point(point.x * scale, point.y * scale)) - } - val result = MatOfPoint2f() - result.fromList(resultPoints) - return result -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/ViewExtensions.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/ViewExtensions.kt deleted file mode 100644 index 17375b9edb..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/extensions/ViewExtensions.kt +++ /dev/null @@ -1,30 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.common.extensions - -import android.view.View - -internal fun View.hide() { - visibility = View.GONE -} - -internal fun View.show() { - visibility = View.VISIBLE -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/FileUriUtils.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/FileUriUtils.kt deleted file mode 100644 index f65058f84b..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/FileUriUtils.kt +++ /dev/null @@ -1,240 +0,0 @@ -package com.zynksoftware.documentscanner.common.utils - -import android.content.ContentUris -import android.content.Context -import android.database.Cursor -import android.net.Uri -import android.os.Build -import android.os.Environment -import android.provider.DocumentsContract -import android.provider.MediaStore -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream - -/** - * This file was taken from - * https://gist.github.com/HBiSoft/15899990b8cd0723c3a894c1636550a8 - * - * Later on it was modified from the below resource: - * https://raw.githubusercontent.com/iPaulPro/aFileChooser/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java - * https://raw.githubusercontent.com/iPaulPro/aFileChooser/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java - */ - -internal object FileUriUtils { - - fun getRealPath(context: Context, uri: Uri): String? { - var path = getPathFromLocalUri(context, uri) - if (path == null) { - path = getPathFromRemoteUri(context, uri) - } - return path - } - - private fun getPathFromLocalUri(context: Context, uri: Uri): String? { - - val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT - - // DocumentProvider - if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { - // ExternalStorageProvider - if (isExternalStorageDocument(uri)) { - val docId = DocumentsContract.getDocumentId(uri) - val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val type = split[0] - - // This is for checking Main Memory - return if ("primary".equals(type, ignoreCase = true)) { - if (split.size > 1) { - Environment.getExternalStorageDirectory().toString() + "/" + split[1] - } else { - Environment.getExternalStorageDirectory().toString() + "/" - } - // This is for checking SD Card - } else { - val path = "storage" + "/" + docId.replace(":", "/") - if (File(path).exists()) { - path - } else { - "/storage/sdcard/" + split[1] - } - } - } else if (isDownloadsDocument(uri)) { - val fileName = getFilePath(context, uri) - if (fileName != null) { - return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName - } - - val id = DocumentsContract.getDocumentId(uri) - val contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id) - ) - return getDataColumn(context, contentUri, null, null) - } else if (isMediaDocument(uri)) { - val docId = DocumentsContract.getDocumentId(uri) - val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val type = split[0] - - var contentUri: Uri? = null - if ("image" == type) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI - } else if ("video" == type) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI - } else if ("audio" == type) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - } - - val selection = "_id=?" - val selectionArgs = arrayOf(split[1]) - - return getDataColumn(context, contentUri, selection, selectionArgs) - } // MediaProvider - // DownloadsProvider - } else if ("content".equals(uri.scheme!!, ignoreCase = true)) { - - // Return the remote address - return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(context, uri, null, null) - } else if ("file".equals(uri.scheme!!, ignoreCase = true)) { - return uri.path - } // File - // MediaStore (and general) - - return null - } - - private fun getDataColumn( - context: Context, - uri: Uri?, - selection: String?, - selectionArgs: Array? - ): String? { - - var cursor: Cursor? = null - val column = "_data" - val projection = arrayOf(column) - - try { - cursor = context.contentResolver.query(uri!!, projection, selection, selectionArgs, null) - if (cursor != null && cursor.moveToFirst()) { - val index = cursor.getColumnIndexOrThrow(column) - return cursor.getString(index) - } - } catch (ex: Exception) { - } finally { - cursor?.close() - } - return null - } - - private fun getFilePath(context: Context, uri: Uri): String? { - - var cursor: Cursor? = null - val projection = arrayOf(MediaStore.MediaColumns.DISPLAY_NAME) - - try { - cursor = context.contentResolver.query(uri, projection, null, null, null) - if (cursor != null && cursor.moveToFirst()) { - val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME) - return cursor.getString(index) - } - } finally { - cursor?.close() - } - return null - } - - private fun getPathFromRemoteUri(context: Context, uri: Uri): String? { - // The code below is why Java now has try-with-resources and the Files utility. - var file: File? = null - var inputStream: InputStream? = null - var outputStream: OutputStream? = null - var success = false - try { - val extension = getImageExtension(uri) - inputStream = context.contentResolver.openInputStream(uri) - val storageDir = context.cacheDir - if (!storageDir.exists()) { - storageDir.mkdirs() - } - file = File(storageDir, "remotePicture${extension}") - file.createNewFile() - outputStream = FileOutputStream(file) - if (inputStream != null) { - inputStream.copyTo(outputStream, bufferSize = 4 * 1024) - success = true - } - } catch (ignored: IOException) { - } finally { - try { - inputStream?.close() - } catch (ignored: IOException) { - } - - try { - outputStream?.close() - } catch (ignored: IOException) { - // If closing the output stream fails, we cannot be sure that the - // target file was written in full. Flushing the stream merely moves - // the bytes into the OS, not necessarily to the file. - success = false - } - } - return if (success) file!!.path else null - } - - /** @return extension of image with dot, or default .jpg if it none. - */ - private fun getImageExtension(uriImage: Uri): String { - var extension: String? = null - - try { - val imagePath = uriImage.path - if (imagePath != null && imagePath.lastIndexOf(".") != -1) { - extension = imagePath.substring(imagePath.lastIndexOf(".") + 1) - } - } catch (e: Exception) { - extension = null - } - - if (extension == null || extension.isEmpty()) { - // default extension for matches the previous behavior of the plugin - extension = "jpg" - } - - return ".$extension" - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - private fun isExternalStorageDocument(uri: Uri): Boolean { - return "com.android.externalstorage.documents" == uri.authority - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - private fun isDownloadsDocument(uri: Uri): Boolean { - return "com.android.providers.downloads.documents" == uri.authority - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - private fun isMediaDocument(uri: Uri): Boolean { - return "com.android.providers.media.documents" == uri.authority - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is Google Photos. - */ - private fun isGooglePhotosUri(uri: Uri): Boolean { - return "com.google.android.apps.photos.content" == uri.authority - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/ImageDetectionProperties.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/ImageDetectionProperties.kt deleted file mode 100644 index 39310e1df5..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/ImageDetectionProperties.kt +++ /dev/null @@ -1,81 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.common.utils - -import org.opencv.core.MatOfPoint2f -import org.opencv.core.Point -import kotlin.math.abs - -internal class ImageDetectionProperties( - private val previewWidth: Double, private val previewHeight: Double, - private val topLeftPoint: Point, private val bottomLeftPoint: Point, - private val bottomRightPoint: Point, private val topRightPoint: Point, - private val resultWidth: Int, private val resultHeight: Int -) { - - companion object { - private const val SMALLEST_ANGLE_COS = 0.172 //80 degrees - } - - fun isNotValidImage(approx: MatOfPoint2f): Boolean { - return isEdgeTouching || isAngleNotCorrect(approx) || isDetectedAreaBelowLimits() - } - - private fun isAngleNotCorrect(approx: MatOfPoint2f): Boolean { - return getMaxCosine(approx) || isLeftEdgeDistorted || isRightEdgeDistorted - } - - private val isRightEdgeDistorted: Boolean - get() = abs(topRightPoint.y - bottomRightPoint.y) > 100 - - private val isLeftEdgeDistorted: Boolean - get() = abs(topLeftPoint.y - bottomLeftPoint.y) > 100 - - private fun getMaxCosine(approx: MatOfPoint2f): Boolean { - var maxCosine = 0.0 - val approxPoints = approx.toArray() - maxCosine = MathUtils.getMaxCosine(maxCosine, approxPoints) - return maxCosine >= SMALLEST_ANGLE_COS - } - - private val isEdgeTouching: Boolean - get() = isTopEdgeTouching || isBottomEdgeTouching || isLeftEdgeTouching || isRightEdgeTouching - - private val isBottomEdgeTouching: Boolean - get() = bottomLeftPoint.x >= previewHeight - 10 || bottomRightPoint.x >= previewHeight - 10 - - private val isTopEdgeTouching: Boolean - get() = topLeftPoint.x <= 10 || topRightPoint.x <= 10 - - private val isRightEdgeTouching: Boolean - get() = topRightPoint.y >= previewWidth - 10 || bottomRightPoint.y >= previewWidth - 10 - - private val isLeftEdgeTouching: Boolean - get() = topLeftPoint.y <= 10 || bottomLeftPoint.y <= 10 - - private fun isDetectedAreaBelowLimits(): Boolean { - return !(previewWidth / previewHeight >= 1 && - resultWidth.toDouble() / resultHeight.toDouble() >= 0.9 && - resultHeight.toDouble() >= 0.70 * previewHeight || - previewHeight / previewWidth >= 1 && - resultHeight.toDouble() / resultWidth.toDouble() >= 0.9 && - resultWidth.toDouble() >= 0.70 * previewWidth) - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/MathUtils.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/MathUtils.kt deleted file mode 100644 index 1c6b1d16d0..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/MathUtils.kt +++ /dev/null @@ -1,51 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.common.utils - -import org.opencv.core.Point -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.sqrt - -internal object MathUtils { - - private fun angle(p1: Point, p2: Point, p0: Point): Double { - val dx1 = p1.x - p0.x - val dy1 = p1.y - p0.y - val dx2 = p2.x - p0.x - val dy2 = p2.y - p0.y - return (dx1 * dx2 + dy1 * dy2) / sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2) + 1e-10) - } - - fun getDistance(p1: Point, p2: Point): Double { - val dx = p2.x - p1.x - val dy = p2.y - p1.y - return sqrt(dx * dx + dy * dy) - } - - fun getMaxCosine(maxCosine: Double, approxPoints: Array): Double { - var newMaxCosine = maxCosine - for (i in 2..4) { - val cosine: Double = abs(angle(approxPoints[i % 4], approxPoints[i - 2], approxPoints[i - 1])) - newMaxCosine = max(cosine, newMaxCosine) - } - return newMaxCosine - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/OpenCvNativeBridge.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/OpenCvNativeBridge.kt deleted file mode 100644 index 0916409902..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/OpenCvNativeBridge.kt +++ /dev/null @@ -1,247 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.common.utils - -import android.graphics.Bitmap -import android.graphics.PointF -import com.zynksoftware.documentscanner.common.extensions.scaleRectangle -import com.zynksoftware.documentscanner.common.extensions.toBitmap -import com.zynksoftware.documentscanner.common.extensions.toMat -import com.zynksoftware.documentscanner.ui.components.Quadrilateral -import org.opencv.core.* -import org.opencv.imgproc.Imgproc -import java.util.* -import kotlin.collections.ArrayList -import kotlin.math.* - - -internal class OpenCvNativeBridge { - - companion object { - private const val ANGLES_NUMBER = 4 - private const val EPSILON_CONSTANT = 0.02 - private const val CLOSE_KERNEL_SIZE = 10.0 - private const val CANNY_THRESHOLD_LOW = 75.0 - private const val CANNY_THRESHOLD_HIGH = 200.0 - private const val CUTOFF_THRESHOLD = 155.0 - private const val TRUNCATE_THRESHOLD = 150.0 - private const val NORMALIZATION_MIN_VALUE = 0.0 - private const val NORMALIZATION_MAX_VALUE = 255.0 - private const val BLURRING_KERNEL_SIZE = 5.0 - private const val DOWNSCALE_IMAGE_SIZE = 600.0 - private const val FIRST_MAX_CONTOURS = 10 - } - - fun getScannedBitmap(bitmap: Bitmap, x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float, x4: Float, y4: Float): Bitmap { - val rectangle = MatOfPoint2f() - rectangle.fromArray( - Point(x1.toDouble(), y1.toDouble()), - Point(x2.toDouble(), y2.toDouble()), - Point(x3.toDouble(), y3.toDouble()), - Point(x4.toDouble(), y4.toDouble()) - ) - val dstMat = PerspectiveTransformation.transform(bitmap.toMat(), rectangle) - return dstMat.toBitmap() - } - - fun getContourEdgePoints(tempBitmap: Bitmap): List { - var point2f = getPoint(tempBitmap) - if (point2f == null) point2f = MatOfPoint2f() - val points: List = point2f.toArray().toList() - val result: MutableList = ArrayList() - for (i in points.indices) { - result.add(PointF(points[i].x.toFloat(), points[i].y.toFloat())) - } - - return result - } - - fun getPoint(bitmap: Bitmap): MatOfPoint2f? { - val src = bitmap.toMat() - - val ratio = DOWNSCALE_IMAGE_SIZE / max(src.width(), src.height()) - val downscaledSize = Size(src.width() * ratio, src.height() * ratio) - val downscaled = Mat(downscaledSize, src.type()) - Imgproc.resize(src, downscaled, downscaledSize) - val largestRectangle = detectLargestQuadrilateral(downscaled) - - return largestRectangle?.contour?.scaleRectangle(1f / ratio) - } - - // patch from Udayraj123 (https://github.com/Udayraj123/LiveEdgeDetection) - fun detectLargestQuadrilateral(src: Mat): Quadrilateral? { - val destination = Mat() - Imgproc.blur(src, src, Size(BLURRING_KERNEL_SIZE, BLURRING_KERNEL_SIZE)) - - Core.normalize(src, src, NORMALIZATION_MIN_VALUE, NORMALIZATION_MAX_VALUE, Core.NORM_MINMAX) - - Imgproc.threshold(src, src, TRUNCATE_THRESHOLD, NORMALIZATION_MAX_VALUE, Imgproc.THRESH_TRUNC) - Core.normalize(src, src, NORMALIZATION_MIN_VALUE, NORMALIZATION_MAX_VALUE, Core.NORM_MINMAX) - - Imgproc.Canny(src, destination, CANNY_THRESHOLD_HIGH, CANNY_THRESHOLD_LOW) - - Imgproc.threshold(destination, destination, CUTOFF_THRESHOLD, NORMALIZATION_MAX_VALUE, Imgproc.THRESH_TOZERO) - - Imgproc.morphologyEx( - destination, destination, Imgproc.MORPH_CLOSE, - Mat(Size(CLOSE_KERNEL_SIZE, CLOSE_KERNEL_SIZE), CvType.CV_8UC1, Scalar(NORMALIZATION_MAX_VALUE)), - Point(-1.0, -1.0), 1 - ) - - val largestContour: List? = findLargestContours(destination) - if (null != largestContour) { - return findQuadrilateral(largestContour) - } - return null - } - - private fun findQuadrilateral(mContourList: List): Quadrilateral? { - for (c in mContourList) { - val c2f = MatOfPoint2f(*c.toArray()) - val peri = Imgproc.arcLength(c2f, true) - val approx = MatOfPoint2f() - Imgproc.approxPolyDP(c2f, approx, EPSILON_CONSTANT * peri, true) - val points = approx.toArray() - // select biggest 4 angles polygon - if (approx.rows() == ANGLES_NUMBER) { - val foundPoints: Array = sortPoints(points) - return Quadrilateral(approx, foundPoints) - } else if(approx.rows() == 5) { - // if document has a bent corner - var shortestDistance = Int.MAX_VALUE.toDouble() - var shortestPoint1: Point? = null - var shortestPoint2: Point? = null - - var diagonal = 0.toDouble() - var diagonalPoint1: Point? = null - var diagonalPoint2: Point? = null - - for (i in 0 until 4) { - for (j in i + 1 until 5) { - val d = distance(points[i], points[j]) - if (d < shortestDistance) { - shortestDistance = d - shortestPoint1 = points[i] - shortestPoint2 = points[j] - } - if(d > diagonal) { - diagonal = d - diagonalPoint1 = points[i] - diagonalPoint2 = points[j] - } - } - } - - val trianglePointWithHypotenuse: Point? = points.toList().minus(arrayListOf(shortestPoint1, shortestPoint2, diagonalPoint1, diagonalPoint2))[0] - - val newPoint = if(trianglePointWithHypotenuse!!.x > shortestPoint1!!.x && trianglePointWithHypotenuse.x > shortestPoint2!!.x && - trianglePointWithHypotenuse.y > shortestPoint1.y && trianglePointWithHypotenuse.y > shortestPoint2.y) { - Point(min(shortestPoint1.x, shortestPoint2.x), min(shortestPoint1.y, shortestPoint2.y)) - } else if(trianglePointWithHypotenuse.x < shortestPoint1.x && trianglePointWithHypotenuse.x < shortestPoint2!!.x && - trianglePointWithHypotenuse.y > shortestPoint1.y && trianglePointWithHypotenuse.y > shortestPoint2.y) { - Point(max(shortestPoint1.x, shortestPoint2.x), min(shortestPoint1.y, shortestPoint2.y)) - } else if(trianglePointWithHypotenuse.x < shortestPoint1.x && trianglePointWithHypotenuse.x < shortestPoint2!!.x && - trianglePointWithHypotenuse.y < shortestPoint1.y && trianglePointWithHypotenuse.y < shortestPoint2.y) { - Point(max(shortestPoint1.x, shortestPoint2.x), max(shortestPoint1.y, shortestPoint2.y)) - } else if(trianglePointWithHypotenuse.x > shortestPoint1.x && trianglePointWithHypotenuse.x > shortestPoint2!!.x && - trianglePointWithHypotenuse.y < shortestPoint1.y && trianglePointWithHypotenuse.y < shortestPoint2.y) { - Point(min(shortestPoint1.x, shortestPoint2.x), max(shortestPoint1.y, shortestPoint2.y)) - } else { - Point(0.0, 0.0) - } - - val sortedPoints = sortPoints(arrayOf(trianglePointWithHypotenuse, diagonalPoint1!!, diagonalPoint2!!, newPoint)) - val newApprox = MatOfPoint2f() - newApprox.fromArray(*sortedPoints) - return Quadrilateral(newApprox, sortedPoints) - } - } - return null - } - - private fun distance(p1: Point, p2: Point): Double { - return sqrt((p1.x - p2.x).pow(2.0) + (p1.y - p2.y).pow(2.0)) - } - - private fun sortPoints(src: Array): Array { - val srcPoints: ArrayList = ArrayList(src.toList()) - val result = arrayOf(null, null, null, null) - val sumComparator: Comparator = Comparator { lhs, rhs -> (lhs.y + lhs.x).compareTo(rhs.y + rhs.x) } - val diffComparator: Comparator = Comparator { lhs, rhs -> (lhs.y - lhs.x).compareTo(rhs.y - rhs.x) } - - // top-left corner = minimal sum - result[0] = Collections.min(srcPoints, sumComparator) - // bottom-right corner = maximal sum - result[2] = Collections.max(srcPoints, sumComparator) - // top-right corner = minimal difference - result[1] = Collections.min(srcPoints, diffComparator) - // bottom-left corner = maximal difference - result[3] = Collections.max(srcPoints, diffComparator) - return result.map { - it!! - }.toTypedArray() - } - - private fun findLargestContours(inputMat: Mat): List? { - val mHierarchy = Mat() - val mContourList: List = ArrayList() - //finding contours - as we are sorting by area anyway, we can use RETR_LIST - faster than RETR_EXTERNAL. - Imgproc.findContours(inputMat, mContourList, mHierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE) - - // Convert the contours to their Convex Hulls i.e. removes minor nuances in the contour - val mHullList: MutableList = ArrayList() - val tempHullIndices = MatOfInt() - for (i in mContourList.indices) { - Imgproc.convexHull(mContourList[i], tempHullIndices) - mHullList.add(hull2Points(tempHullIndices, mContourList[i])) - } - // Release mContourList as its job is done - for (c in mContourList) { - c.release() - } - tempHullIndices.release() - mHierarchy.release() - if (mHullList.size != 0) { - mHullList.sortWith { lhs, rhs -> - Imgproc.contourArea(rhs).compareTo(Imgproc.contourArea(lhs)) - } - return mHullList.subList(0, min(mHullList.size, FIRST_MAX_CONTOURS)) - } - return null - } - - private fun hull2Points(hull: MatOfInt, contour: MatOfPoint): MatOfPoint { - val indexes = hull.toList() - val points: MutableList = ArrayList() - val ctrList = contour.toList() - for (index in indexes) { - points.add(ctrList[index]) - } - val point = MatOfPoint() - point.fromList(points) - return point - } - - fun contourArea(approx: MatOfPoint2f): Double { - return Imgproc.contourArea(approx) - } - - -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/PerspectiveTransformation.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/PerspectiveTransformation.kt deleted file mode 100644 index d5a077b8f9..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/common/utils/PerspectiveTransformation.kt +++ /dev/null @@ -1,117 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.common.utils - -import com.zynksoftware.documentscanner.common.utils.MathUtils.getDistance -import org.opencv.core.Mat -import org.opencv.core.MatOfPoint2f -import org.opencv.core.Point -import org.opencv.core.Size -import org.opencv.imgproc.Imgproc -import java.util.* - -internal object PerspectiveTransformation { - - fun transform(src: Mat, corners: MatOfPoint2f): Mat { - val sortedCorners = sortCorners(corners) - val size = getRectangleSize(sortedCorners) - val result = Mat.zeros(size, src.type()) - val imageOutline = getOutline(result) - val transformation = Imgproc.getPerspectiveTransform(sortedCorners, imageOutline) - Imgproc.warpPerspective(src, result, transformation, size) - return result - } - - private fun getRectangleSize(rectangle: MatOfPoint2f): Size { - val corners = rectangle.toArray() - val top = getDistance(corners[0], corners[1]) - val right = getDistance(corners[1], corners[2]) - val bottom = getDistance(corners[2], corners[3]) - val left = getDistance(corners[3], corners[0]) - val averageWidth = (top + bottom) / 2f - val averageHeight = (right + left) / 2f - return Size(Point(averageWidth, averageHeight)) - } - - private fun getOutline(image: Mat): MatOfPoint2f { - val topLeft = Point(0.toDouble(), 0.toDouble()) - val topRight = Point(image.cols().toDouble(), 0.toDouble()) - val bottomRight = Point(image.cols().toDouble(), image.rows().toDouble()) - val bottomLeft = Point(0.toDouble(), image.rows().toDouble()) - val points = arrayOf(topLeft, topRight, bottomRight, bottomLeft) - val result = MatOfPoint2f() - result.fromArray(*points) - return result - } - - private fun sortCorners(corners: MatOfPoint2f): MatOfPoint2f { - val center = getMassCenter(corners) - val points = corners.toList() - val topPoints: MutableList = ArrayList() - val bottomPoints: MutableList = ArrayList() - for (point in points) { - if (point.y < center.y) { - topPoints.add(point) - } else { - bottomPoints.add(point) - } - } - - val topLeft = if (topPoints[0].x > topPoints[1].x) { - topPoints[1] - } else { - topPoints[0] - } - - val topRight = if (topPoints[0].x > topPoints[1].x) { - topPoints[0] - } else { - topPoints[1] - } - - val bottomLeft = if (bottomPoints[0].x > bottomPoints[1].x) { - bottomPoints[1] - } else { - bottomPoints[0] - } - - val bottomRight = if (bottomPoints[0].x > bottomPoints[1].x) { - bottomPoints[0] - } else { - bottomPoints[1] - } - val result = MatOfPoint2f() - val sortedPoints = arrayOf(topLeft, topRight, bottomRight, bottomLeft) - result.fromArray(*sortedPoints) - return result - } - - private fun getMassCenter(points: MatOfPoint2f): Point { - var xSum = 0.0 - var ySum = 0.0 - val pointList = points.toList() - val len = pointList.size - for (point in pointList) { - xSum += point.x - ySum += point.y - } - return Point(xSum / len, ySum / len) - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/manager/SessionManager.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/manager/SessionManager.kt deleted file mode 100644 index b1d786476a..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/manager/SessionManager.kt +++ /dev/null @@ -1,68 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.manager - -import android.content.Context -import android.graphics.Bitmap -import id.zelory.compressor.extension -import java.util.Locale - -internal class SessionManager(context: Context) { - - companion object { - private const val IMAGE_SIZE_KEY = "IMAGE_SIZE_KEY" - private const val IMAGE_QUALITY_KEY = "IMAGE_QUALITY_KEY" - private const val IMAGE_TYPE_KEY = "IMAGE_TYPE_KEY" - - private const val DEFAULT_IMAGE_TYPE = "jpg" - } - private val preferences = context.getSharedPreferences("ZDC_Shared_Preferences", Context.MODE_PRIVATE) - - - fun getImageSize(): Long { - return preferences.getLong(IMAGE_SIZE_KEY, -1L) - } - - fun setImageSize(size: Long) { - preferences.edit().putLong(IMAGE_SIZE_KEY, size).apply() - } - - fun getImageQuality(): Int { - return preferences.getInt(IMAGE_QUALITY_KEY, 100) - } - - fun setImageQuality(quality: Int) { - preferences.edit().putInt(IMAGE_QUALITY_KEY, quality).apply() - } - - fun getImageType(): Bitmap.CompressFormat { - return compressFormat(preferences.getString(IMAGE_TYPE_KEY, DEFAULT_IMAGE_TYPE)!!) - } - - fun setImageType(type: Bitmap.CompressFormat) { - preferences.edit().putString(IMAGE_TYPE_KEY, type.extension()).apply() - } - - private fun compressFormat(format: String) = when (format.lowercase(Locale.getDefault())) { - "png" -> Bitmap.CompressFormat.PNG - "webp" -> Bitmap.CompressFormat.WEBP - else -> Bitmap.CompressFormat.JPEG - } -} diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/model/DocumentScannerErrorModel.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/model/DocumentScannerErrorModel.kt deleted file mode 100644 index 1cc0f9d99a..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/model/DocumentScannerErrorModel.kt +++ /dev/null @@ -1,38 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.model - -data class DocumentScannerErrorModel( - var errorMessage: ErrorMessage? = null, - var throwable: Throwable? = null -) { - enum class ErrorMessage(val error: String){ - TAKE_IMAGE_FROM_GALLERY_ERROR("TAKE_IMAGE_FROM_GALLERY_ERROR"), - PHOTO_CAPTURE_FAILED("PHOTO_CAPTURE_FAILED"), - CAMERA_USE_CASE_BINDING_FAILED("CAMERA_USE_CASE_BINDING_FAILED"), - DETECT_LARGEST_QUADRILATERAL_FAILED("DETECT_LARGEST_QUADRILATERAL_FAILED"), - INVALID_IMAGE("INVALID_IMAGE"), - CAMERA_PERMISSION_REFUSED_WITHOUT_NEVER_ASK_AGAIN("CAMERA_PERMISSION_REFUSED_WITHOUT_NEVER_ASK_AGAIN"), - CAMERA_PERMISSION_REFUSED_GO_TO_SETTINGS("CAMERA_PERMISSION_REFUSED_GO_TO_SETTINGS"), - STORAGE_PERMISSION_REFUSED_WITHOUT_NEVER_ASK_AGAIN("STORAGE_PERMISSION_REFUSED_WITHOUT_NEVER_ASK_AGAIN"), - STORAGE_PERMISSION_REFUSED_GO_TO_SETTINGS("STORAGE_PERMISSION_REFUSED_GO_TO_SETTINGS"), - CROPPING_FAILED("CROPPING_FAILED"); - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/model/ScannerResults.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/model/ScannerResults.kt deleted file mode 100644 index 6bdf126742..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/model/ScannerResults.kt +++ /dev/null @@ -1,28 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.model - -import java.io.File - -data class ScannerResults ( - val originalImageFile: File? = null, - val croppedImageFile: File? = null, - val transformedImageFile: File? = null -) \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/DocumentScanner.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/DocumentScanner.kt deleted file mode 100644 index c335a5093c..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/DocumentScanner.kt +++ /dev/null @@ -1,46 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui - -import android.content.Context -import android.graphics.Bitmap -import com.zynksoftware.documentscanner.manager.SessionManager - -object DocumentScanner { - - fun init(context: Context, configuration: Configuration = Configuration()) { - System.loadLibrary("opencv_java4") - val sessionManager = SessionManager(context) - if(configuration.imageQuality in 1..100) { - sessionManager.setImageQuality(configuration.imageQuality) - } - sessionManager.setImageSize(configuration.imageSize) - sessionManager.setImageType(configuration.imageType) - } - - - data class Configuration( - var imageQuality: Int = 100, - var imageSize: Long = -1, - var imageType: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG - ){ - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/base/BaseFragment.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/base/BaseFragment.kt deleted file mode 100644 index 0984bb5311..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/base/BaseFragment.kt +++ /dev/null @@ -1,58 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.ui.base - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.RelativeLayout -import androidx.fragment.app.Fragment -import androidx.viewbinding.ViewBinding -import com.zynksoftware.documentscanner.R -import com.zynksoftware.documentscanner.common.extensions.hide -import com.zynksoftware.documentscanner.common.extensions.show - -internal abstract class BaseFragment(private val bindingInflater: (layoutInflater: LayoutInflater) -> BINDING) : Fragment() { - - private var _binding: BINDING? = null - - // This property is only valid between onCreateView and onDestroyView. - val binding get() = _binding!! - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - _binding = bindingInflater(inflater) - return binding.root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - fun showProgressBar() { - view?.findViewById(R.id.progressLayout)?.show() - } - - fun hideProgressBar() { - view?.findViewById(R.id.progressLayout)?.hide() - } - -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/camerascreen/CameraScreenFragment.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/camerascreen/CameraScreenFragment.kt deleted file mode 100644 index 903a80640c..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/camerascreen/CameraScreenFragment.kt +++ /dev/null @@ -1,213 +0,0 @@ -/** - Copyright 2020 ZynkSoftware SRL - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - associated documentation files (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, publish, distribute, - sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or - substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.ui.camerascreen - -import android.Manifest -import android.net.Uri -import android.os.Bundle -import android.util.Log -import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.content.ContextCompat -import com.zynksoftware.documentscanner.R -import com.zynksoftware.documentscanner.common.extensions.hide -import com.zynksoftware.documentscanner.common.extensions.show -import com.zynksoftware.documentscanner.common.utils.FileUriUtils -import com.zynksoftware.documentscanner.databinding.FragmentCameraScreenBinding -import com.zynksoftware.documentscanner.model.DocumentScannerErrorModel -import com.zynksoftware.documentscanner.ui.base.BaseFragment -import com.zynksoftware.documentscanner.ui.components.scansurface.ScanSurfaceListener -import com.zynksoftware.documentscanner.ui.scan.InternalScanActivity -import java.io.File -import java.io.FileNotFoundException - - -internal class CameraScreenFragment: BaseFragment(FragmentCameraScreenBinding::inflate), ScanSurfaceListener { - - private val requestCameraPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> - if (isGranted) { - startCamera() - } else { - onError(DocumentScannerErrorModel(DocumentScannerErrorModel.ErrorMessage.CAMERA_PERMISSION_REFUSED_GO_TO_SETTINGS)) - } - } - - private val filePickerContract = registerForActivityResult(ActivityResultContracts.GetContent()) { - handleSelectedFile(it) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) { - super.onViewCreated(view, savedInstanceState) - - scanSurfaceView.lifecycleOwner = this@CameraScreenFragment - scanSurfaceView.listener = this@CameraScreenFragment - scanSurfaceView.originalImageFile = getScanActivity().originalImageFile - - checkForCameraPermissions() - initListeners() - } - - override fun onDestroy() { - super.onDestroy() - if(getScanActivity().shouldCallOnClose) { - getScanActivity().onClose() - } - } - - override fun onResume() { - super.onResume() - getScanActivity().reInitOriginalImageFile() - binding.scanSurfaceView.originalImageFile = getScanActivity().originalImageFile - } - - private fun initListeners() = with(binding) { - cameraCaptureButton.setOnClickListener { - takePhoto() - } - cancelButton.setOnClickListener { - finishActivity() - } - flashButton.setOnClickListener { - switchFlashState() - } - galleryButton.setOnClickListener { - selectImageFromGallery() - } - autoButton.setOnClickListener { - toggleAutoManualButton() - } - } - - private fun toggleAutoManualButton() = with(binding) { - scanSurfaceView.isAutoCaptureOn = !scanSurfaceView.isAutoCaptureOn - if (scanSurfaceView.isAutoCaptureOn) { - autoButton.text = getString(R.string.zdc_auto) - } else { - autoButton.text = getString(R.string.zdc_manual) - } - } - - private fun checkForCameraPermissions() { - ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.CAMERA).let { - if (it == android.content.pm.PackageManager.PERMISSION_GRANTED) { - startCamera() - } else { - requestCameraPermission.launch(Manifest.permission.CAMERA) - } - } - } - - private fun startCamera() { - binding.scanSurfaceView.start() - } - - private fun takePhoto() { - binding.scanSurfaceView.takePicture() - } - - private fun getScanActivity(): InternalScanActivity { - return (requireActivity() as InternalScanActivity) - } - - private fun finishActivity() { - getScanActivity().finish() - } - - private fun switchFlashState() { - binding.scanSurfaceView.switchFlashState() - } - - override fun showFlash() { - binding.flashButton.show() - } - - override fun hideFlash() { - binding.flashButton.hide() - } - - private fun selectImageFromGallery() { - filePickerContract.launch("image/*") - } - - private fun handleSelectedFile(imageUri: Uri?) { - try { - if (imageUri != null) { - val realPath = FileUriUtils.getRealPath(getScanActivity(), imageUri) - if (realPath != null) { - getScanActivity().reInitOriginalImageFile() - getScanActivity().originalImageFile = File(realPath) - startCroppingProcess() - } else { - Log.e(TAG, DocumentScannerErrorModel.ErrorMessage.TAKE_IMAGE_FROM_GALLERY_ERROR.error) - onError(DocumentScannerErrorModel( - DocumentScannerErrorModel.ErrorMessage.TAKE_IMAGE_FROM_GALLERY_ERROR, null)) - } - } else { - Log.e(TAG, DocumentScannerErrorModel.ErrorMessage.TAKE_IMAGE_FROM_GALLERY_ERROR.error) - onError(DocumentScannerErrorModel( - DocumentScannerErrorModel.ErrorMessage.TAKE_IMAGE_FROM_GALLERY_ERROR, null)) - } - } catch (e: FileNotFoundException) { - Log.e(TAG, "FileNotFoundException", e) - onError(DocumentScannerErrorModel( - DocumentScannerErrorModel.ErrorMessage.TAKE_IMAGE_FROM_GALLERY_ERROR, e)) - } - } - - override fun scanSurfacePictureTaken() { - startCroppingProcess() - } - - private fun startCroppingProcess() { - if (isAdded) { - getScanActivity().showImageCropFragment() - } - } - - override fun scanSurfaceShowProgress() { - showProgressBar() - } - - override fun scanSurfaceHideProgress() { - hideProgressBar() - } - - override fun onError(error: DocumentScannerErrorModel) { - if(isAdded) { - getScanActivity().onError(error) - } - } - - override fun showFlashModeOn() { - binding.flashButton.setImageResource(R.drawable.zdc_flash_on) - } - - override fun showFlashModeOff() { - binding.flashButton.setImageResource(R.drawable.zdc_flash_off) - } - - companion object { - private val TAG = CameraScreenFragment::class.simpleName - - fun newInstance(): CameraScreenFragment { - return CameraScreenFragment() - } - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/ProgressView.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/ProgressView.kt deleted file mode 100644 index f8ab5787e8..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/ProgressView.kt +++ /dev/null @@ -1,38 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.components - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.RelativeLayout -import com.zynksoftware.documentscanner.R - -internal class ProgressView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : RelativeLayout(context, attrs, defStyleAttr) { - - init { - LayoutInflater.from(context).inflate(R.layout.progress_layout, this, true) - } -} diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/Quadrilateral.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/Quadrilateral.kt deleted file mode 100644 index e00482089e..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/Quadrilateral.kt +++ /dev/null @@ -1,26 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.components - -import org.opencv.core.MatOfPoint2f -import org.opencv.core.Point - -internal class Quadrilateral(val contour: MatOfPoint2f, val points: Array) \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/ScanCanvasView.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/ScanCanvasView.kt deleted file mode 100644 index af550b984b..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/ScanCanvasView.kt +++ /dev/null @@ -1,183 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.components - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.Path -import android.os.Handler -import android.os.Looper -import android.util.AttributeSet -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.core.content.ContextCompat -import com.zynksoftware.documentscanner.R -import org.opencv.core.Point - -internal class ScanCanvasView : FrameLayout { - - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - companion object { - private const val CLEAR_SHAPE_DELAY_IN_MILLIS = 600L - private const val POINTER_ANIMATION_DURATION = 300L - } - - private var paint = Paint() - private var border = Paint() - private val handlerClear = Handler(Looper.getMainLooper()) - - private var shouldAnimate = true - - var pointer1: View = View(context) - var pointer2: View = View(context) - var pointer3: View = View(context) - var pointer4: View = View(context) - - init { - paint.color = ContextCompat.getColor(context, R.color.zdc_white_transparent) - border.color = ContextCompat.getColor(context, android.R.color.white) - border.strokeWidth = context.resources.getDimension(R.dimen.zdc_polygon_line_stroke_width) - border.style = Paint.Style.STROKE - border.isAntiAlias = true - paint.isAntiAlias = true - - pointer1.layoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - pointer2.layoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - pointer3.layoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - pointer4.layoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - - clearPointersPosition() - - addView(pointer1) - addView(pointer2) - addView(pointer3) - addView(pointer4) - } - - private fun clearPointersPosition() { - pointer1.x = 0F - pointer1.y = 0F - pointer2.x = 0F - pointer2.y = 0F - pointer3.x = 0F - pointer3.y = 0F - pointer4.x = 0F - pointer4.y = 0F - } - - override fun dispatchDraw(canvas: Canvas) { - super.dispatchDraw(canvas) - - previewWidth?.let { previewWidth -> - previewHeight?.let { previewHeight -> - canvas.scale(width / previewWidth, height / previewHeight) - } - } - - canvas.drawLine(pointer1.x, pointer1.y, pointer4.x, pointer4.y, border) - canvas.drawLine(pointer1.x, pointer1.y, pointer2.x, pointer2.y, border) - canvas.drawLine(pointer3.x, pointer3.y, pointer4.x, pointer4.y, border) - canvas.drawLine(pointer2.x, pointer2.y, pointer3.x, pointer3.y, border) - - val path = Path() - path.moveTo(pointer1.x, pointer1.y) - path.lineTo(pointer2.x, pointer2.y) - path.lineTo(pointer3.x, pointer3.y) - path.lineTo(pointer4.x, pointer4.y) - path.close() - - path.let { - canvas.drawPath(it, paint) - } - } - - var previewWidth: Float? = null - var previewHeight: Float? = null - - fun showShape(previewWidth: Float, previewHeight: Float, points: Array) { - this.previewWidth = previewWidth - this.previewHeight = previewHeight - - val pointer1x = previewWidth - points[0].y.toFloat() - val pointer1y = points[0].x.toFloat() - val pointer2x = previewWidth - points[1].y.toFloat() - val pointer2y = points[1].x.toFloat() - val pointer3x = previewWidth - points[2].y.toFloat() - val pointer3y = points[2].x.toFloat() - val pointer4x = previewWidth - points[3].y.toFloat() - val pointer4y = points[3].x.toFloat() - - if (pointer1.x == 0F && pointer1.y == 0F) { - pointer1.x = pointer1x - pointer1.y = pointer1y - pointer2.x = pointer2x - pointer2.y = pointer2y - pointer3.x = pointer3x - pointer3.y = pointer3y - pointer4.x = pointer4x - pointer4.y = pointer4y - } else { - if (shouldAnimate) { - shouldAnimate = false - - pointer1.animate().translationX(pointer1x).translationY(pointer1y) - .setDuration(POINTER_ANIMATION_DURATION).withEndAction { - shouldAnimate = true - }.start() - - pointer2.animate().translationX(pointer2x).translationY(pointer2y) - .setDuration(POINTER_ANIMATION_DURATION).withEndAction { - shouldAnimate = true - }.start() - - pointer3.animate().translationX(pointer3x).translationY(pointer3y) - .setDuration(POINTER_ANIMATION_DURATION).withEndAction { - shouldAnimate = true - }.start() - - pointer4.animate().translationX(pointer4x).translationY(pointer4y) - .setDuration(POINTER_ANIMATION_DURATION).withEndAction { - shouldAnimate = true - }.start() - } - } - - handlerClear.removeCallbacks(runnable) - invalidate() - } - - fun clearShape() { - handlerClear.postDelayed(runnable, CLEAR_SHAPE_DELAY_IN_MILLIS) - } - - private val runnable = Runnable { - pointer1.clearAnimation() - pointer2.clearAnimation() - pointer3.clearAnimation() - pointer4.clearAnimation() - clearPointersPosition() - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/polygon/PolygonPointImageView.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/polygon/PolygonPointImageView.kt deleted file mode 100644 index 15b82ece1c..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/polygon/PolygonPointImageView.kt +++ /dev/null @@ -1,85 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.zynksoftware.documentscanner.ui.components.polygon - -import android.content.Context -import android.graphics.PointF -import android.util.AttributeSet -import android.view.MotionEvent -import androidx.appcompat.widget.AppCompatImageView -import androidx.core.content.ContextCompat -import com.zynksoftware.documentscanner.R - -internal class PolygonPointImageView @JvmOverloads constructor( - context: Context, - private val polygonView: PolygonView? = null, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : AppCompatImageView(context, attrs, defStyleAttr) { - - private var downPoint = PointF() - private var startPoint = PointF() - - override fun onTouchEvent(event: MotionEvent): Boolean { - super.onTouchEvent(event) - - if (polygonView != null) { - when (event.action) { - MotionEvent.ACTION_MOVE -> { - val mv = PointF(event.x - downPoint.x, event.y - downPoint.y) - if (startPoint.x + mv.x + width < polygonView.width && - startPoint.y + mv.y + height < polygonView.height && - startPoint.x + mv.x > 0 && startPoint.y + mv.y > 0 - ) { - x = startPoint.x + mv.x - y = startPoint.y + mv.y - startPoint = PointF(x, y) - } - } - MotionEvent.ACTION_DOWN -> { - downPoint.x = event.x - downPoint.y = event.y - startPoint = PointF(x, y) - } - MotionEvent.ACTION_UP -> { - performClick() - } - } - polygonView.invalidate() - } - return true - } - - // Because we call this from onTouchEvent, this code will be executed for both - // normal touch events and for when the system calls this using Accessibility - override fun performClick(): Boolean { - super.performClick() - - val color = if (polygonView?.isValidShape(polygonView.getPoints()) == true) { - ContextCompat.getColor(context, android.R.color.white) - } else { - ContextCompat.getColor(context, R.color.zdc_red) - } - polygonView?.paint?.color = color - - return true - } - -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/polygon/PolygonView.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/polygon/PolygonView.kt deleted file mode 100644 index 001f697f5d..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/polygon/PolygonView.kt +++ /dev/null @@ -1,183 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.components.polygon - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.PointF -import android.util.AttributeSet -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.core.content.ContextCompat -import com.zynksoftware.documentscanner.R -import java.util.* - -internal class PolygonView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - - var paint: Paint = Paint() - private var pointer1: ImageView - private var pointer2: ImageView - private var pointer3: ImageView - private var pointer4: ImageView - private var pointPadding = resources.getDimension(R.dimen.zdc_point_padding).toInt() - - companion object { - private val TAG = PolygonView::class.simpleName - private const val HALF = 2 - private const val THREE_PARTS = 3 - } - - init { - pointer1 = getImageView(0, 0) - pointer2 = getImageView(width, 0) - pointer3 = getImageView(0, height) - pointer4 = getImageView(width, height) - - addView(pointer1) - addView(pointer2) - addView(pointer3) - addView(pointer4) - - paint.color = ContextCompat.getColor(context, android.R.color.white) - paint.strokeWidth = context.resources.getDimension(R.dimen.zdc_polygon_line_stroke_width) - paint.isAntiAlias = true - } - - fun getOrderedValidEdgePoints(tempBitmap: Bitmap, pointFs: List): Map { - var orderedPoints: Map = getOrderedPoints(pointFs) - if (!isValidShape(orderedPoints)) { - orderedPoints = getOutlinePoints(tempBitmap) - } - return orderedPoints - } - - fun setPoints(pointFMap: Map) { - if (pointFMap.size == 4) { - setPointsCoordinates(pointFMap) - } - } - - fun getPoints(): Map { - val points: MutableList = ArrayList() - points.add(PointF(pointer1.x, pointer1.y)) - points.add(PointF(pointer2.x, pointer2.y)) - points.add(PointF(pointer3.x, pointer3.y)) - points.add(PointF(pointer4.x, pointer4.y)) - return getOrderedPoints(points) - } - - fun isValidShape(pointFMap: Map): Boolean { - return pointFMap.size == 4 - } - - private fun getOutlinePoints(tempBitmap: Bitmap): Map { - val offsetWidth = (tempBitmap.width / THREE_PARTS).toFloat() - val offsetHeight = (tempBitmap.height / THREE_PARTS).toFloat() - val screenXCenter = tempBitmap.width / HALF - val screenYCenter = tempBitmap.height / HALF - val outlinePoints: MutableMap = HashMap() - outlinePoints[0] = PointF(screenXCenter - offsetWidth, screenYCenter - offsetHeight) - outlinePoints[1] = PointF(screenXCenter + offsetWidth, screenYCenter - offsetHeight) - outlinePoints[2] = PointF(screenXCenter - offsetWidth , screenYCenter + offsetHeight) - outlinePoints[3] = PointF(screenXCenter + offsetWidth, screenYCenter + offsetHeight) - return outlinePoints - } - - private fun getOrderedPoints(points: List): Map { - val centerPoint = PointF() - val size = points.size - for (pointF in points) { - centerPoint.x += pointF.x / size - centerPoint.y += pointF.y / size - } - val orderedPoints: MutableMap = HashMap() - for (pointF in points) { - var index = -1 - if (pointF.x < centerPoint.x && pointF.y < centerPoint.y) { - index = 0 - } else if (pointF.x > centerPoint.x && pointF.y < centerPoint.y) { - index = 1 - } else if (pointF.x < centerPoint.x && pointF.y > centerPoint.y) { - index = 2 - } else if (pointF.x > centerPoint.x && pointF.y > centerPoint.y) { - index = 3 - } - orderedPoints[index] = pointF - } - return orderedPoints - } - - private fun setPointsCoordinates(pointFMap: Map) { - pointer1.x = pointFMap.getValue(0).x - pointPadding - pointer1.y = pointFMap.getValue(0).y - pointPadding - - pointer2.x = pointFMap.getValue(1).x - pointPadding - pointer2.y = pointFMap.getValue(1).y - pointPadding - - pointer3.x = pointFMap.getValue(2).x - pointPadding - pointer3.y = pointFMap.getValue(2).y - pointPadding - - pointer4.x = pointFMap.getValue(3).x - pointPadding - pointer4.y = pointFMap.getValue(3).y - pointPadding - } - - override fun dispatchDraw(canvas: Canvas) { - super.dispatchDraw(canvas) - canvas.drawLine( - pointer1.x + pointer1.width / 2, pointer1.y + pointer1.height / 2, - pointer3.x + pointer3.width / 2, pointer3.y + pointer3.height / 2, - paint - ) - canvas.drawLine( - pointer1.x + pointer1.width / 2, pointer1.y + pointer1.height / 2, - pointer2.x + pointer2.width / 2, pointer2.y + pointer2.height / 2, - paint - ) - canvas.drawLine( - pointer2.x + pointer2.width / 2, pointer2.y + pointer2.height / 2, - pointer4.x + pointer4.width / 2, pointer4.y + pointer4.height / 2, - paint - ) - canvas.drawLine( - pointer3.x + pointer3.width / 2, pointer3.y + pointer3.height / 2, - pointer4.x + pointer4.width / 2, pointer4.y + pointer4.height / 2, - paint - ) - } - - private fun getImageView(x: Int, y: Int): ImageView { - val imageView = PolygonPointImageView(context, this) - val layoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - imageView.layoutParams = layoutParams - imageView.setImageResource(R.drawable.crop_corner_circle) - imageView.setPadding(pointPadding, pointPadding, pointPadding, pointPadding) - imageView.x = x.toFloat() - imageView.y = y.toFloat() - return imageView - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/scansurface/ScanSurfaceListener.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/scansurface/ScanSurfaceListener.kt deleted file mode 100644 index 0c742f817b..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/scansurface/ScanSurfaceListener.kt +++ /dev/null @@ -1,35 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.components.scansurface - -import com.zynksoftware.documentscanner.model.DocumentScannerErrorModel - -internal interface ScanSurfaceListener { - fun scanSurfacePictureTaken() - fun scanSurfaceShowProgress() - fun scanSurfaceHideProgress() - fun onError(error: DocumentScannerErrorModel) - - fun showFlash() - fun hideFlash() - fun showFlashModeOn() - fun showFlashModeOff() -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/scansurface/ScanSurfaceView.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/scansurface/ScanSurfaceView.kt deleted file mode 100755 index 88e14f42fb..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/components/scansurface/ScanSurfaceView.kt +++ /dev/null @@ -1,289 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.components.scansurface - -import android.content.Context -import android.os.CountDownTimer -import android.util.AttributeSet -import android.util.Log -import android.view.LayoutInflater -import android.view.Surface -import android.widget.FrameLayout -import androidx.camera.core.* -import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.core.content.ContextCompat -import androidx.lifecycle.LifecycleOwner -import com.zynksoftware.documentscanner.R -import com.zynksoftware.documentscanner.common.extensions.yuvToRgba -import com.zynksoftware.documentscanner.common.utils.ImageDetectionProperties -import com.zynksoftware.documentscanner.common.utils.OpenCvNativeBridge -import com.zynksoftware.documentscanner.databinding.ScanSurfaceViewBinding -import com.zynksoftware.documentscanner.model.DocumentScannerErrorModel -import com.zynksoftware.documentscanner.model.DocumentScannerErrorModel.ErrorMessage -import org.opencv.core.MatOfPoint2f -import org.opencv.core.Point -import org.opencv.core.Size -import java.io.File -import kotlin.math.max -import kotlin.math.min -import kotlin.math.roundToInt - -private val TAG = ScanSurfaceView::class.simpleName - -private const val TIME_POST_PICTURE = 1500L -private const val DEFAULT_TIME_POST_PICTURE = 1500L -private const val IMAGE_ANALYSIS_SCALE_WIDTH = 400 - -internal class ScanSurfaceView : FrameLayout { - - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - private val binding: ScanSurfaceViewBinding - - lateinit var lifecycleOwner: LifecycleOwner - lateinit var listener: ScanSurfaceListener - lateinit var originalImageFile: File - - private val nativeClass = OpenCvNativeBridge() - private var autoCaptureTimer: CountDownTimer? = null - private var millisLeft = 0L - private var isAutoCaptureScheduled = false - private var isCapturing = false - - private var imageAnalysis: ImageAnalysis? = null - private var camera: Camera? = null - private var imageCapture: ImageCapture? = null - private var preview: Preview? = null - private var cameraProvider: ProcessCameraProvider? = null - private lateinit var previewSize: android.util.Size - - var isAutoCaptureOn: Boolean = true - private var isFlashEnabled: Boolean = false - private var flashMode: Int = ImageCapture.FLASH_MODE_OFF - - init { - binding = ScanSurfaceViewBinding.inflate(LayoutInflater.from(context), this, true) - } - - fun start() = with(binding) { - viewFinder.post { - viewFinder.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) - previewSize = android.util.Size(viewFinder.width, viewFinder.height) - openCamera() - } - } - - private fun clearAndInvalidateCanvas() { - binding.scanCanvasView.clearShape() - } - - private fun openCamera() { - val cameraProviderFuture = ProcessCameraProvider.getInstance(context) - - cameraProviderFuture.addListener(Runnable { - cameraProvider = cameraProviderFuture.get() - - try { - bindCamera() - checkIfFlashIsPresent() - } catch (exc: Exception) { - Log.e(TAG, ErrorMessage.CAMERA_USE_CASE_BINDING_FAILED.error, exc) - listener.onError(DocumentScannerErrorModel(ErrorMessage.CAMERA_USE_CASE_BINDING_FAILED, exc)) - } - }, ContextCompat.getMainExecutor(context)) - } - - private fun bindCamera() { - cameraProvider?.unbindAll() - camera = null - setUseCases() - } - - private fun setImageCapture() { - if(imageCapture != null && cameraProvider?.isBound(imageCapture!!) == true) { - cameraProvider?.unbind(imageCapture) - } - - imageCapture = null - imageCapture = ImageCapture.Builder() - .setTargetRotation(Surface.ROTATION_0) - .setFlashMode(flashMode) - .build() - } - - fun unbindCamera() { - cameraProvider?.unbind(imageAnalysis) - } - - private fun setUseCases() { - preview = Preview.Builder() - .setTargetResolution(previewSize) - .setTargetRotation(Surface.ROTATION_0) - .build() - .also { - it.setSurfaceProvider(binding.viewFinder.surfaceProvider) - } - - setImageCapture() - - val aspectRatio: Float = previewSize.width / previewSize.height.toFloat() - val width = IMAGE_ANALYSIS_SCALE_WIDTH - val height = (width / aspectRatio).roundToInt() - - imageAnalysis = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .setTargetResolution(android.util.Size(width, height)) - .setTargetRotation(Surface.ROTATION_0) - .build() - - imageAnalysis?.setAnalyzer(ContextCompat.getMainExecutor(context), { image -> - if (isAutoCaptureOn) { - try { - val mat = image.yuvToRgba() - val originalPreviewSize = mat.size() - val largestQuad = nativeClass.detectLargestQuadrilateral(mat) - mat.release() - if (null != largestQuad) { - drawLargestRect(largestQuad.contour, largestQuad.points, originalPreviewSize) - } else { - clearAndInvalidateCanvas() - } - } catch (e: Exception) { - Log.e(TAG, ErrorMessage.DETECT_LARGEST_QUADRILATERAL_FAILED.error, e) - listener.onError(DocumentScannerErrorModel(ErrorMessage.DETECT_LARGEST_QUADRILATERAL_FAILED, e)) - clearAndInvalidateCanvas() - } - } else { - clearAndInvalidateCanvas() - } - image.close() - }) - - camera = cameraProvider!!.bindToLifecycle(lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis, imageCapture) - } - - private fun drawLargestRect(approx: MatOfPoint2f, points: Array, stdSize: Size) { - // Attention: axis are swapped - val previewWidth = stdSize.height.toFloat() - val previewHeight = stdSize.width.toFloat() - - val resultWidth = max(previewWidth - points[0].y.toFloat(), previewWidth - points[1].y.toFloat()) - - min(previewWidth - points[2].y.toFloat(), previewWidth - points[3].y.toFloat()) - - val resultHeight = max(points[1].x.toFloat(), points[2].x.toFloat()) - min(points[0].x.toFloat(), points[3].x.toFloat()) - - val imgDetectionPropsObj = ImageDetectionProperties(previewWidth.toDouble(), previewHeight.toDouble(), - points[0], points[1], points[2], points[3], resultWidth.toInt(), resultHeight.toInt()) - if (imgDetectionPropsObj.isNotValidImage(approx)) { - binding.scanCanvasView.clearShape() - cancelAutoCapture() - } else { - if (!isAutoCaptureScheduled) { - scheduleAutoCapture() - } - binding.scanCanvasView.showShape(previewWidth, previewHeight, points) - } - } - - private fun scheduleAutoCapture() { - isAutoCaptureScheduled = true - millisLeft = 0L - autoCaptureTimer = object : CountDownTimer(DEFAULT_TIME_POST_PICTURE, 100) { - override fun onTick(millisUntilFinished: Long) { - if (millisUntilFinished != millisLeft) { - millisLeft = millisUntilFinished - } - } - - override fun onFinish() { - isAutoCaptureScheduled = false - autoCapture() - } - } - autoCaptureTimer?.start() - } - - private fun autoCapture() { - if (isCapturing) - return - cancelAutoCapture() - takePicture() - } - - fun takePicture() { - Log.d(TAG, "ZDCtakePicture Starts ${System.currentTimeMillis()}") - listener.scanSurfaceShowProgress() - isCapturing = true - - val imageCapture = imageCapture ?: return - val outputOptions = ImageCapture.OutputFileOptions.Builder(originalImageFile).build() - - imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(context), - object : ImageCapture.OnImageSavedCallback { - override fun onError(exc: ImageCaptureException) { - listener.scanSurfaceHideProgress() - Log.e(TAG, "${ErrorMessage.PHOTO_CAPTURE_FAILED.error}: ${exc.message}", exc) - listener.onError(DocumentScannerErrorModel(ErrorMessage.PHOTO_CAPTURE_FAILED, exc)) - } - - override fun onImageSaved(output: ImageCapture.OutputFileResults) { - listener.scanSurfaceHideProgress() - - unbindCamera() - - clearAndInvalidateCanvas() - listener.scanSurfacePictureTaken() - postDelayed({ isCapturing = false }, TIME_POST_PICTURE) - Log.d(TAG, "ZDCtakePicture ends ${System.currentTimeMillis()}") - } - }) - } - - private fun checkIfFlashIsPresent() { - if (camera?.cameraInfo?.hasFlashUnit() == true) { - listener.showFlash() - } else { - listener.hideFlash() - } - } - - private fun cancelAutoCapture() { - if (isAutoCaptureScheduled) { - isAutoCaptureScheduled = false - autoCaptureTimer?.cancel() - } - } - - fun switchFlashState() { - isFlashEnabled = !isFlashEnabled - flashMode = if (isFlashEnabled) { - listener.showFlashModeOn() - ImageCapture.FLASH_MODE_ON - } else { - listener.showFlashModeOff() - ImageCapture.FLASH_MODE_OFF - } - setImageCapture() - camera = cameraProvider!!.bindToLifecycle(lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, imageCapture) - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/imagecrop/ImageCropFragment.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/imagecrop/ImageCropFragment.kt deleted file mode 100644 index 8ab1ef6633..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/imagecrop/ImageCropFragment.kt +++ /dev/null @@ -1,159 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.imagecrop - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.PointF -import android.graphics.drawable.BitmapDrawable -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.util.Log -import android.view.Gravity -import android.view.View -import android.widget.FrameLayout -import com.zynksoftware.documentscanner.R -import com.zynksoftware.documentscanner.ScanActivity -import com.zynksoftware.documentscanner.common.extensions.scaledBitmap -import com.zynksoftware.documentscanner.common.utils.OpenCvNativeBridge -import com.zynksoftware.documentscanner.databinding.FragmentImageCropBinding -import com.zynksoftware.documentscanner.model.DocumentScannerErrorModel -import com.zynksoftware.documentscanner.ui.base.BaseFragment -import com.zynksoftware.documentscanner.ui.scan.InternalScanActivity -import id.zelory.compressor.determineImageRotation - -internal class ImageCropFragment : BaseFragment(FragmentImageCropBinding::inflate) { - - private val nativeClass = OpenCvNativeBridge() - - private var selectedImage: Bitmap? = null - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val sourceBitmap = BitmapFactory.decodeFile(getScanActivity().originalImageFile.absolutePath) - if (sourceBitmap != null) { - selectedImage = determineImageRotation(getScanActivity().originalImageFile, sourceBitmap) - } else { - Log.e(TAG, DocumentScannerErrorModel.ErrorMessage.INVALID_IMAGE.error) - onError(DocumentScannerErrorModel(DocumentScannerErrorModel.ErrorMessage.INVALID_IMAGE)) - Handler(Looper.getMainLooper()).post{ - closeFragment() - } - } - binding.holderImageView.post { - if (this.view != null) initializeCropping() - } - - initListeners() - } - - private fun initListeners() = with(binding) { - closeButton.setOnClickListener { - closeFragment() - } - confirmButton.setOnClickListener { - onConfirmButtonClicked() - } - } - - private fun getScanActivity(): InternalScanActivity { - return (requireActivity() as InternalScanActivity) - } - - private fun initializeCropping() = with(binding) { - if(selectedImage != null && selectedImage!!.width > 0 && selectedImage!!.height > 0) { - val scaledBitmap: Bitmap = selectedImage!!.scaledBitmap(holderImageCrop.width, holderImageCrop.height) - imagePreview.setImageBitmap(scaledBitmap) - val tempBitmap = (imagePreview.drawable as BitmapDrawable).bitmap - val pointFs = getEdgePoints(tempBitmap) - Log.d(TAG, "ZDCgetEdgePoints ends ${System.currentTimeMillis()}") - polygonView.setPoints(pointFs) - polygonView.visibility = View.VISIBLE - val padding = resources.getDimension(R.dimen.zdc_polygon_dimens).toInt() - val layoutParams = FrameLayout.LayoutParams(tempBitmap.width + padding, tempBitmap.height + padding) - layoutParams.gravity = Gravity.CENTER - polygonView.layoutParams = layoutParams - } - } - - private fun onError(error: DocumentScannerErrorModel) { - if (isAdded) { - getScanActivity().onError(error) - } - } - - private fun onConfirmButtonClicked() { - getCroppedImage() - (activity as ScanActivity).finalScannerResult() - } - - private fun getEdgePoints(tempBitmap: Bitmap): Map { - Log.d(TAG, "ZDCgetEdgePoints Starts ${System.currentTimeMillis()}") - val pointFs: List = nativeClass.getContourEdgePoints(tempBitmap) - return binding.polygonView.getOrderedValidEdgePoints(tempBitmap, pointFs) - } - - private fun getCroppedImage() { - if(selectedImage != null) { - try { - Log.d(TAG, "ZDCgetCroppedImage starts ${System.currentTimeMillis()}") - val points: Map = binding.polygonView.getPoints() - val xRatio: Float = selectedImage!!.width.toFloat() / binding.imagePreview.width - val yRatio: Float = selectedImage!!.height.toFloat() / binding.imagePreview.height - val pointPadding = requireContext().resources.getDimension(R.dimen.zdc_point_padding).toInt() - val x1: Float = (points.getValue(0).x + pointPadding) * xRatio - val x2: Float = (points.getValue(1).x + pointPadding) * xRatio - val x3: Float = (points.getValue(2).x + pointPadding) * xRatio - val x4: Float = (points.getValue(3).x + pointPadding) * xRatio - val y1: Float = (points.getValue(0).y + pointPadding) * yRatio - val y2: Float = (points.getValue(1).y + pointPadding) * yRatio - val y3: Float = (points.getValue(2).y + pointPadding) * yRatio - val y4: Float = (points.getValue(3).y + pointPadding) * yRatio - getScanActivity().croppedImage = nativeClass.getScannedBitmap(selectedImage!!, x1, y1, x2, y2, x3, y3, x4, y4) - Log.d(TAG, "ZDCgetCroppedImage ends ${System.currentTimeMillis()}") - } catch (e: java.lang.Exception) { - Log.e(TAG, DocumentScannerErrorModel.ErrorMessage.CROPPING_FAILED.error, e) - onError(DocumentScannerErrorModel(DocumentScannerErrorModel.ErrorMessage.CROPPING_FAILED, e)) - } - } else { - Log.e(TAG, DocumentScannerErrorModel.ErrorMessage.INVALID_IMAGE.error) - onError(DocumentScannerErrorModel(DocumentScannerErrorModel.ErrorMessage.INVALID_IMAGE)) - } - } - - private fun startImageProcessingFragment() { - getScanActivity().showImageProcessingFragment() - } - - private fun closeFragment() { - getScanActivity().closeCurrentFragment() - } - - companion object { - private val TAG = ImageCropFragment::class.simpleName - - fun newInstance(): ImageCropFragment { - return ImageCropFragment() - } - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/imageprocessing/ImageProcessingFragment.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/imageprocessing/ImageProcessingFragment.kt deleted file mode 100644 index 735a8fb1d6..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/imageprocessing/ImageProcessingFragment.kt +++ /dev/null @@ -1,136 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.imageprocessing - -import android.graphics.* -import android.os.Bundle -import android.util.Log -import android.view.View -import com.zynksoftware.documentscanner.common.extensions.rotateBitmap -import com.zynksoftware.documentscanner.databinding.FragmentImageProcessingBinding -import com.zynksoftware.documentscanner.ui.base.BaseFragment -import com.zynksoftware.documentscanner.ui.scan.InternalScanActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch - -internal class ImageProcessingFragment : BaseFragment(FragmentImageProcessingBinding::inflate) { - - private var isInverted = false - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.imagePreview.setImageBitmap(getScanActivity().croppedImage) - - initListeners() - } - - private fun initListeners() = with(binding) { - closeButton.setOnClickListener { - closeFragment() - } - confirmButton.setOnClickListener { - selectFinalScannerResults() - } - magicButton.setOnClickListener { - applyGrayScaleFilter() - } - rotateButton.setOnClickListener { - rotateImage() - } - } - - private fun getScanActivity(): InternalScanActivity { - return (requireActivity() as InternalScanActivity) - } - - private fun rotateImage() { - Log.d(TAG, "ZDCrotate starts ${System.currentTimeMillis()}") - showProgressBar() - GlobalScope.launch(Dispatchers.IO) { - if(isAdded) { - getScanActivity().transformedImage = getScanActivity().transformedImage?.rotateBitmap(ANGLE_OF_ROTATION) - getScanActivity().croppedImage = getScanActivity().croppedImage?.rotateBitmap(ANGLE_OF_ROTATION) - } - - if(isAdded) { - getScanActivity().runOnUiThread { - hideProgressBar() - if (isInverted) { - binding.imagePreview?.setImageBitmap(getScanActivity().transformedImage) - } else { - binding.imagePreview?.setImageBitmap(getScanActivity().croppedImage) - } - } - } - Log.d(TAG, "ZDCrotate ends ${System.currentTimeMillis()}") - } - } - - private fun closeFragment() { - getScanActivity().closeCurrentFragment() - } - - private fun applyGrayScaleFilter() { - Log.d(TAG, "ZDCgrayscale starts ${System.currentTimeMillis()}") - showProgressBar() - GlobalScope.launch(Dispatchers.IO) { - if(isAdded) { - if (!isInverted) { - val bmpMonochrome = Bitmap.createBitmap(getScanActivity().croppedImage!!.width, getScanActivity().croppedImage!!.height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bmpMonochrome) - val ma = ColorMatrix() - ma.setSaturation(0f) - val paint = Paint() - paint.colorFilter = ColorMatrixColorFilter(ma) - getScanActivity().croppedImage?.let { canvas.drawBitmap(it, 0f, 0f, paint) } - getScanActivity().transformedImage = - bmpMonochrome.config?.let { bmpMonochrome.copy(it, true) } - getScanActivity().runOnUiThread { - hideProgressBar() - binding.imagePreview.setImageBitmap(getScanActivity().transformedImage) - } - } else { - getScanActivity().runOnUiThread { - hideProgressBar() - binding.imagePreview.setImageBitmap(getScanActivity().croppedImage) - } - } - isInverted = !isInverted - Log.d(TAG, "ZDCgrayscale ends ${System.currentTimeMillis()}") - } - } - } - - private fun selectFinalScannerResults() { - getScanActivity().finalScannerResult() - } - - companion object { - private val TAG = ImageProcessingFragment::class.simpleName - private const val ANGLE_OF_ROTATION = 90 - - fun newInstance(): ImageProcessingFragment { - return ImageProcessingFragment() - } - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/scan/InternalScanActivity.kt b/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/scan/InternalScanActivity.kt deleted file mode 100644 index 6ae5012f43..0000000000 --- a/libs/DocumentScanner/src/main/java/com/zynksoftware/documentscanner/ui/scan/InternalScanActivity.kt +++ /dev/null @@ -1,196 +0,0 @@ -/** -Copyright 2020 ZynkSoftware SRL - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - -package com.zynksoftware.documentscanner.ui.scan - -import android.graphics.Bitmap -import android.os.Bundle -import android.util.Log -import android.widget.FrameLayout -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentTransaction -import com.zynksoftware.documentscanner.R -import com.zynksoftware.documentscanner.common.extensions.hide -import com.zynksoftware.documentscanner.common.extensions.show -import com.zynksoftware.documentscanner.manager.SessionManager -import com.zynksoftware.documentscanner.model.DocumentScannerErrorModel -import com.zynksoftware.documentscanner.model.ScannerResults -import com.zynksoftware.documentscanner.ui.camerascreen.CameraScreenFragment -import com.zynksoftware.documentscanner.ui.components.ProgressView -import com.zynksoftware.documentscanner.ui.imagecrop.ImageCropFragment -import com.zynksoftware.documentscanner.ui.imageprocessing.ImageProcessingFragment -import id.zelory.compressor.Compressor -import id.zelory.compressor.constraint.format -import id.zelory.compressor.constraint.quality -import id.zelory.compressor.constraint.size -import id.zelory.compressor.extension -import id.zelory.compressor.saveBitmap -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import java.io.File - -abstract class InternalScanActivity : AppCompatActivity() { - - abstract fun onError(error: DocumentScannerErrorModel) - abstract fun onSuccess(scannerResults: ScannerResults) - abstract fun onClose() - - companion object { - private val TAG = InternalScanActivity::class.simpleName - internal const val CAMERA_SCREEN_FRAGMENT_TAG = "CameraScreenFragmentTag" - internal const val IMAGE_CROP_FRAGMENT_TAG = "ImageCropFragmentTag" - internal const val IMAGE_PROCESSING_FRAGMENT_TAG = "ImageProcessingFragmentTag" - internal const val ORIGINAL_IMAGE_NAME = "original" - internal const val CROPPED_IMAGE_NAME = "cropped" - internal const val TRANSFORMED_IMAGE_NAME = "transformed" - internal const val NOT_INITIALIZED = -1L - } - - internal lateinit var originalImageFile: File - internal var croppedImage: Bitmap? = null - internal var transformedImage: Bitmap? = null - private var imageQuality: Int = 100 - private var imageSize: Long = NOT_INITIALIZED - private lateinit var imageType: Bitmap.CompressFormat - internal var shouldCallOnClose = true - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val sessionManager = SessionManager(this) - imageType = sessionManager.getImageType() - imageSize = sessionManager.getImageSize() - imageQuality = sessionManager.getImageQuality() - reInitOriginalImageFile() - } - - internal fun reInitOriginalImageFile() { - originalImageFile = File(filesDir, "${ORIGINAL_IMAGE_NAME}_${System.currentTimeMillis()}.${imageType.extension()}") - } - - private fun showCameraScreen() { - val cameraScreenFragment = CameraScreenFragment.newInstance() - addFragmentToBackStack(cameraScreenFragment, CAMERA_SCREEN_FRAGMENT_TAG) - } - - internal fun showImageCropFragment() { - val imageCropFragment = ImageCropFragment.newInstance() - addFragmentToBackStack(imageCropFragment, IMAGE_CROP_FRAGMENT_TAG) - } - - internal fun showImageProcessingFragment() { - val imageProcessingFragment = ImageProcessingFragment.newInstance() - addFragmentToBackStack(imageProcessingFragment, IMAGE_PROCESSING_FRAGMENT_TAG) - } - - internal fun closeCurrentFragment() { - supportFragmentManager.popBackStackImmediate() - } - - private fun addFragmentToBackStack(fragment: Fragment, fragmentTag: String) { - val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction() - fragmentTransaction.replace(R.id.zdcContent, fragment, fragmentTag) - if (supportFragmentManager.findFragmentByTag(fragmentTag) == null) { - fragmentTransaction.addToBackStack(fragmentTag) - } - fragmentTransaction.commit() - } - - internal fun finalScannerResult() { - findViewById(R.id.zdcContent).hide() - compressFiles() - } - - private fun compressFiles() { - Log.d(TAG, "ZDCcompress starts ${System.currentTimeMillis()}") - findViewById(R.id.zdcProgressView).show() - GlobalScope.launch(Dispatchers.IO) { - var croppedImageFile: File? = null - croppedImage?.let { - croppedImageFile = File(filesDir, "${CROPPED_IMAGE_NAME}_${System.currentTimeMillis()}.${imageType.extension()}") - saveBitmap(it, croppedImageFile!!, imageType, imageQuality) - } - - var transformedImageFile: File? = null - transformedImage?.let { - transformedImageFile = File(filesDir, "${TRANSFORMED_IMAGE_NAME}_${System.currentTimeMillis()}.${imageType.extension()}") - saveBitmap(it, transformedImageFile!!, imageType, imageQuality) - } - - originalImageFile = Compressor.compress(this@InternalScanActivity, originalImageFile) { - quality(imageQuality) - if (imageSize != NOT_INITIALIZED) size(imageSize) - format(imageType) - } - - croppedImageFile = croppedImageFile?.let { - Compressor.compress(this@InternalScanActivity, it) { - quality(imageQuality) - if (imageSize != NOT_INITIALIZED) size(imageSize) - format(imageType) - } - } - - transformedImageFile = transformedImageFile?.let { - Compressor.compress(this@InternalScanActivity, it) { - quality(imageQuality) - if (imageSize != NOT_INITIALIZED) size(imageSize) - format(imageType) - } - } - - val scannerResults = ScannerResults(originalImageFile, croppedImageFile, transformedImageFile) - runOnUiThread { - findViewById(R.id.zdcProgressView).hide() - shouldCallOnClose = false - supportFragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) - shouldCallOnClose = true - onSuccess(scannerResults) - Log.d(TAG, "ZDCcompress ends ${System.currentTimeMillis()}") - } - } - } - - internal fun addFragmentContentLayoutInternal() { - val frameLayout = FrameLayout(this) - frameLayout.id = R.id.zdcContent - addContentView( - frameLayout, FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ) - ) - - val progressView = ProgressView(this) - progressView.id = R.id.zdcProgressView - addContentView( - progressView, FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT - ) - ) - - progressView.hide() - - showCameraScreen() - } -} \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/drawable/camera_button_circle.xml b/libs/DocumentScanner/src/main/res/drawable/camera_button_circle.xml deleted file mode 100644 index 5bf8734e68..0000000000 --- a/libs/DocumentScanner/src/main/res/drawable/camera_button_circle.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/drawable/crop_corner_circle.xml b/libs/DocumentScanner/src/main/res/drawable/crop_corner_circle.xml deleted file mode 100644 index c42fe5877a..0000000000 --- a/libs/DocumentScanner/src/main/res/drawable/crop_corner_circle.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/drawable/iconclose.png b/libs/DocumentScanner/src/main/res/drawable/iconclose.png deleted file mode 100644 index 0297199d79..0000000000 Binary files a/libs/DocumentScanner/src/main/res/drawable/iconclose.png and /dev/null differ diff --git a/libs/DocumentScanner/src/main/res/drawable/zdc_flash_off.png b/libs/DocumentScanner/src/main/res/drawable/zdc_flash_off.png deleted file mode 100644 index ee5fe4afec..0000000000 Binary files a/libs/DocumentScanner/src/main/res/drawable/zdc_flash_off.png and /dev/null differ diff --git a/libs/DocumentScanner/src/main/res/drawable/zdc_flash_on.png b/libs/DocumentScanner/src/main/res/drawable/zdc_flash_on.png deleted file mode 100644 index 7dd46e03b2..0000000000 Binary files a/libs/DocumentScanner/src/main/res/drawable/zdc_flash_on.png and /dev/null differ diff --git a/libs/DocumentScanner/src/main/res/drawable/zdc_gallery_icon.png b/libs/DocumentScanner/src/main/res/drawable/zdc_gallery_icon.png deleted file mode 100644 index 0ef7562371..0000000000 Binary files a/libs/DocumentScanner/src/main/res/drawable/zdc_gallery_icon.png and /dev/null differ diff --git a/libs/DocumentScanner/src/main/res/drawable/zdc_magic_wand_icon.png b/libs/DocumentScanner/src/main/res/drawable/zdc_magic_wand_icon.png deleted file mode 100644 index 68a9b36a04..0000000000 Binary files a/libs/DocumentScanner/src/main/res/drawable/zdc_magic_wand_icon.png and /dev/null differ diff --git a/libs/DocumentScanner/src/main/res/drawable/zdc_rotation_icon.png b/libs/DocumentScanner/src/main/res/drawable/zdc_rotation_icon.png deleted file mode 100644 index 2f8a18d82b..0000000000 Binary files a/libs/DocumentScanner/src/main/res/drawable/zdc_rotation_icon.png and /dev/null differ diff --git a/libs/DocumentScanner/src/main/res/drawable/zdc_tick_icon.png b/libs/DocumentScanner/src/main/res/drawable/zdc_tick_icon.png deleted file mode 100644 index 15376f01c0..0000000000 Binary files a/libs/DocumentScanner/src/main/res/drawable/zdc_tick_icon.png and /dev/null differ diff --git a/libs/DocumentScanner/src/main/res/layout/fragment_camera_screen.xml b/libs/DocumentScanner/src/main/res/layout/fragment_camera_screen.xml deleted file mode 100644 index 3ed89f9606..0000000000 --- a/libs/DocumentScanner/src/main/res/layout/fragment_camera_screen.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/layout/fragment_image_crop.xml b/libs/DocumentScanner/src/main/res/layout/fragment_image_crop.xml deleted file mode 100644 index 726f2648a0..0000000000 --- a/libs/DocumentScanner/src/main/res/layout/fragment_image_crop.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/layout/fragment_image_processing.xml b/libs/DocumentScanner/src/main/res/layout/fragment_image_processing.xml deleted file mode 100644 index 34901f292d..0000000000 --- a/libs/DocumentScanner/src/main/res/layout/fragment_image_processing.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/libs/DocumentScanner/src/main/res/layout/progress_layout.xml b/libs/DocumentScanner/src/main/res/layout/progress_layout.xml deleted file mode 100644 index 09ba456ec0..0000000000 --- a/libs/DocumentScanner/src/main/res/layout/progress_layout.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/layout/scan_surface_view.xml b/libs/DocumentScanner/src/main/res/layout/scan_surface_view.xml deleted file mode 100644 index f0274c73b4..0000000000 --- a/libs/DocumentScanner/src/main/res/layout/scan_surface_view.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/values-night/colors.xml b/libs/DocumentScanner/src/main/res/values-night/colors.xml deleted file mode 100644 index 9444cbd565..0000000000 --- a/libs/DocumentScanner/src/main/res/values-night/colors.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - #232323 - #FFFFFF - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/values/colors.xml b/libs/DocumentScanner/src/main/res/values/colors.xml deleted file mode 100644 index 1e7ce9d650..0000000000 --- a/libs/DocumentScanner/src/main/res/values/colors.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - #99000000 - #afadae - - #E9001C - - #77ffffff - - #FFFFFF - #000000 - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/values/dimens.xml b/libs/DocumentScanner/src/main/res/values/dimens.xml deleted file mode 100644 index 6bd0df5ac0..0000000000 --- a/libs/DocumentScanner/src/main/res/values/dimens.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - 2dp - 10dp - - 12sp - 14sp - 18sp - 50dp - - 80dp - 40dp - 20dp - 12dp - 10dp - 8dp - - 100dp - 0.5dp - 0dp - 15dp - 50dp - 40dp - 32dp - 1dp - 2dp - 4dp - 20dp - 50dp - 40dp - -20dp - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/values/ids.xml b/libs/DocumentScanner/src/main/res/values/ids.xml deleted file mode 100644 index 5f7ceae156..0000000000 --- a/libs/DocumentScanner/src/main/res/values/ids.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/values/strings.xml b/libs/DocumentScanner/src/main/res/values/strings.xml deleted file mode 100644 index 1a6465b0bd..0000000000 --- a/libs/DocumentScanner/src/main/res/values/strings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - Cancel - Auto - Manual - \ No newline at end of file diff --git a/libs/DocumentScanner/src/main/res/values/styles.xml b/libs/DocumentScanner/src/main/res/values/styles.xml deleted file mode 100644 index 6070315a53..0000000000 --- a/libs/DocumentScanner/src/main/res/values/styles.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/libs/annotations/build.gradle b/libs/annotations/build.gradle index 072df18231..9fbeaa9100 100644 --- a/libs/annotations/build.gradle +++ b/libs/annotations/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' static String isTesting() { if ( System.getenv("IS_TESTING") == "true" ) { @@ -96,7 +96,7 @@ repositories { username pspdfMavenUser password pspdfMavenPass } - url 'https://customers.pspdfkit.com/maven/' + url 'https://my.nutrient.io/maven' } } @@ -105,7 +105,7 @@ dependencies { api project(path: ':pandautils') api project(path: ':pandares') - api Libs.PSPDFKIT + api Libs.NUTRIENT androidTestImplementation Libs.JUNIT testImplementation Libs.JUNIT diff --git a/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt b/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt index 99fabce95c..18774af417 100644 --- a/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt +++ b/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt @@ -115,7 +115,7 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation .setAnnotationInspectorEnabled(true) .layoutMode(PageLayoutMode.SINGLE) .textSelectionEnabled(false) - .disableCopyPaste() + .copyPastEnabled(false) .build() private val annotationCreationToolbar = AnnotationCreationToolbar(context) @@ -174,9 +174,8 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation protected fun unregisterPdfFragmentListeners() { pdfFragment?.removeOnAnnotationCreationModeChangeListener(this) pdfFragment?.removeOnAnnotationEditingModeChangeListener(this) - pdfFragment?.document?.annotationProvider?.removeOnAnnotationUpdatedListener(annotationUpdateListener) + pdfFragment?.removeOnAnnotationUpdatedListener(annotationUpdateListener) pdfFragment?.removeOnAnnotationSelectedListener(annotationSelectedListener) - pdfFragment?.removeOnAnnotationDeselectedListener(annotationDeselectedListener) } /** @@ -198,12 +197,12 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation if (toolbar is AnnotationCreationToolbar) { setUpGrabAnnotationTool(toolbar) + toolbar.setMenuItemGroupingRule(AnnotationCreationGroupingRule(context)) } } }) annotationCreationToolbar.closeButton.setGone() - annotationCreationToolbar.setMenuItemGroupingRule(AnnotationCreationGroupingRule(context)) annotationEditingToolbar.setMenuItemGroupingRule(object : MenuItemGroupingRule { override fun groupMenuItems(items: MutableList, i: Int) = configureEditMenuItemGrouping(items) @@ -415,9 +414,6 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation annotationsJob = tryWeave { // Snag them annotations with the session id val annotations = awaitApi { CanvaDocsManager.getAnnotations(apiValues.sessionId, apiValues.canvaDocsDomain, it) } - // We don't want to trigger the annotation events here, so unregister and re-register after - pdfFragment?.document?.annotationProvider?.removeOnAnnotationUpdatedListener(annotationUpdateListener) - // Grab all the annotations and sort them by type (descending). // This will result in all of the comments being iterated over first as the COMMENT_REPLY type is last in the AnnotationType enum. val sortedAnnotationList = annotations.data.sortedByDescending { it.annotationType } @@ -456,9 +452,8 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation } noteHinter?.notifyDrawablesChanged() - pdfFragment?.document?.annotationProvider?.addOnAnnotationUpdatedListener(annotationUpdateListener) + pdfFragment?.addOnAnnotationUpdatedListener(annotationUpdateListener) pdfFragment?.addOnAnnotationSelectedListener(annotationSelectedListener) - pdfFragment?.addOnAnnotationDeselectedListener(annotationDeselectedListener) } catch { // Show error toast(R.string.annotationErrorOccurred) @@ -618,10 +613,6 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation && commentRepliesHashMap[currentAnnotation.annotationId]?.isNotEmpty() == true } - private val annotationDeselectedListener = AnnotationManager.OnAnnotationDeselectedListener { _, _ -> - commentsButton.setGone() - } - //region Annotation Manipulation fun createNewAnnotation(annotation: Annotation) { if (annotation.type == AnnotationType.FREETEXT) { @@ -642,9 +633,7 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation // Edit the annotation with the appropriate id annotation.name = newAnnotation.annotationId - pdfFragment?.document?.annotationProvider?.removeOnAnnotationUpdatedListener(annotationUpdateListener) pdfFragment?.notifyAnnotationHasChanged(annotation) - pdfFragment?.document?.annotationProvider?.addOnAnnotationUpdatedListener(annotationUpdateListener) commentsButton.isEnabled = true if (annotation.type == AnnotationType.STAMP) { commentsButton.setVisible() @@ -1017,7 +1006,7 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation // If the user has read/write/manage we want to let them delete (and only delete) non-authored annotations val annotation = pdfFragment?.selectedAnnotations?.get(0) - if (docSession.annotationMetadata?.canManage() == true && annotation?.flags?.contains(AnnotationFlags.LOCKED) == true) { + if (::docSession.isInitialized && docSession.annotationMetadata?.canManage() == true && annotation?.flags?.contains(AnnotationFlags.LOCKED) == true) { // We need to only return a list with the delete menu item delete = ContextualToolbarMenuItem.createSingleItem(context, View.generateViewId(), ContextCompat.getDrawable(context, R.drawable.ic_trash)!!, diff --git a/libs/canvas-api-2/build.gradle b/libs/canvas-api-2/build.gradle index 7f69f3ed91..a94afaacf2 100644 --- a/libs/canvas-api-2/build.gradle +++ b/libs/canvas-api-2/build.gradle @@ -19,7 +19,7 @@ apply plugin: 'com.android.library' apply plugin: 'com.apollographql.apollo' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' def pineDebugBaseUrl = "https://pine-api-dev.domain-svcs.nonprod.inseng.io" @@ -202,10 +202,10 @@ dependencies { implementation Libs.FIREBASE_CONFIG implementation Libs.HILT - kapt Libs.HILT_COMPILER + ksp Libs.HILT_COMPILER implementation Libs.ROOM - kapt Libs.ROOM_COMPILER + ksp Libs.ROOM_COMPILER implementation Libs.PENDO diff --git a/libs/horizon/build.gradle.kts b/libs/horizon/build.gradle.kts index b10db9822a..41d8a6056d 100644 --- a/libs/horizon/build.gradle.kts +++ b/libs/horizon/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.plugin.compose") id("kotlin-android") - id("kotlin-kapt") + id("com.google.devtools.ksp") id("dagger.hilt.android.plugin") kotlin("plugin.serialization") version "2.1.20" id("jacoco") @@ -16,7 +16,6 @@ android { defaultConfig { minSdk = Versions.MIN_SDK - targetSdk = Versions.TARGET_SDK testInstrumentationRunner = "com.instructure.horizon.espresso.HorizonCustomTestRunner" consumerProguardFiles("consumer-rules.pro") @@ -69,11 +68,11 @@ dependencies { implementation(Libs.NAVIGATION_COMPOSE) implementation(Libs.HILT) - kapt(Libs.HILT_COMPILER) + ksp(Libs.HILT_COMPILER) implementation(Libs.HILT_ANDROIDX_WORK) - kapt(Libs.HILT_ANDROIDX_COMPILER) + ksp(Libs.HILT_ANDROIDX_COMPILER) - implementation(Libs.PSPDFKIT) + implementation(Libs.NUTRIENT) implementation(Libs.ANDROIDX_ANNOTATION) implementation(Libs.ANDROIDX_APPCOMPAT) diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/account/filepreview/PdfPreview.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/account/filepreview/PdfPreview.kt index 8e144427f0..fc3443b886 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/account/filepreview/PdfPreview.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/account/filepreview/PdfPreview.kt @@ -25,10 +25,8 @@ import com.pspdfkit.configuration.activity.UserInterfaceViewMode import com.pspdfkit.configuration.page.PageScrollDirection import com.pspdfkit.configuration.page.PageScrollMode import com.pspdfkit.jetpack.compose.interactors.rememberDocumentState -import com.pspdfkit.jetpack.compose.utilities.ExperimentalPSPDFKitApi import com.pspdfkit.jetpack.compose.views.DocumentView -@OptIn(ExperimentalPSPDFKitApi::class) @Composable fun PdfPreview( documentUri: Uri, diff --git a/libs/interactions/build.gradle b/libs/interactions/build.gradle index f54245df48..05450a79f4 100644 --- a/libs/interactions/build.gradle +++ b/libs/interactions/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' apply plugin: 'kotlin-parcelize' static String isTesting() { diff --git a/libs/login-api-2/build.gradle b/libs/login-api-2/build.gradle index 2a49d712e0..38a16be117 100644 --- a/libs/login-api-2/build.gradle +++ b/libs/login-api-2/build.gradle @@ -20,7 +20,7 @@ import java.security.MessageDigest apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' static String isTesting() { @@ -134,7 +134,7 @@ dependencies { implementation Libs.ANDROIDX_FRAGMENT_KTX implementation Libs.HILT - kapt Libs.HILT_COMPILER + ksp Libs.HILT_COMPILER } task copySnickerDoodles(type: Copy) { diff --git a/libs/pandautils/build.gradle b/libs/pandautils/build.gradle index 9cce7f814b..345053752e 100644 --- a/libs/pandautils/build.gradle +++ b/libs/pandautils/build.gradle @@ -19,6 +19,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'org.jetbrains.kotlin.plugin.compose' @@ -48,6 +49,9 @@ android { ) } } + ksp { + arg("room.schemaLocation", "$projectDir/schemas".toString()) + } } buildTypes { @@ -116,6 +120,7 @@ configurations { androidTestImplementation.exclude module:'protobuf-lite' all*.resolutionStrategy { + force 'io.grpc:grpc-netty:1.75.0' force Libs.KOTLIN_STD_LIB } } @@ -132,6 +137,9 @@ dependencies { implementation Libs.LOTTIE + // Explicitly add secure grpc-netty version to override any transitive dependencies + implementation 'io.grpc:grpc-netty:1.75.0' + /* Kotlin */ implementation Libs.KOTLIN_STD_LIB implementation Libs.ANDROIDX_BROWSER @@ -158,7 +166,7 @@ dependencies { api (Libs.GLIDE_OKHTTP) { exclude group: "com.android.support" } - kapt Libs.GLIDE_COMPILER + ksp Libs.GLIDE_COMPILER api Libs.ANDROID_SVG @@ -197,9 +205,9 @@ dependencies { /* DI */ implementation Libs.HILT - kapt Libs.HILT_COMPILER + ksp Libs.HILT_COMPILER implementation Libs.HILT_ANDROIDX_WORK - kapt Libs.HILT_ANDROIDX_COMPILER + ksp Libs.HILT_ANDROIDX_COMPILER /* AAC */ implementation Libs.VIEW_MODEL @@ -210,7 +218,7 @@ dependencies { /* ROOM */ implementation Libs.ROOM - kapt Libs.ROOM_COMPILER + ksp Libs.ROOM_COMPILER implementation Libs.ROOM_COROUTINES /* Compose */ @@ -245,8 +253,6 @@ dependencies { // More details here: classpath https://github.com/google/ExoPlayer/issues/7905 implementation 'com.google.guava:guava:29.0-android' - kaptTest Libs.ANDROIDX_DATABINDING_COMPILER - androidTestImplementation Libs.KOTLIN_COROUTINES_TEST androidTestImplementation (project(':espresso')) { exclude group: 'org.checkerframework', module: 'checker' diff --git a/libs/pandautils/src/androidTest/java/com/instructure/pandautils/compose/features/settings/SettingsScreenTest.kt b/libs/pandautils/src/androidTest/java/com/instructure/pandautils/compose/features/settings/SettingsScreenTest.kt index 4aed3d92a7..56ee96a137 100644 --- a/libs/pandautils/src/androidTest/java/com/instructure/pandautils/compose/features/settings/SettingsScreenTest.kt +++ b/libs/pandautils/src/androidTest/java/com/instructure/pandautils/compose/features/settings/SettingsScreenTest.kt @@ -72,7 +72,20 @@ class SettingsScreenTest { } items.forEach { (title, items) -> - composeTestRule.onNodeWithText(context.getString(title)).assertExists() + retry(catchBlock = { + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + val y = device.displayHeight / 2 + val x = device.displayWidth / 2 + device.swipe( + x, + y, + x, + 0, + 10 + ) + }) { + composeTestRule.onNodeWithText(context.getString(title)).assertExists() + } items.forEach { item -> retry(catchBlock = { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) diff --git a/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/GradingPeriodDaoTest.kt b/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/GradingPeriodDaoTest.kt index 3b786f8448..1badd32d6e 100644 --- a/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/GradingPeriodDaoTest.kt +++ b/libs/pandautils/src/androidTest/java/com/instructure/pandautils/room/offline/daos/GradingPeriodDaoTest.kt @@ -61,15 +61,13 @@ class GradingPeriodDaoTest { Assert.assertEquals(gradingPeriodEntity, result) } - @Test - fun testFindEntityByIdReturnsNullIfNotFound() = runTest { + @Test(expected = IllegalStateException::class) + fun testFindEntityByIdThrowsExceptionIfNotFound() = runTest { val gradingPeriodEntity = GradingPeriodEntity(GradingPeriod(id = 1, "Grading period 1")) val gradingPeriodEntity2 = GradingPeriodEntity(GradingPeriod(id = 2, "Grading period 2")) gradingPeriodDao.insert(gradingPeriodEntity) gradingPeriodDao.insert(gradingPeriodEntity2) val result = gradingPeriodDao.findById(3) - - Assert.assertNull(result) } } \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/TriStateBottomSheet.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/TriStateBottomSheet.kt index 63086fa181..fbabb10864 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/TriStateBottomSheet.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/TriStateBottomSheet.kt @@ -135,8 +135,8 @@ fun TriStateBottomSheet( anchoredDraggableState.updateAnchors(newAnchors) val currentTarget = anchoredDraggableState.targetValue - if (!newAnchors.hasAnchorFor(currentTarget)) { - if (newAnchors.hasAnchorFor(initialAnchor)) { + if (!newAnchors.hasPositionFor(currentTarget)) { + if (newAnchors.hasPositionFor(initialAnchor)) { anchoredDraggableState.snapTo(initialAnchor) } else if (newAnchors.size > 0) { newAnchors.closestAnchor(anchoredDraggableState.offset.takeIf { !it.isNaN() } @@ -153,7 +153,7 @@ fun TriStateBottomSheet( LaunchedEffect(anchoredDraggableState.anchors, initialAnchor) { val currentAnchors = anchoredDraggableState.anchors - if (currentAnchors.hasAnchorFor(initialAnchor) && anchoredDraggableState.currentValue != initialAnchor) { + if (currentAnchors.hasPositionFor(initialAnchor) && anchoredDraggableState.currentValue != initialAnchor) { if (anchoredDraggableState.targetValue != initialAnchor || !anchoredDraggableState.isAnimationRunning) { anchoredDraggableState.snapTo(initialAnchor) } @@ -168,8 +168,8 @@ fun TriStateBottomSheet( if (offset.isNaN() || currentAnchors.size == 0) { getPixelForAnchor(initialAnchor) } else { - val minAnchorValue = currentAnchors.minAnchor() - val maxAnchorValue = currentAnchors.maxAnchor() + val minAnchorValue = currentAnchors.minPosition() + val maxAnchorValue = currentAnchors.maxPosition() offset.coerceIn(minAnchorValue, maxAnchorValue) } } @@ -259,7 +259,7 @@ fun TriStateBottomSheet( coroutineScope.launch { when (anchoredDraggableState.targetValue) { AnchorPoints.BOTTOM -> { - if (anchoredDraggableState.anchors.hasAnchorFor(AnchorPoints.MIDDLE)) { + if (anchoredDraggableState.anchors.hasPositionFor(AnchorPoints.MIDDLE)) { anchoredDraggableState.animateTo(AnchorPoints.MIDDLE) } else { anchoredDraggableState.animateTo(AnchorPoints.BOTTOM) @@ -271,7 +271,7 @@ fun TriStateBottomSheet( } AnchorPoints.TOP -> { - if (anchoredDraggableState.anchors.hasAnchorFor(AnchorPoints.MIDDLE)) { + if (anchoredDraggableState.anchors.hasPositionFor(AnchorPoints.MIDDLE)) { anchoredDraggableState.animateTo(AnchorPoints.MIDDLE) } else { anchoredDraggableState.animateTo(AnchorPoints.BOTTOM) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModel.kt index 028f55bfbb..de52b82926 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModel.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModel.kt @@ -24,7 +24,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.work.WorkInfo import androidx.work.WorkManager -import androidx.work.await import com.instructure.canvasapi2.apis.EnrollmentAPI import com.instructure.canvasapi2.managers.AccountNotificationManager import com.instructure.canvasapi2.managers.ConferenceManager @@ -64,6 +63,7 @@ import com.instructure.pandautils.room.offline.daos.StudioMediaProgressDao import com.instructure.pandautils.utils.orDefault import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.threeten.bp.OffsetDateTime import java.util.Locale @@ -292,36 +292,33 @@ class DashboardNotificationsViewModel @Inject constructor( private suspend fun getUploads(fileUploadEntities: List?) = fileUploadEntities?.mapNotNull { fileUploadEntity -> val workerId = UUID.fromString(fileUploadEntity.workerId) - workManager.getWorkInfoById(workerId).await()?.let { workInfo -> + val workInfo = workManager.getWorkInfoByIdFlow(workerId).first() + workInfo?.let { val icon: Int val background: Int - when (workInfo.state) { + when (it.state) { WorkInfo.State.FAILED -> { icon = R.drawable.ic_exclamation_mark background = R.color.backgroundDanger } - WorkInfo.State.SUCCEEDED -> { icon = R.drawable.ic_check_white_24dp background = R.color.backgroundSuccess } - else -> { icon = R.drawable.ic_upload background = R.color.backgroundInfo } } - val uploadViewData = UploadViewData( fileUploadEntity.title.orEmpty(), fileUploadEntity.subtitle.orEmpty(), - icon, background, workInfo.state == WorkInfo.State.RUNNING + icon, background, it.state == WorkInfo.State.RUNNING ) - UploadItemViewModel( workerId = workerId, workManager = workManager, data = uploadViewData, - open = { uuid -> openUploadNotification(workInfo.state, uuid, fileUploadEntity) }, + open = { uuid -> openUploadNotification(it.state, uuid, fileUploadEntity) }, remove = { removeUploadNotification(fileUploadEntity, workerId) } ) } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/itemviewmodels/UploadItemViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/itemviewmodels/UploadItemViewModel.kt index f0d2e65c20..48d45185c7 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/itemviewmodels/UploadItemViewModel.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/itemviewmodels/UploadItemViewModel.kt @@ -19,8 +19,6 @@ package com.instructure.pandautils.features.dashboard.notifications.itemviewmode import androidx.databinding.BaseObservable import androidx.databinding.Bindable -import androidx.lifecycle.Observer -import androidx.work.WorkInfo import androidx.work.WorkManager import com.instructure.pandautils.BR import com.instructure.pandautils.R @@ -28,7 +26,12 @@ import com.instructure.pandautils.features.dashboard.notifications.UploadViewDat import com.instructure.pandautils.features.file.upload.worker.FileUploadWorker.Companion.PROGRESS_DATA_FULL_SIZE import com.instructure.pandautils.features.file.upload.worker.FileUploadWorker.Companion.PROGRESS_DATA_UPLOADED_SIZE import com.instructure.pandautils.mvvm.ItemViewModel -import java.util.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import java.util.UUID class UploadItemViewModel( val workerId: UUID, @@ -39,23 +42,25 @@ class UploadItemViewModel( val remove: () -> Unit, @get:Bindable var loading: Boolean = false ) : ItemViewModel, BaseObservable() { - override val layoutId = R.layout.item_dashboard_upload - - private val observer = Observer { - val uploadedSize = it.progress.getLong(PROGRESS_DATA_UPLOADED_SIZE, 0L) - val fullSize = it.progress.getLong(PROGRESS_DATA_FULL_SIZE, 1L) - - progress = ((uploadedSize.toDouble() / fullSize.toDouble()) * 100.0).toInt() - notifyPropertyChanged(BR.progress) - } + private var job: Job? = null init { - workManager.getWorkInfoByIdLiveData(workerId).observeForever(observer) + job = CoroutineScope(Dispatchers.Main).launch { + workManager.getWorkInfoByIdFlow(workerId).collectLatest { workInfo -> + workInfo?.let { + val uploadedSize = it.progress.getLong(PROGRESS_DATA_UPLOADED_SIZE, 0L) + val fullSize = it.progress.getLong(PROGRESS_DATA_FULL_SIZE, 1L) + + progress = ((uploadedSize.toDouble() / fullSize.toDouble()) * 100.0).toInt() + notifyPropertyChanged(BR.progress) + } + } + } } fun clear() { - workManager.getWorkInfoByIdLiveData(workerId).removeObserver(observer) + job?.cancel() } fun open() = open.invoke(workerId) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogFragment.kt index 9a835b82fd..82072ba28a 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogFragment.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogFragment.kt @@ -248,7 +248,7 @@ class FileUploadDialogFragment : BaseCanvasDialogFragment() { } is FileUploadAction.UploadStartedAction -> { getParent()?.selectedUriStringsCallback(action.selectedUris) - getParent()?.workInfoLiveDataCallback(action.id, action.liveData) + getParent()?.workInfoLiveDataCallback(action.id,action.liveData) lifecycleScope.launch { fileUploadEventHandler.postEvent( FileUploadEvent.FileSelected(action.selectedUris) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogParent.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogParent.kt index 11d4099ef7..4a16d76981 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogParent.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogParent.kt @@ -28,5 +28,5 @@ interface FileUploadDialogParent { fun selectedUriStringsCallback(filePaths: List) = Unit - fun workInfoLiveDataCallback(uuid: UUID? = null, workInfoLiveData: LiveData) = Unit + fun workInfoLiveDataCallback(uuid: UUID? = null, workInfoLiveData: LiveData) = Unit } \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewData.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewData.kt index bc47a0eaf8..185b8efb7e 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewData.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewData.kt @@ -42,6 +42,6 @@ sealed class FileUploadAction { object UploadStarted : FileUploadAction() data class ShowToast(val toast: String) : FileUploadAction() data class AttachmentSelectedAction(val event: Int, val attachment: FileSubmitObject?) : FileUploadAction() - data class UploadStartedAction(val id: UUID, val liveData: LiveData, val selectedUris: List) : FileUploadAction() + data class UploadStartedAction(val id: UUID, val liveData: LiveData, val selectedUris: List) : FileUploadAction() } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadEventHandler.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadEventHandler.kt index 225cb4f9fc..9510bbc275 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadEventHandler.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadEventHandler.kt @@ -29,7 +29,7 @@ sealed class FileUploadEvent { data class FileSelected(val filePaths: List) : FileUploadEvent() data class UploadStarted( val uuid: UUID?, - val workInfoLiveData: LiveData, + val workInfoLiveData: LiveData, val filePaths: List ) : FileUploadEvent() } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/worker/FileUploadWorker.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/worker/FileUploadWorker.kt index 1d6f2c845d..0a7d5accce 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/worker/FileUploadWorker.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/worker/FileUploadWorker.kt @@ -331,7 +331,7 @@ class FileUploadWorker @AssistedInject constructor( } }).dataOrThrow - val updatedList = workDataBuilder.build() + val updatedList: Array = workDataBuilder.build() .getStringArray(PROGRESS_DATA_UPLOADED_FILES) .orEmpty() .toMutableList() diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/grades/GradesScreen.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/grades/GradesScreen.kt index c4ab26e01f..ead5c82dd7 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/grades/GradesScreen.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/grades/GradesScreen.kt @@ -82,7 +82,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.invisibleToUser +import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag @@ -291,7 +291,7 @@ private fun GradesScreenContent( modifier = Modifier .height(24.dp) .semantics { - invisibleToUser() + hideFromAccessibility() } ) } @@ -301,19 +301,19 @@ private fun GradesScreenContent( } } - uiState.items.forEach { + uiState.items.forEach { item -> stickyHeader { GroupHeader( - name = it.name, - expanded = it.expanded, + name = item.name, + expanded = item.expanded, onClick = { - actionHandler(GradesAction.GroupHeaderClick(it.id)) + actionHandler(GradesAction.GroupHeaderClick(item.id)) } ) } - if (it.expanded) { - items(it.assignments) { assignment -> + if (item.expanded) { + items(item.assignments) { assignment -> AssignmentItem(assignment, actionHandler, userColor) } } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/compose/InboxComposeFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/compose/InboxComposeFragment.kt index 74f3227384..c8097976b8 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/compose/InboxComposeFragment.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/inbox/compose/InboxComposeFragment.kt @@ -111,9 +111,11 @@ class InboxComposeFragment : BaseCanvasFragment(), FragmentInteractions, FileUpl viewModel.addUploadingAttachments(filePaths) } - override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { + override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { workInfoLiveData.observe(viewLifecycleOwner) { workInfo -> - viewModel.updateAttachments(uuid, workInfo) + workInfo?.let { + viewModel.updateAttachments(uuid, workInfo) + } } } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelper.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelper.kt index 920c6f0367..f97088c9a1 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelper.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelper.kt @@ -25,10 +25,10 @@ import androidx.work.OneTimeWorkRequest import androidx.work.PeriodicWorkRequest import androidx.work.WorkInfo import androidx.work.WorkManager -import androidx.work.await import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.pandautils.features.offline.sync.settings.SyncFrequency import com.instructure.pandautils.room.offline.facade.SyncSettingsFacade +import kotlinx.coroutines.flow.first import java.util.concurrent.TimeUnit class OfflineSyncHelper( @@ -103,17 +103,16 @@ class OfflineSyncHelper( } private suspend fun isWorkScheduled(): Boolean { - return workManager.getWorkInfosForUniqueWork(apiPrefs.user?.id.toString()).await() - .any { it.state != WorkInfo.State.CANCELLED } + return workManager.getWorkInfosForUniqueWorkFlow(apiPrefs.user?.id.toString()).first().any { it.state != WorkInfo.State.CANCELLED } } private suspend fun isPeriodicWorkRunning(): Boolean { - return workManager.getWorkInfosForUniqueWork(apiPrefs.user?.id.toString()).await() + return workManager.getWorkInfosForUniqueWorkFlow(apiPrefs.user?.id.toString()).first() .any { it.state == WorkInfo.State.RUNNING } } private suspend fun getRunningOneTimeWorkInfo(): WorkInfo? { - return workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG).await() + return workManager.getWorkInfosByTagFlow(OfflineSyncWorker.ONE_TIME_TAG).first() .firstOrNull { it.state == WorkInfo.State.RUNNING } } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt index 058d8d1caf..26c5e20609 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt @@ -170,7 +170,7 @@ abstract class ShareExtensionActivity : BaseCanvasActivity(), FileUploadDialogPa } } - override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { + override fun workInfoLiveDataCallback(uuid: UUID?, workInfoLiveData: LiveData) { uuid?.let { shareExtensionViewModel.workerCallback(it) } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressDialogViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressDialogViewModel.kt index c88be6a8f3..7bb6fc7ec3 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressDialogViewModel.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressDialogViewModel.kt @@ -5,7 +5,6 @@ import android.net.Uri import androidx.annotation.DrawableRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.work.* @@ -24,7 +23,9 @@ import com.instructure.pandautils.utils.fromJson import com.instructure.pandautils.utils.humanReadableByteCount import com.instructure.pandautils.utils.orDefault import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.collectLatest import java.io.File import java.util.* import javax.inject.Inject @@ -56,31 +57,35 @@ class ShareExtensionProgressDialogViewModel @Inject constructor( private var workerId: UUID? = null private var fileUploadType = FileUploadType.USER - - private val observer = Observer { - when (it.state) { - WorkInfo.State.SUCCEEDED -> { - _events.postValue(Event((ShareExtensionProgressAction.ShowSuccessDialog(fileUploadType)))) - } - WorkInfo.State.RUNNING -> { - viewModelScope.launch { updateViewData(it.progress, false) } - } - WorkInfo.State.FAILED -> { - viewModelScope.launch { updateViewData(it.outputData, true) } - } - else -> {} - } - } + private var job: Job? = null fun setUUID(uuid: UUID) { this.workerId = uuid _state.postValue(ViewState.Loading) - workManager.getWorkInfoByIdLiveData(uuid).observeForever(observer) + job?.cancel() + job = viewModelScope.launch { + workManager.getWorkInfoByIdFlow(uuid).collectLatest { it -> + it?.let { + when (it.state) { + WorkInfo.State.SUCCEEDED -> { + _events.postValue(Event((ShareExtensionProgressAction.ShowSuccessDialog(fileUploadType)))) + } + WorkInfo.State.RUNNING -> { + updateViewData(it.progress, false) + } + WorkInfo.State.FAILED -> { + updateViewData(it.outputData, true) + } + else -> {} + } + } + } + } } override fun onCleared() { super.onCleared() - workerId?.let { workManager.getWorkInfoByIdLiveData(it).removeObserver(observer) } + job?.cancel() } private suspend fun updateViewData(progress: Data, failed: Boolean) { diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsUiState.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsUiState.kt index 416a4b6e82..a0c0895198 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsUiState.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsUiState.kt @@ -77,6 +77,6 @@ sealed class SpeedGraderCommentsAction { data object ChooseFilesClicked : SpeedGraderCommentsAction() data object FileUploadDialogClosed : SpeedGraderCommentsAction() data class FilesSelected(val filePaths: List) : SpeedGraderCommentsAction() - data class FileUploadStarted(val workInfoLiveData: LiveData) : SpeedGraderCommentsAction() + data class FileUploadStarted(val workInfoLiveData: LiveData) : SpeedGraderCommentsAction() data class MediaRecorded(val file: File) : SpeedGraderCommentsAction() } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsViewModel.kt index 891080153a..e45765c333 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsViewModel.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/SpeedGraderCommentsViewModel.kt @@ -356,7 +356,7 @@ class SpeedGraderCommentsViewModel @Inject constructor( } } - private fun onFileUploadStarted(workInfoLiveData: LiveData, filePaths: List) { + private fun onFileUploadStarted(workInfoLiveData: LiveData, filePaths: List) { _uiState.update { state -> state.copy( fileSelectorDialogData = null, @@ -480,7 +480,7 @@ class SpeedGraderCommentsViewModel @Inject constructor( attemptId = selectedAttemptId, mediaCommentId = id ).collect { result -> - when (result.state) { + when (result?.state) { WorkInfo.State.SUCCEEDED -> { fetchedComments.add( SpeedGraderComment( diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/commentlibrary/SpeedGraderCommentLibraryScreen.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/commentlibrary/SpeedGraderCommentLibraryScreen.kt index fc067d1739..0827475a76 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/commentlibrary/SpeedGraderCommentLibraryScreen.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/comments/commentlibrary/SpeedGraderCommentLibraryScreen.kt @@ -164,6 +164,7 @@ private fun SpeedGraderCommentLibraryContent( uiState.onCommentValueChanged(item) } .padding(vertical = 14.dp) + .testTag("commentLibraryItem") ) if (index != uiState.items.lastIndex) { CanvasDivider() diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/DiscussionTopicEntity.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/DiscussionTopicEntity.kt index 32430df43e..f083aaa169 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/DiscussionTopicEntity.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/room/offline/entities/DiscussionTopicEntity.kt @@ -10,7 +10,7 @@ import com.instructure.canvasapi2.models.DiscussionTopic data class DiscussionTopicEntity( @PrimaryKey val id: Long, - val unreadEntries: MutableList, + val unreadEntries: List, val participantIds: List, val viewIds: List, ) { @@ -23,7 +23,7 @@ data class DiscussionTopicEntity( fun toApiModel(participants: List, views: List): DiscussionTopic { return DiscussionTopic( - unreadEntries = unreadEntries, + unreadEntries = unreadEntries.toMutableList(), participants = participants, unreadEntriesMap = hashMapOf(), entryRatings = hashMapOf(), diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/services/NotoriousUploadWorker.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/services/NotoriousUploadWorker.kt index 3d09f4304c..5b5d748eba 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/services/NotoriousUploadWorker.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/services/NotoriousUploadWorker.kt @@ -284,7 +284,7 @@ class NotoriousUploadWorker @AssistedInject constructor( pageId: String?, attemptId: Long?, mediaCommentId: Long? - ): Flow { + ): Flow { val data = workDataOf( Const.MEDIA_FILE_PATH to mediaFilePath, Const.ASSIGNMENT to assignment?.toJson(), diff --git a/libs/pandautils/src/main/res/layout/view_canvas_web_view_wrapper.xml b/libs/pandautils/src/main/res/layout/view_canvas_web_view_wrapper.xml index 7b658476ef..301bb65318 100644 --- a/libs/pandautils/src/main/res/layout/view_canvas_web_view_wrapper.xml +++ b/libs/pandautils/src/main/res/layout/view_canvas_web_view_wrapper.xml @@ -35,7 +35,7 @@ android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:background="@drawable/bg_button_full_rounded" - android:foreground="?selectableItemBackground" + android:foreground="?android:attr/selectableItemBackground" android:gravity="center" android:minHeight="48dp" android:orientation="horizontal" diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModelTest.kt index 933fe7d9ea..e1e5f32f13 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModelTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewModelTest.kt @@ -23,11 +23,9 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.observe import androidx.work.Data import androidx.work.WorkInfo import androidx.work.WorkManager -import com.google.common.util.concurrent.Futures import com.instructure.canvasapi2.apis.EnrollmentAPI import com.instructure.canvasapi2.managers.AccountNotificationManager import com.instructure.canvasapi2.managers.ConferenceManager @@ -73,6 +71,7 @@ import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertNull import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.setMain import org.junit.After @@ -504,7 +503,7 @@ class DashboardNotificationsViewModelTest { DashboardFileUploadEntity(workerId3.toString(), 1, title3, subTitle3, null, null, null, null) ) - every { workManager.getWorkInfoById(workerId) } returns Futures.immediateFuture( + every { workManager.getWorkInfoByIdFlow(workerId) } returns flowOf( WorkInfo( workerId, WorkInfo.State.RUNNING, @@ -521,7 +520,7 @@ class DashboardNotificationsViewModelTest { ) ) - every { workManager.getWorkInfoById(workerId2) } returns Futures.immediateFuture( + every { workManager.getWorkInfoByIdFlow(workerId2) } returns flowOf( WorkInfo( workerId2, WorkInfo.State.SUCCEEDED, @@ -538,7 +537,7 @@ class DashboardNotificationsViewModelTest { ) ) - every { workManager.getWorkInfoById(workerId3) } returns Futures.immediateFuture( + every { workManager.getWorkInfoByIdFlow(workerId3) } returns flowOf( WorkInfo( workerId3, WorkInfo.State.FAILED, @@ -585,7 +584,7 @@ class DashboardNotificationsViewModelTest { DashboardFileUploadEntity(workerId.toString(), 1, title, subTitle, null, null, null, null) ) - every { workManager.getWorkInfoById(workerId) } returns Futures.immediateFuture( + every { workManager.getWorkInfoByIdFlow(workerId) } returns flowOf( WorkInfo( workerId, WorkInfo.State.RUNNING, @@ -602,7 +601,7 @@ class DashboardNotificationsViewModelTest { assertEquals(1, viewModel.data.value?.uploadItems?.size) assertEquals(expectedRunning, viewModel.data.value?.uploadItems?.first()?.data) - every { workManager.getWorkInfoById(workerId) } returns Futures.immediateFuture( + every { workManager.getWorkInfoByIdFlow(workerId) } returns flowOf( WorkInfo( workerId, WorkInfo.State.SUCCEEDED, @@ -630,7 +629,7 @@ class DashboardNotificationsViewModelTest { DashboardFileUploadEntity(workerId.toString(), 1, "", "", null, null, null, null) ) - every { workManager.getWorkInfoById(workerId) } returns Futures.immediateFuture( + every { workManager.getWorkInfoByIdFlow(workerId) } returns flowOf( WorkInfo( workerId, WorkInfo.State.RUNNING, @@ -665,7 +664,7 @@ class DashboardNotificationsViewModelTest { DashboardFileUploadEntity(workerId.toString(), 1, "", "", 1, 2, 3, null) ) - every { workManager.getWorkInfoById(workerId) } returns Futures.immediateFuture( + every { workManager.getWorkInfoByIdFlow(workerId) } returns flowOf( WorkInfo( workerId, WorkInfo.State.SUCCEEDED, @@ -706,7 +705,7 @@ class DashboardNotificationsViewModelTest { DashboardFileUploadEntity(workerId.toString(), 1, "", "", null, null, null, 0) ) - every { workManager.getWorkInfoById(workerId) } returns Futures.immediateFuture( + every { workManager.getWorkInfoByIdFlow(workerId) } returns flowOf( WorkInfo( workerId, WorkInfo.State.SUCCEEDED, diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelperTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelperTest.kt index f4bd92e64e..3ea7820f13 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelperTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/offline/sync/OfflineSyncHelperTest.kt @@ -29,8 +29,6 @@ import androidx.work.PeriodicWorkRequest import androidx.work.WorkInfo import androidx.work.WorkManager import androidx.work.WorkRequest -import androidx.work.impl.OperationImpl -import com.google.common.util.concurrent.Futures import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.pandautils.features.offline.sync.settings.SyncFrequency @@ -45,6 +43,7 @@ import io.mockk.verify import junit.framework.TestCase.assertEquals import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain @@ -77,8 +76,10 @@ class OfflineSyncHelperTest { every { apiPrefs.user } returns User(1L) - every { workManager.enqueue(any()) } returns OperationImpl() - every { workManager.enqueueUniquePeriodicWork(any(), any(), any()) } returns OperationImpl() + every { workManager.enqueue(any()) } returns mockk() + every { workManager.enqueueUniquePeriodicWork(any(), any(), any()) } returns mockk() + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf(emptyList()) + every { workManager.getWorkInfosByTagFlow(any()) } returns flowOf(emptyList()) coEvery { syncSettingsFacade.getSyncSettings() } returns SyncSettingsEntity( 1L, @@ -100,8 +101,8 @@ class OfflineSyncHelperTest { SyncFrequency.DAILY, true ) - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture(mockk(relaxed = true)) - every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf(listOf(mockk(relaxed = true))) + every { workManager.getWorkInfosByTagFlow(OfflineSyncWorker.ONE_TIME_TAG) } returns flowOf( emptyList() ) @@ -116,7 +117,7 @@ class OfflineSyncHelperTest { val courseIds = listOf(1L, 2L, 3L) coEvery { syncSettingsFacade.getSyncSettings() } returns SyncSettingsEntity(1L, true, SyncFrequency.DAILY, true) - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture(emptyList()) + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf(emptyList()) offlineSyncHelper.syncCourses(courseIds) @@ -134,8 +135,8 @@ class OfflineSyncHelperTest { SyncFrequency.DAILY, true ) - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture(emptyList()) - every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf(emptyList()) + every { workManager.getWorkInfosByTagFlow(OfflineSyncWorker.ONE_TIME_TAG) } returns flowOf( emptyList() ) @@ -147,7 +148,7 @@ class OfflineSyncHelperTest { @Test fun `Cancel should cancel work with correct id`() { - every { workManager.cancelUniqueWork(any()) } returns OperationImpl() + every { workManager.cancelUniqueWork(any()) } returns mockk() offlineSyncHelper.cancelWork() @@ -164,8 +165,8 @@ class OfflineSyncHelperTest { SyncFrequency.DAILY, true ) - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture(emptyList()) - every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf(emptyList()) + every { workManager.getWorkInfosByTagFlow(OfflineSyncWorker.ONE_TIME_TAG) } returns flowOf( emptyList() ) @@ -260,7 +261,7 @@ class OfflineSyncHelperTest { coVerify { workManager.enqueueUniquePeriodicWork(any(), any(), capture(originalCaptor)) } val originalRequest = originalCaptor.captured - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture( + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf( listOf( WorkInfo(originalRequest.id, WorkInfo.State.ENQUEUED, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) @@ -278,8 +279,8 @@ class OfflineSyncHelperTest { @Test fun `Cancel running one time workers`() = runTest { val uuid = UUID.randomUUID() - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture(emptyList()) - every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf(emptyList()) + every { workManager.getWorkInfosByTagFlow(OfflineSyncWorker.ONE_TIME_TAG) } returns flowOf( listOf( WorkInfo(uuid, WorkInfo.State.RUNNING, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) @@ -295,12 +296,12 @@ class OfflineSyncHelperTest { @Test fun `Cancel running periodic workers`() = runTest { val uuid = UUID.randomUUID() - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture( + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf( listOf( WorkInfo(uuid, WorkInfo.State.RUNNING, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) ) - every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( + every { workManager.getWorkInfosByTagFlow(OfflineSyncWorker.ONE_TIME_TAG) } returns flowOf( emptyList() ) @@ -320,7 +321,7 @@ class OfflineSyncHelperTest { @Test fun `scheduleWorkAfterLogin should schedule work when auto sync is enabled and no work is already scheduled`() = runTest { - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture(emptyList()) + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf(emptyList()) coEvery { syncSettingsFacade.getSyncSettings() } returns SyncSettingsEntity( autoSyncEnabled = true, syncFrequency = SyncFrequency.DAILY, @@ -348,12 +349,12 @@ class OfflineSyncHelperTest { @Test fun `Restart periodic worker`() = runTest { val uuid = UUID.randomUUID() - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture( + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf( listOf( WorkInfo(uuid, WorkInfo.State.RUNNING, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) ) - every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( + every { workManager.getWorkInfosByTagFlow(OfflineSyncWorker.ONE_TIME_TAG) } returns flowOf( emptyList() ) @@ -372,10 +373,10 @@ class OfflineSyncHelperTest { @Test fun `Restart one time worker`() = runTest { val uuid = UUID.randomUUID() - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture( + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf( emptyList() ) - every { workManager.getWorkInfosByTag(OfflineSyncWorker.ONE_TIME_TAG) } returns Futures.immediateFuture( + every { workManager.getWorkInfosByTagFlow(OfflineSyncWorker.ONE_TIME_TAG) } returns flowOf( listOf( WorkInfo(uuid, WorkInfo.State.RUNNING, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) ) @@ -390,7 +391,7 @@ class OfflineSyncHelperTest { @Test fun `scheduleWorkAfterLogin should not schedule work when work is already scheduled`() = runTest { - every { workManager.getWorkInfosForUniqueWork(any()) } returns Futures.immediateFuture(mockk(relaxed = true)) + every { workManager.getWorkInfosForUniqueWorkFlow(any()) } returns flowOf(mockk(relaxed = true)) offlineSyncHelper.scheduleWorkAfterLogin() diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressViewModelTest.kt index bf6149478d..f334304ab0 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressViewModelTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressViewModelTest.kt @@ -5,7 +5,6 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry -import androidx.lifecycle.MutableLiveData import androidx.work.Data import androidx.work.WorkInfo import androidx.work.WorkManager @@ -24,6 +23,7 @@ import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain @@ -51,7 +51,7 @@ class ShareExtensionProgressViewModelTest { private val dashboardFileUploadDao: DashboardFileUploadDao = mockk(relaxed = true) private val fileUploadUtilsHelper: FileUploadUtilsHelper = mockk(relaxed = true) - private lateinit var mockLiveData: MutableLiveData + private lateinit var mockFlow: MutableStateFlow private lateinit var viewModel: ShareExtensionProgressDialogViewModel private lateinit var uuid: UUID @@ -64,8 +64,8 @@ class ShareExtensionProgressViewModelTest { uuid = UUID.randomUUID() - mockLiveData = MutableLiveData() - every { workManager.getWorkInfoByIdLiveData(uuid) } returns mockLiveData + mockFlow = MutableStateFlow(null) + every { workManager.getWorkInfoByIdFlow(uuid) } returns mockFlow viewModel = createViewModel() } @@ -78,7 +78,7 @@ class ShareExtensionProgressViewModelTest { @Test fun `Show success dialog after uploading`() { viewModel.setUUID(uuid) - mockLiveData.postValue(WorkInfo(uuid, WorkInfo.State.SUCCEEDED, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1)) + mockFlow.value = WorkInfo(uuid, WorkInfo.State.SUCCEEDED, emptySet(), Data.EMPTY, Data.EMPTY, 1, 1) viewModel.events.observe(lifecycleOwner) {} assertEquals(ShareExtensionProgressAction.ShowSuccessDialog(FileUploadType.USER), viewModel.events.value?.getContentIfNotHandled()) @@ -93,7 +93,7 @@ class ShareExtensionProgressViewModelTest { .putStringArray(FileUploadWorker.PROGRESS_DATA_FILES_TO_UPLOAD, emptyArray()) .build() - mockLiveData.postValue(WorkInfo(uuid, WorkInfo.State.FAILED, emptySet(), outputData, Data.EMPTY, 1, 1)) + mockFlow.value = WorkInfo(uuid, WorkInfo.State.FAILED, emptySet(), outputData, Data.EMPTY, 1, 1) assertEquals("Error", viewModel.data.value?.subtitle) } @@ -127,16 +127,14 @@ class ShareExtensionProgressViewModelTest { .putLong(FileUploadWorker.PROGRESS_DATA_FULL_SIZE, 1L) .putStringArray(FileUploadWorker.PROGRESS_DATA_FILES_TO_UPLOAD, emptyArray()) .build() - mockLiveData.postValue( - WorkInfo( - uuid, - WorkInfo.State.RUNNING, - emptySet(), - Data.EMPTY, - progressData, - 1, - 1 - ) + mockFlow.value = WorkInfo( + uuid, + WorkInfo.State.RUNNING, + emptySet(), + Data.EMPTY, + progressData, + 1, + 1 ) assertEquals(ViewState.Success, viewModel.state.value) @@ -147,7 +145,7 @@ class ShareExtensionProgressViewModelTest { every { resources.getString(R.string.submissionProgressSubtitle, "Assignment") } returns "Uploading submission to \"Assignment\"" - val filesToUpload = listOf( + val filesToUpload: Array = listOf( FileSubmitObject(name = "Test 1", size = 1L, contentType = "text/file", fullPath = ""), FileSubmitObject(name = "Test 2", size = 1L, contentType = "text/file", fullPath = "") ).map { it.toJson() }.toTypedArray() @@ -159,16 +157,14 @@ class ShareExtensionProgressViewModelTest { .putString(FileUploadWorker.PROGRESS_DATA_ASSIGNMENT_NAME, "Assignment") .build() - mockLiveData.postValue( - WorkInfo( - uuid, - WorkInfo.State.RUNNING, - emptySet(), - Data.EMPTY, - progressData, - 1, - 1 - ) + mockFlow.value = WorkInfo( + uuid, + WorkInfo.State.RUNNING, + emptySet(), + Data.EMPTY, + progressData, + 1, + 1 ) val expectedItemData = listOf( @@ -205,7 +201,7 @@ class ShareExtensionProgressViewModelTest { fun `Update view data when live data changes`() { every { resources.getString(R.string.submissionProgressSubtitle, "Assignment") } returns "Uploading submission to \"Assignment\"" - val filesToUpload = listOf( + val filesToUpload: Array = listOf( FileSubmitObject(name = "Test 1", size = 1L, contentType = "text/file", fullPath = ""), FileSubmitObject(name = "Test 2", size = 1L, contentType = "text/file", fullPath = "") ).map { it.toJson() }.toTypedArray() @@ -216,16 +212,14 @@ class ShareExtensionProgressViewModelTest { .putLong(FileUploadWorker.PROGRESS_DATA_UPLOADED_SIZE, 0L) .putString(FileUploadWorker.PROGRESS_DATA_ASSIGNMENT_NAME, "Assignment") - mockLiveData.postValue( - WorkInfo( - uuid, - WorkInfo.State.RUNNING, - emptySet(), - Data.EMPTY, - progressData.build(), - 1, - 1 - ) + mockFlow.value = WorkInfo( + uuid, + WorkInfo.State.RUNNING, + emptySet(), + Data.EMPTY, + progressData.build(), + 1, + 1 ) val expectedItemData = listOf( @@ -251,16 +245,14 @@ class ShareExtensionProgressViewModelTest { .putStringArray(FileUploadWorker.PROGRESS_DATA_UPLOADED_FILES, arrayOf(filesToUpload[0])) .putLong(FileUploadWorker.PROGRESS_DATA_UPLOADED_SIZE, 1L) - mockLiveData.postValue( - WorkInfo( - uuid, - WorkInfo.State.RUNNING, - emptySet(), - Data.EMPTY, - progressData.build(), - 1, - 1 - ) + mockFlow.value = WorkInfo( + uuid, + WorkInfo.State.RUNNING, + emptySet(), + Data.EMPTY, + progressData.build(), + 1, + 1 ) val viewData = viewModel.data.value @@ -277,12 +269,12 @@ class ShareExtensionProgressViewModelTest { @Test fun `Failed upload maps correctly`() { - val filesToUpload = listOf( + val filesToUpload: Array = listOf( FileSubmitObject(name = "Test 1", size = 1L, contentType = "text/file", fullPath = ""), FileSubmitObject(name = "Test 2", size = 1L, contentType = "text/file", fullPath = "") ).map { it.toJson() }.toTypedArray() - val uploadedFiles = listOf( + val uploadedFiles: Array = listOf( FileSubmitObject(name = "Test 1", size = 1L, contentType = "text/file", fullPath = "") ).map { it.toJson() }.toTypedArray() @@ -293,16 +285,14 @@ class ShareExtensionProgressViewModelTest { .putString(FileUploadWorker.PROGRESS_DATA_ASSIGNMENT_NAME, "Assignment") .build() - mockLiveData.postValue( - WorkInfo( - uuid, - WorkInfo.State.FAILED, - emptySet(), - progressData, - Data.EMPTY, - 1, - 1 - ) + mockFlow.value = WorkInfo( + uuid, + WorkInfo.State.FAILED, + emptySet(), + progressData, + Data.EMPTY, + 1, + 1 ) val expectedItemData = listOf( @@ -336,7 +326,7 @@ class ShareExtensionProgressViewModelTest { @Test fun `Failed upload retry`() { - val filesToUpload = listOf( + val filesToUpload: Array = listOf( FileSubmitObject(name = "Test 1", size = 1L, contentType = "text/file", fullPath = "") ).map { it.toJson() }.toTypedArray() @@ -345,69 +335,65 @@ class ShareExtensionProgressViewModelTest { .putStringArray(FileUploadWorker.PROGRESS_DATA_FILES_TO_UPLOAD, filesToUpload) .build() - mockLiveData.postValue( - WorkInfo( - uuid, - WorkInfo.State.FAILED, - emptySet(), - failedOutputData, - Data.EMPTY, - 1, - 1 - ) + mockFlow.value = WorkInfo( + uuid, + WorkInfo.State.FAILED, + emptySet(), + failedOutputData, + Data.EMPTY, + 1, + 1 ) viewModel.setUUID(uuid) + viewModel.data.observe(lifecycleOwner) {} + viewModel.events.observe(lifecycleOwner) {} val viewData = viewModel.data.value assertEquals("File Upload", viewData?.dialogTitle) assertEquals("Error", viewData?.subtitle) assertEquals(true, viewData?.failed) - viewModel.onRetryClick() - coEvery { fileUploadInputDao.findByWorkerId(uuid.toString()) } returns FileUploadInputEntity( workerId = uuid.toString(), action = "", filePaths = emptyList() ) - every { workManager.getWorkInfoByIdLiveData(any()) } returns mockLiveData + every { workManager.getWorkInfoByIdFlow(any()) } returns mockFlow every { resources.getString(R.string.fileUploadProgressSubtitle) } returns "Uploading files" + viewModel.onRetryClick() + val successProgressData = Data.Builder() .putLong(FileUploadWorker.PROGRESS_DATA_FULL_SIZE, 1L) .putStringArray(FileUploadWorker.PROGRESS_DATA_FILES_TO_UPLOAD, filesToUpload) .putStringArray(FileUploadWorker.PROGRESS_DATA_UPLOADED_FILES, filesToUpload) .build() - mockLiveData.postValue( - WorkInfo( - uuid, - WorkInfo.State.RUNNING, - emptySet(), - Data.EMPTY, - successProgressData, - 1, - 1 - ) + mockFlow.value = WorkInfo( + uuid, + WorkInfo.State.RUNNING, + emptySet(), + Data.EMPTY, + successProgressData, + 1, + 1 ) val successViewData = viewModel.data.value assertEquals("Uploading files", successViewData?.subtitle) assertEquals(false, successViewData?.failed) - mockLiveData.postValue( - WorkInfo( - uuid, - WorkInfo.State.SUCCEEDED, - emptySet(), - Data.EMPTY, - successProgressData, - 1, - 1 - ) + mockFlow.value = WorkInfo( + uuid, + WorkInfo.State.SUCCEEDED, + emptySet(), + Data.EMPTY, + successProgressData, + 1, + 1 ) assertEquals(ShareExtensionProgressAction.ShowSuccessDialog(FileUploadType.USER), viewModel.events.value?.getContentIfNotHandled())