diff --git a/apps/student/build.gradle b/apps/student/build.gradle index d5fa70025c..6b3ce3a2d4 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -39,8 +39,8 @@ android { applicationId "com.instructure.candroid" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 269 - versionName = '7.7.0' + versionCode = 271 + versionName = '7.8.1' vectorDrawables.useSupportLibrary = true testInstrumentationRunner 'com.instructure.student.espresso.StudentHiltTestRunner' diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt index dd3447c0fd..7dfe268665 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/AssignmentsE2ETest.kt @@ -748,6 +748,7 @@ class AssignmentsE2ETest: StudentComposeTest() { submissionDetailsPage.assertSelectedAttempt("Attempt 1") } + @Stub @E2E @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.SUBMISSIONS, TestCategory.E2E) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineAssignmentsE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineAssignmentsE2ETest.kt index 94734038fa..4fd917a814 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineAssignmentsE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/offline/OfflineAssignmentsE2ETest.kt @@ -21,6 +21,7 @@ import com.instructure.canvas.espresso.FeatureCategory import com.instructure.canvas.espresso.OfflineE2E import com.instructure.canvas.espresso.Priority import com.instructure.canvas.espresso.SecondaryFeatureCategory +import com.instructure.canvas.espresso.Stub import com.instructure.canvas.espresso.TestCategory import com.instructure.canvas.espresso.TestMetaData import com.instructure.dataseeding.api.AssignmentGroupsApi @@ -48,6 +49,7 @@ class OfflineAssignmentsE2ETest : StudentTest() { override fun enableAndConfigureAccessibilityChecks() = Unit + @Stub @OfflineE2E @Test @TestMetaData(Priority.MANDATORY, FeatureCategory.ASSIGNMENTS, TestCategory.E2E, SecondaryFeatureCategory.OFFLINE_MODE) diff --git a/apps/student/src/main/java/com/instructure/student/features/coursebrowser/CourseBrowserFragment.kt b/apps/student/src/main/java/com/instructure/student/features/coursebrowser/CourseBrowserFragment.kt index 41cb2303db..c21774b16d 100644 --- a/apps/student/src/main/java/com/instructure/student/features/coursebrowser/CourseBrowserFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/features/coursebrowser/CourseBrowserFragment.kt @@ -44,10 +44,10 @@ import com.instructure.interactions.Navigation import com.instructure.interactions.router.Route import com.instructure.pandautils.analytics.SCREEN_VIEW_COURSE_BROWSER import com.instructure.pandautils.analytics.ScreenView -import com.instructure.pandautils.binding.viewBinding -import com.instructure.pandautils.compose.composables.SearchBar import com.instructure.pandautils.base.BaseCanvasFragment +import com.instructure.pandautils.binding.viewBinding import com.instructure.pandautils.compose.CanvasTheme +import com.instructure.pandautils.compose.composables.SearchBar import com.instructure.pandautils.features.smartsearch.SmartSearchFragment import com.instructure.pandautils.utils.ParcelableArg import com.instructure.pandautils.utils.ViewStyler @@ -59,7 +59,6 @@ import com.instructure.pandautils.utils.setCourseImage import com.instructure.pandautils.utils.setVisible import com.instructure.pandautils.utils.setupAsBackButton import com.instructure.pandautils.utils.toast -import com.instructure.student.BuildConfig import com.instructure.student.R import com.instructure.student.adapter.CourseBrowserAdapter import com.instructure.student.databinding.FragmentCourseBrowserBinding @@ -268,8 +267,7 @@ class CourseBrowserFragment : BaseCanvasFragment(), FragmentInteractions, val tabs = repository.getTabs(canvasContext, isRefresh) - //TODO: Remove the debug flag when the search tab is ready - binding.searchBar.setVisible(BuildConfig.IS_DEBUG && tabs.find { it.tabId == Tab.SEARCH_ID } != null) + binding.searchBar.setVisible(tabs.find { it.tabId == Tab.SEARCH_ID } != null) // Finds the home tab so we can reorder them if necessary val sortedTabs = tabs.filter { it.tabId != Tab.SEARCH_ID }.toMutableList() diff --git a/apps/student/src/main/java/com/instructure/student/router/EnabledTabs.kt b/apps/student/src/main/java/com/instructure/student/router/EnabledTabs.kt index f667dcdb85..af75ce51d5 100644 --- a/apps/student/src/main/java/com/instructure/student/router/EnabledTabs.kt +++ b/apps/student/src/main/java/com/instructure/student/router/EnabledTabs.kt @@ -43,7 +43,13 @@ class EnabledTabsImpl( } if (uri.pathSegments.contains("courses")) { val courseIdIndex = uri.pathSegments.indexOf("courses") + 1 - val courseId = uri.pathSegments[courseIdIndex] + var courseId = uri.pathSegments[courseIdIndex] + if (courseId.contains("~")) { + val parts = courseId.split("~") + val length = parts[0].length + parts[1].length + val padding = 18 - length + courseId = parts[0] + "0".repeat(padding) + parts[1] + } return !isPathTabEnabled(courseId.toLong(), uri) } diff --git a/apps/student/src/test/java/com/instructure/student/router/EnabledTabsTest.kt b/apps/student/src/test/java/com/instructure/student/router/EnabledTabsTest.kt index a867ee56ff..d089d9a07b 100644 --- a/apps/student/src/test/java/com/instructure/student/router/EnabledTabsTest.kt +++ b/apps/student/src/test/java/com/instructure/student/router/EnabledTabsTest.kt @@ -214,4 +214,20 @@ class EnabledTabsTest { result = enabledTabs.isPathTabNotEnabled(route) assertFalse(result) } + + @Test + fun `replace ~ in consortia courseId to 0s`() = runTest { + coEvery { courseApi.getFirstPageCourses(any()) } returns DataResult.Success( + listOf( + Course(id = 110000000000000012, tabs = listOf(Tab(tabId = "assignments", htmlUrl = "/courses/11~12/assignments"))), + ) + ) + enabledTabs.initTabs() + + val route = Route(uri = mockUri) + every { mockUri.path } returns "http://www.google.com/courses/11~12/assignments" + every { mockUri.pathSegments } returns listOf("courses", "11~12", "assignments") + val result = enabledTabs.isPathTabNotEnabled(route) + assert(result) + } } \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/lti/LtiLaunchFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/lti/LtiLaunchFragment.kt index f4e0aff2d5..a38e267fd8 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/lti/LtiLaunchFragment.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/lti/LtiLaunchFragment.kt @@ -16,6 +16,7 @@ */ package com.instructure.pandautils.features.lti +import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper @@ -23,6 +24,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.webkit.WebView +import android.widget.Toast import androidx.fragment.app.FragmentActivity import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle @@ -50,6 +52,8 @@ import com.instructure.pandautils.utils.Const import com.instructure.pandautils.utils.NullableParcelableArg import com.instructure.pandautils.utils.NullableStringArg import com.instructure.pandautils.utils.ParcelableArg +import com.instructure.pandautils.utils.PermissionRequester +import com.instructure.pandautils.utils.PermissionUtils import com.instructure.pandautils.utils.ViewStyler import com.instructure.pandautils.utils.argsWithContext import com.instructure.pandautils.utils.collectOneOffEvents @@ -65,6 +69,8 @@ import com.instructure.pandautils.views.CanvasWebView import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import java.net.URLDecoder import javax.inject.Inject @@ -148,8 +154,10 @@ class LtiLaunchFragment : BaseCanvasFragment(), NavigationCallbacks { } private fun loadLtiToolIntoWebView(url: String) { + setupFilePicker() binding.webView.enableAlgorithmicDarkening() binding.webView.setZoomSettings(false) + binding.webView.addVideoClient(requireActivity()) binding.webView.canvasWebViewClientCallback = object : CanvasWebView.CanvasWebViewClientCallback { override fun openMediaFromWebView(mime: String, url: String, filename: String) = Unit @@ -185,6 +193,44 @@ class LtiLaunchFragment : BaseCanvasFragment(), NavigationCallbacks { fun contextLink() = "${ApiPrefs.fullDomain}${canvasContext.toAPIString()}" + private fun setupFilePicker() { + binding.webView.setCanvasWebChromeClientShowFilePickerCallback(object : CanvasWebView.VideoPickerCallback { + override fun requestStartActivityForResult(intent: Intent, requestCode: Int) { + startActivityForResult(intent, requestCode) + } + + override fun permissionsGranted(): Boolean { + return if (PermissionUtils.hasPermissions(requireActivity(), PermissionUtils.WRITE_EXTERNAL_STORAGE)) { + true + } else { + requestFilePermissions() + false + } + } + }) + } + + private fun requestFilePermissions() { + requestPermissions( + PermissionUtils.makeArray(PermissionUtils.WRITE_EXTERNAL_STORAGE, PermissionUtils.CAMERA), + PermissionUtils.PERMISSION_REQUEST_CODE + ) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onRequestPermissionsResult(result: PermissionRequester.PermissionResult) { + if (PermissionUtils.allPermissionsGrantedResultSummary(result.grantResults)) { + binding.webView.clearPickerCallback() + Toast.makeText(requireContext(), R.string.pleaseTryAgain, Toast.LENGTH_SHORT).show() + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (!binding.webView.handleOnActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data) + } + } + companion object { const val LTI_URL = "lti_url" const val LTI_TITLE = "lti_title"