From 8db22da38b2e50935161ad8c42bb978c45c207dc Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Fri, 17 Jun 2022 10:42:01 +0200 Subject: [PATCH 001/115] Updated version. --- apps/student/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/student/build.gradle b/apps/student/build.gradle index bf0f74afac..b7874cdaf5 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -54,8 +54,8 @@ android { applicationId "com.instructure.candroid" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 238 - versionName = '6.17.1' + versionCode = 239 + versionName = '6.18.0' vectorDrawables.useSupportLibrary = true multiDexEnabled = true From 9103b1f244da9806887bbc09d61c9d460be7c4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81kos=20Hermann?= Date: Fri, 17 Jun 2022 10:52:21 +0200 Subject: [PATCH 002/115] Release Teacher 1.18.0 (48) --- apps/teacher/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle index 4fefc2a55e..e38b35dc14 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -39,8 +39,8 @@ android { defaultConfig { minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 47 - versionName = '1.17.0' + versionCode = 48 + versionName = '1.18.0' vectorDrawables.useSupportLibrary = true multiDexEnabled true testInstrumentationRunner 'com.instructure.teacher.ui.espresso.TeacherHiltTestRunner' From 54af4bc7d4273f2f9a62407abcfb1acf977a96fc Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Fri, 17 Jun 2022 10:59:04 +0200 Subject: [PATCH 003/115] Stub failing render test. --- .../com/instructure/teacher/ui/renderTests/SyllabusRenderTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/renderTests/SyllabusRenderTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/renderTests/SyllabusRenderTest.kt index 29f96eceea..90b5195be2 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/renderTests/SyllabusRenderTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/renderTests/SyllabusRenderTest.kt @@ -102,6 +102,7 @@ class SyllabusRenderTest : TeacherRenderTest() { } @Test + @Stub fun tappingEventsDisplaysEvents() { val model = baseModel.copy(events = DataResult.Success(List(3) { ScheduleItem(title = it.toString()) })) loadPageWithModel(model) From 43975703ccada7c6d937874e2ef7d87d419c1422 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Fri, 17 Jun 2022 14:27:05 +0200 Subject: [PATCH 004/115] [MBL-16107][Student] Fix widget styles #1612 refs: MBL-16107 affects: Student release note: none --- .../instructure/student/widget/BaseRemoteViewsService.kt | 8 +++++++- .../instructure/student/widget/GradesViewWidgetService.kt | 1 + .../student/widget/NotificationViewWidgetService.kt | 3 ++- .../instructure/student/widget/TodoViewWidgetService.kt | 3 +++ apps/student/src/main/res/drawable/widget_dark_bg.xml | 2 +- apps/student/src/main/res/drawable/widget_light_bg.xml | 2 +- .../student/src/main/res/layout/activity_widget_setup.xml | 8 ++++---- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/student/src/main/java/com/instructure/student/widget/BaseRemoteViewsService.kt b/apps/student/src/main/java/com/instructure/student/widget/BaseRemoteViewsService.kt index 1863b747ee..a9d238e3f1 100644 --- a/apps/student/src/main/java/com/instructure/student/widget/BaseRemoteViewsService.kt +++ b/apps/student/src/main/java/com/instructure/student/widget/BaseRemoteViewsService.kt @@ -32,7 +32,7 @@ abstract class BaseRemoteViewsService : RemoteViewsService() { fun getWidgetTextColor(widgetId: Int, context: Context): Int { val widgetBackgroundPref = getWidgetBackgroundPref(widgetId) return if (widgetBackgroundPref.equals(WidgetSetupActivity.WIDGET_BACKGROUND_COLOR_LIGHT, ignoreCase = true)) - ContextCompat.getColor(context, R.color.textDarkest) else ContextCompat.getColor(context, R.color.textLightest) + ContextCompat.getColor(context, R.color.licorice) else ContextCompat.getColor(context, R.color.white) } fun getWidgetBackgroundResourceId(widgetId: Int): Int { @@ -40,6 +40,12 @@ abstract class BaseRemoteViewsService : RemoteViewsService() { return if (widgetBackgroundPref.equals(WidgetSetupActivity.WIDGET_BACKGROUND_COLOR_LIGHT, ignoreCase = true)) R.drawable.widget_light_bg else R.drawable.widget_dark_bg } + fun getWidgetSecondaryTextColor(widgetId: Int, context: Context): Int { + val widgetBackgroundPref = getWidgetBackgroundPref(widgetId) + return if (widgetBackgroundPref.equals(WidgetSetupActivity.WIDGET_BACKGROUND_COLOR_LIGHT, ignoreCase = true)) + ContextCompat.getColor(context, R.color.ash) else ContextCompat.getColor(context, R.color.tiara) + } + fun shouldHideDetails(appWidgetId: Int): Boolean { return StudentPrefs.getBoolean(WidgetSetupActivity.WIDGET_DETAILS_PREFIX + appWidgetId) } diff --git a/apps/student/src/main/java/com/instructure/student/widget/GradesViewWidgetService.kt b/apps/student/src/main/java/com/instructure/student/widget/GradesViewWidgetService.kt index bfa3a06c96..acb854a860 100644 --- a/apps/student/src/main/java/com/instructure/student/widget/GradesViewWidgetService.kt +++ b/apps/student/src/main/java/com/instructure/student/widget/GradesViewWidgetService.kt @@ -79,6 +79,7 @@ class GradesViewWidgetService : BaseRemoteViewsService(), Serializable { if (!BaseRemoteViewsService.shouldHideDetails(appWidgetId)) { row.setTextViewText(R.id.courseTerm, streamItem.term?.name) + row.setTextColor(R.id.courseTerm, BaseRemoteViewsService.getWidgetSecondaryTextColor(appWidgetId, applicationContext)) } if (streamItem.isTeacher || streamItem.isTA) { diff --git a/apps/student/src/main/java/com/instructure/student/widget/NotificationViewWidgetService.kt b/apps/student/src/main/java/com/instructure/student/widget/NotificationViewWidgetService.kt index 6c63b27a39..a529985323 100644 --- a/apps/student/src/main/java/com/instructure/student/widget/NotificationViewWidgetService.kt +++ b/apps/student/src/main/java/com/instructure/student/widget/NotificationViewWidgetService.kt @@ -21,7 +21,6 @@ import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Build import android.text.Html import android.view.View import android.widget.RemoteViews @@ -89,6 +88,7 @@ class NotificationViewWidgetService : BaseRemoteViewsService(), Serializable { if (!BaseRemoteViewsService.shouldHideDetails(appWidgetId)) { if (streamItem.getMessage(ContextKeeper.appContext) != null) { row.setTextViewText(R.id.message, StringUtilities.simplifyHTML(Html.fromHtml(streamItem.getMessage(ContextKeeper.appContext), Html.FROM_HTML_MODE_LEGACY))) + row.setTextColor(R.id.message, BaseRemoteViewsService.getWidgetSecondaryTextColor(appWidgetId, applicationContext)) } else { row.setTextViewText(R.id.message, "") row.setViewVisibility(R.id.message, View.GONE) @@ -101,6 +101,7 @@ class NotificationViewWidgetService : BaseRemoteViewsService(), Serializable { } courseAndDate += DateHelper.getDateTimeString(ContextKeeper.appContext, streamItem.updatedDate) row.setTextViewText(R.id.course_and_date, courseAndDate) + row.setTextColor(R.id.course_and_date, BaseRemoteViewsService.getWidgetSecondaryTextColor(appWidgetId, applicationContext)) row.setOnClickFillInIntent(R.id.widget_root, createIntent(streamItem)) diff --git a/apps/student/src/main/java/com/instructure/student/widget/TodoViewWidgetService.kt b/apps/student/src/main/java/com/instructure/student/widget/TodoViewWidgetService.kt index 0e5eb57d5f..6ceec1abe0 100644 --- a/apps/student/src/main/java/com/instructure/student/widget/TodoViewWidgetService.kt +++ b/apps/student/src/main/java/com/instructure/student/widget/TodoViewWidgetService.kt @@ -87,6 +87,7 @@ class TodoViewWidgetService : BaseRemoteViewsService(), Serializable { val formattedDueDate = DateHelper.getDateTimeString(ContextKeeper.appContext, streamItem.dueDate) row.setTextViewText(R.id.message, formattedDueDate) row.setViewVisibility(R.id.message, View.VISIBLE) + row.setTextColor(R.id.message, BaseRemoteViewsService.getWidgetSecondaryTextColor(appWidgetId, applicationContext)) } else { row.setViewVisibility(R.id.message, View.GONE) } @@ -95,6 +96,7 @@ class TodoViewWidgetService : BaseRemoteViewsService(), Serializable { if (!TextUtils.isEmpty(message)) { row.setTextViewText(R.id.message, message) row.setViewVisibility(R.id.message, View.VISIBLE) + row.setTextColor(R.id.message, BaseRemoteViewsService.getWidgetSecondaryTextColor(appWidgetId, applicationContext)) } else { row.setViewVisibility(R.id.message, View.GONE) } @@ -104,6 +106,7 @@ class TodoViewWidgetService : BaseRemoteViewsService(), Serializable { val formattedDueDate = DateHelper.getDateTimeString(ContextKeeper.appContext, streamItem.dueDate) row.setTextViewText(R.id.course_and_date, formattedDueDate) row.setViewVisibility(R.id.course_and_date, View.VISIBLE) + row.setTextColor(R.id.course_and_date, BaseRemoteViewsService.getWidgetSecondaryTextColor(appWidgetId, applicationContext)) } else { row.setViewVisibility(R.id.course_and_date, View.GONE) } diff --git a/apps/student/src/main/res/drawable/widget_dark_bg.xml b/apps/student/src/main/res/drawable/widget_dark_bg.xml index 35b1292a2e..b3ec22f981 100644 --- a/apps/student/src/main/res/drawable/widget_dark_bg.xml +++ b/apps/student/src/main/res/drawable/widget_dark_bg.xml @@ -18,7 +18,7 @@ - + - + @@ -63,7 +63,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/light" - android:textColor="@color/textDarkest" /> + android:textColor="@color/licorice" /> @@ -73,7 +73,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:foreground="?android:attr/selectableItemBackground" - app:cardBackgroundColor="@color/backgroundDarkest" + app:cardBackgroundColor="@color/licorice" app:cardElevation="8dp" app:cardUseCompatPadding="true" app:contentPadding="8dp"> @@ -83,7 +83,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/dark" - android:textColor="@color/backgroundLightest" /> + android:textColor="@color/white" /> From a8b5cb0c5b5c18764156a02ed584be260e542e24 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Fri, 17 Jun 2022 16:55:35 +0200 Subject: [PATCH 005/115] Fixed new discussions crash. Fixed grades progress on tablet. Added more delay to color inverting script in discussions to work on slower devices. --- .../instructure/student/fragment/DiscussionDetailsFragment.kt | 2 +- .../teacher/fragments/DiscussionsDetailsFragment.kt | 2 +- .../discussion/details/DiscussionDetailsWebViewFragment.kt | 2 +- libs/pandautils/src/main/res/layout-sw720dp/item_grade_row.xml | 3 +++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionDetailsFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionDetailsFragment.kt index b54f39a311..43529465ca 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionDetailsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionDetailsFragment.kt @@ -387,7 +387,7 @@ class DiscussionDetailsFragment : ParentFragment(), Bookmarkable { // It won't work exactl when the page starts to load, because the html document is not yet created, // so we add a little delay to make sure the script can modify the document. if (addDarkTheme) { - webView.postDelayed({ webView.addDarkThemeToHtmlDocument() }, 50) + webView.postDelayed({ webView.addDarkThemeToHtmlDocument() }, 100) } } override fun onPageFinishedCallback(webView: WebView, url: String) { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsDetailsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsDetailsFragment.kt index 57706813ca..969e8874be 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsDetailsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsDetailsFragment.kt @@ -528,7 +528,7 @@ class DiscussionsDetailsFragment : BasePresenterFragment< // It won't work exactl when the page starts to load, because the html document is not yet created, // so we add a little delay to make sure the script can modify the document. if (addDarkTheme) { - webView.postDelayed({ webView.addDarkThemeToHtmlDocument() }, 50) + webView.postDelayed({ webView.addDarkThemeToHtmlDocument() }, 100) } } override fun onPageFinishedCallback(webView: WebView, url: String) { diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/discussion/details/DiscussionDetailsWebViewFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/discussion/details/DiscussionDetailsWebViewFragment.kt index 4355e0a9f8..0a2d6a4277 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/discussion/details/DiscussionDetailsWebViewFragment.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/discussion/details/DiscussionDetailsWebViewFragment.kt @@ -75,7 +75,7 @@ class DiscussionDetailsWebViewFragment : Fragment() { override fun onPageFinishedCallback(webView: WebView, url: String) { viewModel.setLoading(false) - discussionSwipeRefreshLayout.isRefreshing = false + discussionSwipeRefreshLayout?.isRefreshing = false } override fun routeInternallyCallback(url: String) { diff --git a/libs/pandautils/src/main/res/layout-sw720dp/item_grade_row.xml b/libs/pandautils/src/main/res/layout-sw720dp/item_grade_row.xml index 83ad198f26..3bf9e1021b 100644 --- a/libs/pandautils/src/main/res/layout-sw720dp/item_grade_row.xml +++ b/libs/pandautils/src/main/res/layout-sw720dp/item_grade_row.xml @@ -26,6 +26,8 @@ + + Date: Mon, 20 Jun 2022 15:07:47 +0200 Subject: [PATCH 006/115] Fixed Inbox FAB styling after theme change. --- .../main/java/com/instructure/student/fragment/InboxFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/InboxFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/InboxFragment.kt index 2721d36458..7ce2c4d92f 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/InboxFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/InboxFragment.kt @@ -131,6 +131,8 @@ class InboxFragment : ParentFragment() { } } }) + + applyTheme() } override fun onConfigurationChanged(newConfig: Configuration) { From 85cdb6eaddedb0a42497a53cacce05a44b636e86 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Mon, 20 Jun 2022 16:02:52 +0200 Subject: [PATCH 007/115] [MBL-16111][Student] K5 Schedule only shows the first 10 planner items per week #1613 refs: MBL-16111 affects: Student release note: Fixed a bug in elementary schedule view where maximum of 10 items were displayed per week --- .../java/com/instructure/canvasapi2/managers/PlannerManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt index 8cd21e8663..5179f9f738 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/managers/PlannerManager.kt @@ -38,7 +38,7 @@ class PlannerManager(private val plannerApi: PlannerAPI) { endDate: String? = null ) { val adapter = RestBuilder(callback) - val params = RestParams(isForceReadFromNetwork = forceNetwork) + val params = RestParams(isForceReadFromNetwork = forceNetwork, usePerPageQueryParam = true) plannerApi.getPlannerItems(adapter, callback, params, startDate, endDate) } From f9636479d4110bb7c70ba3289e34853840695945 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 21 Jun 2022 10:16:37 +0200 Subject: [PATCH 008/115] Updated version. --- apps/student/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/student/build.gradle b/apps/student/build.gradle index b7874cdaf5..709769964c 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -54,8 +54,8 @@ android { applicationId "com.instructure.candroid" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 239 - versionName = '6.18.0' + versionCode = 240 + versionName = '6.18.1' vectorDrawables.useSupportLibrary = true multiDexEnabled = true From 279df296eefb300d786443cea8b98be7e8ab826c Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 21 Jun 2022 11:13:09 +0200 Subject: [PATCH 009/115] Fixed color parsing crash. --- .../java/com/instructure/pandautils/utils/ColorKeeper.kt | 2 +- .../java/com/instructure/pandautils/utils/ColorUtils.kt | 9 +++++++-- .../java/com/instructure/pandautils/utils/ThemePrefs.kt | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt index 7c9bc7e1a5..1bf12a8539 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt @@ -135,7 +135,7 @@ object ColorKeeper : PrefManager(PREFERENCE_FILE_NAME) { * @return The parsed color, or [defaultColor] if the string could not be parsed */ private fun parseColor(hexColor: String): Int = try { - ColorUtils.parseColor("#${hexColor.trimMargin("#")}", "") + ColorUtils.parseColor("#${hexColor.trimMargin("#")}", defaultColor = defaultColor) } catch (e: IllegalArgumentException) { defaultColor } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt index bfcabf2976..e8fd0cf2f4 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt @@ -19,6 +19,7 @@ package com.instructure.pandautils.utils import android.graphics.* import android.graphics.drawable.Drawable import android.widget.ImageView +import androidx.annotation.ColorInt import androidx.core.graphics.drawable.DrawableCompat object ColorUtils { @@ -46,7 +47,7 @@ object ColorUtils { @JvmStatic @JvmOverloads - fun parseColor(colorCode: String?, defaultColorCode: String = ColorApiHelper.K5_DEFAULT_COLOR): Int { + fun parseColor(colorCode: String?, @ColorInt defaultColor: Int? = null): Int { return try { val fullColorCode = if (colorCode?.length == 4 && colorCode[0].toString() == "#") { "#${colorCode[1]}${colorCode[1]}${colorCode[2]}${colorCode[2]}${colorCode[3]}${colorCode[3]}" @@ -55,7 +56,11 @@ object ColorUtils { } Color.parseColor(fullColorCode) } catch (e: Exception) { - Color.parseColor(defaultColorCode) + if (defaultColor != null) { + defaultColor + } else { + Color.parseColor(ColorApiHelper.K5_DEFAULT_COLOR) + } } } } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt index abf7656c1f..d39aa7824c 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt @@ -122,7 +122,7 @@ object ThemePrefs : PrefManager("CanvasTheme") { private fun parseColor(hexColor: String, defaultColor: Int): Int { try { - return ColorUtils.parseColor("#${hexColor.trimMargin("#")}", "") + return ColorUtils.parseColor("#${hexColor.trimMargin("#")}", defaultColor = defaultColor) } catch (e: IllegalArgumentException) { return defaultColor } From 2719fb4102de775a6919cbb295b7e2ad2d60240c Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 21 Jun 2022 12:50:49 +0200 Subject: [PATCH 010/115] Removed retainInstance from DiscussionListFragment.kt so it can work with Hilt injection. --- .../com/instructure/student/fragment/DiscussionListFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt index 928f671281..0245b19e81 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt @@ -88,7 +88,6 @@ open class DiscussionListFragment : ParentFragment(), Bookmarkable { super.onCreate(savedInstanceState) checkForPermission() checkFeatureFlags() - retainInstance = true } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -235,6 +234,7 @@ open class DiscussionListFragment : ParentFragment(), Bookmarkable { super.onDestroyView() permissionJob?.cancel() featureFlagsJob?.cancel() + groupsJob?.cancel() recyclerAdapter.cancel() } //endregion From 2a1fd24beb07be5732fd959c9fd88e74929f7108 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 21 Jun 2022 14:22:41 +0200 Subject: [PATCH 011/115] Fixed color parsing for color names. --- .../com/instructure/pandautils/utils/ColorKeeper.kt | 13 +++++++++++-- .../com/instructure/pandautils/utils/ThemePrefs.kt | 12 +++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt index 1bf12a8539..6e5da51f04 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt @@ -16,7 +16,6 @@ package com.instructure.pandautils.utils import android.content.Context -import android.graphics.Color import android.graphics.PorterDuff import android.graphics.drawable.Drawable import androidx.annotation.ColorInt @@ -135,11 +134,21 @@ object ColorKeeper : PrefManager(PREFERENCE_FILE_NAME) { * @return The parsed color, or [defaultColor] if the string could not be parsed */ private fun parseColor(hexColor: String): Int = try { - ColorUtils.parseColor("#${hexColor.trimMargin("#")}", defaultColor = defaultColor) + val trimmedColorCode = getTrimmedColorCode(hexColor) + ColorUtils.parseColor(trimmedColorCode, defaultColor = defaultColor) } catch (e: IllegalArgumentException) { defaultColor } + // There might be cases where the color code from the response contains whitespaces. + private fun getTrimmedColorCode(colorCode: String): String { + return if (colorCode.contains("#")) { + "#${colorCode.trimMargin("#")}" + } else { + colorCode + } + } + /** * Generates a generic color based on the canvas context id, this will produce consistent colors for a given course * @param canvasContext a valid canvas context diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt index d39aa7824c..c28b2e9392 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt @@ -122,10 +122,20 @@ object ThemePrefs : PrefManager("CanvasTheme") { private fun parseColor(hexColor: String, defaultColor: Int): Int { try { - return ColorUtils.parseColor("#${hexColor.trimMargin("#")}", defaultColor = defaultColor) + val trimmedColorCode = getTrimmedColorCode(hexColor) + return ColorUtils.parseColor(trimmedColorCode, defaultColor = defaultColor) } catch (e: IllegalArgumentException) { return defaultColor } } + // There might be cases where the color codes from the response contain whitespaces. + private fun getTrimmedColorCode(colorCode: String): String { + return if (colorCode.contains("#")) { + "#${colorCode.trimMargin("#")}" + } else { + colorCode + } + } + } From 81ded703773c1e151da935f3ab07688487ad87e5 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 21 Jun 2022 14:31:18 +0200 Subject: [PATCH 012/115] Updated version --- apps/teacher/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle index e38b35dc14..18071fc0b5 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -39,8 +39,8 @@ android { defaultConfig { minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 48 - versionName = '1.18.0' + versionCode = 49 + versionName = '1.18.1' vectorDrawables.useSupportLibrary = true multiDexEnabled true testInstrumentationRunner 'com.instructure.teacher.ui.espresso.TeacherHiltTestRunner' From 6202a9d6a144215817fc093b888f27f9a2f465a6 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 21 Jun 2022 15:02:13 +0200 Subject: [PATCH 013/115] Fixed color parsing bug. --- .../com/instructure/pandautils/utils/ColorKeeper.kt | 13 +++++++++++-- .../com/instructure/pandautils/utils/ColorUtils.kt | 9 +++++++-- .../com/instructure/pandautils/utils/ThemePrefs.kt | 12 +++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt index 7c9bc7e1a5..6e5da51f04 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorKeeper.kt @@ -16,7 +16,6 @@ package com.instructure.pandautils.utils import android.content.Context -import android.graphics.Color import android.graphics.PorterDuff import android.graphics.drawable.Drawable import androidx.annotation.ColorInt @@ -135,11 +134,21 @@ object ColorKeeper : PrefManager(PREFERENCE_FILE_NAME) { * @return The parsed color, or [defaultColor] if the string could not be parsed */ private fun parseColor(hexColor: String): Int = try { - ColorUtils.parseColor("#${hexColor.trimMargin("#")}", "") + val trimmedColorCode = getTrimmedColorCode(hexColor) + ColorUtils.parseColor(trimmedColorCode, defaultColor = defaultColor) } catch (e: IllegalArgumentException) { defaultColor } + // There might be cases where the color code from the response contains whitespaces. + private fun getTrimmedColorCode(colorCode: String): String { + return if (colorCode.contains("#")) { + "#${colorCode.trimMargin("#")}" + } else { + colorCode + } + } + /** * Generates a generic color based on the canvas context id, this will produce consistent colors for a given course * @param canvasContext a valid canvas context diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt index bfcabf2976..e8fd0cf2f4 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ColorUtils.kt @@ -19,6 +19,7 @@ package com.instructure.pandautils.utils import android.graphics.* import android.graphics.drawable.Drawable import android.widget.ImageView +import androidx.annotation.ColorInt import androidx.core.graphics.drawable.DrawableCompat object ColorUtils { @@ -46,7 +47,7 @@ object ColorUtils { @JvmStatic @JvmOverloads - fun parseColor(colorCode: String?, defaultColorCode: String = ColorApiHelper.K5_DEFAULT_COLOR): Int { + fun parseColor(colorCode: String?, @ColorInt defaultColor: Int? = null): Int { return try { val fullColorCode = if (colorCode?.length == 4 && colorCode[0].toString() == "#") { "#${colorCode[1]}${colorCode[1]}${colorCode[2]}${colorCode[2]}${colorCode[3]}${colorCode[3]}" @@ -55,7 +56,11 @@ object ColorUtils { } Color.parseColor(fullColorCode) } catch (e: Exception) { - Color.parseColor(defaultColorCode) + if (defaultColor != null) { + defaultColor + } else { + Color.parseColor(ColorApiHelper.K5_DEFAULT_COLOR) + } } } } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt index abf7656c1f..c28b2e9392 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt @@ -122,10 +122,20 @@ object ThemePrefs : PrefManager("CanvasTheme") { private fun parseColor(hexColor: String, defaultColor: Int): Int { try { - return ColorUtils.parseColor("#${hexColor.trimMargin("#")}", "") + val trimmedColorCode = getTrimmedColorCode(hexColor) + return ColorUtils.parseColor(trimmedColorCode, defaultColor = defaultColor) } catch (e: IllegalArgumentException) { return defaultColor } } + // There might be cases where the color codes from the response contain whitespaces. + private fun getTrimmedColorCode(colorCode: String): String { + return if (colorCode.contains("#")) { + "#${colorCode.trimMargin("#")}" + } else { + colorCode + } + } + } From ab9fb5e1e898042874d819034140497bb6cc28ad Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Tue, 21 Jun 2022 16:33:06 +0200 Subject: [PATCH 014/115] Nightly fixes (#1616) * Fix breaking FilesE2ETest test in Student nightly. refs: affects: Student release note: none * Fix breaking FilesE2ETest and ProfileSettingsE2E test in Teacher nightly. refs: affects: Teacher release note: none --- .../java/com/instructure/student/ui/e2e/FilesE2ETest.kt | 3 +-- .../java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt | 3 +++ .../java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt index 6431b77053..fc6ce70ea3 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/FilesE2ETest.kt @@ -196,7 +196,7 @@ class FilesE2ETest: StudentTest() { submissionDetailsPage.assertCommentAttachmentDisplayed(commentUploadInfo.fileName, student) Log.d(STEP_TAG,"Navigate back to Dashboard Page.") - ViewUtils.pressBackButton(5) + ViewUtils.pressBackButton(4) Log.d(STEP_TAG,"Navigate to 'Files' menu in user left-side menubar.") dashboardPage.gotoGlobalFiles() @@ -217,7 +217,6 @@ class FilesE2ETest: StudentTest() { Log.d(STEP_TAG,"Delete $newFileName file.") fileListPage.deleteFile(newFileName) - fileListPage.assertPageObjects() Log.d(STEP_TAG,"Assert that empty view is displayed after deletion.") fileListPage.assertViewEmpty() diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt index f824cda036..f755422921 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/FilesE2ETest.kt @@ -20,6 +20,7 @@ import android.os.Environment import android.util.Log import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E +import com.instructure.canvas.espresso.KnownBug import com.instructure.canvasapi2.managers.DiscussionManager import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.DiscussionEntry @@ -58,6 +59,7 @@ class FilesE2ETest: TeacherTest() { @E2E @Test + @KnownBug @TestMetaData(Priority.MANDATORY, FeatureCategory.FILES, TestCategory.E2E) fun testFilesE2E() { @@ -204,6 +206,7 @@ class FilesE2ETest: TeacherTest() { Log.d(STEP_TAG,"Delete $newFileName file.") fileListPage.deleteFile(newFileName) + //TODO bug: https://instructure.atlassian.net/browse/MBL-16108 fileListPage.assertPageObjects() Log.d(STEP_TAG,"Assert that empty view is displayed after deletion.") diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt index 089059ad9a..ef373d381e 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt @@ -94,7 +94,6 @@ class SettingsE2ETest : TeacherTest() { Log.d(STEP_TAG,"Edit username to 'Unsaved userName' but DO NOT CLICK ON SAVE. Navigate back to Profile Settings Page without saving.") editProfileSettingsPage.editUserName("Unsaved userName") ViewUtils.pressBackButton(2) - profileSettingsPage.assertPageObjects() Log.d(STEP_TAG,"Assert that the username value remained $newUserName.") profileSettingsPage.assertUserNameIs(newUserName) From 0e1f6d4d8145e712be279bfc9c5c46079e706b64 Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:17:02 +0200 Subject: [PATCH 015/115] Extend and refactor inbox E2E test with some untested features. (#1614) refs: MBL-16103 affects: Student release note: none --- .../student/ui/e2e/InboxE2ETest.kt | 77 ++++++++++++++++--- .../ui/interaction/InboxInteractionTest.kt | 2 +- .../student/ui/pages/InboxConversationPage.kt | 9 ++- .../instructure/student/ui/pages/InboxPage.kt | 61 ++++++++++++--- 4 files changed, 126 insertions(+), 23 deletions(-) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt index 657015df25..95e09eecd4 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/InboxE2ETest.kt @@ -18,7 +18,11 @@ package com.instructure.student.ui.e2e import android.os.SystemClock.sleep import android.util.Log +import androidx.test.espresso.Espresso +import androidx.test.espresso.matcher.ViewMatchers import com.instructure.canvas.espresso.E2E +import com.instructure.canvas.espresso.refresh +import com.instructure.canvasapi2.apis.InboxApi import com.instructure.dataseeding.api.ConversationsApi import com.instructure.dataseeding.api.GroupsApi import com.instructure.panda_annotations.FeatureCategory @@ -53,26 +57,30 @@ class InboxE2ETest: StudentTest() { val student1 = data.studentsList[0] val student2 = data.studentsList[1] - // Create a group and put both students in it val groupCategory = GroupsApi.createCourseGroupCategory(course.id, teacher.token) val group = GroupsApi.createGroup(groupCategory.id, teacher.token) Log.d(PREPARATION_TAG, "Create group membership for ${student1.name} and ${student2.name} students to the group: ${group.name}.") GroupsApi.createGroupMembership(group.id, student1.id, teacher.token) GroupsApi.createGroupMembership(group.id, student2.id, teacher.token) - Log.d(PREPARATION_TAG,"Seed an email from the teacher to ${student1.name} and ${student2.name} students.") - val seededConversation = ConversationsApi.createConversation( - token = teacher.token, - recipients = listOf(student1.id.toString(), student2.id.toString()) - ).get(0) - Log.d(STEP_TAG,"Login with user: ${student1.name}, login id: ${student1.loginId} , password: ${student1.password}") tokenLogin(student1) dashboardPage.waitForRender() + dashboardPage.assertDisplaysCourse(course) Log.d(STEP_TAG,"Open Inbox Page. Assert that the previously seeded conversation is displayed.") dashboardPage.clickInboxTab() - inboxPage.assertPageObjects() //TODO: Refactor to assert to the empty view just like in teacher would be better. AFTER THAT, seed the conversation. + inboxPage.assertInboxEmpty() + + Log.d(PREPARATION_TAG,"Seed an email from the teacher to ${student1.name} and ${student2.name} students.") + val seededConversation = ConversationsApi.createConversation( + token = teacher.token, + recipients = listOf(student1.id.toString(), student2.id.toString()) + )[0] + + Log.d(STEP_TAG,"Refresh the page. Assert that there is a conversation and it is the previously seeded one.") + refresh() + inboxPage.assertHasConversation() inboxPage.assertConversationDisplayed(seededConversation) Log.d(STEP_TAG,"Click on 'New Message' button.") @@ -123,7 +131,58 @@ class InboxE2ETest: StudentTest() { Log.d(STEP_TAG,"Open Inbox Page. Assert that both, the previously seeded 'normal' conversation and the group conversation are displayed.") dashboardPage.clickInboxTab() inboxPage.assertConversationDisplayed(seededConversation) - inboxPage.assertConversationDisplayed("Hey There") + inboxPage.assertConversationDisplayed(newMessageSubject) + inboxPage.assertConversationDisplayed("Group Message") + + Log.d(STEP_TAG,"Select $newGroupMessageSubject conversation.") + inboxPage.selectConversation(newMessageSubject) + val newReplyMessage = "This is a quite new reply message." + Log.d(STEP_TAG,"Reply to $newGroupMessageSubject conversation with '$newReplyMessage' message. Assert that the reply is displayed.") + inboxConversationPage.replyToMessage(newReplyMessage) + + Log.d(STEP_TAG,"Delete $newReplyMessage reply and assert is has been deleted.") + inboxConversationPage.deleteMessage(newReplyMessage) + inboxConversationPage.assertMessageNotDisplayed(newReplyMessage) + + Log.d(STEP_TAG,"Delete the whole '$newGroupMessageSubject' subject and assert that it has been removed from the conversation list on the Inbox Page.") + inboxConversationPage.deleteConversation() //After deletion we will be navigated back to Inbox Page + inboxPage.assertConversationNotDisplayed(newMessageSubject) + inboxPage.assertConversationDisplayed(seededConversation) inboxPage.assertConversationDisplayed("Group Message") + + Log.d(STEP_TAG,"Select ${seededConversation.subject} conversation. Assert that is has not been starred already.") + inboxPage.selectConversation(seededConversation) + inboxConversationPage.assertNotStarred() + + Log.d(STEP_TAG,"Toggle Starred to mark ${seededConversation.subject} conversation as favourite. Assert that it has became starred.") + inboxConversationPage.toggleStarred() + inboxConversationPage.assertStarred() + + Log.d(STEP_TAG,"Navigate back to Inbox Page and assert that the conversation itself is starred as well.") + Espresso.pressBack() // To main inbox page + inboxPage.assertConversationStarred(seededConversation.subject) + + Log.d(STEP_TAG,"Select ${seededConversation.subject} conversation. Mark as Unread by clicking on the 'More Options' menu, 'Mark as Unread' menu point.") + inboxPage.assertUnreadMarkerVisibility(seededConversation.subject, ViewMatchers.Visibility.GONE) + inboxPage.selectConversation(seededConversation) + inboxConversationPage.markUnread() //After select 'Mark as Unread', we will be navigated back to Inbox Page + + Log.d(STEP_TAG,"Assert that ${seededConversation.subject} conversation has been marked as unread.") + inboxPage.assertUnreadMarkerVisibility(seededConversation.subject, ViewMatchers.Visibility.VISIBLE) + + Log.d(STEP_TAG,"Select ${seededConversation.subject} conversation. Archive it by clicking on the 'More Options' menu, 'Archive' menu point.") + inboxPage.selectConversation(seededConversation) + inboxConversationPage.archive() //After select 'Archive', we will be navigated back to Inbox Page + + Log.d(STEP_TAG,"Assert that ${seededConversation.subject} conversation has removed from 'All' tab.") //TODO: Discuss this logic if it's ok if we don't show Archived messages on 'All' tab... + inboxPage.assertConversationNotDisplayed(seededConversation) + + Log.d(STEP_TAG,"Select 'Archived' conversation filter.") + inboxPage.selectInboxScope(InboxApi.Scope.ARCHIVED) + + Log.d(STEP_TAG,"Assert that ${seededConversation.subject} conversation is displayed by the 'Archived' filter, and other conversations are not displayed.") + inboxPage.assertConversationDisplayed(seededConversation) + inboxPage.assertConversationNotDisplayed("Group Message") + } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt index b4c7a157f4..f3f75e8622 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/InboxInteractionTest.kt @@ -394,7 +394,7 @@ class InboxInteractionTest : StudentTest() { inboxConversationPage.toggleStarred() inboxConversationPage.assertStarred() Espresso.pressBack() // To main inbox page - inboxPage.assertConversationStarred(conversation) + inboxPage.assertConversationStarred(conversation.subject!!) } @Test diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxConversationPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxConversationPage.kt index f289d611af..c703f81202 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxConversationPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxConversationPage.kt @@ -36,6 +36,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withHint +import androidx.test.platform.app.InstrumentationRegistry import com.instructure.canvas.espresso.containsTextCaseInsensitive import com.instructure.canvas.espresso.explicitClick import com.instructure.canvas.espresso.scrollRecyclerView @@ -119,8 +120,8 @@ class InboxConversationPage : BasePage(R.id.inboxConversationPage) { fun assertMessageDisplayed(message: String) { val itemMatcher = CoreMatchers.allOf( - ViewMatchers.hasSibling(withId(R.id.attachmentContainer)), - ViewMatchers.hasSibling(withId(R.id.headerDivider)), + hasSibling(withId(R.id.attachmentContainer)), + hasSibling(withId(R.id.headerDivider)), withId(R.id.messageBody), withText(message) ) @@ -137,8 +138,8 @@ class InboxConversationPage : BasePage(R.id.inboxConversationPage) { } fun refresh() { - Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.swipeRefreshLayout), ViewMatchers.isDisplayingAtLeast(10))) - .perform(withCustomConstraints(ViewActions.swipeDown(), ViewMatchers.isDisplayingAtLeast(10))) + onView(allOf(ViewMatchers.withId(R.id.swipeRefreshLayout), isDisplayingAtLeast(10))) + .perform(withCustomConstraints(ViewActions.swipeDown(), isDisplayingAtLeast(10))) } fun toggleStarred() { diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt index c4579b654a..4095b8436b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/InboxPage.kt @@ -35,10 +35,13 @@ import com.instructure.canvasapi2.models.Conversation import com.instructure.canvasapi2.models.Course import com.instructure.dataseeding.model.ConversationApiModel import com.instructure.espresso.OnViewWithId +import com.instructure.espresso.RecyclerViewItemCountGreaterThanAssertion +import com.instructure.espresso.WaitForViewWithId import com.instructure.espresso.assertDisplayed import com.instructure.espresso.click import com.instructure.espresso.page.* import com.instructure.espresso.scrollTo +import com.instructure.espresso.swipeDown import com.instructure.student.R import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.not @@ -49,6 +52,7 @@ class InboxPage : BasePage(R.id.inboxPage) { private val createMessageButton by OnViewWithId(R.id.addMessage) private val scopeButton by OnViewWithId(R.id.filterButton) private val filterButton by OnViewWithId(R.id.inboxFilter) + private val inboxRecyclerView by WaitForViewWithId(R.id.inboxRecyclerView) fun assertConversationDisplayed(conversation: ConversationApiModel) { assertConversationDisplayed(conversation.subject) @@ -60,6 +64,10 @@ class InboxPage : BasePage(R.id.inboxPage) { onView(matcher).assertDisplayed() } + fun assertConversationNotDisplayed(conversation: ConversationApiModel) { + assertConversationNotDisplayed(conversation.subject) + } + fun assertConversationNotDisplayed(subject: String) { val matcher = withText(subject) onView(matcher).check(doesNotExist()) @@ -72,12 +80,16 @@ class InboxPage : BasePage(R.id.inboxPage) { onView(matcher).assertDisplayed() } - fun selectConversation(conversation: ConversationApiModel) { - val matcher = withText(conversation.subject) + fun selectConversation(subject: String) { + val matcher = withText(subject) scrollRecyclerView(R.id.inboxRecyclerView, matcher) onView(matcher).click() } + fun selectConversation(conversation: ConversationApiModel) { + selectConversation(conversation.subject) + } + fun selectConversation(conversation: Conversation) { waitForView(withId(R.id.inboxRecyclerView)) val matcher = withText(conversation.subject) @@ -110,13 +122,15 @@ class InboxPage : BasePage(R.id.inboxPage) { onView(withId(R.id.bottomNavigationHome)).click() } - fun assertConversationStarred(conversation: Conversation) { + fun assertConversationStarred(subject: String) { val matcher = allOf( - withId(R.id.star), - withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), - ViewMatchers.withParent(ViewMatchers.withParent(withChild( - allOf(withId(R.id.message), withText(conversation.lastMessage)) - )))) + withId(R.id.star), + withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), + hasSibling(withId(R.id.userName)), + hasSibling(withId(R.id.date)), + ViewMatchers.withParent(ViewMatchers.withParent(withChild( + allOf(withId(R.id.subjectView), withText(subject)))) + )) waitForMatcherWithRefreshes(matcher) // May need to refresh before the star shows up scrollRecyclerView(R.id.inboxRecyclerView, matcher) onView(matcher).assertDisplayed() @@ -127,7 +141,7 @@ class InboxPage : BasePage(R.id.inboxPage) { val matcher = allOf( withId(R.id.unreadMark), withEffectiveVisibility(visibility), - ViewMatchers.withParent(ViewMatchers.hasSibling(withChild( + ViewMatchers.withParent(hasSibling(withChild( allOf(withId(R.id.message), withText(conversation.lastMessage)) )))) @@ -141,5 +155,34 @@ class InboxPage : BasePage(R.id.inboxPage) { } } + fun assertUnreadMarkerVisibility(subject: String, visibility: ViewMatchers.Visibility) { + val matcher = allOf( + withId(R.id.unreadMark), + withEffectiveVisibility(visibility), + hasSibling(allOf(withId(R.id.avatar))), + ViewMatchers.withParent(hasSibling(withChild( + allOf(withId(R.id.subjectView), withText(subject)))) + ) + ) + if(visibility == ViewMatchers.Visibility.VISIBLE) { + waitForMatcherWithRefreshes(matcher) // May need to refresh before the unread mark shows up + scrollRecyclerView(R.id.inboxRecyclerView, matcher) + onView(matcher).assertDisplayed() + } + else if(visibility == ViewMatchers.Visibility.GONE) { + onView(matcher).check(matches(not(isDisplayed()))) + } + } + + fun assertInboxEmpty() { + onView(withId(R.id.emptyInboxView)).assertDisplayed() + } + fun assertHasConversation() { + assertConversationCountIsGreaterThan(0) + } + + fun assertConversationCountIsGreaterThan(count: Int) { + inboxRecyclerView.check(RecyclerViewItemCountGreaterThanAssertion(count)) + } } \ No newline at end of file From a824d8b2bd55d8bb9fd4afde15bc2e20d5424437 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Fri, 24 Jun 2022 10:29:08 +0200 Subject: [PATCH 016/115] Updated version. --- apps/student/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/student/build.gradle b/apps/student/build.gradle index 709769964c..fd904fddd4 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -54,8 +54,8 @@ android { applicationId "com.instructure.candroid" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 240 - versionName = '6.18.1' + versionCode = 241 + versionName = '6.18.2' vectorDrawables.useSupportLibrary = true multiDexEnabled = true From 398366d3c41af6e40a3da5b4a5310e62d2fbd5c1 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Fri, 24 Jun 2022 10:48:00 +0200 Subject: [PATCH 017/115] Added check for force dark strategy. --- .../pandautils/utils/WebViewExtensions.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt index 587a2b9e5b..4eaaa1b859 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt @@ -18,6 +18,7 @@ package com.instructure.pandautils.utils import android.content.Context import android.content.res.Configuration import android.webkit.JavascriptInterface +import android.webkit.WebSettings import android.webkit.WebView import androidx.webkit.WebSettingsCompat import androidx.webkit.WebViewFeature @@ -158,10 +159,8 @@ fun WebView.setDarkModeSupport(webThemeDarkeningOnly: Boolean = false) { val nightModeFlags: Int = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_ON) - if (webThemeDarkeningOnly) { - WebSettingsCompat.setForceDarkStrategy(settings, WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY) - } else { - WebSettingsCompat.setForceDarkStrategy(settings, WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING) + if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) { + setForceDarkStrategy(webThemeDarkeningOnly, settings) } } else { WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_OFF) @@ -169,6 +168,14 @@ fun WebView.setDarkModeSupport(webThemeDarkeningOnly: Boolean = false) { } } +private fun setForceDarkStrategy(webThemeDarkeningOnly: Boolean, settings: WebSettings) { + if (webThemeDarkeningOnly) { + WebSettingsCompat.setForceDarkStrategy(settings, WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY) + } else { + WebSettingsCompat.setForceDarkStrategy(settings, WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING) + } +} + fun WebView.addDarkThemeToHtmlDocument() { val css = """ @media (prefers-color-scheme: dark) { From 7bf9eef5d3592c351de019d8a367334ad718660c Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Fri, 24 Jun 2022 10:50:03 +0200 Subject: [PATCH 018/115] Updated version. --- apps/teacher/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle index 18071fc0b5..905873291d 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -39,8 +39,8 @@ android { defaultConfig { minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 49 - versionName = '1.18.1' + versionCode = 50 + versionName = '1.18.2' vectorDrawables.useSupportLibrary = true multiDexEnabled true testInstrumentationRunner 'com.instructure.teacher.ui.espresso.TeacherHiltTestRunner' From adfd4d5a6365f6dffa3cb469181a1aad098d03e8 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Fri, 24 Jun 2022 10:48:00 +0200 Subject: [PATCH 019/115] Added check for force dark strategy. --- .../pandautils/utils/WebViewExtensions.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt index 587a2b9e5b..4eaaa1b859 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/WebViewExtensions.kt @@ -18,6 +18,7 @@ package com.instructure.pandautils.utils import android.content.Context import android.content.res.Configuration import android.webkit.JavascriptInterface +import android.webkit.WebSettings import android.webkit.WebView import androidx.webkit.WebSettingsCompat import androidx.webkit.WebViewFeature @@ -158,10 +159,8 @@ fun WebView.setDarkModeSupport(webThemeDarkeningOnly: Boolean = false) { val nightModeFlags: Int = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_ON) - if (webThemeDarkeningOnly) { - WebSettingsCompat.setForceDarkStrategy(settings, WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY) - } else { - WebSettingsCompat.setForceDarkStrategy(settings, WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING) + if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) { + setForceDarkStrategy(webThemeDarkeningOnly, settings) } } else { WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_OFF) @@ -169,6 +168,14 @@ fun WebView.setDarkModeSupport(webThemeDarkeningOnly: Boolean = false) { } } +private fun setForceDarkStrategy(webThemeDarkeningOnly: Boolean, settings: WebSettings) { + if (webThemeDarkeningOnly) { + WebSettingsCompat.setForceDarkStrategy(settings, WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY) + } else { + WebSettingsCompat.setForceDarkStrategy(settings, WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING) + } +} + fun WebView.addDarkThemeToHtmlDocument() { val css = """ @media (prefers-color-scheme: dark) { From 68c298f4233f7dca4b76ba7b99a0dbc1ed17cfc8 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Fri, 24 Jun 2022 11:03:09 +0200 Subject: [PATCH 020/115] [MBL-16097][Student][Teacher] Viewing rotated images for a submission crashes the app #1619 refs: MBL-16097 affects: Student, Teacher release note: Fixed a crash when opening rotated image submissions. --- .../java/com/instructure/annotations/PdfSubmissionView.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 600246d2af..5493c28904 100644 --- a/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt +++ b/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt @@ -461,14 +461,15 @@ abstract class PdfSubmissionView(context: Context) : FrameLayout(context), Annot } private fun handlePageRotation(pdfDocument: PdfDocument, rotationMap: HashMap) { + // Removing the listener prevents an infinite loop with onDocumentLoaded, which is triggered + // by the calls to setRotationOffset() + pdfFragment?.removeDocumentListener(documentListener) + rotationMap.forEach { pageRotation -> pageRotation.key.toIntOrNull()?.let { pageIndex -> pdfDocument.setRotationOffset(calculateRotationOffset(pdfDocument.getPageRotation(pageIndex), pageRotation.value), pageIndex) } } - // Removing the listener prevents an infinite loop with onDocumentLoaded, which is triggered - // by the calls to setRotationOffset() - pdfFragment?.removeDocumentListener(documentListener) } @Suppress("EXPERIMENTAL_FEATURE_WARNING") From 3b6553276eb8756c15112798c5726b6984bb69d4 Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Mon, 27 Jun 2022 12:15:42 +0200 Subject: [PATCH 021/115] [MBL-16108][Teacher] FileList folder delete refresh (#1620) refs: MBL-16108 affects: Teacher release note: Fixed a bug where deleted folders would still be accessible until manual refresh. test plan: Delete a folder on the Files screen > The folder should no longer be visible. --- .../com/instructure/teacher/fragments/EditFileFolderFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditFileFolderFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditFileFolderFragment.kt index eb757b190c..5a8eb1676f 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditFileFolderFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditFileFolderFragment.kt @@ -118,7 +118,7 @@ class EditFileFolderFragment : BasePresenterFragment< override fun onRefreshStarted() = Unit override fun folderDeleted(deletedFileFolder: FileFolder) { - FileFolderDeletedEvent(deletedFileFolder).post() + FileFolderDeletedEvent(deletedFileFolder).postSticky() requireActivity().onBackPressed() } From 6419e6a9f5846752a15863a5e0cdb5170722c937 Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Mon, 27 Jun 2022 12:16:37 +0200 Subject: [PATCH 022/115] [MBL-16112][Teacher] Update empty state on folder creation (#1623) refs: MBL-16112 affects: Teacher release note: Fixed a bug where creating the first folder does not update the view. test plan: See ticket. --- .../com/instructure/teacher/fragments/FileListFragment.kt | 4 ++++ .../com/instructure/teacher/presenters/FileListPresenter.kt | 2 ++ .../com/instructure/teacher/viewinterface/FileListView.kt | 1 + 3 files changed, 7 insertions(+) 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 36df5c9006..dc05caab89 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 @@ -220,6 +220,10 @@ class FileListFragment : BaseSyncFragment< } override fun folderCreationError() = toast(R.string.folderCreationError) + override fun folderCreationSuccess() { + checkIfEmpty() + } + private fun setupViews() { ViewStyler.themeFAB(addFab, ThemePrefs.buttonColor) ViewStyler.themeFAB(addFileFab, ThemePrefs.buttonColor) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/FileListPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/FileListPresenter.kt index 907fb475d1..db83b17a9f 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/FileListPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/FileListPresenter.kt @@ -26,6 +26,7 @@ import com.instructure.canvasapi2.utils.weave.awaitApis import com.instructure.canvasapi2.utils.weave.catch import com.instructure.canvasapi2.utils.weave.tryWeave import com.instructure.teacher.viewinterface.FileListView +import instructure.androidblueprint.ListChangeCallback import instructure.androidblueprint.SyncPresenter import kotlinx.coroutines.Job @@ -80,6 +81,7 @@ class FileListPresenter(var currentFolder: FileFolder, val mCanvasContext: Canva createFolderCall = tryWeave { val newFolder = awaitApi { FileFolderManager.createFolder(currentFolder.id, CreateFolder(folderName), it) } data.addOrUpdate(newFolder) + viewCallback?.folderCreationSuccess() } catch { viewCallback?.folderCreationError() } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/FileListView.kt b/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/FileListView.kt index 1030de974a..e2342334ee 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/FileListView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/FileListView.kt @@ -21,4 +21,5 @@ import instructure.androidblueprint.SyncManager interface FileListView : SyncManager { fun folderCreationError() + fun folderCreationSuccess() } \ No newline at end of file From 3d5ec783e33c4980177f80a55bbf89125b96d1fc Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Mon, 27 Jun 2022 15:06:22 +0200 Subject: [PATCH 023/115] [MBL-16040] [Student] - Refactor announcement interaction tests (#1625) * Implement group announcement creation with student user. Implement new API endpoint for MockCanvas. +Some refactor refs: MBL-16040 affects: Student release note: none * Code duplication refactor refs: MBL-16040 affects: Student release note: none * remove unused imports. refs: MBL-16040 affects: Student release note: none * remove string parameter overload for courseId in mockcanvas method. refs: MBL-16040 affects: Student release note: none * set back wildcard import threshold to default (5) refs: MBL-16040 affects: Student release note: none --- .../AnnouncementInteractionTest.kt | 98 ++++++++++++++++--- .../student/ui/pages/DashboardPage.kt | 2 +- .../teacher/ui/e2e/SettingsE2ETest.kt | 2 + .../canvas/espresso/mockCanvas/MockCanvas.kt | 19 ++-- .../mockCanvas/endpoints/ApiEndpoint.kt | 26 +++++ 5 files changed, 125 insertions(+), 22 deletions(-) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt index 56ffbf4801..11a7d8cae7 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/AnnouncementInteractionTest.kt @@ -20,9 +20,12 @@ import androidx.test.espresso.web.webdriver.Locator import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addCoursePermissions import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse +import com.instructure.canvas.espresso.mockCanvas.addGroupToCourse import com.instructure.canvas.espresso.mockCanvas.init import com.instructure.canvasapi2.models.CanvasContextPermission import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.DiscussionTopicHeader +import com.instructure.canvasapi2.models.Group import com.instructure.canvasapi2.models.Tab import com.instructure.canvasapi2.models.User import com.instructure.panda_annotations.FeatureCategory @@ -42,6 +45,10 @@ class AnnouncementInteractionTest : StudentTest() { private lateinit var course: Course private lateinit var user: User + private lateinit var group : Group + private lateinit var discussion : DiscussionTopicHeader + private lateinit var announcement : DiscussionTopicHeader + // Student enrolled in intended section can see and reply to the announcement // (This kind of seems like more of a test of the mocked endpoint, but we'll go with it.) @Test @@ -159,7 +166,7 @@ class AnnouncementInteractionTest : StudentTest() { discussionListPage.createAnnouncement("Announcement Topic", "Awesome announcement topic") } - // Tests code around closing / aborting announcement creation + // Tests code around closing / aborting announcement creation (as a teacher) @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) fun testAnnouncementCreate_abort() { @@ -177,7 +184,7 @@ class AnnouncementInteractionTest : StudentTest() { discussionListPage.assertAnnouncementCount(2) // header + the one test announcement } - // Tests code around creating an announcement with no description + // Tests code around creating an announcement with no description (as a teacher) @Test @TestMetaData(Priority.COMMON, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) fun testAnnouncementCreate_missingDescription() { @@ -188,7 +195,7 @@ class AnnouncementInteractionTest : StudentTest() { discussionListPage.assertOnNewAnnouncementPage() } - // Tests code around creating an announcement with no title + // Tests code around creating an announcement with no title (as a teacher) @Test @TestMetaData(Priority.COMMON, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) fun testAnnouncementCreate_missingTitle() { @@ -196,6 +203,15 @@ class AnnouncementInteractionTest : StudentTest() { discussionListPage.createAnnouncement("", "description") } + @Test + @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) + fun testGroupAnnouncementCreateAsStudent() { + getToGroup() + + courseBrowserPage.selectAnnouncements() + discussionListPage.createAnnouncement("Student created Group Announcement", "Cool group announcement", true) + } + @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.ANNOUNCEMENTS, TestCategory.INTERACTION, false) fun testSearchAnnouncement() { @@ -225,24 +241,82 @@ class AnnouncementInteractionTest : StudentTest() { courseCount: Int = 1, createSections: Boolean = false ): MockCanvas { + + val data = initData(studentCount,courseCount,createSections) + + val token = data.tokenFor(user)!! + tokenLogin(data.domain, token, user) + dashboardPage.waitForRender() + + dashboardPage.selectCourse(course) + + return data + } + + private fun getToGroup( + studentCount: Int = 1, + courseCount: Int = 1, + createSections: Boolean = false + ): MockCanvas { + + val data = initData(studentCount,courseCount,createSections) + + val token = data.tokenFor(user)!! + tokenLogin(data.domain, token, user) + dashboardPage.waitForRender() + + dashboardPage.selectGroup(group) + + return data + } + + private fun initData( studentCount: Int = 1, + courseCount: Int = 1, + createSections: Boolean = false): MockCanvas { val data = MockCanvas.init( - studentCount = studentCount, - courseCount = courseCount, - favoriteCourseCount = courseCount, - createSections = createSections) + studentCount = studentCount, + courseCount = courseCount, + favoriteCourseCount = courseCount, + createSections = createSections) course = data.courses.values.first() user = data.students[0] + // Add a group + val user = data.users.values.first() + group = data.addGroupToCourse( + course = course, + members = listOf(user), + isFavorite = true + ) + + // Add a discussion + discussion = data.addDiscussionTopicToCourse( + course = course, + user = user, + groupId = group.id + ) + + // Add an announcement + announcement = data.addDiscussionTopicToCourse( + course = course, + user = user, + groupId = group.id, + isAnnouncement = true + ) + val announcementsTab = Tab(position = 2, label = "Announcements", visibility = "public", tabId = Tab.ANNOUNCEMENTS_ID) data.courseTabs[course.id]!! += announcementsTab - val token = data.tokenFor(user)!! - tokenLogin(data.domain, token, user) - dashboardPage.waitForRender() - - dashboardPage.selectCourse(course) + data.groupTabs[group.id] = mutableListOf( + Tab(position = 0, label = "Discussions", tabId = Tab.DISCUSSIONS_ID, visibility = "public"), + Tab(position = 1, label = "Announcements", tabId = Tab.ANNOUNCEMENTS_ID, visibility = "public"), + ) + MockCanvas.data.addCoursePermissions( + course.id, + CanvasContextPermission(canCreateAnnouncement = true) + ) return data } diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt index 551826ff7b..e6272d29f7 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/DashboardPage.kt @@ -238,7 +238,7 @@ class DashboardPage : BasePage(R.id.dashboardPage) { fun selectCourse(course: Course) { assertDisplaysCourse(course) - onView(withText(course.originalName)).perform(withCustomConstraints(click(), isDisplayingAtLeast(10))) + onView(withId(R.id.titleTextView) + withText(course.originalName)).perform(withCustomConstraints(click(), isDisplayingAtLeast(10))) } fun selectGroup(group: Group) { diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt index ef373d381e..de5fd0312d 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/SettingsE2ETest.kt @@ -37,6 +37,7 @@ import com.instructure.teacher.ui.utils.seedData import com.instructure.teacher.ui.utils.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +import java.lang.Thread.sleep @HiltAndroidTest class SettingsE2ETest : TeacherTest() { @@ -94,6 +95,7 @@ class SettingsE2ETest : TeacherTest() { Log.d(STEP_TAG,"Edit username to 'Unsaved userName' but DO NOT CLICK ON SAVE. Navigate back to Profile Settings Page without saving.") editProfileSettingsPage.editUserName("Unsaved userName") ViewUtils.pressBackButton(2) + sleep(3000) //Give some time to "realize" we are on Profile Settings Page. Log.d(STEP_TAG,"Assert that the username value remained $newUserName.") profileSettingsPage.assertUserNameIs(newUserName) diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt index 177258bc1a..0a6671f9fc 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt @@ -19,7 +19,6 @@ package com.instructure.canvas.espresso.mockCanvas import android.util.Log -import com.github.javafaker.Bool import com.github.javafaker.Faker import com.instructure.canvas.espresso.mockCanvas.utils.Randomizer import com.instructure.canvasapi2.apis.EnrollmentAPI @@ -1360,9 +1359,9 @@ fun MockCanvas.addDiscussionTopicToCourse( var topicHeader = prePopulatedTopicHeader if(topicHeader == null) { topicHeader = DiscussionTopicHeader( - title = topicTitle, - discussionType = "side_comment", - message = topicDescription + title = topicTitle, + discussionType = "side_comment", + message = topicDescription ) } @@ -1374,7 +1373,7 @@ fun MockCanvas.addDiscussionTopicToCourse( topicHeader.id = newItemId() topicHeader.postedDate = Calendar.getInstance().time if(attachment != null) { - topicHeader.attachments = mutableListOf(attachment) + topicHeader.attachments = mutableListOf(attachment) } topicHeader.announcement = isAnnouncement topicHeader.sections = sections @@ -1383,7 +1382,7 @@ fun MockCanvas.addDiscussionTopicToCourse( var topicHeaderList = if(groupId != null) groupDiscussionTopicHeaders[groupId] else courseDiscussionTopicHeaders[course.id] if(topicHeaderList == null) { - topicHeaderList = mutableListOf() + topicHeaderList = mutableListOf() if(groupId != null) { groupDiscussionTopicHeaders[groupId] = topicHeaderList } @@ -1395,9 +1394,9 @@ fun MockCanvas.addDiscussionTopicToCourse( topicHeaderList.add(topicHeader) val topic = DiscussionTopic( - participants = mutableListOf( - DiscussionParticipant(id = user.id, displayName = user.name) - ) + participants = mutableListOf( + DiscussionParticipant(id = user.id, displayName = user.name) + ) ) discussionTopics[topicHeader.id] = topic @@ -1771,6 +1770,8 @@ fun MockCanvas.addGroupToCourse( isFavorite = isFavorite ) + result.permissions = CanvasContextPermission(canCreateAnnouncement = true) + groups[result.id] = result return result diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ApiEndpoint.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ApiEndpoint.kt index 349196d55c..62d81e572c 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ApiEndpoint.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/endpoints/ApiEndpoint.kt @@ -17,7 +17,9 @@ package com.instructure.canvas.espresso.mockCanvas.endpoints import android.util.Log +import com.google.gson.Gson import com.instructure.canvas.espresso.mockCanvas.Endpoint +import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse import com.instructure.canvas.espresso.mockCanvas.endpoint import com.instructure.canvas.espresso.mockCanvas.utils.* import com.instructure.canvasapi2.models.* @@ -218,6 +220,30 @@ object GroupsEndpoint : Endpoint ( request.unauthorizedResponse() } } + POST { + val jsonObject = grabJsonFromMultiPartBody(request.body!!) + var newHeader = Gson().fromJson(jsonObject, DiscussionTopicHeader::class.java) + var group = data.groups.values.find { it.id == pathVars.groupId } + var course = data.courses.values.find {it.id == group!!.courseId} + var user = request.user!! + + newHeader = data.addDiscussionTopicToCourse( + course = course!!, + groupId = group!!.id, + user = user, + prePopulatedTopicHeader = newHeader, + topicTitle = newHeader.title!!, + topicDescription = newHeader.message!!, + allowRating = data.discussionRatingsEnabled, + allowReplies = data.discussionRepliesEnabled, + allowAttachments = data.discussionAttachmentsEnabled, + isAnnouncement = newHeader.announcement + ) + Log.d("<--", "new discussion topic request body: $jsonObject") + Log.d("<--", "new header: $newHeader") + + request.successResponse(newHeader) + } } ), From 54bffc9b6975e6d73fccc53b4a03054ad5c41b34 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Tue, 28 Jun 2022 11:03:03 +0200 Subject: [PATCH 024/115] [MBL-16110][Student] Modifying course nickname triggers an unnecessary reorder on Dashboard #1629 refs: MBL-16110 affects: Student release note: none --- .../java/com/instructure/student/fragment/DashboardFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt index 6acd19c5c4..b700bc17ec 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt @@ -134,7 +134,7 @@ class DashboardFragment : ParentFragment() { course.name = response.nickname!! course.originalName = response.name } - recyclerAdapter?.addOrUpdateItem(DashboardRecyclerAdapter.ItemType.COURSE_HEADER, course) + recyclerAdapter?.notifyDataSetChanged() } catch { toast(R.string.courseNicknameError) } From a89d6d8d25d504527afc6016219098d9bfd9ab13 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 28 Jun 2022 11:41:28 +0200 Subject: [PATCH 025/115] Updated version --- apps/teacher/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/teacher/build.gradle b/apps/teacher/build.gradle index 905873291d..b3baa306d5 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -39,8 +39,8 @@ android { defaultConfig { minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 50 - versionName = '1.18.2' + versionCode = 51 + versionName = '1.18.3' vectorDrawables.useSupportLibrary = true multiDexEnabled true testInstrumentationRunner 'com.instructure.teacher.ui.espresso.TeacherHiltTestRunner' From f876f6c6766917f7c5ddfd7178ddca859da9d4fd Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 28 Jun 2022 11:56:47 +0200 Subject: [PATCH 026/115] Fixed discussion list crash on tablets. --- .../teacher/fragments/DiscussionsListFragment.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsListFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsListFragment.kt index e2b7a853e6..f909a0066a 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsListFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsListFragment.kt @@ -16,14 +16,15 @@ */ package com.instructure.teacher.fragments -import android.graphics.Color import android.view.View import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.instructure.canvasapi2.managers.UserManager import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.DiscussionTopicHeader import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.RemoteConfigUtils import com.instructure.interactions.router.Route import com.instructure.pandautils.analytics.SCREEN_VIEW_DISCUSSION_LIST import com.instructure.pandautils.analytics.ScreenView @@ -39,15 +40,12 @@ import com.instructure.teacher.router.RouteMatcher import com.instructure.teacher.utils.RecyclerViewUtils import com.instructure.teacher.utils.setupBackButton import com.instructure.teacher.viewinterface.DiscussionListView -import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.synthetic.main.fragment_discussion_list.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import javax.inject.Inject @ScreenView(SCREEN_VIEW_DISCUSSION_LIST) -@AndroidEntryPoint open class DiscussionsListFragment : BaseExpandableSyncFragment< String, DiscussionTopicHeader, @@ -56,8 +54,7 @@ open class DiscussionsListFragment : BaseExpandableSyncFragment< RecyclerView.ViewHolder, DiscussionListAdapter>(), DiscussionListView { - @Inject - lateinit var featureFlagProvider: FeatureFlagProvider + val featureFlagProvider: FeatureFlagProvider = FeatureFlagProvider(UserManager, RemoteConfigUtils, ApiPrefs) protected var mCanvasContext: CanvasContext by ParcelableArg(default = CanvasContext.getGenericContext(CanvasContext.Type.COURSE, -1L, "")) From c94b1c3f45836ce13500f627a59df3ff9fd67ad4 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:23:04 +0200 Subject: [PATCH 027/115] [MBL-15648][Student][Teacher] Remove CameraKit dependency #1627 refs: MBL-15648 affects: Student, Teacher release note: none --- .../view_floating_media_recorder_video.xml | 9 ++--- buildSrc/src/main/java/GlobalDependencies.kt | 2 +- libs/pandautils/build.gradle | 2 +- .../pandautils/views/FloatingRecordingView.kt | 34 +++++++++++-------- .../view_floating_media_recorder_video.xml | 9 ++--- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/apps/teacher/src/main/res/layout-sw760dp/view_floating_media_recorder_video.xml b/apps/teacher/src/main/res/layout-sw760dp/view_floating_media_recorder_video.xml index bfd6809b1a..b1aa7337a6 100644 --- a/apps/teacher/src/main/res/layout-sw760dp/view_floating_media_recorder_video.xml +++ b/apps/teacher/src/main/res/layout-sw760dp/view_floating_media_recorder_video.xml @@ -16,19 +16,20 @@ --> - + android:keepScreenOn="true" + app:cameraFacing="front" + app:cameraMode="video" /> Unit lateinit var replayCallback: (File?) -> Unit - lateinit var cameraKitVideoCapturedCallback : CameraKitEventCallback + lateinit var videoCaptureCallback: CameraListener /** The View used to record video. This must be inflated separately from the main layout in * order to capture exceptions during inflation and disable the video functionality */ @@ -114,7 +114,7 @@ class FloatingRecordingView @JvmOverloads constructor( recordingView.setVisible() if (hasVideoError) return resetVideoViews() - recordingView.camera.start() + recordingView.camera.open() } private fun stopVideoView() { @@ -124,7 +124,7 @@ class FloatingRecordingView @JvmOverloads constructor( isRecording = false recordingView.camera.stopVideo() } - recordingView.camera.stop() + recordingView.camera.close() timerHandler.removeCallbacks(timerRunnable) stoppedCallback() } @@ -256,9 +256,11 @@ class FloatingRecordingView @JvmOverloads constructor( videoFile = File(context.cacheDir, "temp.mp4") - cameraKitVideoCapturedCallback = CameraKitEventCallback { video -> - timerHandler.removeCallbacks(timerRunnable) - videoFile = video.videoFile + videoCaptureCallback = object : CameraListener() { + override fun onVideoTaken(result: VideoResult) { + timerHandler.removeCallbacks(timerRunnable) + videoFile = result.file + } } // Set the close button. @@ -267,11 +269,13 @@ class FloatingRecordingView @JvmOverloads constructor( } recordingView.startRecordingButton.onClick { - recordingView.camera.captureVideo(videoFile, cameraKitVideoCapturedCallback) - isRecording = true - startTime = System.currentTimeMillis() - timerHandler.postDelayed(timerRunnable, 0) - setViewStateStartRecording() + videoFile?.let { file -> + recordingView.camera.takeVideo(file) + isRecording = true + startTime = System.currentTimeMillis() + timerHandler.postDelayed(timerRunnable, 0) + setViewStateStartRecording() + } } recordingView.endRecordingButton.onClick { @@ -284,6 +288,8 @@ class FloatingRecordingView @JvmOverloads constructor( recordingView.deleteButton.onClick { videoFile?.delete() resetVideoViews() + recordingView.camera.close() + recordingView.camera.open() } recordingView.replayButton.onClick { @@ -300,7 +306,7 @@ class FloatingRecordingView @JvmOverloads constructor( isRecording = false recordingView.camera.stopVideo() } - recordingView.camera.stop() + recordingView.camera.close() timerHandler.removeCallbacks(timerRunnable) } } diff --git a/libs/pandautils/src/main/res/layout/view_floating_media_recorder_video.xml b/libs/pandautils/src/main/res/layout/view_floating_media_recorder_video.xml index 7a1674d1ff..fe11c8d126 100644 --- a/libs/pandautils/src/main/res/layout/view_floating_media_recorder_video.xml +++ b/libs/pandautils/src/main/res/layout/view_floating_media_recorder_video.xml @@ -16,19 +16,20 @@ --> - + android:keepScreenOn="true" + app:cameraFacing="front" + app:cameraMode="video"/> Date: Tue, 28 Jun 2022 14:27:55 +0200 Subject: [PATCH 028/115] [MBL-15849][Teacher] Dashboard Notifications for Teacher (#1628) refs: MBL-15849 affects: Teacher release note: Added Announcements, Invites and Conferences to the Dashboard. test plan: Check to see if the Dashboard Notifications are working. --- .../instructure/teacher/di/DashboardModule.kt | 5 ++- .../notifications/TeacherDashboardRouter.kt | 16 +++++++- .../teacher/fragments/CoursesFragment.kt | 4 ++ .../fragments/InternalWebViewFragment.kt | 3 +- .../src/main/res/layout/fragment_courses.xml | 39 ++++++++++++------- 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/di/DashboardModule.kt b/apps/teacher/src/main/java/com/instructure/teacher/di/DashboardModule.kt index ea60425fa0..1516dc4f00 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/di/DashboardModule.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/di/DashboardModule.kt @@ -16,6 +16,7 @@ package com.instructure.teacher.di +import androidx.fragment.app.FragmentActivity import com.instructure.pandautils.features.dashboard.notifications.DashboardRouter import com.instructure.teacher.features.dashboard.notifications.TeacherDashboardRouter import dagger.Module @@ -28,7 +29,7 @@ import dagger.hilt.android.components.FragmentComponent class DashboardModule { @Provides - fun provideHomeroomRouter(): DashboardRouter { - return TeacherDashboardRouter() + fun provideHomeroomRouter(activity: FragmentActivity): DashboardRouter { + return TeacherDashboardRouter(activity) } } \ No newline at end of file diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/dashboard/notifications/TeacherDashboardRouter.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/dashboard/notifications/TeacherDashboardRouter.kt index 413a9b30b3..3dfef9a1b1 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/dashboard/notifications/TeacherDashboardRouter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/dashboard/notifications/TeacherDashboardRouter.kt @@ -16,8 +16,20 @@ package com.instructure.teacher.features.dashboard.notifications +import androidx.fragment.app.FragmentActivity +import com.instructure.interactions.router.Route import com.instructure.pandautils.features.dashboard.notifications.DashboardRouter +import com.instructure.teacher.fragments.InternalWebViewFragment +import com.instructure.teacher.router.RouteMatcher -class TeacherDashboardRouter : DashboardRouter { - override fun routeToGlobalAnnouncement(subject: String, message: String) = Unit +class TeacherDashboardRouter(private val activity: FragmentActivity) : DashboardRouter { + override fun routeToGlobalAnnouncement(subject: String, message: String) { + val args = InternalWebViewFragment.makeBundle( + url ="", + title = subject, + html = message + ) + val route = Route(null, InternalWebViewFragment::class.java, null, args) + RouteMatcher.route(activity, route) + } } \ No newline at end of file diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CoursesFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CoursesFragment.kt index 290cd18955..33f8ab9b9e 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CoursesFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CoursesFragment.kt @@ -25,6 +25,7 @@ import com.instructure.canvasapi2.utils.APIHelper import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.pandautils.analytics.SCREEN_VIEW_DASHBOARD import com.instructure.pandautils.analytics.ScreenView +import com.instructure.pandautils.features.dashboard.notifications.DashboardNotificationsFragment import com.instructure.pandautils.fragments.BaseSyncFragment import com.instructure.pandautils.utils.* import com.instructure.teacher.R @@ -193,6 +194,8 @@ class CoursesFragment : BaseSyncFragment - - diff --git a/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt b/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt index 4e8e1cea7c..8642112373 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt @@ -27,6 +27,8 @@ import android.view.View import android.widget.CompoundButton import android.widget.TextView import android.widget.Toast +import androidx.annotation.IdRes +import androidx.annotation.PluralsRes import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat @@ -369,36 +371,24 @@ class InitActivity : BasePresenterActivity 0) { - // Update content description - if (bottomBar.menu.size() > 1) { - val title = String.format(Locale.getDefault(), getString(R.string.todoUnreadCount), todoCount) - MenuItemCompat.setContentDescription(bottomBar.menu.getItem(1), if (selectedTab == TODO_TAB) "${getString(R.string.selected)} $title" else title) - } + updateBottomBarBadge(R.id.tab_todo, todoCount) + } + + override fun updateInboxUnreadCount(unreadCount: Int) { + updateBottomBarBadge(R.id.tab_inbox, unreadCount, R.plurals.a11y_inboxUnreadCount) + } - val todoCountDisplay = if (todoCount > 99) getString(R.string.max_count) else todoCount.toString() - - // First child is the imageView that we use for the bottom bar, second is a layout for the label - (view.getChildAt(2) as? TextView)?.let { - it.text = todoCountDisplay - } ?: run { - // No badge, we need to create one - val badge = LayoutInflater.from(this).inflate(R.layout.unread_count, bottomBar, false) as TextView - badge.text = todoCountDisplay - ColorUtils.colorIt(getColorCompat(R.color.textInfo), badge.background) - view.addView(badge) + private fun updateBottomBarBadge(@IdRes menuItemId: Int, count: Int, @PluralsRes quantityContentDescription: Int? = null) { + if (count > 0) { + bottomBar.getOrCreateBadge(menuItemId).number = count + bottomBar.getOrCreateBadge(menuItemId).backgroundColor = getColor(R.color.backgroundInfo) + bottomBar.getOrCreateBadge(menuItemId).badgeTextColor = getColor(R.color.white) + if (quantityContentDescription != null) { + bottomBar.getOrCreateBadge(menuItemId).setContentDescriptionQuantityStringsResource(quantityContentDescription) } } else { // Don't set the badge or display it, remove any badge - (view.getChildAt(2) as? TextView)?.let { view.removeView(it) } - - // Update content description - if (bottomBar.menu.size() > 1) { - MenuItemCompat.setContentDescription(bottomBar.menu.getItem(1), if (selectedTab == TODO_TAB) "${getString(R.string.selected)} ${getString(R.string.tab_todo)}" else getString(R.string.tab_todo)) - } + bottomBar.removeBadge(menuItemId) } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/InboxFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/InboxFragment.kt index 3648ec8260..966e89aa3f 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/InboxFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/InboxFragment.kt @@ -247,6 +247,13 @@ class InboxFragment : BaseSyncFragment Unit = { item -> when (item.itemId) { R.id.inboxFilter -> { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/InboxPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/InboxPresenter.kt index 40c497b02c..d4f6db34c0 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/InboxPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/InboxPresenter.kt @@ -17,9 +17,14 @@ package com.instructure.teacher.presenters import com.instructure.canvasapi2.apis.InboxApi import com.instructure.canvasapi2.managers.InboxManager +import com.instructure.canvasapi2.managers.UnreadCountManager import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.Conversation +import com.instructure.canvasapi2.models.UnreadConversationCount import com.instructure.canvasapi2.utils.weave.WeaveJob +import com.instructure.canvasapi2.utils.weave.awaitApi +import com.instructure.canvasapi2.utils.weave.catch +import com.instructure.canvasapi2.utils.weave.tryWeave import com.instructure.canvasapi2.utils.weave.weavePaginated import com.instructure.teacher.viewinterface.InboxView import instructure.androidblueprint.SyncPresenter @@ -34,9 +39,14 @@ class InboxPresenter : SyncPresenter(Conversation::clas var canvasContext: CanvasContext? = null var apiCall: WeaveJob? = null + var unreadCountCall: WeaveJob? = null + + private var isLoading = false override fun loadData(forceNetwork: Boolean) { - if (data.size() > 0 && !forceNetwork) return + if ((data.size() > 0 || isLoading) && !forceNetwork) return + + isLoading = true viewCallback?.onRefreshStarted() apiCall = weavePaginated> { onRequest { callback -> @@ -45,16 +55,26 @@ class InboxPresenter : SyncPresenter(Conversation::clas ?: InboxManager.getConversations(scope, forceNetwork, callback) } onResponse { response -> + isLoading = false data.addOrUpdate(response) viewCallback?.onRefreshFinished() viewCallback?.checkIfEmpty() } - onError { } + onError { + isLoading = false + } } + + unreadCountCall = tryWeave { + val inboxUnreadCount = awaitApi { UnreadCountManager.getUnreadConversationCount(it, true) } + val unreadCountInt = (inboxUnreadCount.unreadCount ?: "0").toInt() + viewCallback?.unreadCountUpdated(unreadCountInt) + } catch {} } override fun refresh(forceNetwork: Boolean) { apiCall?.cancel() + unreadCountCall?.cancel() clearData() loadData(forceNetwork) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/InitActivityPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/InitActivityPresenter.kt index 8f33ad1e5b..dbd890c8df 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/InitActivityPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/InitActivityPresenter.kt @@ -19,9 +19,11 @@ package com.instructure.teacher.presenters import com.instructure.canvasapi2.CanvasRestAdapter import com.instructure.canvasapi2.managers.LaunchDefinitionsManager import com.instructure.canvasapi2.managers.ToDoManager +import com.instructure.canvasapi2.managers.UnreadCountManager import com.instructure.canvasapi2.managers.UserManager import com.instructure.canvasapi2.models.LaunchDefinition import com.instructure.canvasapi2.models.ToDo +import com.instructure.canvasapi2.models.UnreadConversationCount import com.instructure.canvasapi2.utils.weave.WeaveJob import com.instructure.canvasapi2.utils.weave.awaitApi import com.instructure.canvasapi2.utils.weave.catch @@ -65,6 +67,9 @@ class InitActivityPresenter : Presenter { view?.gotLaunchDefinitions(definitions) } + val inboxUnreadCount = awaitApi { UnreadCountManager.getUnreadConversationCount(it, true) } + val unreadCountInt = (inboxUnreadCount.unreadCount ?: "0").toInt() + view?.updateInboxUnreadCount(unreadCountInt) } catch { it.printStackTrace() } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/InboxView.kt b/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/InboxView.kt index 8e8060878c..ac3ab549d2 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/InboxView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/InboxView.kt @@ -18,4 +18,6 @@ package com.instructure.teacher.viewinterface import com.instructure.canvasapi2.models.Conversation import instructure.androidblueprint.SyncManager -interface InboxView : SyncManager +interface InboxView : SyncManager { + fun unreadCountUpdated(unreadCount: Int) +} diff --git a/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/InitActivityView.kt b/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/InitActivityView.kt index 68799bcbf8..8988f6c216 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/InitActivityView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/viewinterface/InitActivityView.kt @@ -22,4 +22,5 @@ interface InitActivityView { fun updateTodoCount(todoCount: Int) fun gotLaunchDefinitions(launchDefinitions: List?) fun updateColorOverlaySwitch(isChecked: Boolean, isFailed: Boolean) + fun updateInboxUnreadCount(unreadCount: Int) } diff --git a/apps/teacher/src/main/res/layout/unread_count.xml b/apps/teacher/src/main/res/layout/unread_count.xml deleted file mode 100644 index 89417282f4..0000000000 --- a/apps/teacher/src/main/res/layout/unread_count.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/apps/teacher/src/main/res/values-ar/strings.xml b/apps/teacher/src/main/res/values-ar/strings.xml index b8db1987d2..f18ecf9a1f 100644 --- a/apps/teacher/src/main/res/values-ar/strings.xml +++ b/apps/teacher/src/main/res/values-ar/strings.xml @@ -928,7 +928,6 @@ -%s نقطة -%s نقاط - قائمة مهام %d غير مقروءة إعدادات ملف التعريف تنزيل الملف… تم تنزيل الملف بنجاح. diff --git a/apps/teacher/src/main/res/values-b+da+instk12/strings.xml b/apps/teacher/src/main/res/values-b+da+instk12/strings.xml index abd699dd1f..5b34c6f15e 100644 --- a/apps/teacher/src/main/res/values-b+da+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+da+instk12/strings.xml @@ -874,7 +874,6 @@ -%s point -%s point - Opgaveliste %d ulæst Profilindstillinger Downloader fil… Fil blev downloaded! diff --git a/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml b/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml index e7180fe4be..fa9a938f76 100644 --- a/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml +++ b/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml @@ -872,7 +872,6 @@ -%s point -%s points - To do %d unread Profile Settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml b/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml index 3d43fe160d..45b932a9cc 100644 --- a/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml +++ b/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml @@ -872,7 +872,6 @@ -%s point -%s points - To do %d unread Profile settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-b+nb+instk12/strings.xml b/apps/teacher/src/main/res/values-b+nb+instk12/strings.xml index 0eba6238dc..ae96287f2b 100644 --- a/apps/teacher/src/main/res/values-b+nb+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+nb+instk12/strings.xml @@ -875,7 +875,6 @@ -%s poeng -%s poeng - Gjøremål %d ulest Profilinnstillinger Laster ned fil… Filnedlasting utført. diff --git a/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml b/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml index eb7a5025ae..1b867f6658 100644 --- a/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml @@ -874,7 +874,6 @@ -%s poäng -%s poäng - Att göra %d olästa Profilinställningar Hämtar fil… Filen hämtades. diff --git a/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml b/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml index 1e1ac223c3..9c9e942bed 100644 --- a/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml +++ b/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml @@ -858,7 +858,6 @@ -%s 分 - 待办%d未读 个人资料设置 正在下载文件… 文件下载成功 diff --git a/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml b/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml index a00c82be81..25d255a0eb 100644 --- a/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml +++ b/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml @@ -858,7 +858,6 @@ -%s 分 - 待辦 %d 未讀 個人檔案設定 下載檔案… 檔案已成功下載。 diff --git a/apps/teacher/src/main/res/values-ca/strings.xml b/apps/teacher/src/main/res/values-ca/strings.xml index c73fe877fa..ab74b7afe7 100644 --- a/apps/teacher/src/main/res/values-ca/strings.xml +++ b/apps/teacher/src/main/res/values-ca/strings.xml @@ -875,7 +875,6 @@ -%s punt -%s punts - La tasca pendent %d no s\'ha llegit Configuració del perfil S\'està baixant el fitxer… S\'ha baixat correctament el fitxer. diff --git a/apps/teacher/src/main/res/values-cy/strings.xml b/apps/teacher/src/main/res/values-cy/strings.xml index a7f86304c6..9369186baf 100644 --- a/apps/teacher/src/main/res/values-cy/strings.xml +++ b/apps/teacher/src/main/res/values-cy/strings.xml @@ -872,7 +872,6 @@ %s pwynt %s pwynt - I\'w gwneud %d heb eu darllen Gosodiadau Proffil Llwytho ffeil i lawr… Wedi llwyddo i lwytho ffeil i lawr. diff --git a/apps/teacher/src/main/res/values-da/strings.xml b/apps/teacher/src/main/res/values-da/strings.xml index 8045a7237c..90ead3f6ed 100644 --- a/apps/teacher/src/main/res/values-da/strings.xml +++ b/apps/teacher/src/main/res/values-da/strings.xml @@ -872,7 +872,6 @@ -%s point -%s point - Opgaveliste %d ulæst Profilindstillinger Downloader fil… Fil blev downloaded! diff --git a/apps/teacher/src/main/res/values-de/strings.xml b/apps/teacher/src/main/res/values-de/strings.xml index 9c370dd4c8..7d36e5f78f 100644 --- a/apps/teacher/src/main/res/values-de/strings.xml +++ b/apps/teacher/src/main/res/values-de/strings.xml @@ -872,7 +872,6 @@ %s Punkt %s Punkte - %d Ungelesene erledigen Profileinstellungen Datei herunterladen… Datei erfolgreich heruntergeladen. diff --git a/apps/teacher/src/main/res/values-en-rAU/strings.xml b/apps/teacher/src/main/res/values-en-rAU/strings.xml index de27af29f6..a9318f2e70 100644 --- a/apps/teacher/src/main/res/values-en-rAU/strings.xml +++ b/apps/teacher/src/main/res/values-en-rAU/strings.xml @@ -872,7 +872,6 @@ -%s point -%s points - To do %d unread Profile Settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-en-rCA/strings.xml b/apps/teacher/src/main/res/values-en-rCA/strings.xml index e663c539c4..4b5c65d9ec 100644 --- a/apps/teacher/src/main/res/values-en-rCA/strings.xml +++ b/apps/teacher/src/main/res/values-en-rCA/strings.xml @@ -875,7 +875,6 @@ -%s point -%s points - To do %d unread Profile Settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-en-rCY/strings.xml b/apps/teacher/src/main/res/values-en-rCY/strings.xml index 3d43fe160d..45b932a9cc 100644 --- a/apps/teacher/src/main/res/values-en-rCY/strings.xml +++ b/apps/teacher/src/main/res/values-en-rCY/strings.xml @@ -872,7 +872,6 @@ -%s point -%s points - To do %d unread Profile settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-en-rGB/strings.xml b/apps/teacher/src/main/res/values-en-rGB/strings.xml index edcfd0f24c..a7bde06023 100644 --- a/apps/teacher/src/main/res/values-en-rGB/strings.xml +++ b/apps/teacher/src/main/res/values-en-rGB/strings.xml @@ -872,7 +872,6 @@ -%s point -%s points - To do %d unread Profile settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-es-rES/strings.xml b/apps/teacher/src/main/res/values-es-rES/strings.xml index 50bfff9dbc..eae61967f0 100644 --- a/apps/teacher/src/main/res/values-es-rES/strings.xml +++ b/apps/teacher/src/main/res/values-es-rES/strings.xml @@ -875,7 +875,6 @@ -%s punto -%s puntos - %d Tarea pendiente sin leer Configuración del perfil Descargando el archivo… Se ha descargado el archivo. diff --git a/apps/teacher/src/main/res/values-es/strings.xml b/apps/teacher/src/main/res/values-es/strings.xml index 4c2f9a3fc3..00133acd37 100644 --- a/apps/teacher/src/main/res/values-es/strings.xml +++ b/apps/teacher/src/main/res/values-es/strings.xml @@ -873,7 +873,6 @@ -%s punto -%s puntos - Por hacer %d sin leer Configuración del perfil Descargando el archivo… Archivo descargado exitosamente. diff --git a/apps/teacher/src/main/res/values-fi/strings.xml b/apps/teacher/src/main/res/values-fi/strings.xml index 496f5a72c2..a6ee65bcff 100644 --- a/apps/teacher/src/main/res/values-fi/strings.xml +++ b/apps/teacher/src/main/res/values-fi/strings.xml @@ -872,7 +872,6 @@ -%s piste -%s pistettä - Tehtävä %d lukematon Profiiliasetukset Tiedostoa ladataan… Tiedoston lataus onnistui. diff --git a/apps/teacher/src/main/res/values-fr-rCA/strings.xml b/apps/teacher/src/main/res/values-fr-rCA/strings.xml index 8e4a5feca0..4da97d0c32 100644 --- a/apps/teacher/src/main/res/values-fr-rCA/strings.xml +++ b/apps/teacher/src/main/res/values-fr-rCA/strings.xml @@ -872,7 +872,6 @@ -%s point -%s points - À faire %d non lue Paramètres de profil Téléchargement du fichier … Fichiers téléchargé avec succès. diff --git a/apps/teacher/src/main/res/values-fr/strings.xml b/apps/teacher/src/main/res/values-fr/strings.xml index 488d38ab0c..87cc52b294 100644 --- a/apps/teacher/src/main/res/values-fr/strings.xml +++ b/apps/teacher/src/main/res/values-fr/strings.xml @@ -872,7 +872,6 @@ -%s point -%s points - À faire %d non lu Paramètres de profil Téléchargement du fichier… Fichier téléchargé avec succès. diff --git a/apps/teacher/src/main/res/values-ht/strings.xml b/apps/teacher/src/main/res/values-ht/strings.xml index e0a6854a12..cef6543c92 100644 --- a/apps/teacher/src/main/res/values-ht/strings.xml +++ b/apps/teacher/src/main/res/values-ht/strings.xml @@ -872,7 +872,6 @@ -%s pwen -%s pwen yo - Pou fè %d poko li Paramèt Pwofi Telechajman fichye… Fichye telechaje kòrèkteman. diff --git a/apps/teacher/src/main/res/values-is/strings.xml b/apps/teacher/src/main/res/values-is/strings.xml index 274a6471af..22dd6dbb16 100644 --- a/apps/teacher/src/main/res/values-is/strings.xml +++ b/apps/teacher/src/main/res/values-is/strings.xml @@ -873,7 +873,6 @@ -%s punktur -%s punktar - Verkefnalisti %d ólesin Uppsetningarstillingar Sæki skrá… Skrá var sótt. diff --git a/apps/teacher/src/main/res/values-it/strings.xml b/apps/teacher/src/main/res/values-it/strings.xml index 29cd811b7a..dc4f768582 100644 --- a/apps/teacher/src/main/res/values-it/strings.xml +++ b/apps/teacher/src/main/res/values-it/strings.xml @@ -873,7 +873,6 @@ -%s punto -%s punti - Elenco attività: %d da leggere Impostazioni profilo Download del file… Download del file completato. diff --git a/apps/teacher/src/main/res/values-ja/strings.xml b/apps/teacher/src/main/res/values-ja/strings.xml index 15921efd04..53f188a83b 100644 --- a/apps/teacher/src/main/res/values-ja/strings.xml +++ b/apps/teacher/src/main/res/values-ja/strings.xml @@ -858,7 +858,6 @@ -%s 点 - 未読の「するべきこと」%d プロファイル設定 ファイルをダウンロード中… ファイルのダウンロードに成功しました。 diff --git a/apps/teacher/src/main/res/values-mi/strings.xml b/apps/teacher/src/main/res/values-mi/strings.xml index 0891fbac4d..ddbb0efdca 100644 --- a/apps/teacher/src/main/res/values-mi/strings.xml +++ b/apps/teacher/src/main/res/values-mi/strings.xml @@ -872,7 +872,6 @@ -%s koinga -%s ngā koinga - Hei mahi %d kaore i pānuitia Ngā Tautuhinga Kōtaha E tikiake ake ana kōnae… I pai te tikiake i te kōnae. diff --git a/apps/teacher/src/main/res/values-nb/strings.xml b/apps/teacher/src/main/res/values-nb/strings.xml index 1fa082cfe1..734a11a89e 100644 --- a/apps/teacher/src/main/res/values-nb/strings.xml +++ b/apps/teacher/src/main/res/values-nb/strings.xml @@ -875,7 +875,6 @@ -%s poeng -%s poeng - Gjøremål %d ulest Profilinnstillinger Laster ned fil… Filnedlasting utført. diff --git a/apps/teacher/src/main/res/values-nl/strings.xml b/apps/teacher/src/main/res/values-nl/strings.xml index 0f7734ef68..d8b3d90209 100644 --- a/apps/teacher/src/main/res/values-nl/strings.xml +++ b/apps/teacher/src/main/res/values-nl/strings.xml @@ -872,7 +872,6 @@ -%s punt -%s punten - Taken %d ongelezen Profielinstellingen Bestand downloaden… Bestand is gedownload. diff --git a/apps/teacher/src/main/res/values-pl/strings.xml b/apps/teacher/src/main/res/values-pl/strings.xml index 79921c3d6e..c46c055f64 100644 --- a/apps/teacher/src/main/res/values-pl/strings.xml +++ b/apps/teacher/src/main/res/values-pl/strings.xml @@ -900,7 +900,6 @@ -%s pkt -%s pkt - Lista zadań – nieprzeczytane %d Ustawienia profilu Pobieranie pliku… Pobrano plik. diff --git a/apps/teacher/src/main/res/values-pt-rBR/strings.xml b/apps/teacher/src/main/res/values-pt-rBR/strings.xml index 49bd9f5111..03eb48ac17 100644 --- a/apps/teacher/src/main/res/values-pt-rBR/strings.xml +++ b/apps/teacher/src/main/res/values-pt-rBR/strings.xml @@ -873,7 +873,6 @@ -%s ponto -%s pontos - %d da lista de tarefas não lido(s) Configurações do perfil Baixando arquivo… Arquivo baixado com sucesso. diff --git a/apps/teacher/src/main/res/values-pt-rPT/strings.xml b/apps/teacher/src/main/res/values-pt-rPT/strings.xml index 80b26a4a82..b0af875548 100644 --- a/apps/teacher/src/main/res/values-pt-rPT/strings.xml +++ b/apps/teacher/src/main/res/values-pt-rPT/strings.xml @@ -872,7 +872,6 @@ -%s ponto -%s pontos - Para fazer %d não ler Configurações de perfil Download do ficheiro… Ficheiro baixado com sucesso. diff --git a/apps/teacher/src/main/res/values-ru/strings.xml b/apps/teacher/src/main/res/values-ru/strings.xml index 3ed833e356..a2ce13a13b 100644 --- a/apps/teacher/src/main/res/values-ru/strings.xml +++ b/apps/teacher/src/main/res/values-ru/strings.xml @@ -900,7 +900,6 @@ -%s балла/баллов -%s балла/баллов - Выполнить непрочитанные %d Настройки профиля Загрузка файла… Файл успешно загружен. diff --git a/apps/teacher/src/main/res/values-sl/strings.xml b/apps/teacher/src/main/res/values-sl/strings.xml index 42d3901e5c..b1ccd46bc7 100644 --- a/apps/teacher/src/main/res/values-sl/strings.xml +++ b/apps/teacher/src/main/res/values-sl/strings.xml @@ -872,7 +872,6 @@ – %s točka – %s točk - Seznam opravil, %d neprebranih Nastavitve profila Prenos datoteke… Datoteka je uspešno prenesena. diff --git a/apps/teacher/src/main/res/values-sv/strings.xml b/apps/teacher/src/main/res/values-sv/strings.xml index bd1a7dab53..dc2c0a0c91 100644 --- a/apps/teacher/src/main/res/values-sv/strings.xml +++ b/apps/teacher/src/main/res/values-sv/strings.xml @@ -873,7 +873,6 @@ -%s poäng -%s poäng - Att göra %d olästa Profilinställningar Hämtar fil… Filen hämtades. diff --git a/apps/teacher/src/main/res/values-th/strings.xml b/apps/teacher/src/main/res/values-th/strings.xml index 98a1168099..c735ff3f70 100644 --- a/apps/teacher/src/main/res/values-th/strings.xml +++ b/apps/teacher/src/main/res/values-th/strings.xml @@ -874,7 +874,6 @@ -%s คะแนน -%s คะแนน - สิ่งที่ต้องทำ %d ที่ไม่ได้อ่าน ค่าปรับตั้งโพรไฟล์ กำลังดาวน์โหลดไฟล์… ดาวน์โหลดไฟล์เสร็จสิ้น diff --git a/apps/teacher/src/main/res/values-vi/strings.xml b/apps/teacher/src/main/res/values-vi/strings.xml index 9c37b925be..41f70f23ce 100644 --- a/apps/teacher/src/main/res/values-vi/strings.xml +++ b/apps/teacher/src/main/res/values-vi/strings.xml @@ -875,7 +875,6 @@ -%s điểm thành phần -%s điểm thành phần - Cần làm %d chưa đọc Cài Đặt Hồ Sơ Đang tải xuống tập tin… Đã tải xuống tập tin thành công. diff --git a/apps/teacher/src/main/res/values-zh-rHK/strings.xml b/apps/teacher/src/main/res/values-zh-rHK/strings.xml index a00c82be81..25d255a0eb 100644 --- a/apps/teacher/src/main/res/values-zh-rHK/strings.xml +++ b/apps/teacher/src/main/res/values-zh-rHK/strings.xml @@ -858,7 +858,6 @@ -%s 分 - 待辦 %d 未讀 個人檔案設定 下載檔案… 檔案已成功下載。 diff --git a/apps/teacher/src/main/res/values-zh/strings.xml b/apps/teacher/src/main/res/values-zh/strings.xml index 1e1ac223c3..9c9e942bed 100644 --- a/apps/teacher/src/main/res/values-zh/strings.xml +++ b/apps/teacher/src/main/res/values-zh/strings.xml @@ -858,7 +858,6 @@ -%s 分 - 待办%d未读 个人资料设置 正在下载文件… 文件下载成功 diff --git a/apps/teacher/src/main/res/values/strings.xml b/apps/teacher/src/main/res/values/strings.xml index 0039c33bd6..69de0f07e9 100644 --- a/apps/teacher/src/main/res/values/strings.xml +++ b/apps/teacher/src/main/res/values/strings.xml @@ -875,7 +875,6 @@ -%s point -%s points - To do %d unread Profile Settings Downloading file… File downloaded successfully. diff --git a/automation/espresso/src/main/kotlin/com/instructure/espresso/CustomViewAssertions.kt b/automation/espresso/src/main/kotlin/com/instructure/espresso/CustomViewAssertions.kt index 694c6e2dc6..a6248e7e21 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/espresso/CustomViewAssertions.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/espresso/CustomViewAssertions.kt @@ -19,11 +19,13 @@ package com.instructure.espresso import android.graphics.Color import android.view.View import android.widget.TextView +import androidx.annotation.IdRes import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.NoMatchingViewException import androidx.test.espresso.ViewAssertion import androidx.test.espresso.matcher.ViewMatchers import androidx.viewpager.widget.ViewPager +import com.google.android.material.bottomnavigation.BottomNavigationView import org.hamcrest.Matchers import org.junit.Assert.assertEquals @@ -62,3 +64,13 @@ class TextViewColorAssertion(private val colorHexCode: String) : ViewAssertion { assertEquals(item.currentTextColor, Color.parseColor(colorHexCode)) } } + +class NotificationBadgeAssertion(@IdRes private val menuItemId: Int, private val expectedCount: Int) : ViewAssertion { + override fun check(view: View, noViewFoundException: NoMatchingViewException?) { + noViewFoundException?.let { throw it } + val bottomNavigationView = (view as? BottomNavigationView) + ?: throw ClassCastException("View of type ${view.javaClass.simpleName} must be a BottomNavigationView") + val badgeCount = bottomNavigationView.getBadge(menuItemId)?.number ?: -1 + assertEquals(badgeCount, expectedCount) + } +} diff --git a/libs/pandares/src/main/res/values-ar/strings.xml b/libs/pandares/src/main/res/values-ar/strings.xml index 0eba1ea79a..9e8bfd22cf 100644 --- a/libs/pandares/src/main/res/values-ar/strings.xml +++ b/libs/pandares/src/main/res/values-ar/strings.xml @@ -993,7 +993,6 @@ عقوبة التأخير (-%s) الدرجة النهائية - أكثر من 99 أكثر من 99 الفتح في عرض ويب diff --git a/libs/pandares/src/main/res/values-b+da+instk12/strings.xml b/libs/pandares/src/main/res/values-b+da+instk12/strings.xml index bcb582c646..530f057cb3 100644 --- a/libs/pandares/src/main/res/values-b+da+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+da+instk12/strings.xml @@ -958,7 +958,6 @@ Straf for sen aflevering (-%s) Endelig vurdering - 99+ 99+ Åbner i webvisning diff --git a/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml b/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml index b494aa11a8..812e7ad52a 100644 --- a/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml +++ b/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml @@ -958,7 +958,6 @@ Late penalty (-%s) Final Grade - 99+ 99+ Opens in webview diff --git a/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml b/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml index e8c43256d2..ae309ef917 100644 --- a/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml +++ b/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml @@ -958,7 +958,6 @@ Late penalty (-%s) Final grade - 99+ 99+ Opens in webview diff --git a/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml b/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml index fd918c305e..38a46af3d5 100644 --- a/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml @@ -958,7 +958,6 @@ Forsinkelsesstraff (-%s) Sluttvurdering - 99+ 99+ Åpne i nettvisning diff --git a/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml b/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml index 5ad94b0667..813d53116f 100644 --- a/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml @@ -958,7 +958,6 @@ Förseningsbestraffning (-%s) Totalt omdöme - 99+ 99+ Öppnas i webbvy diff --git a/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml b/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml index 30e9cbeba0..923ec7c8eb 100644 --- a/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml @@ -949,7 +949,6 @@ 迟交罚分 (-%s) 最终评分 - 99+ 99+ 在网页视图中打开 diff --git a/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml b/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml index 49c9019e06..e5bb1d5601 100644 --- a/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml @@ -949,7 +949,6 @@ 逾期懲罰 (-%s) 最終評分 - 99+ 99+ 在網頁中打開 diff --git a/libs/pandares/src/main/res/values-ca/strings.xml b/libs/pandares/src/main/res/values-ca/strings.xml index 8509046ba7..6647262d5f 100644 --- a/libs/pandares/src/main/res/values-ca/strings.xml +++ b/libs/pandares/src/main/res/values-ca/strings.xml @@ -958,7 +958,6 @@ Sanció per endarreriment (-%s) Nota final - 99+ 99+ S\'obre en vista web diff --git a/libs/pandares/src/main/res/values-cy/strings.xml b/libs/pandares/src/main/res/values-cy/strings.xml index ef9bafd0a4..9c1f6c6dd8 100644 --- a/libs/pandares/src/main/res/values-cy/strings.xml +++ b/libs/pandares/src/main/res/values-cy/strings.xml @@ -958,7 +958,6 @@ Cosb am fod yn hwyr (-%s) Gradd Derfynol - 99+ 99+ Agor yn webview diff --git a/libs/pandares/src/main/res/values-da/strings.xml b/libs/pandares/src/main/res/values-da/strings.xml index 47a16ce4c7..dc3873f8c9 100644 --- a/libs/pandares/src/main/res/values-da/strings.xml +++ b/libs/pandares/src/main/res/values-da/strings.xml @@ -958,7 +958,6 @@ Straf for sen aflevering (-%s) Endelig karakter - 99+ 99+ Åbner i webvisning diff --git a/libs/pandares/src/main/res/values-de/strings.xml b/libs/pandares/src/main/res/values-de/strings.xml index 88ad68c03b..1092a017be 100644 --- a/libs/pandares/src/main/res/values-de/strings.xml +++ b/libs/pandares/src/main/res/values-de/strings.xml @@ -958,7 +958,6 @@ Strafe für Verspätung (-%s) Gesamtnote - über 99 über 99 In Webview öffnen diff --git a/libs/pandares/src/main/res/values-en-rAU/strings.xml b/libs/pandares/src/main/res/values-en-rAU/strings.xml index 5e317cf5e8..108a19c7ca 100644 --- a/libs/pandares/src/main/res/values-en-rAU/strings.xml +++ b/libs/pandares/src/main/res/values-en-rAU/strings.xml @@ -958,7 +958,6 @@ Late penalty (-%s) Final Grade - 99+ 99+ Opens in webview diff --git a/libs/pandares/src/main/res/values-en-rCA/strings.xml b/libs/pandares/src/main/res/values-en-rCA/strings.xml index 8d0d17f9a4..bb4e4e2d2d 100644 --- a/libs/pandares/src/main/res/values-en-rCA/strings.xml +++ b/libs/pandares/src/main/res/values-en-rCA/strings.xml @@ -958,7 +958,6 @@ Late penalty (-%s) Final Grade - 99+ 99+ Opens in webview diff --git a/libs/pandares/src/main/res/values-en-rCY/strings.xml b/libs/pandares/src/main/res/values-en-rCY/strings.xml index e8c43256d2..ae309ef917 100644 --- a/libs/pandares/src/main/res/values-en-rCY/strings.xml +++ b/libs/pandares/src/main/res/values-en-rCY/strings.xml @@ -958,7 +958,6 @@ Late penalty (-%s) Final grade - 99+ 99+ Opens in webview diff --git a/libs/pandares/src/main/res/values-en-rGB/strings.xml b/libs/pandares/src/main/res/values-en-rGB/strings.xml index a3970e6e28..485158cb7f 100644 --- a/libs/pandares/src/main/res/values-en-rGB/strings.xml +++ b/libs/pandares/src/main/res/values-en-rGB/strings.xml @@ -958,7 +958,6 @@ Late penalty (-%s) Final grade - 99+ 99+ Opens in webview diff --git a/libs/pandares/src/main/res/values-es-rES/strings.xml b/libs/pandares/src/main/res/values-es-rES/strings.xml index 723a3d52f1..7113e6736e 100644 --- a/libs/pandares/src/main/res/values-es-rES/strings.xml +++ b/libs/pandares/src/main/res/values-es-rES/strings.xml @@ -958,7 +958,6 @@ Sanción por entrega con retraso (-%s) Nota final - 99+ 99+ Se abre en WebView diff --git a/libs/pandares/src/main/res/values-es/strings.xml b/libs/pandares/src/main/res/values-es/strings.xml index 3ada559bc1..d426b1465e 100644 --- a/libs/pandares/src/main/res/values-es/strings.xml +++ b/libs/pandares/src/main/res/values-es/strings.xml @@ -958,7 +958,6 @@ Sanción por presentación con atraso (-%s) Calificación final - 99 o + 99 o + Se abre en WebView diff --git a/libs/pandares/src/main/res/values-fi/strings.xml b/libs/pandares/src/main/res/values-fi/strings.xml index a53880ba3e..11088f0754 100644 --- a/libs/pandares/src/main/res/values-fi/strings.xml +++ b/libs/pandares/src/main/res/values-fi/strings.xml @@ -958,7 +958,6 @@ Rangaistus myöhästymisestä (-%s) Lopullinen arvosana - 99+ 99+ Avautuu verkkonäkymässä diff --git a/libs/pandares/src/main/res/values-fr-rCA/strings.xml b/libs/pandares/src/main/res/values-fr-rCA/strings.xml index 8a014b9990..e013bd773b 100644 --- a/libs/pandares/src/main/res/values-fr-rCA/strings.xml +++ b/libs/pandares/src/main/res/values-fr-rCA/strings.xml @@ -958,7 +958,6 @@ Pénalité de retard (-%s) Note finale - Plus de 99 Plus de 99 S’ouvre dans Webview diff --git a/libs/pandares/src/main/res/values-fr/strings.xml b/libs/pandares/src/main/res/values-fr/strings.xml index 7c90f4985d..66b587f219 100644 --- a/libs/pandares/src/main/res/values-fr/strings.xml +++ b/libs/pandares/src/main/res/values-fr/strings.xml @@ -958,7 +958,6 @@ Pénalité de retard (-%s) Note Finale - 99+ 99+ S\'ouvre dans une vue web. diff --git a/libs/pandares/src/main/res/values-ht/strings.xml b/libs/pandares/src/main/res/values-ht/strings.xml index 282571ee82..14564e0a14 100644 --- a/libs/pandares/src/main/res/values-ht/strings.xml +++ b/libs/pandares/src/main/res/values-ht/strings.xml @@ -958,7 +958,6 @@ Penalite pou reta (-%s) Nòt Final - 99+ 99+ Li ouvè nan webview diff --git a/libs/pandares/src/main/res/values-is/strings.xml b/libs/pandares/src/main/res/values-is/strings.xml index 7b44e2cd78..3307a6f048 100644 --- a/libs/pandares/src/main/res/values-is/strings.xml +++ b/libs/pandares/src/main/res/values-is/strings.xml @@ -958,7 +958,6 @@ Viðurlög vegna of seinna skila(-%s) Lokaeinkunn: - 99+ 99+ Opnast í vefglugga diff --git a/libs/pandares/src/main/res/values-it/strings.xml b/libs/pandares/src/main/res/values-it/strings.xml index c837b0b647..28198db5f3 100644 --- a/libs/pandares/src/main/res/values-it/strings.xml +++ b/libs/pandares/src/main/res/values-it/strings.xml @@ -958,7 +958,6 @@ Penale ritardo (-%s) Voto finale - Più di 99 Più di 99 Si apre in visualizzazione web diff --git a/libs/pandares/src/main/res/values-ja/strings.xml b/libs/pandares/src/main/res/values-ja/strings.xml index 0c68af61f0..6b1d3cb661 100644 --- a/libs/pandares/src/main/res/values-ja/strings.xml +++ b/libs/pandares/src/main/res/values-ja/strings.xml @@ -949,7 +949,6 @@ 遅延ペナルティ (-%s) 最終的な評定 - 99+ 99+ ウェブビューで開く diff --git a/libs/pandares/src/main/res/values-mi/strings.xml b/libs/pandares/src/main/res/values-mi/strings.xml index 071e8ab231..4b4cdae7c6 100644 --- a/libs/pandares/src/main/res/values-mi/strings.xml +++ b/libs/pandares/src/main/res/values-mi/strings.xml @@ -958,7 +958,6 @@ Whiu tōmuri (-%s) Kōeke whakamutunga - 99+ 99+ E huaki ana i roto i paetukutuku tirohanga diff --git a/libs/pandares/src/main/res/values-nb/strings.xml b/libs/pandares/src/main/res/values-nb/strings.xml index 599456c788..847aee975d 100644 --- a/libs/pandares/src/main/res/values-nb/strings.xml +++ b/libs/pandares/src/main/res/values-nb/strings.xml @@ -958,7 +958,6 @@ Forsinkelsesstraff (-%s) Sluttvurdering - 99+ 99+ Åpne i nettvisning diff --git a/libs/pandares/src/main/res/values-nl/strings.xml b/libs/pandares/src/main/res/values-nl/strings.xml index 3f29ea5ce9..9bcfac36a6 100644 --- a/libs/pandares/src/main/res/values-nl/strings.xml +++ b/libs/pandares/src/main/res/values-nl/strings.xml @@ -958,7 +958,6 @@ Sanctie indien te laat (-%s) Eindcijfer - 99+ 99+ Wordt geopend in webview diff --git a/libs/pandares/src/main/res/values-pl/strings.xml b/libs/pandares/src/main/res/values-pl/strings.xml index 5ff408be87..5101e49e0c 100644 --- a/libs/pandares/src/main/res/values-pl/strings.xml +++ b/libs/pandares/src/main/res/values-pl/strings.xml @@ -976,7 +976,6 @@ Kara za spóźnienie (-%s) Ocena końcowa - 99+ 99+ Otwiera się w widoku sieciowym diff --git a/libs/pandares/src/main/res/values-pt-rBR/strings.xml b/libs/pandares/src/main/res/values-pt-rBR/strings.xml index 01270125bf..46f17873af 100644 --- a/libs/pandares/src/main/res/values-pt-rBR/strings.xml +++ b/libs/pandares/src/main/res/values-pt-rBR/strings.xml @@ -958,7 +958,6 @@ Penalidade por atraso (-%s) Nota final - +99 +99 Abre na visualização web diff --git a/libs/pandares/src/main/res/values-pt-rPT/strings.xml b/libs/pandares/src/main/res/values-pt-rPT/strings.xml index e8e103f0f3..ff05a940de 100644 --- a/libs/pandares/src/main/res/values-pt-rPT/strings.xml +++ b/libs/pandares/src/main/res/values-pt-rPT/strings.xml @@ -958,7 +958,6 @@ Penalidade tardia (-%s) Nota final - 99+ 99+ Abre no webview diff --git a/libs/pandares/src/main/res/values-ru/strings.xml b/libs/pandares/src/main/res/values-ru/strings.xml index 167198e389..74d9d14556 100644 --- a/libs/pandares/src/main/res/values-ru/strings.xml +++ b/libs/pandares/src/main/res/values-ru/strings.xml @@ -976,7 +976,6 @@ Снижение оценки за опоздание (-%s) Итоговая оценка - 99+ 99+ Открывается в веб-представлении diff --git a/libs/pandares/src/main/res/values-sl/strings.xml b/libs/pandares/src/main/res/values-sl/strings.xml index 229e82f17a..68bd19ac1d 100644 --- a/libs/pandares/src/main/res/values-sl/strings.xml +++ b/libs/pandares/src/main/res/values-sl/strings.xml @@ -958,7 +958,6 @@ Kazen za zamudo (-%s) Končna ocena - 99+ 99+ Odpre se v spletnem pogledu diff --git a/libs/pandares/src/main/res/values-sv/strings.xml b/libs/pandares/src/main/res/values-sv/strings.xml index 9bbfe5673f..ee3ae106e9 100644 --- a/libs/pandares/src/main/res/values-sv/strings.xml +++ b/libs/pandares/src/main/res/values-sv/strings.xml @@ -958,7 +958,6 @@ Förseningsbestraffning (-%s) Totalt omdöme - 99+ 99+ Öppnas i webbvy diff --git a/libs/pandares/src/main/res/values-th/strings.xml b/libs/pandares/src/main/res/values-th/strings.xml index 2b8541ac45..395b34f49d 100644 --- a/libs/pandares/src/main/res/values-th/strings.xml +++ b/libs/pandares/src/main/res/values-th/strings.xml @@ -958,7 +958,6 @@ โทษปรับล่าช้า (-%s) เกรดสรุป - 99+ 99+ เปิดในมุมมองทางเว็บ diff --git a/libs/pandares/src/main/res/values-vi/strings.xml b/libs/pandares/src/main/res/values-vi/strings.xml index 8fb1c5c40b..c53568f8b9 100644 --- a/libs/pandares/src/main/res/values-vi/strings.xml +++ b/libs/pandares/src/main/res/values-vi/strings.xml @@ -958,7 +958,6 @@ Hình phạt trễ (-%s) Điểm Cuối Cùng - 99+ 99+ Mở trong dạng xem web diff --git a/libs/pandares/src/main/res/values-zh-rHK/strings.xml b/libs/pandares/src/main/res/values-zh-rHK/strings.xml index 49c9019e06..e5bb1d5601 100644 --- a/libs/pandares/src/main/res/values-zh-rHK/strings.xml +++ b/libs/pandares/src/main/res/values-zh-rHK/strings.xml @@ -949,7 +949,6 @@ 逾期懲罰 (-%s) 最終評分 - 99+ 99+ 在網頁中打開 diff --git a/libs/pandares/src/main/res/values-zh/strings.xml b/libs/pandares/src/main/res/values-zh/strings.xml index 30e9cbeba0..923ec7c8eb 100644 --- a/libs/pandares/src/main/res/values-zh/strings.xml +++ b/libs/pandares/src/main/res/values-zh/strings.xml @@ -949,7 +949,6 @@ 迟交罚分 (-%s) 最终评分 - 99+ 99+ 在网页视图中打开 diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index 31d30e8832..4daa3ccda1 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -958,7 +958,6 @@ Late penalty (-%s) Final Grade - 99+ 99+ Opens in webview @@ -1278,4 +1277,8 @@ Grab + + %s unread message + %s unread messages + From f2ac6a71d33c4432e7ede0790b65af87ecf8198a Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:55:39 +0200 Subject: [PATCH 040/115] Nightly fix branch (#1645) * fix profileSettingsE2E on nightly (we are using different device than in local thats what caused the flakyness) refs: affects: release note: test plan: * attempt to fix testAnnouncement_reply test case (it may do not wait enough for the webview to be loaded) refs: affects: release note: test plan: * stub testFilters method on landscape mode because on lowres device its too narrow to scroll properly. Will be checked/refactored when we migrated from lowres device in nightly runs. refs: affects: Student release note: none * put back todoe2e tests on weekends. (to test on bitrise) refs: affects: Student, Teacher release note: none * put back todoe2e tests on weekends. (to test on bitrise) refs: affects: Student, Teacher release note: none --- .../java/com/instructure/student/ui/e2e/TodoE2ETest.kt | 9 --------- .../student/ui/interaction/TodoInteractionTest.kt | 3 +++ .../java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt | 9 --------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt index 34cef2228f..50d7e40857 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt @@ -16,7 +16,6 @@ import com.instructure.student.ui.utils.seedData import com.instructure.student.ui.utils.tokenLogin import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test -import java.util.Calendar @HiltAndroidTest class TodoE2ETest: StudentTest() { @@ -33,14 +32,6 @@ class TodoE2ETest: StudentTest() { @TestMetaData(Priority.MANDATORY, FeatureCategory.TODOS, TestCategory.E2E) fun testTodoE2E() { - // Don't attempt this test on a Friday, Saturday or Sunday. - // The TODO tab doesn't seem to behave correctly on Fridays (or presumably weekends). - val dayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - if(dayOfWeek == Calendar.FRIDAY || dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY) { - println("We don't run the TODO E2E test on weekends") - return - } - Log.d(PREPARATION_TAG, "Seeding data.") val data = seedData(students = 1, teachers = 1, courses = 1) val student = data.studentsList[0] diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt index ffe511b42d..d30a428387 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/TodoInteractionTest.kt @@ -16,6 +16,7 @@ package com.instructure.student.ui.interaction import androidx.test.espresso.Espresso +import com.instructure.canvas.espresso.StubLandscape import com.instructure.canvas.espresso.mockCanvas.MockCanvas import com.instructure.canvas.espresso.mockCanvas.addAssignment import com.instructure.canvas.espresso.mockCanvas.addQuizToCourse @@ -63,8 +64,10 @@ class TodoInteractionTest : StudentTest() { } @Test + @StubLandscape("Stubbed because on lowres device in landscape mode, the space is too narrow to scroll properly. Will be refactored and running when we changed to non-lowres device on nightly runs.") @TestMetaData(Priority.IMPORTANT, FeatureCategory.TODOS, TestCategory.INTERACTION, false) fun testFilters() { + //TODO: Check and refactor (if necessary) after migrated nightly runs from lowres device to non-lowres one. val data = goToTodos(courseCount = 2, favoriteCourseCount = 1) val favoriteCourse = data.courses.values.first {course -> course.isFavorite} val notFavoriteCourse = data.courses.values.first {course -> !course.isFavorite} diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt index b0635a2fe7..5c41d4fecf 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/TodoE2ETest.kt @@ -30,7 +30,6 @@ import com.instructure.panda_annotations.TestMetaData import com.instructure.teacher.ui.utils.* import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test -import java.util.* @HiltAndroidTest class TodoE2ETest : TeacherTest() { @@ -44,14 +43,6 @@ class TodoE2ETest : TeacherTest() { @Test @TestMetaData(Priority.MANDATORY, FeatureCategory.TODOS, TestCategory.E2E) fun testTodoE2E() { - // Inherited from student todo tests, may check this out later - // Don't attempt this test on a Friday, Saturday or Sunday. - // The TODO tab doesn't seem to behave correctly on Fridays (or presumably weekends). - val dayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - if(dayOfWeek == Calendar.FRIDAY || dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY) { - println("We don't run the TODO E2E test on weekends") - return - } Log.d(PREPARATION_TAG, "Seeding data.") val data = seedData(students = 1, teachers = 1, courses = 1) From f61b194e0940636f7e2577baf5f40b1898f17b0c Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:58:07 +0200 Subject: [PATCH 041/115] Mbl 16056 investigate todo e2e tests on weekends (#1647) * fix profileSettingsE2E on nightly (we are using different device than in local thats what caused the flakyness) refs: affects: release note: test plan: * attempt to fix testAnnouncement_reply test case (it may do not wait enough for the webview to be loaded) refs: affects: release note: test plan: * stub testFilters method on landscape mode because on lowres device its too narrow to scroll properly. Will be checked/refactored when we migrated from lowres device in nightly runs. refs: affects: Student release note: none * put back todoe2e tests on weekends. (to test on bitrise) refs: affects: Student, Teacher release note: none * put back todoe2e tests on weekends. (to test on bitrise) refs: affects: Student, Teacher release note: none * Extend TodoE2E test in Student app to test the border/threshold date (which is currently 7 days). refs: MBL-16056 affects: Student release note: none --- .../instructure/student/ui/e2e/TodoE2ETest.kt | 26 ++++++++++++++++--- .../instructure/student/ui/pages/TodoPage.kt | 15 ++++++----- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt index 50d7e40857..c0bda5daec 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/e2e/TodoE2ETest.kt @@ -38,14 +38,22 @@ class TodoE2ETest: StudentTest() { val teacher = data.teachersList[0] val course = data.coursesList[0] - Log.d(PREPARATION_TAG,"Seed an assignment for ${course.name} course.") + Log.d(PREPARATION_TAG,"Seed an assignment for ${course.name} course with tomorrow due date.") val seededAssignments = seedAssignments( courseId = course.id, teacherToken = teacher.token, dueAt = 1.days.fromNow.iso8601 ) + Log.d(PREPARATION_TAG,"Seed another assignment for ${course.name} course with 7 days from now due date.") + val seededAssignments2 = seedAssignments( + courseId = course.id, + teacherToken = teacher.token, + dueAt = 7.days.fromNow.iso8601 + ) + val testAssignment = seededAssignments[0] + val borderDateAssignment = seededAssignments2[0] //We show items in the to do section which are within 7 days. Log.d(PREPARATION_TAG,"Seed a quiz for ${course.name} course with tomorrow due date.") val quiz = QuizzesApi.createQuiz( @@ -57,6 +65,16 @@ class TodoE2ETest: StudentTest() { dueAt = 1.days.fromNow.iso8601) ) + Log.d(PREPARATION_TAG,"Seed another quiz for ${course.name} course with 8 days from now due date..") + val tooFarAwayQuiz = QuizzesApi.createQuiz( + QuizzesApi.CreateQuizRequest( + courseId = course.id, + withDescription = true, + published = true, + token = teacher.token, + dueAt = 8.days.fromNow.iso8601) + ) + Log.d(STEP_TAG, "Login with user: ${student.name}, login id: ${student.loginId} , password: ${student.password}") tokenLogin(student) dashboardPage.waitForRender() @@ -64,10 +82,12 @@ class TodoE2ETest: StudentTest() { Log.d(STEP_TAG,"Navigate to 'To Do' page via bottom-menu.") dashboardPage.clickTodoTab() - Log.d(STEP_TAG,"Assert that ${testAssignment.name} assignment is displayed.") + Log.d(STEP_TAG,"Assert that ${testAssignment.name} assignment is displayed and ${borderDateAssignment.name} is displayed because it's 7 days away from now..") todoPage.assertAssignmentDisplayed(testAssignment) + todoPage.assertAssignmentDisplayed(borderDateAssignment) - Log.d(STEP_TAG,"Assert that ${quiz.title} quiz is displayed.") + Log.d(STEP_TAG,"Assert that ${quiz.title} quiz is displayed and ${tooFarAwayQuiz.title} quiz is not displayed because it's end date is more than a week away..") todoPage.assertQuizDisplayed(quiz) + todoPage.assertQuizNotDisplayed(tooFarAwayQuiz) } } \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/TodoPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/TodoPage.kt index e49581bad1..34efa39342 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/TodoPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/TodoPage.kt @@ -17,15 +17,12 @@ package com.instructure.student.ui.pages import android.widget.Button -import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.doesNotExist -import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import com.instructure.canvas.espresso.containsTextCaseInsensitive -import com.instructure.canvas.espresso.scrollRecyclerView import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.Quiz import com.instructure.dataseeding.model.AssignmentApiModel @@ -51,10 +48,18 @@ class TodoPage: BasePage(R.id.todoPage) { assertTextDisplayedInRecyclerView(assignment.name!!) } + fun assertQuizDisplayed(quiz: QuizApiModel) { + assertTextDisplayedInRecyclerView(quiz.title) + } + fun assertQuizDisplayed(quiz: Quiz) { assertTextDisplayedInRecyclerView(quiz.title!!) } + fun assertQuizNotDisplayed(quiz: QuizApiModel) { + onView(withText(quiz.title!!)).check(doesNotExist()) + } + fun assertQuizNotDisplayed(quiz: Quiz) { onView(withText(quiz.title!!)).check(doesNotExist()) } @@ -69,9 +74,7 @@ class TodoPage: BasePage(R.id.todoPage) { onView(withText(quiz.title!!)).click() } - fun assertQuizDisplayed(quiz: QuizApiModel) { - assertTextDisplayedInRecyclerView(quiz.title) - } + fun chooseFavoriteCourseFilter() { onView(withId(R.id.todoListFilter)).click() From 91f7bf82b833eabc0f9efcf816db8db83f9bb838 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Thu, 14 Jul 2022 12:15:55 +0200 Subject: [PATCH 042/115] [MBL-15489][Student][Teacher] Add the ability to add alt text to images in mobile applications (#1646) refs: MBL-15489 affects: Student, Teacher release note: Added option to add alt text to images when editing or creating content. * Added alt text dialog to RCE and applied it on some teacher screens. * Added alt text dialog to the remaining teacher screens. * Student screens and refactoring. * Fixed RCE add link dialog colors and removed unused classes and resources. * Removed unused test page. * Added insert image functionality to edit quiz details. --- .../fragment/CreateAnnouncementFragment.kt | 2 +- .../fragment/CreateDiscussionFragment.kt | 2 +- .../fragment/DiscussionsReplyFragment.kt | 2 +- .../fragment/EditPageDetailsFragment.kt | 2 +- .../text/ui/TextSubmissionUploadView.kt | 9 +- .../teacher/ui/pages/RCEditorPage.kt | 38 --- .../teacher/activities/BottomSheetActivity.kt | 20 +- .../teacher/activities/FullscreenActivity.kt | 28 +- .../syllabus/edit/EditSyllabusView.kt | 11 +- .../fragments/CreateDiscussionFragment.kt | 2 +- .../CreateOrEditAnnouncementFragment.kt | 2 +- .../CreateOrEditPageDetailsFragment.kt | 4 +- .../fragments/DiscussionsReplyFragment.kt | 2 +- .../fragments/DiscussionsUpdateFragment.kt | 2 +- .../EditAssignmentDetailsFragment.kt | 2 +- .../fragments/EditQuizDetailsFragment.kt | 17 ++ .../teacher/interfaces/RceMediaUpload.kt | 2 +- .../presenters/CreateDiscussionPresenter.kt | 4 +- .../CreateOrEditAnnouncementPresenter.kt | 4 +- .../presenters/CreateOrEditPagePresenter.kt | 2 +- .../presenters/DiscussionsReplyPresenter.kt | 3 +- .../presenters/DiscussionsUpdatePresenter.kt | 2 +- .../teacher/router/RouteMatcher.kt | 3 - .../teacher/router/RouteResolver.kt | 3 - .../pandautils/utils/MediaUploadUtils.kt | 4 +- libs/rceditor/src/main/AndroidManifest.xml | 9 - .../java/instructure/rceditor/RCEActivity.kt | 76 ------ .../java/instructure/rceditor/RCEConst.kt | 26 -- .../java/instructure/rceditor/RCEFragment.kt | 246 ------------------ .../instructure/rceditor/RCEInsertDialog.kt | 7 +- .../instructure/rceditor/RCETextEditorView.kt | 43 ++- .../main/res/layout/rce_activity_layout.xml | 31 --- .../main/res/layout/rce_dialog_alt_text.xml | 28 ++ .../main/res/layout/rce_fragment_layout.xml | 71 ----- .../src/main/res/menu/rce_save_menu.xml | 27 -- .../src/main/res/values/rce_strings.xml | 1 + 36 files changed, 128 insertions(+), 609 deletions(-) delete mode 100644 apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/RCEditorPage.kt delete mode 100644 libs/rceditor/src/main/java/instructure/rceditor/RCEActivity.kt delete mode 100644 libs/rceditor/src/main/java/instructure/rceditor/RCEConst.kt delete mode 100644 libs/rceditor/src/main/java/instructure/rceditor/RCEFragment.kt delete mode 100644 libs/rceditor/src/main/res/layout/rce_activity_layout.xml create mode 100644 libs/rceditor/src/main/res/layout/rce_dialog_alt_text.xml delete mode 100644 libs/rceditor/src/main/res/layout/rce_fragment_layout.xml delete mode 100644 libs/rceditor/src/main/res/menu/rce_save_menu.xml diff --git a/apps/student/src/main/java/com/instructure/student/fragment/CreateAnnouncementFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/CreateAnnouncementFragment.kt index 026a1de8cc..299eb26e61 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/CreateAnnouncementFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/CreateAnnouncementFragment.kt @@ -106,7 +106,7 @@ class CreateAnnouncementFragment : ParentFragment() { else -> null }?.let { imageUri -> // If the image Uri is not null, upload it - rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, requireActivity()) { text, alt -> announcementRCEView.insertImage(text, alt) } + rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, requireActivity()) { imageUrl -> announcementRCEView.insertImage(requireActivity(), imageUrl) } } } } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/CreateDiscussionFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/CreateDiscussionFragment.kt index b7d1d89f62..2964157553 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/CreateDiscussionFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/CreateDiscussionFragment.kt @@ -124,7 +124,7 @@ class CreateDiscussionFragment : ParentFragment() { else -> null }?.let { imageUri -> // If the image Uri is not null, upload it - rceImageJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, requireActivity()) { text, alt -> descriptionRCEView.insertImage(text, alt) } + rceImageJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, requireActivity()) { imageUrl -> descriptionRCEView.insertImage(requireActivity(), imageUrl) } } } } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt index 1fa11d9664..1265142e06 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt @@ -119,7 +119,7 @@ class DiscussionsReplyFragment : ParentFragment() { else -> null }?.let { imageUri -> // If the image Uri is not null, upload it - MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, requireActivity()) { text, alt -> rceTextEditor.insertImage(text, alt) } + MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, requireActivity()) { imageUrl -> rceTextEditor.insertImage(requireActivity(), imageUrl) } } } } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/EditPageDetailsFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/EditPageDetailsFragment.kt index 1943616e0d..8135eb2e46 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/EditPageDetailsFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/EditPageDetailsFragment.kt @@ -95,7 +95,7 @@ class EditPageDetailsFragment : ParentFragment() { else -> null }?.let { imageUri -> // If the image Uri is not null, upload it - rceImageJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, requireActivity()) { text, alt -> pageRCEView.insertImage(text, alt) } + rceImageJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, requireActivity()) { imageUrl -> pageRCEView.insertImage(requireActivity(), imageUrl) } } } } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/text/ui/TextSubmissionUploadView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/text/ui/TextSubmissionUploadView.kt index e91fb711af..f591a0773b 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/text/ui/TextSubmissionUploadView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/submission/text/ui/TextSubmissionUploadView.kt @@ -130,8 +130,13 @@ class TextSubmissionUploadView(inflater: LayoutInflater, parent: ViewGroup) : } } - private fun insertImage(text: String, alt: String) { - rce.insertImage(text, alt) + private fun insertImage(imageUrl: String) { + val activity = context as? Activity + if (activity != null) { + rce?.insertImage(activity, imageUrl) + } else { + rce?.insertImage(imageUrl, "") + } } fun showFailedImageMessage() { diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/RCEditorPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/RCEditorPage.kt deleted file mode 100644 index 59e5e0df8c..0000000000 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/RCEditorPage.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package com.instructure.teacher.ui.pages - -import com.instructure.espresso.OnViewWithId -import com.instructure.espresso.page.BasePage -import com.instructure.teacher.R - -class RCEditorPage: BasePage() { - private val webView by OnViewWithId(R.id.rce_webView) - private val saveButton by OnViewWithId(R.id.rce_save) - - private val actionUndo by OnViewWithId(R.id.action_undo) - private val actionRedo by OnViewWithId(R.id.action_redo) - private val actionBold by OnViewWithId(R.id.action_bold) - private val actionItalic by OnViewWithId(R.id.action_italic) - private val actionUnderline by OnViewWithId(R.id.action_underline) - - //These items may be off screen on phones, likely on screen for tablets. - private val actionTextColor by OnViewWithId(R.id.action_txt_color, autoAssert = false) - private val actionBulletList by OnViewWithId(R.id.action_insert_bullets, autoAssert = false) - private val actionUploadImage by OnViewWithId(R.id.actionUploadImage, autoAssert = false) - private val actionInsertLink by OnViewWithId(R.id.action_insert_link, autoAssert = false) -} diff --git a/apps/teacher/src/main/java/com/instructure/teacher/activities/BottomSheetActivity.kt b/apps/teacher/src/main/java/com/instructure/teacher/activities/BottomSheetActivity.kt index d762fd3e91..95b362cbae 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/activities/BottomSheetActivity.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/activities/BottomSheetActivity.kt @@ -48,15 +48,13 @@ import com.instructure.teacher.events.AssignmentDescriptionEvent import com.instructure.teacher.fragments.AddMessageFragment import com.instructure.teacher.router.RouteResolver import com.instructure.teacher.utils.getColorCompat -import instructure.rceditor.RCEConst.HTML_RESULT -import instructure.rceditor.RCEFragment import kotlinx.android.synthetic.main.activity_bottom_sheet.* import net.yslibrary.android.keyboardvisibilityevent.KeyboardVisibilityEvent import net.yslibrary.android.keyboardvisibilityevent.Unregistrar import org.greenrobot.eventbus.EventBus import retrofit2.Response -class BottomSheetActivity : BaseAppCompatActivity(), BottomSheetInteractions, RCEFragment.RCEFragmentCallbacks { +class BottomSheetActivity : BaseAppCompatActivity(), BottomSheetInteractions { private var mRoute: Route? = null private var mWindowHeight = 0 @@ -241,26 +239,10 @@ class BottomSheetActivity : BaseAppCompatActivity(), BottomSheetInteractions, RC return else super.onBackPressed() } else { - //Captures back press to prevent accidental exiting of assignment editing. - if(supportFragmentManager.findFragmentById(R.id.bottom) is RCEFragment) { - (supportFragmentManager.findFragmentById(R.id.bottom) as RCEFragment).showExitDialog() - return - } super.onBackPressed() } } - /** - * Handles RCEFragment results and passes them along - */ - override fun onResult(activityResult: Int, data: Intent?) { - val htmlResult = data?.getStringExtra(HTML_RESULT) - if (activityResult == Activity.RESULT_OK && htmlResult != null) { - EventBus.getDefault().postSticky(AssignmentDescriptionEvent(htmlResult)) - } - super.onBackPressed() - } - private fun keyboardHidden() { val params = bottom.layoutParams as PercentRelativeLayout.LayoutParams params.removeRule(RelativeLayout.ALIGN_PARENT_TOP) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/activities/FullscreenActivity.kt b/apps/teacher/src/main/java/com/instructure/teacher/activities/FullscreenActivity.kt index 147a2d4148..8220b250bf 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/activities/FullscreenActivity.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/activities/FullscreenActivity.kt @@ -16,7 +16,6 @@ */ package com.instructure.teacher.activities -import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle @@ -35,17 +34,13 @@ import com.instructure.interactions.router.Route import com.instructure.pandautils.interfaces.NavigationCallbacks import com.instructure.pandautils.utils.isCourseOrGroup import com.instructure.teacher.R -import com.instructure.teacher.events.AssignmentDescriptionEvent import com.instructure.teacher.router.RouteResolver import dagger.hilt.android.AndroidEntryPoint -import instructure.rceditor.RCEConst.HTML_RESULT -import instructure.rceditor.RCEFragment import kotlinx.android.synthetic.main.activity_fullscreen.* import kotlinx.coroutines.Job -import org.greenrobot.eventbus.EventBus @AndroidEntryPoint -class FullscreenActivity : BaseAppCompatActivity(), RCEFragment.RCEFragmentCallbacks, FullScreenInteractions { +class FullscreenActivity : BaseAppCompatActivity(), FullScreenInteractions { private var mRoute: Route? = null private var groupApiCall: Job? = null @@ -121,29 +116,12 @@ class FullscreenActivity : BaseAppCompatActivity(), RCEFragment.RCEFragmentCallb screen images will be the correct size, and the bottom bar will be easier to implement later*/ override fun onBackPressed() { - // Captures back press to prevent accidental exiting of assignment editing. - if(supportFragmentManager.findFragmentById(R.id.container) is RCEFragment) { - (supportFragmentManager.findFragmentById(R.id.container) as RCEFragment).showExitDialog() - return - } else if(supportFragmentManager.findFragmentById(R.id.container) is NavigationCallbacks) { - if((supportFragmentManager.findFragmentById(R.id.container) as NavigationCallbacks).onHandleBackPressed()) return + if (supportFragmentManager.findFragmentById(R.id.container) is NavigationCallbacks) { + if ((supportFragmentManager.findFragmentById(R.id.container) as NavigationCallbacks).onHandleBackPressed()) return } super.onBackPressed() } - /** - * Handles RCEFragment results and passes them along - */ - override fun onResult(activityResult: Int, data: Intent?) { - val htmlResult = data?.getStringExtra(HTML_RESULT) - if (activityResult == Activity.RESULT_OK && htmlResult != null) { - EventBus.getDefault().postSticky(AssignmentDescriptionEvent(htmlResult)) - super.onBackPressed() - } else { - super.onBackPressed() - } - } - companion object { fun createIntent(context: Context, route: Route): Intent { val intent = Intent(context, FullscreenActivity::class.java) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/edit/EditSyllabusView.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/edit/EditSyllabusView.kt index e64b2273cd..4474faaea1 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/edit/EditSyllabusView.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/edit/EditSyllabusView.kt @@ -120,7 +120,16 @@ class EditSyllabusView(val fragmentManager: FragmentManager, inflater: LayoutInf } fun uploadRceImage(imageUri: Uri, activity: Activity, course: Course) { - MediaUploadUtils.uploadRceImageJob(imageUri, course, activity) { text, alt -> contentRCEView?.insertImage(text, alt) } + MediaUploadUtils.uploadRceImageJob(imageUri, course, activity) { imageUrl -> insertImage(imageUrl) } + } + + private fun insertImage(imageUrl: String) { + val activity = context as? Activity + if (activity != null) { + contentRCEView?.insertImage(activity, imageUrl) + } else { + contentRCEView?.insertImage(imageUrl, "") + } } fun closeEditSyllabus() { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateDiscussionFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateDiscussionFragment.kt index a9c2f5fcb9..f3af866a2d 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateDiscussionFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateDiscussionFragment.kt @@ -188,7 +188,7 @@ class CreateDiscussionFragment : BasePresenterFragment< } } - override fun insertImageIntoRCE(text: String, alt: String) = descriptionRCEView.insertImage(text, alt) + override fun insertImageIntoRCE(imageUrl: String) = descriptionRCEView.insertImage(requireActivity(), imageUrl) override fun onReadySetGo(presenter: CreateDiscussionPresenter) { // If we already have something in the edit date groups we already have the full assignment and don't need to get it again. diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateOrEditAnnouncementFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateOrEditAnnouncementFragment.kt index cc45b36f09..25a376cb6e 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateOrEditAnnouncementFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateOrEditAnnouncementFragment.kt @@ -428,7 +428,7 @@ class CreateOrEditAnnouncementFragment : else getString(R.string.allSections)) } - override fun insertImageIntoRCE(text: String, alt: String) = announcementRCEView.insertImage(text, alt) + override fun insertImageIntoRCE(imageUrl: String) = announcementRCEView.insertImage(requireActivity(), imageUrl) companion object { fun newInstance(bundle: Bundle) = diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateOrEditPageDetailsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateOrEditPageDetailsFragment.kt index 0dede42042..3d84a76bb8 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateOrEditPageDetailsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/CreateOrEditPageDetailsFragment.kt @@ -145,8 +145,8 @@ class CreateOrEditPageDetailsFragment : } } - override fun insertImageIntoRCE(text: String, alt: String) { - pageRCEView.insertImage(text, alt) + override fun insertImageIntoRCE(imageUrl: String) { + pageRCEView.insertImage(requireActivity(), imageUrl) } private fun setupTitle() { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsReplyFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsReplyFragment.kt index 91513d62db..494c3f4fc8 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsReplyFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/DiscussionsReplyFragment.kt @@ -164,7 +164,7 @@ class DiscussionsReplyFragment : BasePresenterFragment Unit = { item -> when (item.itemId) { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditAssignmentDetailsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditAssignmentDetailsFragment.kt index 3465f6fc07..037e694b31 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditAssignmentDetailsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditAssignmentDetailsFragment.kt @@ -171,7 +171,7 @@ class EditAssignmentDetailsFragment : BaseFragment() { RequestCodes.CAMERA_PIC_REQUEST -> MediaUploadUtils.handleCameraPicResult(requireActivity(), null) else -> null }?.let { imageUri -> - rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, mCourse, requireActivity()) { text, alt -> descriptionEditor.insertImage(text, alt) } + rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, mCourse, requireActivity()) { imageUrl -> descriptionEditor.insertImage(requireActivity(), imageUrl) } } } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditQuizDetailsFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditQuizDetailsFragment.kt index 5949bb1352..b4475c9761 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditQuizDetailsFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/EditQuizDetailsFragment.kt @@ -16,6 +16,8 @@ */ package com.instructure.teacher.fragments +import android.app.Activity +import android.content.Intent import android.graphics.Typeface import android.os.Bundle import android.os.Handler @@ -343,10 +345,25 @@ class EditQuizDetailsFragment : BasePresenterFragment< // When the RCE editor has focus we want the label to be darker so it matches the title's functionality descriptionWebView.setLabel(quizDescLabel, R.color.textDarkest, R.color.textDark) + descriptionWebView.actionUploadImageCallback = { MediaUploadUtils.showPickImageDialog(this) } + // Dismiss the progress bar descriptionProgressBar.setGone() } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_OK) { + // Get the image Uri + when (requestCode) { + RequestCodes.PICK_IMAGE_GALLERY -> data?.data + RequestCodes.CAMERA_PIC_REQUEST -> MediaUploadUtils.handleCameraPicResult(requireActivity(), null) + else -> null + }?.let { imageUri -> + MediaUploadUtils.uploadRceImageJob(imageUri, mCourse, requireActivity()) { imageUrl -> descriptionWebView.insertImage(requireActivity(), imageUrl) } + } + } + } + override fun setupOverrides() { overrideContainer.removeAllViews() // Load in overrides diff --git a/apps/teacher/src/main/java/com/instructure/teacher/interfaces/RceMediaUpload.kt b/apps/teacher/src/main/java/com/instructure/teacher/interfaces/RceMediaUpload.kt index fbfe33812d..2261552d5a 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/interfaces/RceMediaUpload.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/interfaces/RceMediaUpload.kt @@ -12,5 +12,5 @@ interface RceMediaUploadPresenter { // Counterpart to the presenters interface RceMediaUploadView { - fun insertImageIntoRCE(text: String, alt: String) + fun insertImageIntoRCE(imageUrl: String) } \ No newline at end of file diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateDiscussionPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateDiscussionPresenter.kt index b50de42532..8056ab960d 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateDiscussionPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateDiscussionPresenter.kt @@ -36,10 +36,8 @@ import com.instructure.teacher.interfaces.RceMediaUploadPresenter import com.instructure.teacher.viewinterface.CreateDiscussionView import instructure.androidblueprint.FragmentPresenter import kotlinx.coroutines.Job -import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody -import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody import retrofit2.Response import java.io.File @@ -148,6 +146,6 @@ class CreateDiscussionPresenter(private val canvasContext: CanvasContext, privat } override fun uploadRceImage(imageUri: Uri, activity: Activity) { - rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, activity) { text, alt -> viewCallback?.insertImageIntoRCE(text, alt) } + rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, activity) { imageUrl -> viewCallback?.insertImageIntoRCE(imageUrl) } } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateOrEditAnnouncementPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateOrEditAnnouncementPresenter.kt index 566ee30757..e24243f73d 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateOrEditAnnouncementPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateOrEditAnnouncementPresenter.kt @@ -40,10 +40,8 @@ import com.instructure.teacher.interfaces.RceMediaUploadPresenter import com.instructure.teacher.viewinterface.CreateOrEditAnnouncementView import instructure.androidblueprint.FragmentPresenter import kotlinx.coroutines.Job -import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody -import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody import java.io.File @@ -158,7 +156,7 @@ class CreateOrEditAnnouncementPresenter( } override fun uploadRceImage(imageUri: Uri, activity: Activity) { - rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, activity) { text, alt -> viewCallback?.insertImageIntoRCE(text, alt) } + rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, activity) { imageUrl -> viewCallback?.insertImageIntoRCE(imageUrl) } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateOrEditPagePresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateOrEditPagePresenter.kt index 157509fcf6..763e4acd09 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateOrEditPagePresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/CreateOrEditPagePresenter.kt @@ -94,7 +94,7 @@ class CreateOrEditPagePresenter(private val canvasContext: CanvasContext, mPage: } override fun uploadRceImage(imageUri: Uri, activity: Activity) { - rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext , activity) { text, alt -> viewCallback?.insertImageIntoRCE(text, alt) } + rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext , activity) { imageUrl -> viewCallback?.insertImageIntoRCE(imageUrl) } } override fun onDestroyed() { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/DiscussionsReplyPresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/DiscussionsReplyPresenter.kt index c7b2502a63..6204d83421 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/DiscussionsReplyPresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/DiscussionsReplyPresenter.kt @@ -28,7 +28,6 @@ import com.instructure.canvasapi2.utils.weave.tryWeave import com.instructure.pandautils.discussions.DiscussionCaching import com.instructure.canvasapi2.models.postmodels.FileSubmitObject import com.instructure.pandautils.utils.MediaUploadUtils -import com.instructure.pandautils.utils.ProfileUtils import com.instructure.teacher.interfaces.RceMediaUploadPresenter import com.instructure.teacher.viewinterface.DiscussionsReplyView import instructure.androidblueprint.FragmentPresenter @@ -92,7 +91,7 @@ class DiscussionsReplyPresenter( fun getAttachment(): FileSubmitObject? = attachment override fun uploadRceImage(imageUri: Uri, activity: Activity) { - rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, activity) { text, alt -> viewCallback?.insertImageIntoRCE(text, alt) } + rceImageUploadJob = MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, activity) { imageUrl -> viewCallback?.insertImageIntoRCE(imageUrl) } } companion object { diff --git a/apps/teacher/src/main/java/com/instructure/teacher/presenters/DiscussionsUpdatePresenter.kt b/apps/teacher/src/main/java/com/instructure/teacher/presenters/DiscussionsUpdatePresenter.kt index 7faffe885a..2887c51c20 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/presenters/DiscussionsUpdatePresenter.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/presenters/DiscussionsUpdatePresenter.kt @@ -85,7 +85,7 @@ class DiscussionsUpdatePresenter( } override fun uploadRceImage(imageUri: Uri, activity: Activity) { - MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, activity) { text, alt -> viewCallback?.insertImageIntoRCE(text, alt) } + MediaUploadUtils.uploadRceImageJob(imageUri, canvasContext, activity) { imageUrl -> viewCallback?.insertImageIntoRCE(imageUrl) } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/router/RouteMatcher.kt b/apps/teacher/src/main/java/com/instructure/teacher/router/RouteMatcher.kt index 2912c9bd96..258e817962 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/router/RouteMatcher.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/router/RouteMatcher.kt @@ -51,7 +51,6 @@ import com.instructure.teacher.features.postpolicies.ui.PostPolicyFragment import com.instructure.teacher.features.syllabus.ui.SyllabusFragment import com.instructure.teacher.fragments.* import com.instructure.teacher.fragments.FileListFragment -import instructure.rceditor.RCEFragment import java.util.Locale object RouteMatcher : BaseRouteMatcher() { @@ -128,7 +127,6 @@ object RouteMatcher : BaseRouteMatcher() { bottomSheetFragments.add(AssigneeListFragment::class.java) bottomSheetFragments.add(EditFavoritesFragment::class.java) bottomSheetFragments.add(CourseSettingsFragment::class.java) - bottomSheetFragments.add(RCEFragment::class.java) bottomSheetFragments.add(EditQuizDetailsFragment::class.java) bottomSheetFragments.add(QuizPreviewWebviewFragment::class.java) bottomSheetFragments.add(AddMessageFragment::class.java) @@ -337,7 +335,6 @@ object RouteMatcher : BaseRouteMatcher() { CourseSettingsFragment::class.java.isAssignableFrom(cls) -> fragment = CourseSettingsFragment.newInstance((canvasContext as Course?)!!) QuizListFragment::class.java.isAssignableFrom(cls) -> fragment = QuizListFragment.newInstance(canvasContext!!) QuizDetailsFragment::class.java.isAssignableFrom(cls) -> fragment = getQuizDetailsFragment(canvasContext, route) - RCEFragment::class.java.isAssignableFrom(cls) -> fragment = RCEFragment.newInstance(route.arguments) EditQuizDetailsFragment::class.java.isAssignableFrom(cls) -> fragment = EditQuizDetailsFragment.newInstance((canvasContext as Course?)!!, route.arguments) QuizPreviewWebviewFragment::class.java.isAssignableFrom(cls) -> fragment = QuizPreviewWebviewFragment.newInstance(route.arguments) EditQuizDetailsFragment::class.java.isAssignableFrom(cls) -> fragment = EditQuizDetailsFragment.newInstance((canvasContext as Course?)!!, route.arguments) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/router/RouteResolver.kt b/apps/teacher/src/main/java/com/instructure/teacher/router/RouteResolver.kt index d5399f9049..06d72bd9c5 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/router/RouteResolver.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/router/RouteResolver.kt @@ -19,7 +19,6 @@ import com.instructure.teacher.features.postpolicies.ui.PostPolicyFragment import com.instructure.teacher.features.syllabus.edit.EditSyllabusFragment import com.instructure.teacher.features.syllabus.ui.SyllabusFragment import com.instructure.teacher.fragments.* -import instructure.rceditor.RCEFragment object RouteResolver { @@ -96,8 +95,6 @@ object RouteResolver { fragment = getModuleListFragment(canvasContext, route) } else if (QuizDetailsFragment::class.java.isAssignableFrom(cls)) { fragment = getQuizDetailsFragment(canvasContext, route) - } else if (RCEFragment::class.java.isAssignableFrom(cls)) { - fragment = RCEFragment.newInstance(route.arguments) } else if (EditQuizDetailsFragment::class.java.isAssignableFrom(cls)) { fragment = EditQuizDetailsFragment.newInstance((canvasContext as Course?)!!, route.arguments) } else if (QuizPreviewWebviewFragment::class.java.isAssignableFrom(cls)) { diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/MediaUploadUtils.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/MediaUploadUtils.kt index 187fd6f184..04734a9fb3 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/MediaUploadUtils.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/MediaUploadUtils.kt @@ -143,7 +143,7 @@ object MediaUploadUtils { ?: activity.startActivityForResult(intent, RequestCodes.PICK_IMAGE_GALLERY) } - fun uploadRceImageJob(uri: Uri, canvasContext: CanvasContext, activity: Activity, insertImageCallback: (text: String, altText: String) -> Unit): WeaveCoroutine { + fun uploadRceImageJob(uri: Uri, canvasContext: CanvasContext, activity: Activity, insertImageCallback: (imageUrl: String) -> Unit): WeaveCoroutine { val isTeacher = (canvasContext as? Course)?.isTeacher == true val tempFile = File(activity.externalCacheDir, "tmp-rce-image") var progressDialog: AlertDialog? = null @@ -213,7 +213,7 @@ object MediaUploadUtils { file = awaitApi { FileFolderManager.updateFile(file.id, updateFileFolder, it) } } - insertImageCallback(file.url ?: "", "") + insertImageCallback(file.url ?: "") // Delete temporary image tempFile.delete() diff --git a/libs/rceditor/src/main/AndroidManifest.xml b/libs/rceditor/src/main/AndroidManifest.xml index 6d9b1af89b..70fb8526a2 100644 --- a/libs/rceditor/src/main/AndroidManifest.xml +++ b/libs/rceditor/src/main/AndroidManifest.xml @@ -18,13 +18,4 @@ - - - - - - diff --git a/libs/rceditor/src/main/java/instructure/rceditor/RCEActivity.kt b/libs/rceditor/src/main/java/instructure/rceditor/RCEActivity.kt deleted file mode 100644 index e5f3d29de6..0000000000 --- a/libs/rceditor/src/main/java/instructure/rceditor/RCEActivity.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package instructure.rceditor - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.graphics.Color -import android.os.Bundle -import androidx.annotation.ColorInt -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import instructure.rceditor.RCEFragment.RCEFragmentCallbacks - -class RCEActivity : AppCompatActivity(), RCEFragmentCallbacks { - private var fragment: RCEFragment? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setResult(Activity.RESULT_CANCELED) - setContentView(R.layout.rce_activity_layout) - } - - override fun onAttachFragment(fragment: Fragment) { - if (fragment is RCEFragment) { - this.fragment = fragment - fragment.loadArguments( - intent.getStringExtra(RCEConst.HTML_CONTENT), - intent.getStringExtra(RCEConst.HTML_TITLE), - intent.getStringExtra(RCEConst.HTML_ACCESSIBILITY_TITLE), - intent.getIntExtra(RCEConst.THEME_COLOR, Color.BLACK), - intent.getIntExtra(RCEConst.BUTTON_COLOR, Color.BLACK) - ) - } - super.onAttachFragment(fragment) - } - - override fun onBackPressed() { - fragment?.showExitDialog() - } - - override fun onResult(activityResult: Int, data: Intent?) { - if (activityResult == Activity.RESULT_OK && data != null) { - setResult(activityResult, data) - } else { - setResult(activityResult) - } - finish() - } - - companion object { - fun createIntent(context: Context?, html: String?, title: String?, accessibilityTitle: String?, @ColorInt themeColor: Int, @ColorInt buttonColor: Int): Intent { - val intent = Intent(context, RCEActivity::class.java) - intent.putExtra(RCEConst.HTML_CONTENT, html) - intent.putExtra(RCEConst.HTML_TITLE, title) - intent.putExtra(RCEConst.HTML_ACCESSIBILITY_TITLE, accessibilityTitle) - intent.putExtra(RCEConst.THEME_COLOR, themeColor) - intent.putExtra(RCEConst.BUTTON_COLOR, buttonColor) - return intent - } - } -} diff --git a/libs/rceditor/src/main/java/instructure/rceditor/RCEConst.kt b/libs/rceditor/src/main/java/instructure/rceditor/RCEConst.kt deleted file mode 100644 index b340c3f4fa..0000000000 --- a/libs/rceditor/src/main/java/instructure/rceditor/RCEConst.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package instructure.rceditor - -object RCEConst { - const val HTML_RESULT = "HTML_CONTENT_RESULT" - const val HTML_CONTENT = "HTML_CONTENT" - const val HTML_TITLE = "HTML_TITLE" - const val HTML_ACCESSIBILITY_TITLE = "HTML_ACCESSIBILITY_TITLE" - const val THEME_COLOR = "THEME_COLOR" - const val BUTTON_COLOR = "BUTTON_COLOR" -} diff --git a/libs/rceditor/src/main/java/instructure/rceditor/RCEFragment.kt b/libs/rceditor/src/main/java/instructure/rceditor/RCEFragment.kt deleted file mode 100644 index a5de3c1b5d..0000000000 --- a/libs/rceditor/src/main/java/instructure/rceditor/RCEFragment.kt +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package instructure.rceditor - -import android.animation.Animator -import android.animation.ObjectAnimator -import android.app.Activity.RESULT_CANCELED -import android.app.Activity.RESULT_OK -import android.content.Context -import android.content.Intent -import android.graphics.Color -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.annotation.ColorInt -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.widget.Toolbar -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import instructure.rceditor.RCEConst.BUTTON_COLOR -import instructure.rceditor.RCEConst.HTML_ACCESSIBILITY_TITLE -import instructure.rceditor.RCEConst.HTML_CONTENT -import instructure.rceditor.RCEConst.HTML_RESULT -import instructure.rceditor.RCEConst.HTML_TITLE -import instructure.rceditor.RCEConst.THEME_COLOR -import kotlinx.android.synthetic.main.rce_color_picker.* -import kotlinx.android.synthetic.main.rce_controller.* -import kotlinx.android.synthetic.main.rce_fragment_layout.* - -class RCEFragment : Fragment() { - - private var callback: RCEFragmentCallbacks? = null - - private val onColorChosen = View.OnClickListener { v -> - when (v.id) { - R.id.rce_colorPickerWhite -> rcEditor.setTextColor(Color.WHITE) - R.id.rce_colorPickerBlack -> rcEditor.setTextColor(Color.BLACK) - R.id.rce_colorPickerGray -> rcEditor.setTextColor(ContextCompat.getColor(requireContext(), R.color.rce_pickerGray)) - R.id.rce_colorPickerRed -> rcEditor.setTextColor(ContextCompat.getColor(requireContext(), R.color.rce_pickerRed)) - R.id.rce_colorPickerOrange -> rcEditor.setTextColor(ContextCompat.getColor(requireContext(), R.color.rce_pickerOrange)) - R.id.rce_colorPickerYellow -> rcEditor.setTextColor(ContextCompat.getColor(requireContext(), R.color.rce_pickerYellow)) - R.id.rce_colorPickerGreen -> rcEditor.setTextColor(ContextCompat.getColor(requireContext(), R.color.rce_pickerGreen)) - R.id.rce_colorPickerBlue -> rcEditor.setTextColor(ContextCompat.getColor(requireContext(), R.color.rce_pickerBlue)) - R.id.rce_colorPickerPurple -> rcEditor.setTextColor(ContextCompat.getColor(requireContext(), R.color.rce_pickerPurple)) - } - - toggleColorPicker() - } - - private val onTextColor = View.OnClickListener { toggleColorPicker() } - private val onUndo = View.OnClickListener { rcEditor.undo() } - private val onRedo = View.OnClickListener { rcEditor.redo() } - private val onBold = View.OnClickListener { rcEditor.setBold() } - private val onItalic = View.OnClickListener { rcEditor.setItalic() } - private val onUnderline = View.OnClickListener { rcEditor.setUnderline() } - private val onInsertBulletList = View.OnClickListener { rcEditor.setBullets() } - - private val onUploadPicture = View.OnClickListener { - val dialog = RCEInsertDialog.newInstance( - getString(R.string.rce_insertImage), - requireArguments().getInt(THEME_COLOR, Color.BLACK), - requireArguments().getInt(BUTTON_COLOR, Color.BLACK)) - dialog.setListener { url, alt -> rcEditor.insertImage(url, alt) }.show(requireFragmentManager(), RCEInsertDialog::class.java.simpleName) - } - - private val onInsertLink = View.OnClickListener { - val dialog = RCEInsertDialog.newInstance( - getString(R.string.rce_insertLink), - requireArguments().getInt(THEME_COLOR, Color.BLACK), - requireArguments().getInt(BUTTON_COLOR, Color.BLACK)) - dialog.setListener { url, alt -> rcEditor.insertLink(url, alt) }.show(requireFragmentManager(), RCEInsertDialog::class.java.simpleName) - } - - interface RCEFragmentCallbacks { - fun onResult(activityResult: Int, data: Intent?) - } - - init { - if (arguments == null) { - arguments = Bundle() - } - } - - override fun onAttach(context: Context) { - super.onAttach(context) - if (context is RCEFragmentCallbacks) { - callback = context - } else { - throw IllegalStateException("Context must implement RCEFragment.RCEFragmentCallbacks()") - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = - inflater.inflate(R.layout.rce_fragment_layout, container, false) - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - setupViews() - } - - private fun setupViews() { - rcEditor.setPadding(10, 10, 10, 10) - rcEditor.applyHtml( - requireArguments().getString(HTML_CONTENT) ?: "", - requireArguments().getString(HTML_ACCESSIBILITY_TITLE) ?: "") - - with(rceToolbar) { - title = requireArguments().getString(HTML_TITLE) - inflateMenu(R.menu.rce_save_menu) - setNavigationIcon(R.drawable.ic_rce_cancel) - setNavigationContentDescription(R.string.rce_cancel) - setNavigationOnClickListener(View.OnClickListener { - // Check to see if we made any changes. If we haven't, just close the fragment - if (rcEditor.html != null && requireArguments().getString(HTML_CONTENT) != null) { - if (rcEditor.html == requireArguments().getString(HTML_CONTENT)) { - callback?.onResult(RESULT_CANCELED, null) - return@OnClickListener - } - } - showExitDialog() - }) - - setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item -> - if (item.itemId == R.id.rce_save) { - val data = Intent() - data.putExtra(HTML_RESULT, if (rcEditor.html == null) - requireArguments().getString(HTML_CONTENT) - else - rcEditor.html) - callback?.onResult(RESULT_OK, data) - return@OnMenuItemClickListener true - } - false - }) - } - - requireActivity().window.statusBarColor = ContextCompat.getColor(requireContext(), R.color.rce_dimStatusBarGray) - requireActivity().window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - - rce_colorPickerWhite.setOnClickListener(onColorChosen) - rce_colorPickerBlack.setOnClickListener(onColorChosen) - rce_colorPickerGray.setOnClickListener(onColorChosen) - rce_colorPickerRed.setOnClickListener(onColorChosen) - rce_colorPickerOrange.setOnClickListener(onColorChosen) - rce_colorPickerYellow.setOnClickListener(onColorChosen) - rce_colorPickerGreen.setOnClickListener(onColorChosen) - rce_colorPickerBlue.setOnClickListener(onColorChosen) - rce_colorPickerPurple.setOnClickListener(onColorChosen) - - action_undo.setOnClickListener(onUndo) - action_redo.setOnClickListener(onRedo) - action_bold.setOnClickListener(onBold) - action_italic.setOnClickListener(onItalic) - action_underline.setOnClickListener(onUnderline) - action_insert_bullets.setOnClickListener(onInsertBulletList) - actionUploadImage.setOnClickListener(onUploadPicture) - action_insert_link.setOnClickListener(onInsertLink) - action_txt_color.setOnClickListener(onTextColor) - } - - private fun toggleColorPicker() { - if (rceColorPickerWrapper.visibility == View.VISIBLE) { - val animator = ObjectAnimator.ofFloat(rceColorPickerWrapper, "translationY", rceColorPickerWrapper.height * -1f, 0f) - animator.duration = 200 - animator.addListener(object : RCEAnimationListener() { - override fun onAnimationFinish(animation: Animator) { - rceColorPickerWrapper?.visibility = View.INVISIBLE - } - }) - animator.start() - } else { - val animator = ObjectAnimator.ofFloat(rceColorPickerWrapper, "translationY", 0f, rceColorPickerWrapper!!.height * -1f) - animator.duration = 230 - animator.addListener(object : RCEAnimationListener() { - override fun onAnimationBegin(animation: Animator) { - rceColorPickerWrapper?.post { rceColorPickerWrapper?.visibility = View.VISIBLE } - } - }) - animator.start() - } - } - - fun loadArguments(html: String?, title: String?, accessibilityTitle: String?, @ColorInt themeColor: Int, @ColorInt buttonColor: Int) { - with (requireArguments()) { - putString(HTML_CONTENT, html) - putString(HTML_TITLE, title) - putString(HTML_ACCESSIBILITY_TITLE, accessibilityTitle) - putInt(THEME_COLOR, themeColor) - putInt(BUTTON_COLOR, buttonColor) - } - } - - fun showExitDialog() { - AlertDialog.Builder(requireContext()) - .setTitle(R.string.rce_dialog_exit_title) - .setMessage(R.string.rce_dialog_exit_message) - .setPositiveButton(R.string.rce_exit) { dialog, _ -> - dialog.dismiss() - callback?.onResult(RESULT_CANCELED, null) - } - .setNegativeButton(R.string.rce_cancel) { dialog, _ -> dialog.dismiss() } - .create() - .show() - } - - companion object { - - fun newInstance(html: String, title: String, accessibilityTitle: String, @ColorInt themeColor: Int, @ColorInt buttonColor: Int): RCEFragment { - val fragment = RCEFragment() - fragment.arguments = makeBundle(html, title, accessibilityTitle, themeColor, buttonColor) - return fragment - } - - fun newInstance(args: Bundle): RCEFragment { - val fragment = RCEFragment() - fragment.arguments = args - return fragment - } - - fun makeBundle(html: String, title: String, accessibilityTitle: String, @ColorInt themeColor: Int, @ColorInt buttonColor: Int): Bundle { - val args = Bundle() - args.putString(HTML_CONTENT, html) - args.putString(HTML_TITLE, title) - args.putString(HTML_ACCESSIBILITY_TITLE, accessibilityTitle) - args.putInt(THEME_COLOR, themeColor) - args.putInt(BUTTON_COLOR, buttonColor) - return args - } - } -} diff --git a/libs/rceditor/src/main/java/instructure/rceditor/RCEInsertDialog.kt b/libs/rceditor/src/main/java/instructure/rceditor/RCEInsertDialog.kt index d541ef3d25..7585d8bc89 100644 --- a/libs/rceditor/src/main/java/instructure/rceditor/RCEInsertDialog.kt +++ b/libs/rceditor/src/main/java/instructure/rceditor/RCEInsertDialog.kt @@ -53,9 +53,10 @@ class RCEInsertDialog : AppCompatDialogFragment() { builder.setTitle(arguments?.getString(TITLE)) builder.setPositiveButton(R.string.rce_dialogDone, null) // Override listener in onShow builder.setNegativeButton(R.string.rce_dialogCancel) { _, _ -> dismiss() } - val themeColor = arguments?.getInt(THEME_COLOR, Color.BLACK) ?: Color.BLACK + val defaultColor = context?.getColor(R.color.rce_defaultTextColor) ?: Color.BLACK + val themeColor = arguments?.getInt(THEME_COLOR, defaultColor) ?: defaultColor val highlightColor = increaseAlpha(themeColor) - val colorStateList = makeEditTextColorStateList(Color.BLACK, themeColor) + val colorStateList = makeEditTextColorStateList(defaultColor, themeColor) altEditText = root.findViewById(R.id.altEditText) urlEditText = root.findViewById(R.id.urlEditText) altEditText.highlightColor = highlightColor @@ -64,7 +65,7 @@ class RCEInsertDialog : AppCompatDialogFragment() { urlEditText.supportBackgroundTintList = colorStateList val dialog = builder.create() dialog.setOnShowListener { - val buttonColor = arguments?.getInt(BUTTON_COLOR, Color.BLACK) ?: Color.BLACK + val buttonColor = arguments?.getInt(BUTTON_COLOR, defaultColor) ?: defaultColor dialog.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(buttonColor) dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(buttonColor) val button = dialog.getButton(AlertDialog.BUTTON_POSITIVE) diff --git a/libs/rceditor/src/main/java/instructure/rceditor/RCETextEditorView.kt b/libs/rceditor/src/main/java/instructure/rceditor/RCETextEditorView.kt index be398c4e53..59017b7a79 100644 --- a/libs/rceditor/src/main/java/instructure/rceditor/RCETextEditorView.kt +++ b/libs/rceditor/src/main/java/instructure/rceditor/RCETextEditorView.kt @@ -19,6 +19,7 @@ package instructure.rceditor import android.animation.Animator import android.animation.ObjectAnimator +import android.app.Activity import android.content.Context import android.content.DialogInterface import android.content.res.Resources @@ -41,6 +42,7 @@ import android.widget.RelativeLayout import android.widget.TextView import kotlinx.android.synthetic.main.rce_color_picker.view.* import kotlinx.android.synthetic.main.rce_controller.view.* +import kotlinx.android.synthetic.main.rce_dialog_alt_text.view.* import kotlinx.android.synthetic.main.rce_text_editor_view.view.rce_bottomDivider as bottomDivider import kotlinx.android.synthetic.main.rce_text_editor_view.view.rce_colorPickerWrapper as colorPickerView import kotlinx.android.synthetic.main.rce_text_editor_view.view.rce_controller as controller @@ -195,10 +197,45 @@ class RCETextEditorView @JvmOverloads constructor( editor.setPadding(left, top, right, bottom) } + fun insertImage(activity: Activity, imageUrl: String) { + showAltTextDialog(activity, { altText -> + editor.insertImage(imageUrl, altText) + }, { + editor.insertImage(imageUrl, "") + }) + } + fun insertImage(url: String, alt: String) { editor.insertImage(url, alt) } + private fun showAltTextDialog(activity: Activity, onPositiveClick: (String) -> Unit, onNegativeClick: () -> Unit) { + val view = View.inflate(activity, R.layout.rce_dialog_alt_text, null) + val altTextInput = view?.altText + + var buttonClicked = false + + val altTextDialog = AlertDialog.Builder(activity) + .setTitle(activity.getString(R.string.rce_dialogAltText)) + .setView(view) + .setPositiveButton(activity.getString(android.R.string.ok)) { _, _ -> + buttonClicked = true + onPositiveClick(altTextInput?.text.toString()) + } + .setNegativeButton(activity.getString(android.R.string.cancel), { _, _ -> + buttonClicked = true + onNegativeClick() + }) + .setOnDismissListener { + if (!buttonClicked) { + onNegativeClick() + } + } + .create() + + altTextDialog.show() + } + fun setHtml( html: String?, accessibilityTitle: String, @@ -208,7 +245,7 @@ class RCETextEditorView @JvmOverloads constructor( ) { editor.applyHtml(html.orEmpty(), accessibilityTitle) editor.setPlaceholder(hint) - setThemeColor(themeColor) + this.themeColor = themeColor this.buttonColor = buttonColor } @@ -241,10 +278,6 @@ class RCETextEditorView @JvmOverloads constructor( } } - fun setThemeColor(@ColorInt color: Int) { - themeColor = color - } - private fun toggleColorPicker() { if (colorPickerView.visibility == View.VISIBLE) { val animator = ObjectAnimator.ofFloat(colorPickerView, "translationY", colorPickerView.height * -1f, 0f) diff --git a/libs/rceditor/src/main/res/layout/rce_activity_layout.xml b/libs/rceditor/src/main/res/layout/rce_activity_layout.xml deleted file mode 100644 index d11072361c..0000000000 --- a/libs/rceditor/src/main/res/layout/rce_activity_layout.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/libs/rceditor/src/main/res/layout/rce_dialog_alt_text.xml b/libs/rceditor/src/main/res/layout/rce_dialog_alt_text.xml new file mode 100644 index 0000000000..7c435c0263 --- /dev/null +++ b/libs/rceditor/src/main/res/layout/rce_dialog_alt_text.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/libs/rceditor/src/main/res/layout/rce_fragment_layout.xml b/libs/rceditor/src/main/res/layout/rce_fragment_layout.xml deleted file mode 100644 index 2e4650f13a..0000000000 --- a/libs/rceditor/src/main/res/layout/rce_fragment_layout.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/libs/rceditor/src/main/res/menu/rce_save_menu.xml b/libs/rceditor/src/main/res/menu/rce_save_menu.xml deleted file mode 100644 index 06c3ad902c..0000000000 --- a/libs/rceditor/src/main/res/menu/rce_save_menu.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/libs/rceditor/src/main/res/values/rce_strings.xml b/libs/rceditor/src/main/res/values/rce_strings.xml index 4754bc1623..65c0243a76 100644 --- a/libs/rceditor/src/main/res/values/rce_strings.xml +++ b/libs/rceditor/src/main/res/values/rce_strings.xml @@ -69,4 +69,5 @@ Url cannot be blank Only https urls are accepted + Add alt text for screen readers From a2a58eefda507fbd37e3bd51d57fe3f5990b88ed Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:28:26 +0200 Subject: [PATCH 043/115] [MBL-16166][Student] Crashlytics stabilisation (#1654) refs: MBL-16166 affects: Student release note: none * Fixed Discussion List crashes. * Fixed Discussion Replies crash. * Fixed PdfSubmissionView crash. * Fixed BasicQuizViewFragment crash. --- .../student/fragment/BasicQuizViewFragment.kt | 4 +++- .../student/fragment/DiscussionListFragment.kt | 10 ++++++---- .../student/fragment/DiscussionsReplyFragment.kt | 10 +++++----- .../com/instructure/annotations/PdfSubmissionView.kt | 10 +++++++++- libs/pandares/src/main/res/values/strings.xml | 2 ++ 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/apps/student/src/main/java/com/instructure/student/fragment/BasicQuizViewFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/BasicQuizViewFragment.kt index 3f7e2150f8..2466ecdfd1 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/BasicQuizViewFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/BasicQuizViewFragment.kt @@ -116,7 +116,9 @@ class BasicQuizViewFragment : InternalWebviewFragment() { view.loadUrl(url, APIHelper.referrer) true } else { // It's content but not a quiz. Could link to a discussion (or whatever) in a quiz. Route - RouteMatcher.canRouteInternally(requireActivity(), url, ApiPrefs.domain, true) + activity?.let { + RouteMatcher.canRouteInternally(it, url, ApiPrefs.domain, true) + } ?: false }// Might need to log in to take the quiz -- the url would say domain/login. If we just use the AppRouter it will take the user // back to the dashboard. This check will keep them here and let them log in and take the quiz } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt index 0245b19e81..6ef8af3715 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionListFragment.kt @@ -121,10 +121,12 @@ open class DiscussionListFragment : ParentFragment(), Bookmarkable { // Show the FAB. if(canPost) createNewDiscussion?.show() if (recyclerAdapter.size() == 0) { - if (isAnnouncement) { - setEmptyView(emptyView, R.drawable.ic_panda_noannouncements, R.string.noAnnouncements, R.string.noAnnouncementsSubtext) - } else { - setEmptyView(emptyView, R.drawable.ic_panda_nodiscussions, R.string.noDiscussions, R.string.noDiscussionsSubtext) + emptyView?.let { + if (isAnnouncement) { + setEmptyView(it, R.drawable.ic_panda_noannouncements, R.string.noAnnouncements, R.string.noAnnouncementsSubtext) + } else { + setEmptyView(it, R.drawable.ic_panda_nodiscussions, R.string.noDiscussions, R.string.noDiscussionsSubtext) + } } } } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt index 1265142e06..f352041ff8 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt @@ -187,7 +187,7 @@ class DiscussionsReplyFragment : ParentFragment() { } } } catch { - if (isAdded && (it as StatusCallbackError).response?.code() != 400) messageFailure() + if (isVisible && (it as StatusCallbackError).response?.code() != 400) messageFailure() } } @@ -213,16 +213,16 @@ class DiscussionsReplyFragment : ParentFragment() { } else { // Post failure // 400 will be handled elsewhere. it means the quota has been reached - if (response.code() != 400 && isAdded) { + if (response.code() != 400 && isVisible) { messageFailure() } } } private fun messageFailure() { - toolbar.menu.findItem(R.id.menu_send).isVisible = true - toolbar.menu.findItem(R.id.menu_attachment).isVisible = true - savingProgressBar.visibility = View.GONE + toolbar.menu.findItem(R.id.menu_send)?.isVisible = true + toolbar.menu.findItem(R.id.menu_attachment)?.isVisible = true + savingProgressBar?.visibility = View.GONE toast(R.string.utils_discussionSentFailure) } //endregion 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 41e3fac0b3..742a899645 100644 --- a/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt +++ b/libs/annotations/src/main/java/com/instructure/annotations/PdfSubmissionView.kt @@ -39,7 +39,11 @@ import com.instructure.canvasapi2.models.ApiValues import com.instructure.canvasapi2.models.DocSession import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotation import com.instructure.canvasapi2.models.canvadocs.CanvaDocAnnotationResponse -import com.instructure.canvasapi2.utils.* +import com.instructure.canvasapi2.utils.APIHelper +import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.extractCanvaDocsDomain +import com.instructure.canvasapi2.utils.extractSessionId +import com.instructure.canvasapi2.utils.isValid import com.instructure.canvasapi2.utils.weave.StatusCallbackError import com.instructure.canvasapi2.utils.weave.awaitApi import com.instructure.canvasapi2.utils.weave.catch @@ -298,6 +302,10 @@ abstract class PdfSubmissionView(context: Context, private val studentAnnotation protected fun openComments() { // Get current annotation in both forms + if (pdfFragment?.selectedAnnotations?.isNullOrEmpty() == true) { + toast(R.string.noAnnotationSelected) + return + } val currentPdfAnnotation = pdfFragment?.selectedAnnotations?.get(0) val currentAnnotation = currentPdfAnnotation?.convertPDFAnnotationToCanvaDoc(docSession.documentId) // Assuming neither is null, continue diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index 4daa3ccda1..f38b138119 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -1281,4 +1281,6 @@ %s unread message %s unread messages + + No annotation selected From dd0cbc2c3ef33a0cab3be39b56746bb9ca4c7d87 Mon Sep 17 00:00:00 2001 From: inst-danger Date: Wed, 20 Jul 2022 15:29:33 +0200 Subject: [PATCH 044/115] Update translations (#1655) * Update translations * Fixed apostrophes. * Fixed localisations. Co-authored-by: Tamas Kozmer --- .../lib/l10n/res/intl_en_GB_instukhe.arb | 2 +- .../src/main/res/values-ar/strings.xml | 1 + .../main/res/values-b+da+instk12/strings.xml | 1 + .../res/values-b+en+AU+unimelb/strings.xml | 1 + .../res/values-b+en+GB+instukhe/strings.xml | 1 + .../main/res/values-b+nb+instk12/strings.xml | 1 + .../main/res/values-b+sv+instk12/strings.xml | 1 + .../src/main/res/values-b+zh+Hans/strings.xml | 1 + .../src/main/res/values-b+zh+Hant/strings.xml | 1 + .../src/main/res/values-ca/strings.xml | 1 + .../src/main/res/values-cy/strings.xml | 1 + .../src/main/res/values-da/strings.xml | 1 + .../src/main/res/values-de/strings.xml | 3 ++- .../src/main/res/values-en-rAU/strings.xml | 1 + .../src/main/res/values-en-rCA/strings.xml | 1 + .../src/main/res/values-en-rCY/strings.xml | 1 + .../src/main/res/values-en-rGB/strings.xml | 1 + .../src/main/res/values-es-rES/strings.xml | 1 + .../src/main/res/values-es/strings.xml | 1 + .../src/main/res/values-fi/strings.xml | 1 + .../src/main/res/values-fr-rCA/strings.xml | 1 + .../src/main/res/values-fr/strings.xml | 1 + .../src/main/res/values-ht/strings.xml | 1 + .../src/main/res/values-is/strings.xml | 1 + .../src/main/res/values-it/strings.xml | 1 + .../src/main/res/values-ja/strings.xml | 1 + .../src/main/res/values-mi/strings.xml | 1 + .../src/main/res/values-nb/strings.xml | 1 + .../src/main/res/values-nl/strings.xml | 1 + .../src/main/res/values-pl/strings.xml | 1 + .../src/main/res/values-pt-rBR/strings.xml | 1 + .../src/main/res/values-pt-rPT/strings.xml | 1 + .../src/main/res/values-ru/strings.xml | 1 + .../src/main/res/values-sl/strings.xml | 1 + .../src/main/res/values-sv/strings.xml | 1 + .../src/main/res/values-th/strings.xml | 1 + .../src/main/res/values-vi/strings.xml | 1 + .../src/main/res/values-zh-rHK/strings.xml | 1 + .../src/main/res/values-zh/strings.xml | 1 + .../src/main/res/values-ar/strings.xml | 12 +++++++-- .../main/res/values-b+da+instk12/strings.xml | 12 +++++++-- .../res/values-b+en+AU+unimelb/strings.xml | 12 +++++++-- .../res/values-b+en+GB+instukhe/strings.xml | 12 +++++++-- .../main/res/values-b+nb+instk12/strings.xml | 12 +++++++-- .../main/res/values-b+sv+instk12/strings.xml | 12 +++++++-- .../src/main/res/values-b+zh+Hans/strings.xml | 12 +++++++-- .../src/main/res/values-b+zh+Hant/strings.xml | 12 +++++++-- .../src/main/res/values-ca/strings.xml | 12 +++++++-- .../src/main/res/values-cy/strings.xml | 12 +++++++-- .../src/main/res/values-da/strings.xml | 12 +++++++-- .../src/main/res/values-de/strings.xml | 26 ++++++++++++------- .../src/main/res/values-en-rAU/strings.xml | 12 +++++++-- .../src/main/res/values-en-rCA/strings.xml | 15 +++++++++-- .../src/main/res/values-en-rCY/strings.xml | 12 +++++++-- .../src/main/res/values-en-rGB/strings.xml | 12 +++++++-- .../src/main/res/values-es-rES/strings.xml | 12 +++++++-- .../src/main/res/values-es/strings.xml | 12 +++++++-- .../src/main/res/values-fi/strings.xml | 12 +++++++-- .../src/main/res/values-fr-rCA/strings.xml | 12 +++++++-- .../src/main/res/values-fr/strings.xml | 12 +++++++-- .../src/main/res/values-ht/strings.xml | 12 +++++++-- .../src/main/res/values-is/strings.xml | 12 +++++++-- .../src/main/res/values-it/strings.xml | 12 +++++++-- .../src/main/res/values-ja/strings.xml | 12 +++++++-- .../src/main/res/values-mi/strings.xml | 12 +++++++-- .../src/main/res/values-nb/strings.xml | 12 +++++++-- .../src/main/res/values-nl/strings.xml | 12 +++++++-- .../src/main/res/values-pl/strings.xml | 12 +++++++-- .../src/main/res/values-pt-rBR/strings.xml | 12 +++++++-- .../src/main/res/values-pt-rPT/strings.xml | 12 +++++++-- .../src/main/res/values-ru/strings.xml | 12 +++++++-- .../src/main/res/values-sl/strings.xml | 12 +++++++-- .../src/main/res/values-sv/strings.xml | 12 +++++++-- .../src/main/res/values-th/strings.xml | 12 +++++++-- .../src/main/res/values-vi/strings.xml | 12 +++++++-- .../src/main/res/values-zh-rHK/strings.xml | 12 +++++++-- .../src/main/res/values-zh/strings.xml | 12 +++++++-- .../src/main/res/values-de/strings.xml | 2 +- 78 files changed, 431 insertions(+), 86 deletions(-) diff --git a/apps/flutter_parent/lib/l10n/res/intl_en_GB_instukhe.arb b/apps/flutter_parent/lib/l10n/res/intl_en_GB_instukhe.arb index 72519075a7..2e89e929cc 100644 --- a/apps/flutter_parent/lib/l10n/res/intl_en_GB_instukhe.arb +++ b/apps/flutter_parent/lib/l10n/res/intl_en_GB_instukhe.arb @@ -2660,4 +2660,4 @@ "placeholders_order": [], "placeholders": {} } -} \ No newline at end of file +} diff --git a/apps/teacher/src/main/res/values-ar/strings.xml b/apps/teacher/src/main/res/values-ar/strings.xml index f18ecf9a1f..b8db1987d2 100644 --- a/apps/teacher/src/main/res/values-ar/strings.xml +++ b/apps/teacher/src/main/res/values-ar/strings.xml @@ -928,6 +928,7 @@ -%s نقطة -%s نقاط + قائمة مهام %d غير مقروءة إعدادات ملف التعريف تنزيل الملف… تم تنزيل الملف بنجاح. diff --git a/apps/teacher/src/main/res/values-b+da+instk12/strings.xml b/apps/teacher/src/main/res/values-b+da+instk12/strings.xml index 5b34c6f15e..abd699dd1f 100644 --- a/apps/teacher/src/main/res/values-b+da+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+da+instk12/strings.xml @@ -874,6 +874,7 @@ -%s point -%s point + Opgaveliste %d ulæst Profilindstillinger Downloader fil… Fil blev downloaded! diff --git a/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml b/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml index fa9a938f76..e7180fe4be 100644 --- a/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml +++ b/apps/teacher/src/main/res/values-b+en+AU+unimelb/strings.xml @@ -872,6 +872,7 @@ -%s point -%s points + To do %d unread Profile Settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml b/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml index 45b932a9cc..3d43fe160d 100644 --- a/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml +++ b/apps/teacher/src/main/res/values-b+en+GB+instukhe/strings.xml @@ -872,6 +872,7 @@ -%s point -%s points + To do %d unread Profile settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-b+nb+instk12/strings.xml b/apps/teacher/src/main/res/values-b+nb+instk12/strings.xml index ae96287f2b..0eba6238dc 100644 --- a/apps/teacher/src/main/res/values-b+nb+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+nb+instk12/strings.xml @@ -875,6 +875,7 @@ -%s poeng -%s poeng + Gjøremål %d ulest Profilinnstillinger Laster ned fil… Filnedlasting utført. diff --git a/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml b/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml index 1b867f6658..eb7a5025ae 100644 --- a/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml +++ b/apps/teacher/src/main/res/values-b+sv+instk12/strings.xml @@ -874,6 +874,7 @@ -%s poäng -%s poäng + Att göra %d olästa Profilinställningar Hämtar fil… Filen hämtades. diff --git a/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml b/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml index 9c9e942bed..1e1ac223c3 100644 --- a/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml +++ b/apps/teacher/src/main/res/values-b+zh+Hans/strings.xml @@ -858,6 +858,7 @@ -%s 分 + 待办%d未读 个人资料设置 正在下载文件… 文件下载成功 diff --git a/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml b/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml index 25d255a0eb..a00c82be81 100644 --- a/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml +++ b/apps/teacher/src/main/res/values-b+zh+Hant/strings.xml @@ -858,6 +858,7 @@ -%s 分 + 待辦 %d 未讀 個人檔案設定 下載檔案… 檔案已成功下載。 diff --git a/apps/teacher/src/main/res/values-ca/strings.xml b/apps/teacher/src/main/res/values-ca/strings.xml index ab74b7afe7..c73fe877fa 100644 --- a/apps/teacher/src/main/res/values-ca/strings.xml +++ b/apps/teacher/src/main/res/values-ca/strings.xml @@ -875,6 +875,7 @@ -%s punt -%s punts + La tasca pendent %d no s\'ha llegit Configuració del perfil S\'està baixant el fitxer… S\'ha baixat correctament el fitxer. diff --git a/apps/teacher/src/main/res/values-cy/strings.xml b/apps/teacher/src/main/res/values-cy/strings.xml index 9369186baf..a7f86304c6 100644 --- a/apps/teacher/src/main/res/values-cy/strings.xml +++ b/apps/teacher/src/main/res/values-cy/strings.xml @@ -872,6 +872,7 @@ %s pwynt %s pwynt + I\'w gwneud %d heb eu darllen Gosodiadau Proffil Llwytho ffeil i lawr… Wedi llwyddo i lwytho ffeil i lawr. diff --git a/apps/teacher/src/main/res/values-da/strings.xml b/apps/teacher/src/main/res/values-da/strings.xml index 90ead3f6ed..8045a7237c 100644 --- a/apps/teacher/src/main/res/values-da/strings.xml +++ b/apps/teacher/src/main/res/values-da/strings.xml @@ -872,6 +872,7 @@ -%s point -%s point + Opgaveliste %d ulæst Profilindstillinger Downloader fil… Fil blev downloaded! diff --git a/apps/teacher/src/main/res/values-de/strings.xml b/apps/teacher/src/main/res/values-de/strings.xml index 7d36e5f78f..a80b3cab5c 100644 --- a/apps/teacher/src/main/res/values-de/strings.xml +++ b/apps/teacher/src/main/res/values-de/strings.xml @@ -690,7 +690,7 @@ Neue Diskussion Optionen Anmelden - Gelistete Antworten zulassen + Antworten aller Diskussionsstränge zulassen Benutzer müssen gepostet haben, bevor sie Antworten sehen können. Benutzern Kommentare erlauben Ein Diskussionstitel muss festgelegt werden. @@ -872,6 +872,7 @@ %s Punkt %s Punkte + %d Ungelesene erledigen Profileinstellungen Datei herunterladen… Datei erfolgreich heruntergeladen. diff --git a/apps/teacher/src/main/res/values-en-rAU/strings.xml b/apps/teacher/src/main/res/values-en-rAU/strings.xml index a9318f2e70..de27af29f6 100644 --- a/apps/teacher/src/main/res/values-en-rAU/strings.xml +++ b/apps/teacher/src/main/res/values-en-rAU/strings.xml @@ -872,6 +872,7 @@ -%s point -%s points + To do %d unread Profile Settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-en-rCA/strings.xml b/apps/teacher/src/main/res/values-en-rCA/strings.xml index 4b5c65d9ec..e663c539c4 100644 --- a/apps/teacher/src/main/res/values-en-rCA/strings.xml +++ b/apps/teacher/src/main/res/values-en-rCA/strings.xml @@ -875,6 +875,7 @@ -%s point -%s points + To do %d unread Profile Settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-en-rCY/strings.xml b/apps/teacher/src/main/res/values-en-rCY/strings.xml index 45b932a9cc..3d43fe160d 100644 --- a/apps/teacher/src/main/res/values-en-rCY/strings.xml +++ b/apps/teacher/src/main/res/values-en-rCY/strings.xml @@ -872,6 +872,7 @@ -%s point -%s points + To do %d unread Profile settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-en-rGB/strings.xml b/apps/teacher/src/main/res/values-en-rGB/strings.xml index a7bde06023..edcfd0f24c 100644 --- a/apps/teacher/src/main/res/values-en-rGB/strings.xml +++ b/apps/teacher/src/main/res/values-en-rGB/strings.xml @@ -872,6 +872,7 @@ -%s point -%s points + To do %d unread Profile settings Downloading file… File downloaded successfully. diff --git a/apps/teacher/src/main/res/values-es-rES/strings.xml b/apps/teacher/src/main/res/values-es-rES/strings.xml index eae61967f0..50bfff9dbc 100644 --- a/apps/teacher/src/main/res/values-es-rES/strings.xml +++ b/apps/teacher/src/main/res/values-es-rES/strings.xml @@ -875,6 +875,7 @@ -%s punto -%s puntos + %d Tarea pendiente sin leer Configuración del perfil Descargando el archivo… Se ha descargado el archivo. diff --git a/apps/teacher/src/main/res/values-es/strings.xml b/apps/teacher/src/main/res/values-es/strings.xml index 00133acd37..4c2f9a3fc3 100644 --- a/apps/teacher/src/main/res/values-es/strings.xml +++ b/apps/teacher/src/main/res/values-es/strings.xml @@ -873,6 +873,7 @@ -%s punto -%s puntos + Por hacer %d sin leer Configuración del perfil Descargando el archivo… Archivo descargado exitosamente. diff --git a/apps/teacher/src/main/res/values-fi/strings.xml b/apps/teacher/src/main/res/values-fi/strings.xml index a6ee65bcff..496f5a72c2 100644 --- a/apps/teacher/src/main/res/values-fi/strings.xml +++ b/apps/teacher/src/main/res/values-fi/strings.xml @@ -872,6 +872,7 @@ -%s piste -%s pistettä + Tehtävä %d lukematon Profiiliasetukset Tiedostoa ladataan… Tiedoston lataus onnistui. diff --git a/apps/teacher/src/main/res/values-fr-rCA/strings.xml b/apps/teacher/src/main/res/values-fr-rCA/strings.xml index 4da97d0c32..8e4a5feca0 100644 --- a/apps/teacher/src/main/res/values-fr-rCA/strings.xml +++ b/apps/teacher/src/main/res/values-fr-rCA/strings.xml @@ -872,6 +872,7 @@ -%s point -%s points + À faire %d non lue Paramètres de profil Téléchargement du fichier … Fichiers téléchargé avec succès. diff --git a/apps/teacher/src/main/res/values-fr/strings.xml b/apps/teacher/src/main/res/values-fr/strings.xml index 87cc52b294..488d38ab0c 100644 --- a/apps/teacher/src/main/res/values-fr/strings.xml +++ b/apps/teacher/src/main/res/values-fr/strings.xml @@ -872,6 +872,7 @@ -%s point -%s points + À faire %d non lu Paramètres de profil Téléchargement du fichier… Fichier téléchargé avec succès. diff --git a/apps/teacher/src/main/res/values-ht/strings.xml b/apps/teacher/src/main/res/values-ht/strings.xml index cef6543c92..e0a6854a12 100644 --- a/apps/teacher/src/main/res/values-ht/strings.xml +++ b/apps/teacher/src/main/res/values-ht/strings.xml @@ -872,6 +872,7 @@ -%s pwen -%s pwen yo + Pou fè %d poko li Paramèt Pwofi Telechajman fichye… Fichye telechaje kòrèkteman. diff --git a/apps/teacher/src/main/res/values-is/strings.xml b/apps/teacher/src/main/res/values-is/strings.xml index 22dd6dbb16..274a6471af 100644 --- a/apps/teacher/src/main/res/values-is/strings.xml +++ b/apps/teacher/src/main/res/values-is/strings.xml @@ -873,6 +873,7 @@ -%s punktur -%s punktar + Verkefnalisti %d ólesin Uppsetningarstillingar Sæki skrá… Skrá var sótt. diff --git a/apps/teacher/src/main/res/values-it/strings.xml b/apps/teacher/src/main/res/values-it/strings.xml index dc4f768582..29cd811b7a 100644 --- a/apps/teacher/src/main/res/values-it/strings.xml +++ b/apps/teacher/src/main/res/values-it/strings.xml @@ -873,6 +873,7 @@ -%s punto -%s punti + Elenco attività: %d da leggere Impostazioni profilo Download del file… Download del file completato. diff --git a/apps/teacher/src/main/res/values-ja/strings.xml b/apps/teacher/src/main/res/values-ja/strings.xml index 53f188a83b..15921efd04 100644 --- a/apps/teacher/src/main/res/values-ja/strings.xml +++ b/apps/teacher/src/main/res/values-ja/strings.xml @@ -858,6 +858,7 @@ -%s 点 + 未読の「するべきこと」%d プロファイル設定 ファイルをダウンロード中… ファイルのダウンロードに成功しました。 diff --git a/apps/teacher/src/main/res/values-mi/strings.xml b/apps/teacher/src/main/res/values-mi/strings.xml index ddbb0efdca..0891fbac4d 100644 --- a/apps/teacher/src/main/res/values-mi/strings.xml +++ b/apps/teacher/src/main/res/values-mi/strings.xml @@ -872,6 +872,7 @@ -%s koinga -%s ngā koinga + Hei mahi %d kaore i pānuitia Ngā Tautuhinga Kōtaha E tikiake ake ana kōnae… I pai te tikiake i te kōnae. diff --git a/apps/teacher/src/main/res/values-nb/strings.xml b/apps/teacher/src/main/res/values-nb/strings.xml index 734a11a89e..1fa082cfe1 100644 --- a/apps/teacher/src/main/res/values-nb/strings.xml +++ b/apps/teacher/src/main/res/values-nb/strings.xml @@ -875,6 +875,7 @@ -%s poeng -%s poeng + Gjøremål %d ulest Profilinnstillinger Laster ned fil… Filnedlasting utført. diff --git a/apps/teacher/src/main/res/values-nl/strings.xml b/apps/teacher/src/main/res/values-nl/strings.xml index d8b3d90209..0f7734ef68 100644 --- a/apps/teacher/src/main/res/values-nl/strings.xml +++ b/apps/teacher/src/main/res/values-nl/strings.xml @@ -872,6 +872,7 @@ -%s punt -%s punten + Taken %d ongelezen Profielinstellingen Bestand downloaden… Bestand is gedownload. diff --git a/apps/teacher/src/main/res/values-pl/strings.xml b/apps/teacher/src/main/res/values-pl/strings.xml index c46c055f64..79921c3d6e 100644 --- a/apps/teacher/src/main/res/values-pl/strings.xml +++ b/apps/teacher/src/main/res/values-pl/strings.xml @@ -900,6 +900,7 @@ -%s pkt -%s pkt + Lista zadań – nieprzeczytane %d Ustawienia profilu Pobieranie pliku… Pobrano plik. diff --git a/apps/teacher/src/main/res/values-pt-rBR/strings.xml b/apps/teacher/src/main/res/values-pt-rBR/strings.xml index 03eb48ac17..49bd9f5111 100644 --- a/apps/teacher/src/main/res/values-pt-rBR/strings.xml +++ b/apps/teacher/src/main/res/values-pt-rBR/strings.xml @@ -873,6 +873,7 @@ -%s ponto -%s pontos + %d da lista de tarefas não lido(s) Configurações do perfil Baixando arquivo… Arquivo baixado com sucesso. diff --git a/apps/teacher/src/main/res/values-pt-rPT/strings.xml b/apps/teacher/src/main/res/values-pt-rPT/strings.xml index b0af875548..80b26a4a82 100644 --- a/apps/teacher/src/main/res/values-pt-rPT/strings.xml +++ b/apps/teacher/src/main/res/values-pt-rPT/strings.xml @@ -872,6 +872,7 @@ -%s ponto -%s pontos + Para fazer %d não ler Configurações de perfil Download do ficheiro… Ficheiro baixado com sucesso. diff --git a/apps/teacher/src/main/res/values-ru/strings.xml b/apps/teacher/src/main/res/values-ru/strings.xml index a2ce13a13b..3ed833e356 100644 --- a/apps/teacher/src/main/res/values-ru/strings.xml +++ b/apps/teacher/src/main/res/values-ru/strings.xml @@ -900,6 +900,7 @@ -%s балла/баллов -%s балла/баллов + Выполнить непрочитанные %d Настройки профиля Загрузка файла… Файл успешно загружен. diff --git a/apps/teacher/src/main/res/values-sl/strings.xml b/apps/teacher/src/main/res/values-sl/strings.xml index b1ccd46bc7..42d3901e5c 100644 --- a/apps/teacher/src/main/res/values-sl/strings.xml +++ b/apps/teacher/src/main/res/values-sl/strings.xml @@ -872,6 +872,7 @@ – %s točka – %s točk + Seznam opravil, %d neprebranih Nastavitve profila Prenos datoteke… Datoteka je uspešno prenesena. diff --git a/apps/teacher/src/main/res/values-sv/strings.xml b/apps/teacher/src/main/res/values-sv/strings.xml index dc2c0a0c91..bd1a7dab53 100644 --- a/apps/teacher/src/main/res/values-sv/strings.xml +++ b/apps/teacher/src/main/res/values-sv/strings.xml @@ -873,6 +873,7 @@ -%s poäng -%s poäng + Att göra %d olästa Profilinställningar Hämtar fil… Filen hämtades. diff --git a/apps/teacher/src/main/res/values-th/strings.xml b/apps/teacher/src/main/res/values-th/strings.xml index c735ff3f70..98a1168099 100644 --- a/apps/teacher/src/main/res/values-th/strings.xml +++ b/apps/teacher/src/main/res/values-th/strings.xml @@ -874,6 +874,7 @@ -%s คะแนน -%s คะแนน + สิ่งที่ต้องทำ %d ที่ไม่ได้อ่าน ค่าปรับตั้งโพรไฟล์ กำลังดาวน์โหลดไฟล์… ดาวน์โหลดไฟล์เสร็จสิ้น diff --git a/apps/teacher/src/main/res/values-vi/strings.xml b/apps/teacher/src/main/res/values-vi/strings.xml index 41f70f23ce..9c37b925be 100644 --- a/apps/teacher/src/main/res/values-vi/strings.xml +++ b/apps/teacher/src/main/res/values-vi/strings.xml @@ -875,6 +875,7 @@ -%s điểm thành phần -%s điểm thành phần + Cần làm %d chưa đọc Cài Đặt Hồ Sơ Đang tải xuống tập tin… Đã tải xuống tập tin thành công. diff --git a/apps/teacher/src/main/res/values-zh-rHK/strings.xml b/apps/teacher/src/main/res/values-zh-rHK/strings.xml index 25d255a0eb..a00c82be81 100644 --- a/apps/teacher/src/main/res/values-zh-rHK/strings.xml +++ b/apps/teacher/src/main/res/values-zh-rHK/strings.xml @@ -858,6 +858,7 @@ -%s 分 + 待辦 %d 未讀 個人檔案設定 下載檔案… 檔案已成功下載。 diff --git a/apps/teacher/src/main/res/values-zh/strings.xml b/apps/teacher/src/main/res/values-zh/strings.xml index 9c9e942bed..1e1ac223c3 100644 --- a/apps/teacher/src/main/res/values-zh/strings.xml +++ b/apps/teacher/src/main/res/values-zh/strings.xml @@ -858,6 +858,7 @@ -%s 分 + 待办%d未读 个人资料设置 正在下载文件… 文件下载成功 diff --git a/libs/pandares/src/main/res/values-ar/strings.xml b/libs/pandares/src/main/res/values-ar/strings.xml index 9e8bfd22cf..15b0ad4877 100644 --- a/libs/pandares/src/main/res/values-ar/strings.xml +++ b/libs/pandares/src/main/res/values-ar/strings.xml @@ -993,6 +993,7 @@ عقوبة التأخير (-%s) الدرجة النهائية + أكثر من 99 أكثر من 99 الفتح في عرض ويب @@ -1315,6 +1316,13 @@ نسق التطبيق فاتح داكن - النظام الافتراضي - + مثل الجهاز + Canvas متاح الآن في النسق الداكن + اختر نسق التطبيق + النسق الفاتح + النسق الداكن + مثل نسق الجهاز + حفظ + يمكنك تغييرها لاحقًا في إعدادات التطبيق + تحديد diff --git a/libs/pandares/src/main/res/values-b+da+instk12/strings.xml b/libs/pandares/src/main/res/values-b+da+instk12/strings.xml index 530f057cb3..c975f0e076 100644 --- a/libs/pandares/src/main/res/values-b+da+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+da+instk12/strings.xml @@ -958,6 +958,7 @@ Straf for sen aflevering (-%s) Endelig vurdering + 99+ 99+ Åbner i webvisning @@ -1260,6 +1261,13 @@ App-tema Lys Mørk - Systemstandard - + Samme som enhed + Canvas er nu tilgængelig i mørkt tema + Vælg app-tema + Lyst tema + Mørkt tema + Samme tema som på enhed + Gem + Du kan ændre det senere i app-indstillinger + Tag fat diff --git a/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml b/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml index 812e7ad52a..cf3e1bf89f 100644 --- a/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml +++ b/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml @@ -958,6 +958,7 @@ Late penalty (-%s) Final Grade + 99+ 99+ Opens in webview @@ -1260,6 +1261,13 @@ App Theme Light Dark - System default - + Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings + Grab diff --git a/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml b/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml index ae309ef917..9b7f1fc049 100644 --- a/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml +++ b/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml @@ -958,6 +958,7 @@ Late penalty (-%s) Final grade + 99+ 99+ Opens in webview @@ -1260,6 +1261,13 @@ App Theme Light Dark - System default - + Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings + Grab diff --git a/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml b/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml index 38a46af3d5..16816624d0 100644 --- a/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml @@ -958,6 +958,7 @@ Forsinkelsesstraff (-%s) Sluttvurdering + 99+ 99+ Åpne i nettvisning @@ -1261,6 +1262,13 @@ App-tema Lys Mørk - Systemstandard - + Samme som enhet + Canvas er nå tilgjengelig med mørkt tema + Velg app-tema + Lyst tema + Mørkt tema + Samme som enhetstema + Lagre + Du kan endre det senere i appinnstillinger + Grip diff --git a/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml b/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml index 813d53116f..fd001e51fb 100644 --- a/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml @@ -958,6 +958,7 @@ Förseningsbestraffning (-%s) Totalt omdöme + 99+ 99+ Öppnas i webbvy @@ -1260,6 +1261,13 @@ Apptema Ljus Mörk - Systemstandard - + Samma som enhet + Canvas finns inte tillgängligt med ett mörkt tema + Välj apptema + Ljust tema + Mörkt tema + Samma tema som enheten + Spara + Du kan ändra det sedan i appinställningarna + Ta tag i diff --git a/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml b/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml index 923ec7c8eb..b5ea856bd0 100644 --- a/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml @@ -949,6 +949,7 @@ 迟交罚分 (-%s) 最终评分 + 99+ 99+ 在网页视图中打开 @@ -1246,6 +1247,13 @@ 应用主题 浅色的、轻的 深色的、黑的 - 系统默认 - + 与设备相同 + Canvas 已推出深色主题 + 选择应用程序主题 + 浅色主题 + 深色主题 + 与设备主题相同 + 保存 + 您可以后在应用设置中更改 + 抓取图像 diff --git a/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml b/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml index e5bb1d5601..6808be99dc 100644 --- a/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml @@ -949,6 +949,7 @@ 逾期懲罰 (-%s) 最終評分 + 99+ 99+ 在網頁中打開 @@ -1246,6 +1247,13 @@ 應用程式主題 亮色 暗色 - 系統預設 - + 和裝置相同 + Canvas 現在可在暗色主題中使用 + 選擇應用程式主題 + 淡色主題 + 暗色主題 + 和裝置主題相同 + 儲存 + 您可以稍後在應用桯式設定中變更 + 擷取 diff --git a/libs/pandares/src/main/res/values-ca/strings.xml b/libs/pandares/src/main/res/values-ca/strings.xml index 6647262d5f..84f6eaac65 100644 --- a/libs/pandares/src/main/res/values-ca/strings.xml +++ b/libs/pandares/src/main/res/values-ca/strings.xml @@ -958,6 +958,7 @@ Sanció per endarreriment (-%s) Nota final + 99+ 99+ S\'obre en vista web @@ -1261,6 +1262,13 @@ Tema de l’aplicació Clar Fosc - Valor predeterminat del sistema - + El mateix que el dispositiu + Ara, el Canvas està disponible en tema fosc + Trieu el tema de l’aplicació + Tema clar + Tema fosc + El mateix tema que el del dispositiu + Desa + Podeu canviar-lo més endavant a la configuració de l’aplicació + Agafa diff --git a/libs/pandares/src/main/res/values-cy/strings.xml b/libs/pandares/src/main/res/values-cy/strings.xml index 9c1f6c6dd8..95b5760db0 100644 --- a/libs/pandares/src/main/res/values-cy/strings.xml +++ b/libs/pandares/src/main/res/values-cy/strings.xml @@ -958,6 +958,7 @@ Cosb am fod yn hwyr (-%s) Gradd Derfynol + 99+ 99+ Agor yn webview @@ -1260,6 +1261,13 @@ Thema Ap Golau Tywyll - Rhagosodiad system - + Yn un fath a’r ddyfais + Mae Canvas bellach ar gael mewn thema dywyll + Dewis thema ap + Thema golau + Thema dywyll + Yr un fath a thema’r ddyfais + Cadw + Gallwch chi ei newid yn nes ymlaen yng ngosodiadau’r ap + Gafael diff --git a/libs/pandares/src/main/res/values-da/strings.xml b/libs/pandares/src/main/res/values-da/strings.xml index dc3873f8c9..c6f82e0923 100644 --- a/libs/pandares/src/main/res/values-da/strings.xml +++ b/libs/pandares/src/main/res/values-da/strings.xml @@ -958,6 +958,7 @@ Straf for sen aflevering (-%s) Endelig karakter + 99+ 99+ Åbner i webvisning @@ -1260,6 +1261,13 @@ App-tema Lys Mørk - Systemstandard - + Samme som enhed + Canvas er nu tilgængelig i mørkt tema + Vælg app-tema + Lyst tema + Mørkt tema + Samme tema som på enhed + Gem + Du kan ændre det senere i app-indstillinger + Tag fat diff --git a/libs/pandares/src/main/res/values-de/strings.xml b/libs/pandares/src/main/res/values-de/strings.xml index 1092a017be..16b28eec4b 100644 --- a/libs/pandares/src/main/res/values-de/strings.xml +++ b/libs/pandares/src/main/res/values-de/strings.xml @@ -113,7 +113,7 @@ %1$s von %2$s Punkten Hypothetische Punktzahl eingeben Gering: %s - Mittel: %s + Mittelwert: %s Hoch: %s @@ -484,7 +484,7 @@ Entschuldigung. Sie sind nicht zu Ankündigungen in diesem Kurs berechtigt. Entschuldigung. Sie sind nicht zu Diskussionen in diesem Kurs berechtigt. - Announcements + Ankündigungen Der Titel darf nicht leer sein. @@ -733,7 +733,7 @@ Neues Ereignis erstellen Panda ausstellen - Announcements + Ankündigungen Konto hinzufügen Benutzer wechseln @@ -797,7 +797,7 @@ Benachrichtigung erhalten, wenn es eine neue Ankündigung in Ihrem Kurs gibt. Benachrichtigung erhalten, wenn jemand auf eine Ankündigung von Ihnen antwortet. Benachrichtigung erhalten, wenn eine Aufgabe/Abgabe benotet/geändert wurde und wenn eine Notengewichtung geändert wurde. - Benachrichtigung erhalten bei Einladungen zu Webkonferenzen, Gruppen, Kooperationen, Bewertung durch Mitstudenten und Erinnerungen. + Benachrichtigung erhalten bei Einladungen zu Webkonferenzen, Gruppen, Kooperationen, Peer Review und Erinnerungen. Nur für Kursleiter und Administratoren. Benachrichtigung erhalten, wenn eine Aufgabe abgegeben oder erneut abgegeben wird. Nur für Kursleiter und Administratoren. Benachrichtigung erhalten, wenn eine Aufgabe zu spät abgegeben wird. Benachrichtigung erhalten, wenn zu Ihrer Einreichung ein Kommentar abgegeben wird. @@ -958,6 +958,7 @@ Strafe für Verspätung (-%s) Gesamtnote + über 99 über 99 In Webview öffnen @@ -998,7 +999,7 @@ Konferenzen werden auf Mobilgeräten noch nicht unterstützt. Dateivorschaubild Fehler beim Versuch diese PDF-Datei zu laden - Tut uns sehr leid! Diese Funktion steht für die Studentenansicht nicht erlaubt. + Tut uns sehr leid! Diese Funktion steht für die Studierendenansicht nicht erlaubt. Hier gibt es nichts zu sehen Nicht unterstützte Funktion @@ -1041,7 +1042,7 @@ Note außer Kraft setzen Aktuelle Note Öffnet in Canvas Student - Studentenansicht + Studierendenansicht @@ -1232,7 +1233,7 @@ Tippen Sie hier, um fortzufahren. Entwurf verfügbar Fehler bei, Laden der Abgabe - Tippen Sie hier, um den vollständigen Content anzuzeigen + Tippen Sie hier, um den vollständigen Inhalt anzuzeigen Wichtige Termine Keine wichtigen Termine Wichtige Termine @@ -1260,6 +1261,13 @@ App-Design Hell Dunkel - Systemstandard - + Wie Gerät + Canvas ist jetzt in dunklem Design verfügbar + App-Design auswählen + Helles Design + Dunkles Design + Entspricht dem Gerätedesign + Speichern + Sie können es später in den App-Einstellungen ändern. + Greifen diff --git a/libs/pandares/src/main/res/values-en-rAU/strings.xml b/libs/pandares/src/main/res/values-en-rAU/strings.xml index 108a19c7ca..e6e3c9557a 100644 --- a/libs/pandares/src/main/res/values-en-rAU/strings.xml +++ b/libs/pandares/src/main/res/values-en-rAU/strings.xml @@ -958,6 +958,7 @@ Late penalty (-%s) Final Grade + 99+ 99+ Opens in webview @@ -1260,6 +1261,13 @@ App Theme Light Dark - System default - + Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings + Grab diff --git a/libs/pandares/src/main/res/values-en-rCA/strings.xml b/libs/pandares/src/main/res/values-en-rCA/strings.xml index bb4e4e2d2d..33b713916c 100644 --- a/libs/pandares/src/main/res/values-en-rCA/strings.xml +++ b/libs/pandares/src/main/res/values-en-rCA/strings.xml @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libs/pandautils/src/main/res/layout/item_notification_header.xml b/libs/pandautils/src/main/res/layout/item_notification_header.xml index 914dc471da..7705647472 100644 --- a/libs/pandautils/src/main/res/layout/item_notification_header.xml +++ b/libs/pandautils/src/main/res/layout/item_notification_header.xml @@ -27,9 +27,7 @@ android:layout_width="match_parent" android:layout_height="48dp" android:paddingStart="16dp" - android:paddingTop="8dp" - android:paddingEnd="16dp" - android:paddingBottom="8dp"> + android:paddingEnd="16dp"> + + diff --git a/libs/pandautils/src/main/res/layout/item_notification_preference.xml b/libs/pandautils/src/main/res/layout/item_push_notification_preference.xml similarity index 98% rename from libs/pandautils/src/main/res/layout/item_notification_preference.xml rename to libs/pandautils/src/main/res/layout/item_push_notification_preference.xml index 511deb4212..2f6be324b2 100644 --- a/libs/pandautils/src/main/res/layout/item_notification_preference.xml +++ b/libs/pandautils/src/main/res/layout/item_push_notification_preference.xml @@ -25,7 +25,7 @@ + type="com.instructure.pandautils.features.notification.preferences.itemviewmodels.PushNotificationCategoryItemViewModel" /> . + * + */ +package com.instructure.pandautils.features.notification.preferences + +import android.content.res.Resources +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import com.instructure.canvasapi2.managers.CommunicationChannelsManager +import com.instructure.canvasapi2.managers.NotificationPreferencesFrequency +import com.instructure.canvasapi2.managers.NotificationPreferencesManager +import com.instructure.canvasapi2.models.CommunicationChannel +import com.instructure.canvasapi2.models.NotificationPreference +import com.instructure.canvasapi2.models.NotificationPreferenceResponse +import com.instructure.canvasapi2.models.User +import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.DataResult +import com.instructure.pandautils.R +import com.instructure.pandautils.features.notification.preferences.itemviewmodels.EmailNotificationCategoryItemViewModel +import com.instructure.pandautils.features.notification.preferences.itemviewmodels.PushNotificationCategoryItemViewModel +import com.instructure.pandautils.mvvm.ViewState +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import junit.framework.Assert.assertEquals +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.setMain +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +@ExperimentalCoroutinesApi +class EmailNotificationPreferencesViewModelTest { + + @get:Rule + var instantExecutorRule = InstantTaskExecutorRule() + + private val lifecycleOwner: LifecycleOwner = mockk(relaxed = true) + private val lifecycleRegistry = LifecycleRegistry(lifecycleOwner) + + private val testDispatcher = TestCoroutineDispatcher() + + private val communicationChannelsManager: CommunicationChannelsManager = mockk(relaxed = true) + private val notificationPreferencesManager: NotificationPreferencesManager = mockk(relaxed = true) + private val apiPrefs: ApiPrefs = mockk(relaxed = true) + private val resources: Resources = mockk(relaxed = true) + private lateinit var notificationPreferenceUtils: NotificationPreferenceUtils + + @Before + fun setUp() { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) + Dispatchers.setMain(testDispatcher) + + every { apiPrefs.user } returns User(id = 1) + + every { communicationChannelsManager.getCommunicationChannelsAsync(any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(listOf(CommunicationChannel(id = 1, userId = 1, type = "email"))) + } + + setupStrings() + notificationPreferenceUtils = NotificationPreferenceUtils(resources) + } + + @Test + fun `Notification categories map correctly`() { + val notificationResponse = NotificationPreferenceResponse( + notificationPreferences = listOf( + NotificationPreference(notification = "notification1", category = "due_date", frequency = "immediately"), + NotificationPreference(notification = "notification2", category = "membership_update", frequency = "daily"), + NotificationPreference(notification = "notification3", category = "discussion", frequency = "weekly"), + NotificationPreference(notification = "notification4", category = "announcement_created_by_you", frequency = "never") + ) + ) + + every { notificationPreferencesManager.getNotificationPreferencesAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(notificationResponse) + } + + val viewModel = createViewModel() + + viewModel.data.observe(lifecycleOwner) {} + + val data = viewModel.data.value + + assertEquals(3, viewModel.data.value?.items?.size) + + //Course Activities + val courseActivitiesHeader = data?.items?.get(0) + assertEquals("Course Activities", courseActivitiesHeader?.data?.title) + assertEquals(0, courseActivitiesHeader?.data?.position) + assertEquals(2, courseActivitiesHeader?.itemViewModels?.size) + + //Due Date + val courseActivitiesItems = courseActivitiesHeader?.itemViewModels as? List + assertEquals(2, courseActivitiesItems?.size) + assertEquals("Due Date", courseActivitiesItems?.get(0)?.data?.title) + assertEquals("Get notified when an assignment due date changes.", courseActivitiesItems?.get(0)?.data?.description) + assertEquals(1, courseActivitiesItems?.get(0)?.data?.position) + assertEquals("Immediately", courseActivitiesItems?.get(0)?.frequency) + + //Announcement Created By You + assertEquals("Announcement Created By You", courseActivitiesItems?.get(1)?.data?.title) + assertEquals("Get notified when you create an announcement and when somebody replies to your announcement.", courseActivitiesItems?.get(1)?.data?.description) + assertEquals(6, courseActivitiesItems?.get(1)?.data?.position) + assertEquals("Never", courseActivitiesItems?.get(1)?.frequency) + + //Discussions + val discussionsHeader = data?.items?.get(1) + assertEquals("Discussions", discussionsHeader?.data?.title) + assertEquals(1, discussionsHeader?.data?.position) + assertEquals(1, discussionsHeader?.itemViewModels?.size) + + //Discussion + val discussionItems = discussionsHeader?.itemViewModels as? List + assertEquals(1, discussionItems?.size) + assertEquals("Discussion", discussionItems?.get(0)?.data?.title) + assertEquals("Get notified when there’s a new discussion topic in your course.", discussionItems?.get(0)?.data?.description) + assertEquals(1, discussionItems?.get(0)?.data?.position) + assertEquals("Weekly", discussionItems?.get(0)?.frequency) + + //Groups + val groupsHeader = data?.items?.get(2) + assertEquals("Groups", groupsHeader?.data?.title) + assertEquals(4, groupsHeader?.data?.position) + assertEquals(1, groupsHeader?.itemViewModels?.size) + + //Membership update + val groupsItems = groupsHeader?.itemViewModels as? List + assertEquals(1, groupsItems?.size) + assertEquals("Membership Update", groupsItems?.get(0)?.data?.title) + assertEquals("Admin only, pending enrollment activated. Get notified when a group enrollment is accepted or rejected.", groupsItems?.get(0)?.data?.description) + assertEquals(1, groupsItems?.get(0)?.data?.position) + assertEquals("Daily", groupsItems?.get(0)?.frequency) + } + + @Test + fun `Error when cannot fetch notification preferences`() { + every { notificationPreferencesManager.getNotificationPreferencesAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Fail() + } + + val viewModel = createViewModel() + + viewModel.state.observe(lifecycleOwner) {} + + assertEquals(ViewState.Error("An unexpected error occurred."), viewModel.state.value) + } + + @Test + fun `Error when user is null`() { + every { apiPrefs.user } returns null + + val viewModel = createViewModel() + + viewModel.state.observe(lifecycleOwner) {} + + assertEquals(ViewState.Error("An unexpected error occurred."), viewModel.state.value) + } + + @Test + fun `Error when cannot fetch notification channels`() { + every { communicationChannelsManager.getCommunicationChannelsAsync(any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Fail() + } + + val viewModel = createViewModel() + + viewModel.state.observe(lifecycleOwner) {} + + assertEquals(ViewState.Error("An unexpected error occurred."), viewModel.state.value) + } + + @Test + fun `Empty state`() { + val notificationResponse = NotificationPreferenceResponse(emptyList()) + + every { notificationPreferencesManager.getNotificationPreferencesAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(notificationResponse) + } + + val viewModel = createViewModel() + + viewModel.state.observe(lifecycleOwner) {} + + assertEquals(ViewState.Empty(emptyTitle = R.string.no_notifications_to_show, emptyImage = R.drawable.ic_panda_noalerts), viewModel.state.value) + } + + @Test + fun `Show frequency selection dialog when notification category is clicked`() { + val notificationResponse = NotificationPreferenceResponse( + notificationPreferences = listOf( + NotificationPreference(notification = "notification1", category = "due_date", frequency = "immediately") + ) + ) + + every { notificationPreferencesManager.getNotificationPreferencesAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(notificationResponse) + } + + val viewModel = createViewModel() + + viewModel.data.observe(lifecycleOwner) {} + + val data = viewModel.data.value + + val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) as? EmailNotificationCategoryItemViewModel + itemViewModel?.onClick() + + val event = viewModel.events.value?.getContentIfNotHandled() + val expectedEvent = NotificationPreferencesAction.ShowFrequencySelectionDialog("notification1", NotificationPreferencesFrequency.IMMEDIATELY) + assertEquals(expectedEvent, event) + } + + @Test + fun `Change notification category frequency`() { + val notificationResponse = NotificationPreferenceResponse( + notificationPreferences = listOf( + NotificationPreference(notification = "notification1", category = "due_date", frequency = "immediately") + ) + ) + + val updatedNotificationResponse = NotificationPreferenceResponse( + notificationPreferences = listOf( + NotificationPreference(notification = "notification1", category = "due_date", frequency = "daily") + ) + ) + + every { notificationPreferencesManager.getNotificationPreferencesAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(notificationResponse) + } + + every { notificationPreferencesManager.updatePreferenceCategoryAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(updatedNotificationResponse) + } + + val viewModel = createViewModel() + + viewModel.data.observe(lifecycleOwner) {} + + val data = viewModel.data.value + + val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) as? EmailNotificationCategoryItemViewModel + + assertEquals("Immediately", itemViewModel?.frequency) + viewModel.updateFrequency("notification1", NotificationPreferencesFrequency.DAILY) + verify { notificationPreferencesManager.updatePreferenceCategoryAsync("notification1", any(), "daily") } + + assertEquals("Daily", itemViewModel?.frequency) + } + + @Test + fun `Revert previous state when changing frequency has error`() { + val notificationResponse = NotificationPreferenceResponse( + notificationPreferences = listOf( + NotificationPreference(notification = "notification1", category = "due_date", frequency = "weekly") + ) + ) + + every { notificationPreferencesManager.getNotificationPreferencesAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(notificationResponse) + } + + every { notificationPreferencesManager.updatePreferenceCategoryAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Fail() + } + + val viewModel = createViewModel() + + viewModel.data.observe(lifecycleOwner) {} + viewModel.events.observe(lifecycleOwner) {} + + val data = viewModel.data.value + + val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) as? EmailNotificationCategoryItemViewModel + + assertEquals("Weekly", itemViewModel?.frequency) + viewModel.updateFrequency("notification1", NotificationPreferencesFrequency.DAILY) + + assertEquals("Weekly", itemViewModel?.frequency) + val event = viewModel.events.value?.getContentIfNotHandled() + assert(event is NotificationPreferencesAction.ShowSnackbar) + assertEquals("An unexpected error occurred.", (event as NotificationPreferencesAction.ShowSnackbar).snackbar) + } + + @Test + fun `Refresh`() { + var notificationResponse = NotificationPreferenceResponse(emptyList()) + + every { notificationPreferencesManager.getNotificationPreferencesAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(notificationResponse) + } + + val viewModel = createViewModel() + + viewModel.state.observe(lifecycleOwner) {} + + assertEquals(ViewState.Empty(emptyTitle = R.string.no_notifications_to_show, emptyImage = R.drawable.ic_panda_noalerts), viewModel.state.value) + + notificationResponse = NotificationPreferenceResponse( + notificationPreferences = listOf( + NotificationPreference(notification = "notification1", category = "due_date", frequency = "never") + ) + ) + + every { notificationPreferencesManager.getNotificationPreferencesAsync(any(), any(), any()) } returns mockk { + coEvery { await() } returns DataResult.Success(notificationResponse) + } + + viewModel.refresh() + assertEquals(ViewState.Success, viewModel.state.value) + assertEquals(1, viewModel.data.value?.items?.size) + } + + private fun createViewModel(): EmailNotificationPreferencesViewModel { + return EmailNotificationPreferencesViewModel(communicationChannelsManager, notificationPreferencesManager, apiPrefs, notificationPreferenceUtils, resources) + } + + private fun setupStrings() { + every { resources.getString(R.string.notification_pref_due_date) } returns "Due Date" + every { resources.getString(R.string.notification_pref_discussion) } returns "Discussion" + every { resources.getString(R.string.notification_pref_announcement_created_by_you) } returns "Announcement Created By You" + every { resources.getString(R.string.notification_pref_membership_update) } returns "Membership Update" + every { resources.getString(R.string.notification_desc_due_date) } returns "Get notified when an assignment due date changes." + every { resources.getString(R.string.notification_desc_announcement_created_by_you) } returns "Get notified when you create an announcement and when somebody replies to your announcement." + every { resources.getString(R.string.notification_desc_discussion) } returns "Get notified when there’s a new discussion topic in your course." + every { resources.getString(R.string.notification_desc_membership_update) } returns "Admin only, pending enrollment activated. Get notified when a group enrollment is accepted or rejected." + every { resources.getString(R.string.notification_cat_course_activities) } returns "Course Activities" + every { resources.getString(R.string.notification_cat_discussions) } returns "Discussions" + every { resources.getString(R.string.notification_cat_groups) } returns "Groups" + every { resources.getString(R.string.errorOccurred) } returns "An unexpected error occurred." + every { resources.getString(R.string.emailNotificationsImmediately) } returns "Immediately" + every { resources.getString(R.string.emailNotificationsNever) } returns "Never" + every { resources.getString(R.string.emailNotificationsDaily) } returns "Daily" + every { resources.getString(R.string.emailNotificationsWeekly) } returns "Weekly" + } +} \ No newline at end of file diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/notification/preferences/NotificationPreferencesViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/notification/preferences/PushNotificationPreferencesViewModelTest.kt similarity index 94% rename from libs/pandautils/src/test/java/com/instructure/pandautils/features/notification/preferences/NotificationPreferencesViewModelTest.kt rename to libs/pandautils/src/test/java/com/instructure/pandautils/features/notification/preferences/PushNotificationPreferencesViewModelTest.kt index 47d3f3bcca..b0b7b7f04f 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/notification/preferences/NotificationPreferencesViewModelTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/notification/preferences/PushNotificationPreferencesViewModelTest.kt @@ -30,11 +30,11 @@ import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.DataResult import com.instructure.pandautils.R +import com.instructure.pandautils.features.notification.preferences.itemviewmodels.PushNotificationCategoryItemViewModel import com.instructure.pandautils.mvvm.ViewState import io.mockk.coEvery import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic import junit.framework.Assert.assertEquals import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,7 +45,7 @@ import org.junit.Rule import org.junit.Test @ExperimentalCoroutinesApi -class NotificationPreferencesViewModelTest { +class PushNotificationPreferencesViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @@ -106,7 +106,7 @@ class NotificationPreferencesViewModelTest { assertEquals(2, courseActivitiesHeader?.itemViewModels?.size) //Due Date - val courseActivitiesItems = courseActivitiesHeader?.itemViewModels + val courseActivitiesItems = courseActivitiesHeader?.itemViewModels as? List assertEquals(2, courseActivitiesItems?.size) assertEquals("Due Date", courseActivitiesItems?.get(0)?.data?.title) assertEquals("Get notified when an assignment due date changes.", courseActivitiesItems?.get(0)?.data?.description) @@ -126,7 +126,7 @@ class NotificationPreferencesViewModelTest { assertEquals(1, discussionsHeader?.itemViewModels?.size) //Discussion - val discussionItems = discussionsHeader?.itemViewModels + val discussionItems = discussionsHeader?.itemViewModels as? List assertEquals(1, discussionItems?.size) assertEquals("Discussion", discussionItems?.get(0)?.data?.title) assertEquals("Get notified when there’s a new discussion topic in your course.", discussionItems?.get(0)?.data?.description) @@ -140,7 +140,7 @@ class NotificationPreferencesViewModelTest { assertEquals(1, groupsHeader?.itemViewModels?.size) //Membership update - val groupsItems = groupsHeader?.itemViewModels + val groupsItems = groupsHeader?.itemViewModels as? List assertEquals(1, groupsItems?.size) assertEquals("Membership Update", groupsItems?.get(0)?.data?.title) assertEquals("Admin only, pending enrollment activated. Get notified when a group enrollment is accepted or rejected.", groupsItems?.get(0)?.data?.description) @@ -228,7 +228,7 @@ class NotificationPreferencesViewModelTest { val data = viewModel.data.value - val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) + val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) as? PushNotificationCategoryItemViewModel assertEquals(true, itemViewModel?.isChecked) itemViewModel?.onCheckedChanged(false) @@ -264,7 +264,7 @@ class NotificationPreferencesViewModelTest { val data = viewModel.data.value - val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) + val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) as? PushNotificationCategoryItemViewModel assertEquals(false, itemViewModel?.isChecked) itemViewModel?.onCheckedChanged(true) @@ -295,7 +295,7 @@ class NotificationPreferencesViewModelTest { val data = viewModel.data.value - val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) + val itemViewModel = data?.items?.get(0)?.itemViewModels?.get(0) as? PushNotificationCategoryItemViewModel assertEquals(false, itemViewModel?.isChecked) itemViewModel?.onCheckedChanged(true) @@ -335,8 +335,8 @@ class NotificationPreferencesViewModelTest { assertEquals(1, viewModel.data.value?.items?.size) } - private fun createViewModel(): NotificationPreferencesViewModel { - return NotificationPreferencesViewModel(communicationChannelsManager, notificationPreferencesManager, apiPrefs, notificationPreferenceUtils, resources) + private fun createViewModel(): PushNotificationPreferencesViewModel { + return PushNotificationPreferencesViewModel(communicationChannelsManager, notificationPreferencesManager, apiPrefs, notificationPreferenceUtils, resources) } private fun setupStrings() { From 4b0c620cdca38da1d54e75c580c0325c0377172a Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Mon, 8 Aug 2022 15:46:27 +0200 Subject: [PATCH 055/115] [MBL-16186][All] Remove Id and domain from firebase reporting refs: MBL-16186 affects: Student, Teacher, Parent release note: none --- apps/flutter_parent/lib/utils/crash_utils.dart | 4 ++-- .../student/activity/CallbackActivity.kt | 13 +++---------- .../teacher/activities/SplashActivity.kt | 10 ++-------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/apps/flutter_parent/lib/utils/crash_utils.dart b/apps/flutter_parent/lib/utils/crash_utils.dart index 1d75550be9..d3dfb4e50f 100644 --- a/apps/flutter_parent/lib/utils/crash_utils.dart +++ b/apps/flutter_parent/lib/utils/crash_utils.dart @@ -25,8 +25,8 @@ class CrashUtils { FirebaseCrashlytics firebase = locator(); FlutterError.onError = (error) async { - await firebase - .setUserIdentifier('domain: ${ApiPrefs.getDomain() ?? 'null'} user_id: ${ApiPrefs.getUser()?.id ?? 'null'}'); + // We don't know how the crashlytics stores the userId so we just set it to empty to make sure we don't log it. + await firebase.setUserIdentifier(''); firebase.recordFlutterError(error); }; diff --git a/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt index 4386a7ba62..ff7e145dd8 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/CallbackActivity.kt @@ -60,8 +60,6 @@ abstract class CallbackActivity : ParentActivity(), InboxFragment.OnUnreadCountI private fun loadInitialData() { loadInitialDataJob = tryWeave { - val crashlytics = FirebaseCrashlytics.getInstance(); - // Determine if user can masquerade if (ApiPrefs.canBecomeUser == null) { if (ApiPrefs.domain.startsWith("siteadmin", true)) { @@ -117,14 +115,9 @@ abstract class CallbackActivity : ParentActivity(), InboxFragment.OnUnreadCountI } if (!ApiPrefs.isMasquerading) { - // Set logged user details - if (Logger.canLogUserDetails()) { - Logger.d("User detail logging allowed. Setting values.") - crashlytics.setUserId("UserID: ${ApiPrefs.user?.id.toString()} User Domain: ${ApiPrefs.domain}") - } else { - Logger.d("User detail logging disallowed. Clearing values.") - crashlytics.setUserId("") - } + // We don't know how the crashlytics stores the userId so we just set it to empty to make sure we don't log it. + val crashlytics = FirebaseCrashlytics.getInstance(); + crashlytics.setUserId("") } // get unread count of conversations diff --git a/apps/teacher/src/main/java/com/instructure/teacher/activities/SplashActivity.kt b/apps/teacher/src/main/java/com/instructure/teacher/activities/SplashActivity.kt index 0fd9d44629..16fce1bdca 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/activities/SplashActivity.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/activities/SplashActivity.kt @@ -185,15 +185,9 @@ class SplashActivity : AppCompatActivity() { Logger.e(e.message) } - // Set logged user details + // We don't know how the crashlytics stores the userId so we just set it to empty to make sure we don't log it. val crashlytics = FirebaseCrashlytics.getInstance(); - if (Logger.canLogUserDetails()) { - Logger.d("User detail logging allowed. Setting values.") - crashlytics.setUserId("UserID: ${ApiPrefs.user?.id.toString()} User Domain: ${ApiPrefs.domain}") - } else { - Logger.d("User detail logging disallowed. Clearing values.") - crashlytics.setUserId("") - } + crashlytics.setUserId("") startActivity(InitActivity.createIntent(this@SplashActivity, intent?.extras)) canvasLoadingView.announceForAccessibility(getString(R.string.loading)) From c8c48476bb3776fa1ff6ca9a33297c6b209e726b Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Mon, 8 Aug 2022 16:14:44 +0200 Subject: [PATCH 056/115] [MBL-16138][Student][Teacher] Share Extension basic UI + File Upload (#1661) --- .../interaction/UserFilesInteractionTest.kt | 2 + apps/student/src/main/AndroidManifest.xml | 2 +- .../student/activity/CandroidPSPDFActivity.kt | 6 +- .../student/activity/NavigationActivity.kt | 11 +- .../activity/ShareFileUploadActivity.kt | 253 ------ .../dialog/ShareFileDestinationDialog.kt | 328 -------- .../StudentShareExtensionActivity.kt | 36 + .../fragment/DiscussionsReplyFragment.kt | 10 +- .../student/fragment/FileListFragment.kt | 6 +- .../fragment/InboxComposeMessageFragment.kt | 7 +- .../ui/AssignmentDetailsView.kt | 2 +- .../student/router/RouteMatcher.kt | 1 + .../com/instructure/student/util/FileUtils.kt | 2 +- .../student/util/UploadCheckboxManager.kt | 143 ---- .../res/layout/upload_file_destination.xml | 192 ----- apps/student/src/main/res/values/styles.xml | 2 +- .../activities/BaseAppCompatActivity.kt | 10 +- .../teacher/activities/SpeedGraderActivity.kt | 10 +- .../teacher/fragments/AddMessageFragment.kt | 6 +- .../fragments/CreateDiscussionFragment.kt | 21 +- .../CreateOrEditAnnouncementFragment.kt | 11 +- .../fragments/DiscussionsReplyFragment.kt | 12 +- .../teacher/fragments/FileListFragment.kt | 11 +- .../canvasapi2/managers/AssignmentManager.kt | 2 + libs/pandares/src/main/assets/confetti.json | 1 + .../src/main/res/anim/ease_in_bottom.xml | 0 .../src/main/res/anim/ease_in_shrink.xml | 0 .../src/main/res/anim/expand_from_middle.xml | 0 .../pandares}/src/main/res/anim/fab_hide.xml | 0 .../src/main/res/anim/fab_reveal.xml | 0 .../src/main/res/anim/fab_rotate_backward.xml | 0 .../src/main/res/anim/fab_rotate_forward.xml | 0 .../src/main/res/anim/fade_in_quick.xml | 0 .../pandares}/src/main/res/anim/fade_out.xml | 0 .../src/main/res/anim/fade_out_quick.xml | 0 .../src/main/res/anim/hs_slide_out_left.xml | 0 .../pandares}/src/main/res/anim/none.xml | 0 .../pandares}/src/main/res/anim/rotate.xml | 0 .../src/main/res/anim/rotate_back.xml | 0 .../main/res/anim/scale_slide_in_bottom.xml | 0 .../res/anim/scale_slide_in_bottom_slow.xml | 0 .../src/main/res/anim/shrink_to_middle.xml | 0 .../main/res/anim/slide_in_from_bottom.xml | 0 .../src/main/res/anim/slide_in_right.xml | 0 .../src/main/res/anim/slide_out_to_bottom.xml | 0 .../src/main/res/anim/slow_push_left_in.xml | 0 .../src/main/res/anim/slow_push_left_out.xml | 0 .../src/main/res/anim/slow_push_right_in.xml | 0 .../src/main/res/anim/slow_push_right_out.xml | 0 .../src/main/res/anim/up_from_bottom.xml | 0 .../src/main/res/drawable/upload_file_bg.xml | 0 libs/pandares/src/main/res/values/strings.xml | 1 + libs/pandautils/build.gradle | 2 + .../binding/BindableSpinnerAdapter.kt | 63 ++ .../pandautils/binding/BindingAdapters.kt | 29 +- .../pandautils/di/ApplicationModule.kt | 7 + .../pandautils/di/FileUploadModule.kt | 43 + .../pandautils/dialogs/UploadFilesDialog.kt | 779 ------------------ .../file/upload/FileUploadDialogFragment.kt | 336 ++++++++ .../file/upload/FileUploadDialogViewData.kt | 40 + .../file/upload/FileUploadDialogViewModel.kt | 275 +++++++ .../features/file/upload/FileUploadType.kt | 21 + .../file/upload/FileUploadUtilsHelper.kt | 37 + .../itemviewmodels/FileItemViewModel.kt | 29 + .../shareextension/ShareExtensionActivity.kt | 199 +++++ .../shareextension/ShareExtensionViewModel.kt | 119 +++ .../ShareExtensionProgressDialogFragment.kt | 47 ++ .../ShareExtensionProgressDialogViewModel.kt | 7 + .../ShareExtensionSuccessDialogFragment.kt | 97 +++ .../ShareExtensionSuccessDialogViewModel.kt | 57 ++ .../success/ShareExtensionSuccessViewData.kt | 28 + .../target/ShareExtensionTargetFragment.kt | 186 +++++ .../target/ShareExtensionTargetViewData.kt | 48 ++ .../target/ShareExtensionTargetViewModel.kt | 160 ++++ .../ShareExtensionAssignmentItemViewModel.kt | 29 + .../ShareExtensionCourseItemViewModel.kt | 26 + .../loaders/OpenMediaAsyncTaskLoader.kt | 5 +- .../pandautils/utils}/AnimationHelpers.kt | 2 +- .../pandautils/utils/FragmentExtensions.kt | 2 + .../pandautils/utils/RequestCodes.kt | 1 + .../pandautils/views/CanvasWebView.kt | 8 +- .../main/res/layout/activity_share_file.xml | 0 .../main/res/layout/adapter_file_uploads.xml | 151 ++-- .../main/res/layout/dialog_files_upload.xml | 211 ----- .../layout/fragment_file_upload_dialog.xml | 227 +++++ ...agment_share_extension_progress_dialog.xml | 29 + ...ragment_share_extension_success_dialog.xml | 130 +++ .../fragment_share_extension_target.xml | 170 ++++ .../res/layout/item_assignment_spinner.xml | 45 + .../layout/item_canvas_context_spinner.xml | 54 ++ .../file/upload/FileUploadViewModelTest.kt | 212 +++++ .../ShareExtensionTargetViewModelTest.kt | 246 ++++++ 92 files changed, 3190 insertions(+), 2055 deletions(-) delete mode 100644 apps/student/src/main/java/com/instructure/student/activity/ShareFileUploadActivity.kt delete mode 100644 apps/student/src/main/java/com/instructure/student/dialog/ShareFileDestinationDialog.kt create mode 100644 apps/student/src/main/java/com/instructure/student/features/shareextension/StudentShareExtensionActivity.kt delete mode 100644 apps/student/src/main/java/com/instructure/student/util/UploadCheckboxManager.kt delete mode 100644 apps/student/src/main/res/layout/upload_file_destination.xml create mode 100644 libs/pandares/src/main/assets/confetti.json rename {apps/student => libs/pandares}/src/main/res/anim/ease_in_bottom.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/ease_in_shrink.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/expand_from_middle.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/fab_hide.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/fab_reveal.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/fab_rotate_backward.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/fab_rotate_forward.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/fade_in_quick.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/fade_out.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/fade_out_quick.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/hs_slide_out_left.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/none.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/rotate.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/rotate_back.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/scale_slide_in_bottom.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/scale_slide_in_bottom_slow.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/shrink_to_middle.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/slide_in_from_bottom.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/slide_in_right.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/slide_out_to_bottom.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/slow_push_left_in.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/slow_push_left_out.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/slow_push_right_in.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/slow_push_right_out.xml (100%) rename {apps/student => libs/pandares}/src/main/res/anim/up_from_bottom.xml (100%) rename {apps/student => libs/pandares}/src/main/res/drawable/upload_file_bg.xml (100%) create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/binding/BindableSpinnerAdapter.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/di/FileUploadModule.kt delete mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/dialogs/UploadFilesDialog.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogFragment.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewData.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewModel.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadType.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadUtilsHelper.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/itemviewmodels/FileItemViewModel.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionActivity.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionViewModel.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressDialogFragment.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/progress/ShareExtensionProgressDialogViewModel.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/success/ShareExtensionSuccessDialogFragment.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/success/ShareExtensionSuccessDialogViewModel.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/success/ShareExtensionSuccessViewData.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/target/ShareExtensionTargetFragment.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/target/ShareExtensionTargetViewData.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/target/ShareExtensionTargetViewModel.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/target/itemviewmodels/ShareExtensionAssignmentItemViewModel.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/target/itemviewmodels/ShareExtensionCourseItemViewModel.kt rename {apps/student/src/main/java/com/instructure/student/util => libs/pandautils/src/main/java/com/instructure/pandautils/utils}/AnimationHelpers.kt (98%) rename {apps/student => libs/pandautils}/src/main/res/layout/activity_share_file.xml (100%) delete mode 100644 libs/pandautils/src/main/res/layout/dialog_files_upload.xml create mode 100644 libs/pandautils/src/main/res/layout/fragment_file_upload_dialog.xml create mode 100644 libs/pandautils/src/main/res/layout/fragment_share_extension_progress_dialog.xml create mode 100644 libs/pandautils/src/main/res/layout/fragment_share_extension_success_dialog.xml create mode 100644 libs/pandautils/src/main/res/layout/fragment_share_extension_target.xml create mode 100644 libs/pandautils/src/main/res/layout/item_assignment_spinner.xml create mode 100644 libs/pandautils/src/main/res/layout/item_canvas_context_spinner.xml create mode 100644 libs/pandautils/src/test/java/com/instructure/pandautils/features/file/upload/FileUploadViewModelTest.kt create mode 100644 libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/target/ShareExtensionTargetViewModelTest.kt diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt index 17cbc628d6..73630d088d 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/UserFilesInteractionTest.kt @@ -85,6 +85,7 @@ class UserFilesInteractionTest : StudentTest() { // Should be able to upload a file from the user's device // Mocks the result from the expected intent, then uploads it. + @Stub @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION, false) fun testUpload_deviceFile() { @@ -156,6 +157,7 @@ class UserFilesInteractionTest : StudentTest() { // Should be able to upload a file from the user's photo gallery // Mocks the result from the expected intent, then uploads it. + @Stub @Test @TestMetaData(Priority.IMPORTANT, FeatureCategory.FILES, TestCategory.INTERACTION, false) fun testUpload_gallery() { diff --git a/apps/student/src/main/AndroidManifest.xml b/apps/student/src/main/AndroidManifest.xml index 0a83d35912..b98710653d 100644 --- a/apps/student/src/main/AndroidManifest.xml +++ b/apps/student/src/main/AndroidManifest.xml @@ -210,7 +210,7 @@ . - * - */ - -package com.instructure.student.activity - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ArgbEvaluator -import android.animation.ValueAnimator -import android.content.DialogInterface -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.os.Parcelable -import android.text.TextUtils -import android.view.ViewTreeObserver -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import androidx.fragment.app.DialogFragment -import com.instructure.canvasapi2.managers.CourseManager -import com.instructure.canvasapi2.models.Assignment -import com.instructure.canvasapi2.models.CanvasContext -import com.instructure.canvasapi2.models.Course -import com.instructure.canvasapi2.models.StorageQuotaExceededError -import com.instructure.canvasapi2.utils.ApiPrefs -import com.instructure.canvasapi2.utils.isNotDeleted -import com.instructure.canvasapi2.utils.weave.awaitApi -import com.instructure.canvasapi2.utils.weave.catch -import com.instructure.canvasapi2.utils.weave.tryWeave -import com.instructure.pandautils.dialogs.UploadFilesDialog -import com.instructure.pandautils.utils.* -import com.instructure.student.R -import com.instructure.student.dialog.ShareFileDestinationDialog -import com.instructure.student.util.Analytics -import com.instructure.student.util.AnimationHelpers -import kotlinx.android.parcel.Parcelize -import kotlinx.android.synthetic.main.activity_share_file.* -import kotlinx.coroutines.Job -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode -import java.util.* - -@Parcelize -data class ShareFileSubmissionTarget( - val course: Course, - val assignment: Assignment -) : Parcelable - -class ShareFileUploadActivity : AppCompatActivity(), ShareFileDestinationDialog.DialogCloseListener { - - private val PERMISSION_REQUEST_WRITE_STORAGE = 0 - - private var loadCoursesJob: Job? = null - private var uploadFileSourceFragment: DialogFragment? = null - private var courses: ArrayList? = null - - private val submissionTarget: ShareFileSubmissionTarget? by lazy { - intent?.extras?.getParcelable(Const.SUBMISSION_TARGET) - } - - private var sharedURI: Uri? = null - - public override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_share_file) - ViewStyler.setStatusBarDark(this, ContextCompat.getColor(this, R.color.studentDocumentSharingColor)) - if (checkLoggedIn()) { - revealBackground() - Analytics.trackAppFlow(this) - sharedURI = parseIntentType() - if (submissionTarget != null) { - // If targeted for submission, skip the picker and go immediately to the submission workflow - val bundle = UploadFilesDialog.createAssignmentBundle( - sharedURI, - submissionTarget!!.course, - submissionTarget!!.assignment - ) - onNext(bundle) - } else { - getCourses() - } - askForStoragePermissionIfNecessary() - } - } - - private fun askForStoragePermissionIfNecessary() { - if ((sharedURI?.scheme?.equals("file") == true || sharedURI?.scheme?.equals("content") == true) && !PermissionUtils.hasPermissions(this, PermissionUtils.WRITE_EXTERNAL_STORAGE)) { - ActivityCompat.requestPermissions(this, PermissionUtils.makeArray(PermissionUtils.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_WRITE_STORAGE) - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == UploadFilesDialog.CAMERA_PIC_REQUEST || - requestCode == UploadFilesDialog.PICK_FILE_FROM_DEVICE || - requestCode == UploadFilesDialog.PICK_IMAGE_GALLERY) { - //File Dialog Fragment will not be notified of onActivityResult(), alert manually - OnActivityResults(ActivityResult(requestCode, resultCode, data), null).postSticky() - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - when(requestCode) { - PERMISSION_REQUEST_WRITE_STORAGE -> { - if (!PermissionUtils.allPermissionsGrantedResultSummary(grantResults)) { - Toast.makeText(this, R.string.permissionDenied, Toast.LENGTH_LONG).show() - finish() - } - } - } - } - - private fun getCourses() { - loadCoursesJob = tryWeave { - val courses = awaitApi> { CourseManager.getCourses(true, it) } - if (courses.isNotEmpty()) { - this@ShareFileUploadActivity.courses = ArrayList(courses) - if (uploadFileSourceFragment == null) showDestinationDialog() - } else { - Toast.makeText(applicationContext, R.string.uploadingFromSourceFailed, Toast.LENGTH_LONG).show() - exitActivity() - } - } catch { - Toast.makeText(this@ShareFileUploadActivity, R.string.uploadingFromSourceFailed, Toast.LENGTH_LONG).show() - exitActivity() - } - } - - private fun revealBackground() { - rootView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - AnimationHelpers.removeGlobalLayoutListeners(rootView, this) - AnimationHelpers.createRevealAnimator(rootView).start() - } - }) - } - - private fun checkLoggedIn(): Boolean { - return if (TextUtils.isEmpty(ApiPrefs.getValidToken())) { - exitActivity() - false - } else { - true - } - } - - private fun exitActivity() { - val intent = LoginActivity.createIntent(this) - startActivity(intent) - finish() - } - - override fun onBackPressed() { - uploadFileSourceFragment?.dismissAllowingStateLoss() - super.onBackPressed() - } - - override fun onDestroy() { - uploadFileSourceFragment?.dismissAllowingStateLoss() - loadCoursesJob?.cancel() - super.onDestroy() - } - - override fun onStart() { - super.onStart() - EventBus.getDefault().register(this) - } - - override fun onStop() { - super.onStop() - EventBus.getDefault().unregister(this) - } - - private fun showDestinationDialog() { - if (sharedURI == null) { - Toast.makeText(applicationContext, R.string.uploadingFromSourceFailed, Toast.LENGTH_LONG).show() - } else { - uploadFileSourceFragment = ShareFileDestinationDialog.newInstance(ShareFileDestinationDialog.createBundle(sharedURI!!, courses!!)) - uploadFileSourceFragment!!.show(supportFragmentManager, ShareFileDestinationDialog.TAG) - } - } - - private fun parseIntentType(): Uri? { - // Get intent, action and MIME type - val intent = intent - val action = intent.action - val type = intent.type - - return if (Intent.ACTION_SEND == action && type != null) { - intent.getParcelableExtra(Intent.EXTRA_STREAM) - } else null - - } - - override fun onCancel(dialog: DialogInterface?) { - finish() - } - - - @Suppress("unused", "UNUSED_PARAMETER") - @Subscribe(threadMode = ThreadMode.MAIN) - fun onQuotaExceeded(errorCode: StorageQuotaExceededError) { - toast(R.string.fileQuotaExceeded) - } - - private fun getColor(bundle: Bundle?): Int { - return if(bundle != null && bundle.containsKey(Const.CANVAS_CONTEXT)) { - val color = ColorKeeper.getOrGenerateColor(bundle.getParcelable(Const.CANVAS_CONTEXT) as CanvasContext) - ViewStyler.setStatusBarDark(this, color) - color - } else { - val color = ContextCompat.getColor(this, R.color.login_studentAppTheme) - ViewStyler.setStatusBarDark(this, color) - color - } - } - - override fun onNext(bundle: Bundle) { - ValueAnimator.ofObject(ArgbEvaluator(), ContextCompat.getColor(this, R.color.login_studentAppTheme), getColor(bundle)).let { - it.addUpdateListener { animation -> rootView!!.setBackgroundColor(animation.animatedValue as Int) } - it.duration = 500 - it.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator) { - UploadFilesDialog.show(supportFragmentManager, bundle) { event -> - if(event == UploadFilesDialog.EVENT_ON_UPLOAD_BEGIN || event == UploadFilesDialog.EVENT_DIALOG_CANCELED) { - finish() - } - } - } - }) - it.start() - } - } -} diff --git a/apps/student/src/main/java/com/instructure/student/dialog/ShareFileDestinationDialog.kt b/apps/student/src/main/java/com/instructure/student/dialog/ShareFileDestinationDialog.kt deleted file mode 100644 index 4d09f4da63..0000000000 --- a/apps/student/src/main/java/com/instructure/student/dialog/ShareFileDestinationDialog.kt +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (C) 2016 - 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.dialog - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.annotation.SuppressLint -import android.app.Dialog -import android.content.DialogInterface -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.view.* -import android.view.animation.AnimationUtils -import android.widget.AdapterView -import android.widget.Toast -import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.DialogFragment -import com.instructure.canvasapi2.managers.AssignmentManager.getAllAssignments -import com.instructure.canvasapi2.models.Assignment -import com.instructure.canvasapi2.models.Course -import com.instructure.canvasapi2.models.User -import com.instructure.canvasapi2.utils.ApiPrefs -import com.instructure.canvasapi2.utils.Pronouns.span -import com.instructure.canvasapi2.utils.weave.awaitApi -import com.instructure.canvasapi2.utils.weave.catch -import com.instructure.canvasapi2.utils.weave.tryWeave -import com.instructure.pandautils.dialogs.UploadFilesDialog -import com.instructure.pandautils.dialogs.UploadFilesDialog.Companion.createAssignmentBundle -import com.instructure.pandautils.dialogs.UploadFilesDialog.Companion.createFilesBundle -import com.instructure.pandautils.utils.Const -import com.instructure.pandautils.utils.ParcelableArg -import com.instructure.pandautils.utils.ParcelableArrayListArg -import com.instructure.pandautils.utils.ThemePrefs.buttonColor -import com.instructure.pandautils.utils.setVisible -import com.instructure.student.R -import com.instructure.student.adapter.FileUploadAssignmentsAdapter -import com.instructure.student.adapter.FileUploadAssignmentsAdapter.Companion.getOnlineUploadAssignmentsList -import com.instructure.student.adapter.FileUploadCoursesAdapter -import com.instructure.student.adapter.FileUploadCoursesAdapter.Companion.getFilteredCourseList -import com.instructure.student.util.AnimationHelpers.createRevealAnimator -import com.instructure.student.util.AnimationHelpers.removeGlobalLayoutListeners -import com.instructure.student.util.UploadCheckboxManager -import com.instructure.student.util.UploadCheckboxManager.OnOptionCheckedListener -import kotlinx.android.synthetic.main.upload_file_destination.* -import kotlinx.coroutines.Job -import java.util.* - -@SuppressLint("InflateParams") -class ShareFileDestinationDialog : DialogFragment(), OnOptionCheckedListener { - // Dismiss interface - interface DialogCloseListener { - fun onCancel(dialog: DialogInterface?) - fun onNext(bundle: Bundle) - } - - private var uri: Uri by ParcelableArg(key = Const.URI) - private var courses: ArrayList by ParcelableArrayListArg(key = Const.COURSES) - private var user: User = ApiPrefs.user!! - - private lateinit var checkboxManager: UploadCheckboxManager - private lateinit var rootView: View - - private var assignmentJob: Job? = null - - private var selectedAssignment: Assignment? = null - private var studentEnrollmentsAdapter: FileUploadCoursesAdapter? = null - - override fun onStart() { - super.onStart() - // Don't dim the background when the dialog is created. - dialog?.window?.apply { - val params = attributes - params.dimAmount = 0f - params.flags = params.flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND - attributes = params - } - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - dialog?.window?.let { - it.attributes.windowAnimations = R.style.FileDestinationDialogAnimation - it.setWindowAnimations(R.style.FileDestinationDialogAnimation) - } - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - rootView = LayoutInflater.from(activity).inflate(R.layout.upload_file_destination, null) - val alertDialog = AlertDialog.Builder(requireContext()) - .setView(rootView) - .setPositiveButton(R.string.next) { _, _ -> validateAndShowNext() } - .setNegativeButton(R.string.cancel) { _, _ -> dismissAllowingStateLoss() } - .setCancelable(true) - .create() - - alertDialog.setOnShowListener { - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(buttonColor) - alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(buttonColor) - } - - return alertDialog - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return rootView - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - userName.text = span(user.name, user.pronouns) - - // Init checkboxes - checkboxManager = UploadCheckboxManager(this, selectionIndicator) - checkboxManager.add(myFilesCheckBox) - checkboxManager.add(assignmentCheckBox) - - setRevealContentsListener() - assignmentContainer.setVisible() - } - - override fun onCancel(dialog: DialogInterface) { - (activity as? DialogCloseListener)?.onCancel(dialog) - } - - override fun onDestroyView() { - if (retainInstance) dialog?.dismiss() - super.onDestroyView() - } - - private fun validateAndShowNext() { - // Validate selections - val errorString = validateForm() - if (errorString.isNotEmpty()) { - Toast.makeText(activity, errorString, Toast.LENGTH_SHORT).show() - } else { - (activity as? DialogCloseListener)?.onNext(uploadBundle) - dismiss() - } - } - - /** - * Checks if user has filled out form completely. - * @return Returns an error string if the form is not valid. - */ - private fun validateForm(): String { - // Make sure the user has selected a course and an assignment - val uploadType = checkboxManager.selectedType - - // Make sure an assignment & course was selected if FileUploadType.Assignment - if (uploadType == UploadFilesDialog.FileUploadType.ASSIGNMENT) { - if (studentCourseSpinner.selectedItem == null) { - return getString(R.string.noCourseSelected) - } else if (assignmentSpinner.selectedItem == null || (assignmentSpinner.selectedItem as? Assignment)?.id == Long.MIN_VALUE) { - return getString(R.string.noAssignmentSelected) - } - } - return "" - } - - private val uploadBundle: Bundle - get() = when (checkboxManager.selectedCheckBox!!.id) { - R.id.myFilesCheckBox -> createFilesBundle(uri, null) - R.id.assignmentCheckBox -> createAssignmentBundle( - uri, - (studentCourseSpinner.selectedItem as Course), - (assignmentSpinner.selectedItem as Assignment) - ) - else -> createFilesBundle(uri, null) - } - - private fun setAssignmentsSpinnerToLoading() { - val loading = Assignment() - val courseAssignments = ArrayList() - loading.name = getString(R.string.loadingAssignments) - loading.id = Long.MIN_VALUE - courseAssignments.add(loading) - assignmentSpinner.adapter = FileUploadAssignmentsAdapter(requireContext(), courseAssignments) - } - - fun fetchAssignments(courseId: Long) { - assignmentJob?.cancel() - assignmentJob = tryWeave { - val assignments = awaitApi> { getAllAssignments(courseId, false, it) } - if (assignments.isNotEmpty() && courseSelectionChanged(assignments[0].courseId)) return@tryWeave - val courseAssignments = getOnlineUploadAssignmentsList(requireContext(), assignments) - - // Init assignment spinner - val adapter = FileUploadAssignmentsAdapter(requireContext(), courseAssignments) - assignmentSpinner.adapter = adapter - if (selectedAssignment != null) { - // Prevent listener from firing the when selection is placed - assignmentSpinner.onItemSelectedListener = null - val position = adapter.getPosition(selectedAssignment) - if (position >= 0) { - // Prevents the network callback from replacing what the user selected while cache was being displayed - assignmentSpinner.setSelection(position, false) - } - } - assignmentSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) { - if (position < 0) return - if (position < adapter.count) { - selectedAssignment = adapter.getItem(position) - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) {} - } - } catch { - // Do nothing - } - } - - private fun setupCourseSpinners() { - if (activity?.isFinishing != false) return - if (studentEnrollmentsAdapter == null) { - studentEnrollmentsAdapter = FileUploadCoursesAdapter( - requireContext(), - requireActivity().layoutInflater, - getFilteredCourseList(courses, FileUploadCoursesAdapter.Type.STUDENT) - ) - studentCourseSpinner.adapter = studentEnrollmentsAdapter - } else { - studentEnrollmentsAdapter?.setCourses(getFilteredCourseList(courses, FileUploadCoursesAdapter.Type.STUDENT)) - } - studentCourseSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { - // Make the allowed extensions disappear - val (courseId) = parent.adapter.getItem(position) as Course - // If the user is a teacher, let them know and don't let them select an assignment - if (courseId > 0) { - setAssignmentsSpinnerToLoading() - fetchAssignments(courseId) - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) {} - } - } - - private fun courseSelectionChanged(newCourseId: Long): Boolean { - return checkboxManager.selectedCheckBox!!.id == R.id.assignmentCheckBox && newCourseId != (studentCourseSpinner.selectedItem as Course).id - } - - private fun setRevealContentsListener() { - val avatarAnimation = AnimationUtils.loadAnimation(activity, R.anim.ease_in_shrink) - val titleAnimation = AnimationUtils.loadAnimation(activity, R.anim.ease_in_bottom) - avatar.viewTreeObserver.addOnGlobalLayoutListener( - object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - removeGlobalLayoutListeners(avatar, this) - avatar.startAnimation(avatarAnimation) - userName.startAnimation(titleAnimation) - dialogTitle.startAnimation(titleAnimation) - } - } - ) - dialogContents.viewTreeObserver.addOnGlobalLayoutListener( - object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - removeGlobalLayoutListeners(dialogContents, this) - val revealAnimator = createRevealAnimator(dialogContents) - Handler().postDelayed({ - if (!isAdded) return@postDelayed - dialogContents.visibility = View.VISIBLE - revealAnimator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - setupCourseSpinners() - } - } - ) - revealAnimator.start() - }, 600) - } - } - ) - } - - private fun enableStudentSpinners(isEnabled: Boolean) { - assignmentSpinner.isEnabled = isEnabled - studentCourseSpinner.isEnabled = isEnabled - } - - override fun onUserFilesSelected() { - enableStudentSpinners(false) - } - - override fun onAssignmentFilesSelected() { - enableStudentSpinners(true) - } - - override fun onDestroy() { - assignmentJob?.cancel() - super.onDestroy() - } - - companion object { - const val TAG = "uploadFileSourceFragment" - - fun newInstance(bundle: Bundle): ShareFileDestinationDialog { - val uploadFileSourceFragment = ShareFileDestinationDialog() - uploadFileSourceFragment.arguments = bundle - return uploadFileSourceFragment - } - - fun createBundle(uri: Uri, courses: ArrayList): Bundle { - val bundle = Bundle() - bundle.putParcelable(Const.URI, uri) - bundle.putParcelableArrayList(Const.COURSES, courses) - return bundle - } - } -} diff --git a/apps/student/src/main/java/com/instructure/student/features/shareextension/StudentShareExtensionActivity.kt b/apps/student/src/main/java/com/instructure/student/features/shareextension/StudentShareExtensionActivity.kt new file mode 100644 index 0000000000..6cd9ede4b9 --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/features/shareextension/StudentShareExtensionActivity.kt @@ -0,0 +1,36 @@ +/* + * 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.shareextension + +import android.os.Bundle +import com.instructure.pandautils.features.shareextension.ShareExtensionActivity +import com.instructure.student.activity.LoginActivity +import com.instructure.student.util.Analytics + +class StudentShareExtensionActivity : ShareExtensionActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Analytics.trackAppFlow(this) + } + + override fun exitActivity() { + val intent = LoginActivity.createIntent(this) + startActivity(intent) + finish() + } +} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt index f352041ff8..809228f1aa 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/DiscussionsReplyFragment.kt @@ -35,8 +35,8 @@ import com.instructure.interactions.router.Route import com.instructure.loginapi.login.dialog.NoInternetConnectionDialog import com.instructure.pandautils.analytics.SCREEN_VIEW_DISCUSSIONS_REPLY import com.instructure.pandautils.analytics.ScreenView -import com.instructure.pandautils.dialogs.UploadFilesDialog import com.instructure.pandautils.discussions.DiscussionCaching +import com.instructure.pandautils.features.file.upload.FileUploadDialogFragment import com.instructure.pandautils.utils.* import com.instructure.pandautils.views.AttachmentView import com.instructure.student.R @@ -76,12 +76,12 @@ class DiscussionsReplyFragment : ParentFragment() { val attachments = ArrayList() if (attachment != null) attachments.add(attachment!!) - val bundle = UploadFilesDialog.createDiscussionsBundle(attachments) - UploadFilesDialog.show(fragmentManager, bundle) { event, attachment -> - if (event == UploadFilesDialog.EVENT_ON_FILE_SELECTED) { + val bundle = FileUploadDialogFragment.createDiscussionsBundle(attachments) + FileUploadDialogFragment.newInstance(bundle, pickerCallback = { event, attachment -> + if (event == FileUploadDialogFragment.EVENT_ON_FILE_SELECTED) { handleAttachment(attachment) } - } + }).show(childFragmentManager, FileUploadDialogFragment.TAG) } else { NoInternetConnectionDialog.show(requireFragmentManager()) } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/FileListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/FileListFragment.kt index 198877c353..9b2b6165bb 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/FileListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/FileListFragment.kt @@ -44,7 +44,7 @@ import com.instructure.interactions.router.Route import com.instructure.interactions.router.RouterParams import com.instructure.pandautils.analytics.SCREEN_VIEW_FILE_LIST import com.instructure.pandautils.analytics.ScreenView -import com.instructure.pandautils.dialogs.UploadFilesDialog +import com.instructure.pandautils.features.file.upload.FileUploadDialogFragment import com.instructure.pandautils.utils.* import com.instructure.student.R import com.instructure.student.adapter.FileFolderCallback @@ -406,8 +406,8 @@ class FileListFragment : ParentFragment(), Bookmarkable { private fun uploadFile() { folder?.let { - val bundle = UploadFilesDialog.createContextBundle(null, canvasContext, it.id) - UploadFilesDialog.show(fragmentManager, bundle) { _ -> } + val bundle = FileUploadDialogFragment.createContextBundle(null, canvasContext, it.id) + FileUploadDialogFragment.newInstance(bundle).show(childFragmentManager, FileUploadDialogFragment.TAG) } } diff --git a/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt index 2af3db8363..392ae0f7a7 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/InboxComposeMessageFragment.kt @@ -35,7 +35,7 @@ import com.instructure.canvasapi2.utils.weave.* import com.instructure.interactions.router.Route import com.instructure.pandautils.analytics.SCREEN_VIEW_INBOX_COMPOSE import com.instructure.pandautils.analytics.ScreenView -import com.instructure.pandautils.dialogs.UploadFilesDialog +import com.instructure.pandautils.features.file.upload.FileUploadDialogFragment import com.instructure.pandautils.services.FileUploadService import com.instructure.pandautils.utils.* import com.instructure.student.R @@ -51,7 +51,6 @@ import kotlinx.android.synthetic.main.fragment_inbox_compose_message.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import java.util.ArrayList @ScreenView(SCREEN_VIEW_INBOX_COMPOSE) class InboxComposeMessageFragment : ParentFragment() { @@ -279,8 +278,8 @@ class InboxComposeMessageFragment : ParentFragment() { sendMessage() } R.id.menu_attachment -> { - val bundle = UploadFilesDialog.createMessageAttachmentsBundle(arrayListOf()) - UploadFilesDialog.show(fragmentManager, bundle, { _ -> }) + val bundle = FileUploadDialogFragment.createMessageAttachmentsBundle(arrayListOf()) + FileUploadDialogFragment.newInstance(bundle).show(childFragmentManager, FileUploadDialogFragment.TAG) } else -> return@setOnMenuItemClickListener false } diff --git a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsView.kt b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsView.kt index 737bf19719..a3545873c9 100644 --- a/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsView.kt +++ b/apps/student/src/main/java/com/instructure/student/mobius/assignmentDetails/ui/AssignmentDetailsView.kt @@ -43,7 +43,7 @@ import com.instructure.pandautils.views.RecordingMediaType import com.instructure.student.R import com.instructure.student.activity.BaseRouterActivity import com.instructure.student.activity.InternalWebViewActivity -import com.instructure.student.activity.ShareFileSubmissionTarget +import com.instructure.pandautils.features.shareextension.ShareFileSubmissionTarget import com.instructure.student.fragment.* import com.instructure.student.mobius.assignmentDetails.AssignmentDetailsEvent import com.instructure.student.mobius.assignmentDetails.submission.annnotation.AnnotationSubmissionUploadFragment diff --git a/apps/student/src/main/java/com/instructure/student/router/RouteMatcher.kt b/apps/student/src/main/java/com/instructure/student/router/RouteMatcher.kt index 96a48a6a7c..a0a6d15a50 100644 --- a/apps/student/src/main/java/com/instructure/student/router/RouteMatcher.kt +++ b/apps/student/src/main/java/com/instructure/student/router/RouteMatcher.kt @@ -38,6 +38,7 @@ import com.instructure.canvasapi2.utils.Logger import com.instructure.interactions.router.* import com.instructure.pandautils.activities.BaseViewMediaActivity import com.instructure.pandautils.features.discussion.details.DiscussionDetailsWebViewFragment +import com.instructure.pandautils.features.shareextension.ShareFileSubmissionTarget import com.instructure.pandautils.loaders.OpenMediaAsyncTaskLoader import com.instructure.pandautils.utils.Const import com.instructure.pandautils.utils.LoaderUtils 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 026a917d4c..428ccbd03d 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 @@ -24,7 +24,7 @@ import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.pandautils.loaders.OpenMediaAsyncTaskLoader import com.instructure.student.R import com.instructure.student.activity.CandroidPSPDFActivity -import com.instructure.student.activity.ShareFileSubmissionTarget +import com.instructure.pandautils.features.shareextension.ShareFileSubmissionTarget import com.pspdfkit.PSPDFKit import com.pspdfkit.annotations.AnnotationType import com.pspdfkit.configuration.activity.PdfActivityConfiguration diff --git a/apps/student/src/main/java/com/instructure/student/util/UploadCheckboxManager.kt b/apps/student/src/main/java/com/instructure/student/util/UploadCheckboxManager.kt deleted file mode 100644 index 60f37ac536..0000000000 --- a/apps/student/src/main/java/com/instructure/student/util/UploadCheckboxManager.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2016 - 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.util - -import android.view.View -import android.view.ViewTreeObserver -import android.view.animation.* -import android.widget.CheckedTextView -import com.instructure.pandautils.dialogs.UploadFilesDialog -import com.instructure.student.R -import java.util.* - -class UploadCheckboxManager(private val listener: OnOptionCheckedListener, private val selectionIndicator: View) { - interface OnOptionCheckedListener { - fun onUserFilesSelected() - fun onAssignmentFilesSelected() - } - - private var checkBoxes: MutableList = ArrayList() - - var selectedCheckBox: CheckedTextView? = null - private set - - private var isAnimating = false - - fun add(checkBox: CheckedTextView) { - if (checkBoxes.size == 0) { - selectedCheckBox = checkBox - setInitialIndicatorHeight() - } - checkBoxes.add(checkBox) - checkBox.setOnClickListener(destinationClickListener) - } - - val selectedType: UploadFilesDialog.FileUploadType - get() = when (selectedCheckBox?.id) { - R.id.myFilesCheckBox -> UploadFilesDialog.FileUploadType.USER - R.id.assignmentCheckBox -> UploadFilesDialog.FileUploadType.ASSIGNMENT - else -> UploadFilesDialog.FileUploadType.USER - } - - private fun setInitialIndicatorHeight() { - selectionIndicator.viewTreeObserver.addOnGlobalLayoutListener( - object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - selectionIndicator.viewTreeObserver.removeOnGlobalLayoutListener(this) - if (selectedCheckBox != null) { - selectionIndicator.layoutParams.height = (selectedCheckBox!!.parent as View).height - selectionIndicator.layoutParams = selectionIndicator.layoutParams - } - listener.onUserFilesSelected() - } - } - ) - } - - private fun moveIndicator(newCurrentCheckBox: CheckedTextView) { - val moveAnimation: Animation = getAnimation(newCurrentCheckBox) - selectionIndicator.startAnimation(moveAnimation) - moveAnimation.setAnimationListener(object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation) { - isAnimating = true - } - - override fun onAnimationEnd(animation: Animation) { - selectedCheckBox = newCurrentCheckBox - isAnimating = false - } - - override fun onAnimationRepeat(animation: Animation) {} - }) - } - - private fun getAnimation(toCheckBox: CheckedTextView): AnimationSet { - val toView = toCheckBox.parent as View - val fromView = selectedCheckBox!!.parent as View - - // get ratio between current height and new height - val toRatio = - toView.height.toFloat() / selectionIndicator.height.toFloat() - val fromRatio = - fromView.height.toFloat() / selectionIndicator.height.toFloat() - val scaleAnimation = ScaleAnimation( - 1f, // fromXType - 1f, // toX - fromRatio, // fromY - toRatio, // toY - .5f, // pivotX - 0.0f - ) // pivotY - val translateAnimation = TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0.0f, // fromXType, fromXValue - Animation.RELATIVE_TO_SELF, 0.0f, // toXType, toXValue - Animation.ABSOLUTE, fromView.top.toFloat(), // fromYType, fromYValue - Animation.ABSOLUTE, toView.top.toFloat() - ) // toYTyp\e, toYValue - translateAnimation.interpolator = AccelerateDecelerateInterpolator() - translateAnimation.fillAfter = true - val animSet = AnimationSet(true) - animSet.addAnimation(scaleAnimation) - animSet.addAnimation(translateAnimation) - animSet.fillAfter = true - animSet.duration = 200 - return animSet - } - - private val destinationClickListener = View.OnClickListener { v: View -> - if (isAnimating) return@OnClickListener - val checkedTextView = v as CheckedTextView - if (!checkedTextView.isChecked) { - checkedTextView.isChecked = true - notifyListener(checkedTextView) - moveIndicator(checkedTextView) - for (checkBox in checkBoxes) { - if (checkBox.id != checkedTextView.id) { - checkBox.isChecked = false - } - } - } - } - - private fun notifyListener(checkedTextView: CheckedTextView) { - when (checkedTextView.id) { - R.id.myFilesCheckBox -> listener.onUserFilesSelected() - R.id.assignmentCheckBox -> listener.onAssignmentFilesSelected() - } - } - -} diff --git a/apps/student/src/main/res/layout/upload_file_destination.xml b/apps/student/src/main/res/layout/upload_file_destination.xml deleted file mode 100644 index 2cc4bc03f3..0000000000 --- a/apps/student/src/main/res/layout/upload_file_destination.xml +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/student/src/main/res/values/styles.xml b/apps/student/src/main/res/values/styles.xml index 94fd5a9aa0..0575a7b29c 100644 --- a/apps/student/src/main/res/values/styles.xml +++ b/apps/student/src/main/res/values/styles.xml @@ -110,7 +110,7 @@ - - - From 60fdfa5c51dca0dfa139165e4dcba703ec6088cc Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Fri, 2 Sep 2022 14:13:15 +0200 Subject: [PATCH 074/115] [MBL-16206][Teacher] - Insert page creation flow on UI. (#1691) refs: MBL-16206 affects: Teacher release note: none --- .../teacher/ui/e2e/PagesE2ETest.kt | 20 +++++++++++++++++++ .../teacher/ui/pages/EditPageDetailsPage.kt | 16 ++++++++------- .../teacher/ui/pages/PageListPage.kt | 7 ++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PagesE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PagesE2ETest.kt index 45df93cd8c..f271eb90ac 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PagesE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/PagesE2ETest.kt @@ -146,6 +146,26 @@ class PagesE2ETest : TeacherTest() { Log.d(STEP_TAG,"Assert that ${testPage2.title} is displayed as a front page.") pageListPage.assertFrontPageDisplayed(testPage2.title) + Log.d(STEP_TAG,"Click on '+' icon on the UI to create a new page.") + pageListPage.clickOnCreateNewPage() + + val newPageTitle = "Test Page Mobile UI" + Log.d(STEP_TAG,"Set '$newPageTitle' as the page's title and set some description as well.") + editPageDetailsPage.editPageName(newPageTitle) + editPageDetailsPage.editDescription("Mobile UI Page description") + + Log.d(STEP_TAG,"Toggle Publish checkbox and save the page.") + editPageDetailsPage.togglePublished() + editPageDetailsPage.savePage() + + Log.d(STEP_TAG,"Assert that '$newPageTitle' page is displayed and published.") + pageListPage.assertPageIsPublished(newPageTitle) + Log.d(STEP_TAG,"Click on the Search icon and type some search query string which matches only with the previously created page's title.") + pageListPage.openSearch() + pageListPage.enterSearchQuery("Test") + Log.d(STEP_TAG,"Assert that the '$newPageTitle' titled page is displayed and it is the only one.") + pageListPage.assertPageIsPublished(newPageTitle) + pageListPage.assertPageCount(1) } } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditPageDetailsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditPageDetailsPage.kt index 83bad7811a..628553e06c 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditPageDetailsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditPageDetailsPage.kt @@ -1,6 +1,7 @@ package com.instructure.teacher.ui.pages -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches import androidx.test.espresso.web.sugar.Web.onWebView import androidx.test.espresso.web.webdriver.DriverAtoms.findElement @@ -8,19 +9,16 @@ import androidx.test.espresso.web.webdriver.DriverAtoms.getText import androidx.test.espresso.web.webdriver.Locator import com.instructure.canvas.espresso.checkToastText import com.instructure.canvas.espresso.withElementRepeat -import com.instructure.espresso.ActivityHelper -import com.instructure.espresso.click +import com.instructure.espresso.* import com.instructure.espresso.page.BasePage import com.instructure.espresso.page.onView -import com.instructure.espresso.page.plus -import com.instructure.espresso.page.withParent -import com.instructure.espresso.replaceText -import com.instructure.espresso.scrollTo import com.instructure.teacher.R +import com.instructure.teacher.ui.utils.TypeInRCETextEditor import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.containsString class EditPageDetailsPage : BasePage() { + private val contentRceView by WaitForViewWithId(R.id.rce_webView) fun runTextChecks(vararg checks : WebViewTextCheck) { for(check in checks) { @@ -59,6 +57,10 @@ class EditPageDetailsPage : BasePage() { onView(withId(R.id.pageNameEditText)).replaceText(editedPageName) } + fun editDescription(newDescription: String) { + contentRceView.perform(TypeInRCETextEditor(newDescription)) + } + fun unableToSaveUnpublishedFrontPage() { savePage() checkToastText(R.string.frontPageUnpublishedError, ActivityHelper.currentActivity()) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/PageListPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/PageListPage.kt index fe66751c2b..b0f5afcb25 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/PageListPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/PageListPage.kt @@ -18,7 +18,8 @@ package com.instructure.teacher.ui.pages import android.view.View import androidx.test.espresso.Espresso import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.hasSibling +import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import com.instructure.canvas.espresso.scrollRecyclerView import com.instructure.canvasapi2.models.Page import com.instructure.espresso.* @@ -38,6 +39,10 @@ class PageListPage : BasePage() { private val toolbar by OnViewWithId(R.id.pageListToolbar) + fun clickOnCreateNewPage() { + onView(withId(R.id.createNewPage)).click() + } + fun assertHasPage(page: Page) { waitForViewWithText(page.title!!).assertDisplayed() } From 42f9e05073f496b0f1547cf064c82a3234a5fa93 Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Fri, 2 Sep 2022 14:16:58 +0200 Subject: [PATCH 075/115] [MBL-16141][Student] Share extension | Dashboard notification refs: MBL-16141 affects: Student release note: none * wip: share extension upload dashboard notifications * removing observer in onCleared * Added upload notification unit tests * Fixed subtitle * On running workers change just updating uploads not calling the loadData * minor tweak * Fixed findings --- .../src/main/res/drawable/ic_upload.xml | 10 ++ libs/pandares/src/main/res/values/strings.xml | 3 + libs/pandautils/build.gradle | 2 + .../pandautils/di/FileUploadModule.kt | 7 +- .../DashboardNotificationsFragment.kt | 17 ++- .../DashboardNotificationsViewData.kt | 25 +++- .../DashboardNotificationsViewModel.kt | 56 +++++-- .../itemviewmodels/UploadItemViewModel.kt | 34 +++++ .../preferences/FileUploadPreferences.kt | 13 +- .../file/upload/worker/FileUploadWorker.kt | 32 +++- .../fragment_dashboard_notifications.xml | 4 +- .../main/res/layout/item_dashboard_upload.xml | 117 +++++++++++++++ .../DashboardNotificationsViewModelTest.kt | 141 +++++++++++++++--- .../ShareExtensionTargetViewModelTest.kt | 6 - 14 files changed, 399 insertions(+), 68 deletions(-) create mode 100644 libs/pandares/src/main/res/drawable/ic_upload.xml create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/itemviewmodels/UploadItemViewModel.kt create mode 100644 libs/pandautils/src/main/res/layout/item_dashboard_upload.xml diff --git a/libs/pandares/src/main/res/drawable/ic_upload.xml b/libs/pandares/src/main/res/drawable/ic_upload.xml new file mode 100644 index 0000000000..eea4fa9ffd --- /dev/null +++ b/libs/pandares/src/main/res/drawable/ic_upload.xml @@ -0,0 +1,10 @@ + + + diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index 651cb50ee9..cfa06b55f6 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -1283,4 +1283,7 @@ Weekly Never Select frequency + + Uploading Submission + Uploading files diff --git a/libs/pandautils/build.gradle b/libs/pandautils/build.gradle index a3c3d773d3..823116b65c 100644 --- a/libs/pandautils/build.gradle +++ b/libs/pandautils/build.gradle @@ -109,6 +109,8 @@ dependencies { testImplementation Libs.ANDROIDX_CORE_TESTING testImplementation Libs.THREETEN_BP + testImplementation project(path: ':pandautils') + /* Media handling */ api(Libs.GLIDE) { exclude group: "com.android.support" diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/di/FileUploadModule.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/di/FileUploadModule.kt index 871f8a2276..1d337f32d9 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/di/FileUploadModule.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/di/FileUploadModule.kt @@ -19,6 +19,7 @@ package com.instructure.pandautils.di import android.content.ContentResolver import android.content.Context import com.instructure.pandautils.features.file.upload.FileUploadUtilsHelper +import com.instructure.pandautils.features.file.upload.preferences.FileUploadPreferences import com.instructure.pandautils.features.file.upload.worker.FileUploadBundleCreator import com.instructure.pandautils.utils.FileUploadUtils import dagger.Module @@ -26,7 +27,6 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -46,4 +46,9 @@ class FileUploadModule { fun provideFileUploadBundleCreator(): FileUploadBundleCreator { return FileUploadBundleCreator() } + + @Provides + fun provideFileUploadPreferences(): FileUploadPreferences { + return FileUploadPreferences + } } \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsFragment.kt index 639ed2c456..9e8377366b 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsFragment.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsFragment.kt @@ -59,11 +59,11 @@ class DashboardNotificationsFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.events.observe(viewLifecycleOwner, { event -> + viewModel.events.observe(viewLifecycleOwner) { event -> event.getContentIfNotHandled()?.let { handleAction(it) } - }) + } } fun refresh() { @@ -74,14 +74,14 @@ class DashboardNotificationsFragment : Fragment() { when (action) { is DashboardNotificationsActions.LaunchConference -> { val colorSchemeParams = CustomTabColorSchemeParams.Builder() - .setToolbarColor(ColorKeeper.getOrGenerateColor(action.canvasContext)) - .build() + .setToolbarColor(ColorKeeper.getOrGenerateColor(action.canvasContext)) + .build() var intent = CustomTabsIntent.Builder() - .setDefaultColorSchemeParams(colorSchemeParams) - .setShowTitle(true) - .build() - .intent + .setDefaultColorSchemeParams(colorSchemeParams) + .setShowTitle(true) + .build() + .intent intent.data = Uri.parse(action.url) @@ -97,6 +97,7 @@ class DashboardNotificationsFragment : Fragment() { action.subject, action.message ) + is DashboardNotificationsActions.OpenProgressDialog -> {} } } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewData.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewData.kt index 58fe29dc3b..4b43a68358 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewData.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/DashboardNotificationsViewData.kt @@ -17,13 +17,20 @@ package com.instructure.pandautils.features.dashboard.notifications import androidx.annotation.DrawableRes +import androidx.databinding.BaseObservable +import androidx.databinding.Bindable import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.Conference import com.instructure.pandautils.mvvm.ItemViewModel +import java.util.* data class DashboardNotificationsViewData( - val items: List -) + val items: List, + var uploadItems: List +) : BaseObservable() { + @Bindable + fun getConcatenatedItems() = uploadItems + items +} data class InvitationViewData( val title: String, @@ -45,8 +52,16 @@ data class AnnouncementViewData( @DrawableRes val icon: Int ) +data class UploadViewData( + val title: String, + val subTitle: String, + val color: String, + @DrawableRes val icon: Int +) + sealed class DashboardNotificationsActions { - data class ShowToast(val toast: String): DashboardNotificationsActions() - data class LaunchConference(val canvasContext: CanvasContext, val url: String): DashboardNotificationsActions() - data class OpenAnnouncement(val subject: String, val message: String): DashboardNotificationsActions() + data class ShowToast(val toast: String) : DashboardNotificationsActions() + data class LaunchConference(val canvasContext: CanvasContext, val url: String) : DashboardNotificationsActions() + data class OpenAnnouncement(val subject: String, val message: String) : DashboardNotificationsActions() + data class OpenProgressDialog(val uuid: UUID): DashboardNotificationsActions() } 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 b2c31b1ab0..90a8972092 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 @@ -19,21 +19,13 @@ package com.instructure.pandautils.features.dashboard.notifications import android.content.res.Resources import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.work.WorkManager import com.instructure.canvasapi2.apis.EnrollmentAPI -import com.instructure.canvasapi2.managers.AccountNotificationManager -import com.instructure.canvasapi2.managers.ConferenceManager -import com.instructure.canvasapi2.managers.CourseManager -import com.instructure.canvasapi2.managers.EnrollmentManager -import com.instructure.canvasapi2.managers.GroupManager -import com.instructure.canvasapi2.managers.OAuthManager -import com.instructure.canvasapi2.models.AccountNotification -import com.instructure.canvasapi2.models.CanvasContext -import com.instructure.canvasapi2.models.Conference -import com.instructure.canvasapi2.models.Course -import com.instructure.canvasapi2.models.Enrollment -import com.instructure.canvasapi2.models.Group +import com.instructure.canvasapi2.managers.* +import com.instructure.canvasapi2.models.* import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.isValidTerm import com.instructure.pandautils.BR @@ -41,6 +33,10 @@ import com.instructure.pandautils.R import com.instructure.pandautils.features.dashboard.notifications.itemviewmodels.AnnouncementItemViewModel import com.instructure.pandautils.features.dashboard.notifications.itemviewmodels.ConferenceItemViewModel import com.instructure.pandautils.features.dashboard.notifications.itemviewmodels.InvitationItemViewModel +import com.instructure.pandautils.features.dashboard.notifications.itemviewmodels.UploadItemViewModel +import com.instructure.pandautils.features.file.upload.preferences.FileUploadPreferences +import com.instructure.pandautils.features.file.upload.worker.FileUploadWorker.Companion.PROGRESS_DATA_SUBTITLE +import com.instructure.pandautils.features.file.upload.worker.FileUploadWorker.Companion.PROGRESS_DATA_TITLE import com.instructure.pandautils.models.ConferenceDashboardBlacklist import com.instructure.pandautils.mvvm.Event import com.instructure.pandautils.mvvm.ItemViewModel @@ -63,7 +59,9 @@ class DashboardNotificationsViewModel @Inject constructor( private val accountNotificationManager: AccountNotificationManager, private val oauthManager: OAuthManager, private val conferenceDashboardBlacklist: ConferenceDashboardBlacklist, - private val apiPrefs: ApiPrefs + private val apiPrefs: ApiPrefs, + private val workManager: WorkManager, + private val fileUploadPreferences: FileUploadPreferences ) : ViewModel() { val state: LiveData @@ -81,6 +79,20 @@ class DashboardNotificationsViewModel @Inject constructor( private var coursesMap: Map = emptyMap() private var groupMap: Map = emptyMap() + private val runningWorkersObserver = Observer> { + _data.value?.uploadItems = getUploads(it) + _data.value?.notifyPropertyChanged(BR.concatenatedItems) + } + + init { + fileUploadPreferences.getRunningWorkersLiveData().observeForever(runningWorkersObserver) + } + + override fun onCleared() { + fileUploadPreferences.getRunningWorkersLiveData().removeObserver(runningWorkersObserver) + super.onCleared() + } + fun loadData(forceNetwork: Boolean = false) { viewModelScope.launch { @@ -102,7 +114,9 @@ class DashboardNotificationsViewModel @Inject constructor( val conferenceViewModels = getConferences(forceNetwork) items.addAll(conferenceViewModels) - _data.postValue(DashboardNotificationsViewData(items)) + val uploadViewModels = getUploads(fileUploadPreferences.getRunningWorkerIds()) + + _data.postValue(DashboardNotificationsViewData(items, uploadViewModels)) } } @@ -204,6 +218,20 @@ class DashboardNotificationsViewModel @Inject constructor( } } + private fun getUploads(runningWorkerIds: List) = runningWorkerIds.map { + val workInfo = workManager.getWorkInfoById(it).get() + UploadItemViewModel( + it, UploadViewData( + workInfo.progress.getString(PROGRESS_DATA_TITLE).orEmpty(), + workInfo.progress.getString(PROGRESS_DATA_SUBTITLE).orEmpty(), + "#${resources.getColor(R.color.backgroundInfo).toHexString()}", + R.drawable.ic_upload + ) + ) { uuid -> + _events.postValue(Event(DashboardNotificationsActions.OpenProgressDialog(uuid))) + } + } + private fun hasValidCourseForEnrollment(enrollment: Enrollment): Boolean { return coursesMap[enrollment.courseId]?.let { course -> course.isValidTerm() && !course.accessRestrictedByDate && isEnrollmentBeforeEndDateOrNotRestricted(course) 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 new file mode 100644 index 0000000000..912b0c0472 --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/dashboard/notifications/itemviewmodels/UploadItemViewModel.kt @@ -0,0 +1,34 @@ +/* + * 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.pandautils.features.dashboard.notifications.itemviewmodels + +import androidx.databinding.BaseObservable +import com.instructure.pandautils.R +import com.instructure.pandautils.features.dashboard.notifications.UploadViewData +import com.instructure.pandautils.mvvm.ItemViewModel +import java.util.* + +class UploadItemViewModel( + private val workerId: UUID, + val data: UploadViewData, + val open: (UUID) -> Unit +) : ItemViewModel, BaseObservable() { + override val layoutId = R.layout.item_dashboard_upload + + fun open() = open.invoke(workerId) +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/preferences/FileUploadPreferences.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/preferences/FileUploadPreferences.kt index 00f395e786..54b0ecc650 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/preferences/FileUploadPreferences.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/preferences/FileUploadPreferences.kt @@ -25,28 +25,27 @@ import java.util.* object FileUploadPreferences : PrefManager("fileUploadPrefs") { private var runningWorkerIds by StringSetPref() - - private val runningWorkersLiveData = MutableLiveData(getRunningWorkerIds()) + private var runningWorkersLiveData: MutableLiveData>? = null fun addWorkerId(id: UUID) { runningWorkerIds = runningWorkerIds + id.toString() - runningWorkersLiveData.postValue(getRunningWorkerIds()) + runningWorkersLiveData?.postValue(getRunningWorkerIds()) } fun removeWorkerId(id: UUID) { val idString = id.toString() if (runningWorkerIds.contains(idString)) { runningWorkerIds = runningWorkerIds - id.toString() - runningWorkersLiveData.postValue(getRunningWorkerIds()) + runningWorkersLiveData?.postValue(getRunningWorkerIds()) } } - private fun getRunningWorkerIds(): List { + fun getRunningWorkerIds(): List { return runningWorkerIds.map { UUID.fromString(it) } } fun getRunningWorkersLiveData(): LiveData> { - return runningWorkersLiveData + if (runningWorkersLiveData == null) runningWorkersLiveData = MutableLiveData(getRunningWorkerIds()) + return runningWorkersLiveData!! } - } \ No newline at end of file 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 c2e2c1c5d0..1bcae3984e 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 @@ -54,7 +54,31 @@ class FileUploadWorker(private val context: Context, private val workerParameter override suspend fun doWork(): Result { try { + var assignmentName = "" + var groupId: Long? = null + if (assignmentId != INVALID_ID && courseId != INVALID_ID) { + val assignment = getAssignment(assignmentId, courseId) + groupId = getGroupId(assignment, courseId) + assignmentName = assignment.name.orEmpty() + } + + val title = context.getString( + if (action == ACTION_ASSIGNMENT_SUBMISSION) { + R.string.dashboardNotificationUploadingSubmissionTitle + } else { + R.string.dashboardNotificationUploadingFilesTitle + } + ) + + setProgress( + Data.Builder() + .putString(PROGRESS_DATA_TITLE, title) + .putString(PROGRESS_DATA_SUBTITLE, assignmentName) + .build() + ) + FileUploadPreferences.addWorkerId(id) + val filePaths = inputData.getStringArray(FILE_PATHS) val fileSubmitObjects = filePaths?.let { @@ -63,12 +87,6 @@ class FileUploadWorker(private val context: Context, private val workerParameter uploadCount = fileSubmitObjects.size - var groupId: Long? = null - if (assignmentId != INVALID_ID && courseId != INVALID_ID) { - val assignment = getAssignment(assignmentId, courseId) - groupId = getGroupId(assignment, courseId) - } - val attachments = uploadFiles(fileSubmitObjects, groupId) val attachmentsIds = attachments.map { it.id }.plus(inputData.getLongArray(Const.ATTACHMENTS)?.toList() @@ -242,5 +260,7 @@ class FileUploadWorker(private val context: Context, private val workerParameter const val RESULT_ATTACHMENTS = "attachments" + const val PROGRESS_DATA_TITLE = "PROGRESS_DATA_TITLE" + const val PROGRESS_DATA_SUBTITLE = "PROGRESS_DATA_SUBTITLE" } } \ No newline at end of file diff --git a/libs/pandautils/src/main/res/layout/fragment_dashboard_notifications.xml b/libs/pandautils/src/main/res/layout/fragment_dashboard_notifications.xml index ee60d77c95..b59b0d311d 100644 --- a/libs/pandautils/src/main/res/layout/fragment_dashboard_notifications.xml +++ b/libs/pandautils/src/main/res/layout/fragment_dashboard_notifications.xml @@ -32,6 +32,6 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingTop="12dp" - android:visibility="@{viewModel.data.items.size() == 0 ? View.GONE : View.VISIBLE}" - app:itemViewModels="@{viewModel.data.items}" /> + android:visibility="@{viewModel.data.concatenatedItems.size() == 0 ? View.GONE : View.VISIBLE}" + app:itemViewModels="@{viewModel.data.concatenatedItems}" /> \ No newline at end of file diff --git a/libs/pandautils/src/main/res/layout/item_dashboard_upload.xml b/libs/pandautils/src/main/res/layout/item_dashboard_upload.xml new file mode 100644 index 0000000000..7f603435fb --- /dev/null +++ b/libs/pandautils/src/main/res/layout/item_dashboard_upload.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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 054c30bf61..e526fbced1 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 @@ -20,9 +20,10 @@ import android.content.Context import android.content.res.Resources import android.graphics.Color import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.* +import androidx.work.Data +import androidx.work.WorkInfo +import androidx.work.WorkManager import com.instructure.canvasapi2.apis.EnrollmentAPI import com.instructure.canvasapi2.managers.* import com.instructure.canvasapi2.models.* @@ -33,24 +34,23 @@ import com.instructure.pandautils.R import com.instructure.pandautils.features.dashboard.notifications.itemviewmodels.AnnouncementItemViewModel import com.instructure.pandautils.features.dashboard.notifications.itemviewmodels.ConferenceItemViewModel import com.instructure.pandautils.features.dashboard.notifications.itemviewmodels.InvitationItemViewModel +import com.instructure.pandautils.features.dashboard.notifications.itemviewmodels.UploadItemViewModel +import com.instructure.pandautils.features.file.upload.preferences.FileUploadPreferences +import com.instructure.pandautils.features.file.upload.worker.FileUploadWorker import com.instructure.pandautils.models.ConferenceDashboardBlacklist -import com.instructure.pandautils.mvvm.ItemViewModel -import com.instructure.pandautils.utils.ThemePrefs -import io.mockk.coEvery -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic +import io.mockk.* import junit.framework.Assert.assertEquals import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.setMain import okhttp3.internal.toHexString +import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.Mockito -import kotlin.math.exp +import java.util.* @ExperimentalCoroutinesApi class DashboardNotificationsViewModelTest { @@ -72,6 +72,7 @@ class DashboardNotificationsViewModelTest { private val oauthManager: OAuthManager = mockk(relaxed = true) private val conferenceDashboardBlacklist: ConferenceDashboardBlacklist = mockk(relaxed = true) private val apiPrefs: ApiPrefs = mockk(relaxed = true) + private val workManager: WorkManager = mockk(relaxed = true) private lateinit var viewModel: DashboardNotificationsViewModel @@ -108,22 +109,33 @@ class DashboardNotificationsViewModelTest { coEvery { await() } returns DataResult.Success(emptyList()) } + mockkObject(FileUploadPreferences) + every { FileUploadPreferences.getRunningWorkerIds() } returns emptyList() + every { FileUploadPreferences.getRunningWorkersLiveData() } returns MutableLiveData(emptyList()) + viewModel = DashboardNotificationsViewModel( - resources, - courseManager, - groupManager, - enrollmentManager, - conferenceManager, - accountNotificationManager, - oauthManager, - conferenceDashboardBlacklist, - apiPrefs + resources, + courseManager, + groupManager, + enrollmentManager, + conferenceManager, + accountNotificationManager, + oauthManager, + conferenceDashboardBlacklist, + apiPrefs, + workManager, + FileUploadPreferences ) viewModel.data.observe(lifecycleOwner, {}) viewModel.events.observe(lifecycleOwner, {}) } + @After + fun tearDown() { + unmockkObject(FileUploadPreferences) + } + private fun setupResources() { every { resources.getColor(R.color.backgroundDanger) } returns Color.parseColor("#EE0612") every { resources.getColor(R.color.backgroundWarning) } returns Color.parseColor("#FC5E13") @@ -409,4 +421,95 @@ class DashboardNotificationsViewModelTest { assertEquals(expectedData[index], (itemViewModel as ConferenceItemViewModel).data) } } + + @Test + fun `Upload map correctly`() { + val workerId = UUID.randomUUID() + val title = "Title" + val subTitle = "SubTitle" + + every { FileUploadPreferences.getRunningWorkerIds() } returns listOf(workerId) + every { workManager.getWorkInfoById(workerId).get() } returns WorkInfo( + workerId, + WorkInfo.State.RUNNING, + Data.EMPTY, + emptyList(), + Data.Builder() + .putString(FileUploadWorker.PROGRESS_DATA_TITLE, title) + .putString(FileUploadWorker.PROGRESS_DATA_SUBTITLE, subTitle) + .build(), + 1 + ) + + val expectedData = listOf( + UploadViewData( + title, + subTitle, + "#${resources.getColor(R.color.backgroundInfo).toHexString()}", + R.drawable.ic_upload + ) + ) + + viewModel.loadData() + + viewModel.data.value?.uploadItems?.forEachIndexed { index, itemViewModel -> + assert(itemViewModel is UploadItemViewModel) + assertEquals(expectedData[index], (itemViewModel as UploadItemViewModel).data) + } + } + + @Test + fun `Upload notification shows up and disappears when it's finished`() { + val workerId = UUID.randomUUID() + + every { FileUploadPreferences.getRunningWorkerIds() } returns listOf(workerId) + every { FileUploadPreferences.getRunningWorkersLiveData() } returns MutableLiveData(listOf(workerId)) + every { workManager.getWorkInfoById(workerId).get() } returns WorkInfo( + workerId, + WorkInfo.State.RUNNING, + Data.EMPTY, + emptyList(), + Data.EMPTY, + 1 + ) + + viewModel.loadData() + assertEquals(false, viewModel.data.value?.uploadItems?.isEmpty()) + + every { FileUploadPreferences.getRunningWorkersLiveData() } returns MutableLiveData(emptyList()) + every { FileUploadPreferences.getRunningWorkerIds() } returns emptyList() + + viewModel.loadData() + assertEquals(true, viewModel.data.value?.uploadItems?.isEmpty()) + } + + @Test + fun `Open progress dialog`() { + val workerId = UUID.randomUUID() + + every { FileUploadPreferences.getRunningWorkerIds() } returns listOf(workerId) + every { FileUploadPreferences.getRunningWorkersLiveData() } returns MutableLiveData(listOf(workerId)) + every { workManager.getWorkInfoById(workerId).get() } returns WorkInfo( + workerId, + WorkInfo.State.RUNNING, + Data.EMPTY, + emptyList(), + Data.EMPTY, + 1 + ) + + viewModel.loadData() + + val itemViewModel = viewModel.data.value?.uploadItems?.first() + assert(itemViewModel is UploadItemViewModel) + + itemViewModel as UploadItemViewModel + itemViewModel.open(workerId) + + val event = viewModel.events.value?.getContentIfNotHandled() + assert(event is DashboardNotificationsActions.OpenProgressDialog) + + event as DashboardNotificationsActions.OpenProgressDialog + assertEquals(workerId, event.uuid) + } } \ No newline at end of file diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/target/ShareExtensionTargetViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/target/ShareExtensionTargetViewModelTest.kt index cebc6a6988..b2d57b8f28 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/target/ShareExtensionTargetViewModelTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/shareextension/target/ShareExtensionTargetViewModelTest.kt @@ -17,7 +17,6 @@ package com.instructure.pandautils.features.shareextension.target import android.content.res.Resources -import android.graphics.Canvas import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner @@ -25,18 +24,13 @@ import androidx.lifecycle.LifecycleRegistry import com.instructure.canvasapi2.managers.AssignmentManager import com.instructure.canvasapi2.managers.CourseManager import com.instructure.canvasapi2.models.Assignment -import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.utils.ApiPrefs -import com.instructure.canvasapi2.utils.ColorPref import com.instructure.canvasapi2.utils.DataResult import com.instructure.pandautils.R import com.instructure.pandautils.features.file.upload.FileUploadType -import com.instructure.pandautils.features.shareextension.target.itemviewmodels.ShareExtensionAssignmentItemViewModel import com.instructure.pandautils.utils.ColorKeeper -import com.instructure.pandautils.utils.ThemePrefs -import com.instructure.pandautils.utils.color import io.mockk.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi From 7165ec47b57c83d7f512dd79f97c50a6b007e065 Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Mon, 5 Sep 2022 11:40:58 +0200 Subject: [PATCH 076/115] [MBL-16220][Student] Calendar needs to be refreshed day by day after filter refs: MBL-16220 affects: Student release note: none --- libs/flutter_student_embed/lib/network/utils/dio_config.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/flutter_student_embed/lib/network/utils/dio_config.dart b/libs/flutter_student_embed/lib/network/utils/dio_config.dart index c17ed9fc79..83f164f3d7 100644 --- a/libs/flutter_student_embed/lib/network/utils/dio_config.dart +++ b/libs/flutter_student_embed/lib/network/utils/dio_config.dart @@ -167,7 +167,7 @@ class DioConfig { if (path == null) { return DioCacheManager(CacheConfig(baseUrl: baseUrl)).clearAll(); } else { - return DioCacheManager(CacheConfig(baseUrl: baseUrl)).deleteByPrimaryKey(path); + return DioCacheManager(CacheConfig(baseUrl: baseUrl)).deleteByPrimaryKey(path, requestMethod: "GET"); } } } From 8dfd3df7c2d555a8d4dc0307b77d23b9a724b539 Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Mon, 5 Sep 2022 14:56:31 +0200 Subject: [PATCH 077/115] [MBL-15753][Parent] Add back the userId param when the api is fixed refs: MBL-15753 affects: Parent release note: none --- apps/flutter_parent/lib/network/api/enrollments_api.dart | 2 +- .../lib/screens/courses/details/course_details_model.dart | 2 -- .../test/screens/courses/course_details_model_test.dart | 3 +-- .../test/screens/courses/course_grades_screen_test.dart | 6 ++---- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/flutter_parent/lib/network/api/enrollments_api.dart b/apps/flutter_parent/lib/network/api/enrollments_api.dart index 848d579e30..c373b780fe 100644 --- a/apps/flutter_parent/lib/network/api/enrollments_api.dart +++ b/apps/flutter_parent/lib/network/api/enrollments_api.dart @@ -41,7 +41,7 @@ class EnrollmentsApi { final dio = canvasDio(forceRefresh: forceRefresh); final params = { 'state[]': ['active', 'completed'], // current_and_concluded state not supported for observers - //'user_id': studentId, <-- add this back when the api is fixed + 'user_id': studentId, if (gradingPeriodId?.isNotEmpty == true) 'grading_period_id': gradingPeriodId, }; diff --git a/apps/flutter_parent/lib/screens/courses/details/course_details_model.dart b/apps/flutter_parent/lib/screens/courses/details/course_details_model.dart index 163457ffcb..60eea98f46 100644 --- a/apps/flutter_parent/lib/screens/courses/details/course_details_model.dart +++ b/apps/flutter_parent/lib/screens/courses/details/course_details_model.dart @@ -98,8 +98,6 @@ class CourseDetailsModel extends BaseModel { final enrollmentsFuture = _interactor() .loadEnrollmentsForGradingPeriod(courseId, student.id, _nextGradingPeriod?.id, forceRefresh: forceRefresh) ?.then((enrollments) { - enrollments = enrollments - .where((element) => element.userId == student.id).toList(); return enrollments.length > 0 ? enrollments.first : null; })?.catchError((_) => null); // Some 'legacy' parents can't read grades for students, so catch and return null diff --git a/apps/flutter_parent/test/screens/courses/course_details_model_test.dart b/apps/flutter_parent/test/screens/courses/course_details_model_test.dart index 34749806dd..3285c6bf1f 100644 --- a/apps/flutter_parent/test/screens/courses/course_details_model_test.dart +++ b/apps/flutter_parent/test/screens/courses/course_details_model_test.dart @@ -150,8 +150,7 @@ void main() { // Initial setup final termEnrollment = Enrollment((b) => b ..id = '10' - ..enrollmentState = 'active' - ..userId = _studentId); + ..enrollmentState = 'active'); final gradingPeriods = [ GradingPeriod((b) => b ..id = '123' diff --git a/apps/flutter_parent/test/screens/courses/course_grades_screen_test.dart b/apps/flutter_parent/test/screens/courses/course_grades_screen_test.dart index db0ae0ef27..7e03ef1cc9 100644 --- a/apps/flutter_parent/test/screens/courses/course_grades_screen_test.dart +++ b/apps/flutter_parent/test/screens/courses/course_grades_screen_test.dart @@ -326,8 +326,7 @@ void main() { ]; final enrollment = Enrollment((b) => b ..enrollmentState = 'active' - ..grades = _mockGrade(currentScore: 1.2345) - ..userId = _studentId); + ..grades = _mockGrade(currentScore: 1.2345)); final model = CourseDetailsModel(_student, _courseId); model.course = _mockCourse(); @@ -351,8 +350,7 @@ void main() { ]; final enrollment = Enrollment((b) => b ..enrollmentState = 'active' - ..grades = _mockGrade(currentGrade: grade) - ..userId = _studentId); + ..grades = _mockGrade(currentGrade: grade)); final model = CourseDetailsModel(_student, _courseId); model.course = _mockCourse(); when(interactor.loadAssignmentGroups(_courseId, _studentId, null)).thenAnswer((_) async => groups); From 7f491611c0644cd46d7fa390254b460df6a79ddb Mon Sep 17 00:00:00 2001 From: balintbartok <72031065+balintbartok@users.noreply.github.com> Date: Tue, 6 Sep 2022 11:20:21 +0200 Subject: [PATCH 078/115] Create PULL_REQUEST_TEMPLATE --- PULL_REQUEST_TEMPLATE | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 PULL_REQUEST_TEMPLATE diff --git a/PULL_REQUEST_TEMPLATE b/PULL_REQUEST_TEMPLATE new file mode 100644 index 0000000000..b56e60264e --- /dev/null +++ b/PULL_REQUEST_TEMPLATE @@ -0,0 +1,16 @@ +--- edit or delete this section --- +## Screenshots + + + + + + + +
BeforeAfter
+ +## Checklist + +- [ ] Follow-up e2e test ticket created or not needed +- [ ] A11y checked +- [ ] Approve from product or not needed From bd3d8cacb90e53011f637463ee5a4da65fa85833 Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:55:09 +0200 Subject: [PATCH 079/115] [MBL-16227][Parent] - Rename build apk names to contains the appname (#1698) add variant section define an apk build name which contains the word 'parent' instead of using a default build name (app-debug.apk or app-release.apk) add timestamp to debug builds to be more accurate refs: MBL-16227 affects: Parent release note: test plan: --- apps/flutter_parent/android/app/build.gradle | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/flutter_parent/android/app/build.gradle b/apps/flutter_parent/android/app/build.gradle index ea3bc8685a..532b872ab9 100644 --- a/apps/flutter_parent/android/app/build.gradle +++ b/apps/flutter_parent/android/app/build.gradle @@ -78,6 +78,19 @@ android { shrinkResources false // Must be false, otherwise resources we need are erroneously stripped out proguardFiles 'proguard-rules.pro' } + applicationVariants.all{ + variant -> + variant.outputs.each{ + output-> + project.ext { appName = 'parent' } + def dateTimeStamp = new Date().format('yyyy-MM-dd-HH-mm-ss') + def newName = output.outputFile.name + newName = newName.replace("app-", "$project.ext.appName-") + newName = newName.replace("-debug", "-dev-debug-" + dateTimeStamp) + newName = newName.replace("-release", "-prod-release") + output.outputFileName = newName + } + } } } From ac66e9e13d47f6adf7667ee0f3c653ea500c87ce Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:59:37 +0200 Subject: [PATCH 080/115] [MBL-16225][Student][Teacher] - Introduce StubMultiAPILevel annotation (#1695) Create StubMultiAPILevel annotation. Put annotation on some Multi API Level flaky tests. Modify flank files to exclude the tests with the annotation. refs: MBL-16225 affects: Student, Teacher release note: none --- apps/student/flank_multi_api_level.yml | 2 +- apps/teacher/flank_multi_api_level.yml | 2 +- .../teacher/ui/SpeedGraderGradePageTest.kt | 3 +++ .../canvas/espresso/StubMultiAPILevel.kt | 22 +++++++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/StubMultiAPILevel.kt diff --git a/apps/student/flank_multi_api_level.yml b/apps/student/flank_multi_api_level.yml index d56fee62c7..dd25260b89 100644 --- a/apps/student/flank_multi_api_level.yml +++ b/apps/student/flank_multi_api_level.yml @@ -12,7 +12,7 @@ gcloud: record-video: true timeout: 60m test-targets: - - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.FlakyE2E, com.instructure.canvas.espresso.KnownBug + - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.StubMultiAPILevel, com.instructure.canvas.espresso.FlakyE2E, com.instructure.canvas.espresso.KnownBug device: - model: NexusLowRes version: 27 diff --git a/apps/teacher/flank_multi_api_level.yml b/apps/teacher/flank_multi_api_level.yml index c90ecd1a9a..2274d848d1 100644 --- a/apps/teacher/flank_multi_api_level.yml +++ b/apps/teacher/flank_multi_api_level.yml @@ -12,7 +12,7 @@ gcloud: record-video: true timeout: 60m test-targets: - - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.FlakyE2E, com.instructure.canvas.espresso.KnownBug + - notAnnotation com.instructure.canvas.espresso.E2E, com.instructure.canvas.espresso.Stub, com.instructure.canvas.espresso.StubMultiAPILevel, com.instructure.canvas.espresso.FlakyE2E, com.instructure.canvas.espresso.KnownBug device: - model: NexusLowRes version: 27 diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/SpeedGraderGradePageTest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/SpeedGraderGradePageTest.kt index 3b9cd7322b..c96af11ac7 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/SpeedGraderGradePageTest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/SpeedGraderGradePageTest.kt @@ -16,6 +16,7 @@ package com.instructure.teacher.ui import android.util.Log +import com.instructure.canvas.espresso.StubMultiAPILevel import com.instructure.canvas.espresso.mockCanvas.* import com.instructure.canvasapi2.models.Assignment import com.instructure.canvasapi2.models.CanvasContextPermission @@ -137,6 +138,7 @@ class SpeedGraderGradePageTest : TeacherTest() { } @Test + @StubMultiAPILevel("Failed API levels = { 27, 28, 29 }") fun overgradePointAssignment() { val pointsPossible = 20 goToSpeedGraderGradePage(pointsPossible = pointsPossible) @@ -159,6 +161,7 @@ class SpeedGraderGradePageTest : TeacherTest() { } @Test + @StubMultiAPILevel("Failed API levels = { 27, 28, 29 }") fun clearGrade() { goToSpeedGraderGradePage() speedGraderPage.swipeUpGradesTab() diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/StubMultiAPILevel.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/StubMultiAPILevel.kt new file mode 100644 index 0000000000..90080e9df1 --- /dev/null +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/StubMultiAPILevel.kt @@ -0,0 +1,22 @@ +/* + * 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.canvas.espresso + +// Apply on a test method which is failing on Firebase Test Lab (FTL) on some API levels. Write the failing API Levels into the parameter. +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class StubMultiAPILevel(val failedApiLevels: String = "") \ No newline at end of file From 010636a8c0acd790b107f0e8adebce84fa6db7cd Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Wed, 7 Sep 2022 14:21:47 +0200 Subject: [PATCH 081/115] [MBL-13517][Student] - Investigate and put in order lti related tests (#1696) Implement testModules_launchesIntoExternalTool interaction test. Extend MockCanvas class addItemToModule method to handle LTITool type as well. Add URL assertion to testModules_launchesIntoExternalURL method. refs: MBL-13517 affects: Student release note: none --- .../ui/interaction/ModuleInteractionTest.kt | 45 +++++++------------ .../NavigationDrawerInteractionTest.kt | 4 +- .../student/ui/pages/CanvasWebViewPage.kt | 11 ++++- .../canvas/espresso/mockCanvas/MockCanvas.kt | 5 +++ 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt index b1a6092ddb..6e84a2e81e 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/ModuleInteractionTest.kt @@ -18,34 +18,12 @@ package com.instructure.student.ui.interaction import android.text.Html import androidx.test.espresso.Espresso import androidx.test.espresso.web.webdriver.Locator -import com.instructure.canvas.espresso.Stub -import com.instructure.canvas.espresso.mockCanvas.MockCanvas -import com.instructure.canvas.espresso.mockCanvas.addAssignment -import com.instructure.canvas.espresso.mockCanvas.addDiscussionTopicToCourse -import com.instructure.canvas.espresso.mockCanvas.addFileToCourse -import com.instructure.canvas.espresso.mockCanvas.addItemToModule -import com.instructure.canvas.espresso.mockCanvas.addModuleToCourse -import com.instructure.canvas.espresso.mockCanvas.addPageToCourse -import com.instructure.canvas.espresso.mockCanvas.addQuestionToQuiz -import com.instructure.canvas.espresso.mockCanvas.addQuizToCourse -import com.instructure.canvas.espresso.mockCanvas.init -import com.instructure.canvasapi2.models.Assignment -import com.instructure.canvasapi2.models.DiscussionTopicHeader -import com.instructure.canvasapi2.models.LockInfo -import com.instructure.canvasapi2.models.LockedModule -import com.instructure.canvasapi2.models.ModuleObject -import com.instructure.canvasapi2.models.Page -import com.instructure.canvasapi2.models.Quiz -import com.instructure.canvasapi2.models.QuizAnswer -import com.instructure.canvasapi2.models.Tab +import com.instructure.canvas.espresso.mockCanvas.* +import com.instructure.canvasapi2.models.* import com.instructure.dataseeding.util.days import com.instructure.dataseeding.util.fromNow import com.instructure.dataseeding.util.iso8601 -import com.instructure.panda_annotations.FeatureCategory -import com.instructure.panda_annotations.Priority -import com.instructure.panda_annotations.SecondaryFeatureCategory -import com.instructure.panda_annotations.TestCategory -import com.instructure.panda_annotations.TestMetaData +import com.instructure.panda_annotations.* import com.instructure.student.R import com.instructure.student.ui.pages.WebViewTextCheck import com.instructure.student.ui.utils.StudentTest @@ -101,12 +79,16 @@ class ModuleInteractionTest : StudentTest() { discussionDetailsPage.assertTopicInfoShowing(topicHeader!!) } - // I'm punting on LTI testing for now. But MBL-13517 captures this work. - @Stub @Test - @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION, true) + @TestMetaData(Priority.MANDATORY, FeatureCategory.MODULES, TestCategory.INTERACTION) fun testModules_launchesIntoExternalTool() { // Tapping an ExternalTool module item should navigate to that item's detail page + val data = getToCourseModules(studentCount = 1, courseCount = 1) + val course1 = data.courses.values.first() + val module = data.courseModules[course1.id]!!.first() + + modulesPage.clickModuleItem(module, "Google Drive") + canvasWebViewPage.assertTitle("Google Drive") } // Tapping an ExternalURL module item should navigate to that item's detail page @@ -122,6 +104,7 @@ class ModuleInteractionTest : StudentTest() { modulesPage.clickModuleItem(module,externalUrl) // Not much we can test here, as it is an external URL, but testModules_navigateToNextAndPreviousModuleItems // will test that the module name and module item name are displayed correctly. + canvasWebViewPage.checkWebViewURL("https://www.google.com") } // Tapping a File module item should navigate to that item's detail page @@ -487,6 +470,12 @@ class ModuleInteractionTest : StudentTest() { item = quiz!! ) + val ltiTool = data.addLTITool("Google Drive", "http://google.com", course1, 1234L) + data.addItemToModule( + course = course1, + moduleId = module.id, + item = ltiTool!! + ) // Sign in val student = data.students[0] diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt index 020c69234d..0ce11e4de2 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/NavigationDrawerInteractionTest.kt @@ -34,9 +34,9 @@ import com.instructure.panda_annotations.FeatureCategory import com.instructure.panda_annotations.Priority import com.instructure.panda_annotations.TestCategory import com.instructure.panda_annotations.TestMetaData +import com.instructure.student.R import com.instructure.student.ui.utils.StudentTest import com.instructure.student.ui.utils.tokenLogin -import com.instructure.student.R import dagger.hilt.android.testing.HiltAndroidTest import org.hamcrest.CoreMatchers import org.junit.Before @@ -164,7 +164,7 @@ class NavigationDrawerInteractionTest : StudentTest() { dashboardPage.goToHelp() helpPage.launchGuides() - canvasWebViewPage.verifyTitle(R.string.searchGuides) + canvasWebViewPage.assertTitle(R.string.searchGuides) } // Should send an error report diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CanvasWebViewPage.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CanvasWebViewPage.kt index d30fa91f02..64aa9bc941 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CanvasWebViewPage.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/pages/CanvasWebViewPage.kt @@ -21,7 +21,10 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches import androidx.test.espresso.web.model.Atoms.getCurrentUrl import androidx.test.espresso.web.sugar.Web.onWebView -import androidx.test.espresso.web.webdriver.DriverAtoms.* +import androidx.test.espresso.web.webdriver.DriverAtoms.findElement +import androidx.test.espresso.web.webdriver.DriverAtoms.getText +import androidx.test.espresso.web.webdriver.DriverAtoms.webClick +import androidx.test.espresso.web.webdriver.DriverAtoms.webScrollIntoView import androidx.test.espresso.web.webdriver.Locator import com.instructure.canvas.espresso.withElementRepeat import com.instructure.espresso.assertVisible @@ -35,7 +38,11 @@ import org.hamcrest.Matchers.containsString */ open class CanvasWebViewPage : BasePage(R.id.canvasWebView) { - fun verifyTitle(@StringRes title: Int) { + fun assertTitle(@StringRes title: Int) { + onView(withAncestor(R.id.toolbar) + withText(title)).assertVisible() + } + + fun assertTitle(title: String) { onView(withAncestor(R.id.toolbar) + withText(title)).assertVisible() } diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt index 0a6671f9fc..ba6744f500 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockCanvas/MockCanvas.kt @@ -1510,6 +1510,11 @@ fun MockCanvas.addItemToModule( itemTitle = item itemUrl = item } + is LTITool -> { + itemType = ModuleItem.Type.ExternalTool + itemTitle = item.name + itemUrl = item.url + } else -> { throw Exception("Unknown item type: ${item::class.java.simpleName}") } From f810d0f0827bc537fd7116e68c42c8ac646a4952 Mon Sep 17 00:00:00 2001 From: Kristof Deak <92309696+kdeakinstructure@users.noreply.github.com> Date: Fri, 9 Sep 2022 10:49:53 +0200 Subject: [PATCH 082/115] [MBL-16210][Teacher] - Insert discussion creation on UI #1700 implement discussion creation flow on UI. add searching to the discussionE2E test add expand/collapse to the test as well. refs: MBL-16210 affects: Teacher release note: none --- .../teacher/ui/e2e/DiscussionsE2ETest.kt | 59 +++++++++++++++---- .../teacher/ui/pages/DiscussionsListPage.kt | 26 ++++++-- .../ui/pages/EditDiscussionsDetailsPage.kt | 25 +++++--- 3 files changed, 88 insertions(+), 22 deletions(-) diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DiscussionsE2ETest.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DiscussionsE2ETest.kt index 49a500d636..788a7a6a80 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DiscussionsE2ETest.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/e2e/DiscussionsE2ETest.kt @@ -17,6 +17,7 @@ package com.instructure.teacher.ui.e2e import android.util.Log +import androidx.test.espresso.Espresso import com.instructure.canvas.espresso.E2E import com.instructure.panda_annotations.FeatureCategory import com.instructure.panda_annotations.Priority @@ -32,9 +33,7 @@ import org.junit.Test class DiscussionsE2ETest : TeacherTest() { override fun displaysPageObjects() = Unit - override fun enableAndConfigureAccessibilityChecks() { - //We dont want to see accessibility errors on E2E tests - } + override fun enableAndConfigureAccessibilityChecks() = Unit @E2E @Test @@ -59,35 +58,73 @@ class DiscussionsE2ETest : TeacherTest() { courseBrowserPage.openDiscussionsTab() discussionsListPage.assertHasDiscussion(discussion) - Log.d(STEP_TAG,"Click on ${discussion.title} discussion and navigate to Discussions Details Page by clicking on 'Edit'.") + Log.d(STEP_TAG,"Click on '${discussion.title}' discussion and navigate to Discussions Details Page by clicking on 'Edit'.") discussionsListPage.clickDiscussion(discussion) discussionsDetailsPage.openEdit() val newTitle = "New Discussion" - Log.d(STEP_TAG,"Edit the discussions's title to: $newTitle. Click on 'Save'.") + Log.d(STEP_TAG,"Edit the discussion's title to: '$newTitle'. Click on 'Save'.") editDiscussionsDetailsPage.editTitle(newTitle) editDiscussionsDetailsPage.clickSave() - Log.d(STEP_TAG,"Refresh the page. Assert that the discussion's name has been changed to $newTitle and it is published.") + Log.d(STEP_TAG,"Refresh the page. Assert that the discussion's name has been changed to '$newTitle' and it is published.") discussionsDetailsPage.refresh() discussionsDetailsPage.assertDiscussionTitle(newTitle) discussionsDetailsPage.assertDiscussionPublished() - Log.d(STEP_TAG,"Navigate to Discussions Details Page by clicking on 'Edit'. Unpublish the $newTitle discussion and click on 'Save'.") + Log.d(STEP_TAG,"Navigate to Discussions Details Page by clicking on 'Edit'. Unpublish the '$newTitle' discussion and click on 'Save'.") discussionsDetailsPage.openEdit() - editDiscussionsDetailsPage.switchPublished() + editDiscussionsDetailsPage.togglePublished() editDiscussionsDetailsPage.clickSave() - Log.d(STEP_TAG,"Refresh the page. Assert that the $newTitle discussion has been unpublished.") + Log.d(STEP_TAG,"Refresh the page. Assert that the '$newTitle' discussion has been unpublished.") discussionsDetailsPage.refresh() discussionsDetailsPage.assertDiscussionUnpublished() - Log.d(STEP_TAG,"Navigate to Discussions Details Page by clicking on 'Edit'. Delete the $newTitle discussion.") + Log.d(STEP_TAG,"Navigate to Discussions Details Page by clicking on 'Edit'. Delete the '$newTitle' discussion.") discussionsDetailsPage.openEdit() editDiscussionsDetailsPage.deleteDiscussion() - Log.d(STEP_TAG,"Refresh the page. Assert that there is no discussion, so the $newTitle discussion has been deleted successfully.") + Log.d(STEP_TAG,"Refresh the page. Assert that there is no discussion, so the '$newTitle' discussion has been deleted successfully.") discussionsListPage.refresh() discussionsListPage.assertNoDiscussion() + + Log.d(STEP_TAG,"Click on '+' icon on the UI to create a new discussion.") + discussionsListPage.createNewDiscussion() + + val newDiscussionTitle = "Test Discussion Mobile UI" + Log.d(STEP_TAG,"Set '$newDiscussionTitle' as the discussion's title and set some description as well.") + editDiscussionsDetailsPage.editTitle(newDiscussionTitle) + editDiscussionsDetailsPage.editDescription("Mobile UI Discussion description") + + Log.d(STEP_TAG,"Toggle Publish checkbox and save the page.") + editDiscussionsDetailsPage.togglePublished() + editDiscussionsDetailsPage.clickSendNewDiscussion() + + Log.d(STEP_TAG,"Assert that '$newDiscussionTitle' discussion is displayed and published.") + discussionsListPage.assertHasDiscussion(newDiscussionTitle) + discussionsListPage.clickDiscussion(newDiscussionTitle) + discussionsDetailsPage.assertDiscussionPublished() + Espresso.pressBack() + + Log.d(STEP_TAG,"Click on the Search icon and type some search query string which matches only with the previously created discussion's title.") + discussionsListPage.openSearch() + discussionsListPage.enterSearchQuery("Test Discussion") + + Log.d(STEP_TAG,"Assert that the '$newDiscussionTitle' discussion is displayed and it is the only one.") + discussionsListPage.assertDiscussionCount(2) // header + single search result + discussionsListPage.assertHasDiscussion(newDiscussionTitle) + Espresso.pressBack() // need to press back to exit from the search input field + + Log.d(STEP_TAG,"Collapse the discussion list and assert that the '$newDiscussionTitle' discussion can NOT be seen.") + discussionsListPage.toggleCollapseExpandIcon() + discussionsListPage.assertDiscussionCount(1) // header only + discussionsListPage.assertDiscussionDoesNotExist(newDiscussionTitle) + + Log.d(STEP_TAG,"Expand the discussion list and assert that the '$newDiscussionTitle' discussion can be seen.") + discussionsListPage.toggleCollapseExpandIcon() + discussionsListPage.assertDiscussionCount(2) // header only + single search result + discussionsListPage.assertHasDiscussion(newDiscussionTitle) + } } \ No newline at end of file diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/DiscussionsListPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/DiscussionsListPage.kt index 2270b02a17..d3c6ba4499 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/DiscussionsListPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/DiscussionsListPage.kt @@ -17,13 +17,11 @@ package com.instructure.teacher.ui.pages import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import com.instructure.canvasapi2.models.DiscussionTopicHeader import com.instructure.dataseeding.model.DiscussionApiModel import com.instructure.espresso.* -import com.instructure.espresso.page.BasePage -import com.instructure.espresso.page.onView -import com.instructure.espresso.page.waitForViewWithText -import com.instructure.espresso.page.withId +import com.instructure.espresso.page.* import com.instructure.teacher.R class DiscussionsListPage : BasePage() { @@ -38,10 +36,22 @@ class DiscussionsListPage : BasePage() { waitForViewWithText(discussion.title).click() } + fun clickDiscussion(discussionTitle: String) { + waitForViewWithText(discussionTitle).click() + } + fun assertHasDiscussion(discussion: DiscussionApiModel) { waitForViewWithText(discussion.title).assertDisplayed() } + fun assertHasDiscussion(discussionTitle: String) { + waitForViewWithText(discussionTitle).assertDisplayed() + } + + fun assertDiscussionDoesNotExist(discussionTitle: String) { + onView(withText(discussionTitle)).check(doesNotExist()) + } + fun assertNoDiscussion() { onView(withId(R.id.emptyPandaView)).assertDisplayed() } @@ -65,4 +75,12 @@ class DiscussionsListPage : BasePage() { fun refresh() { onView(withId(R.id.swipeRefreshLayout)).swipeDown() } + + fun createNewDiscussion() { + onView(withId(R.id.createNewDiscussion)).click() + } + + fun toggleCollapseExpandIcon() { + onView(withId(R.id.collapseIcon)).click() + } } diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditDiscussionsDetailsPage.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditDiscussionsDetailsPage.kt index cdbe9c1b73..d4d6000b08 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditDiscussionsDetailsPage.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/pages/EditDiscussionsDetailsPage.kt @@ -17,6 +17,7 @@ package com.instructure.teacher.ui.pages import androidx.test.espresso.Espresso +import com.instructure.espresso.WaitForViewWithId import com.instructure.espresso.click import com.instructure.espresso.page.BasePage import com.instructure.espresso.page.onView @@ -24,26 +25,36 @@ import com.instructure.espresso.page.withId import com.instructure.espresso.replaceText import com.instructure.espresso.scrollTo import com.instructure.teacher.R +import com.instructure.teacher.ui.utils.TypeInRCETextEditor class EditDiscussionsDetailsPage : BasePage() { + private val contentRceView by WaitForViewWithId(R.id.rce_webView) + fun editTitle(newTitle: String) { onView(withId(R.id.editDiscussionName)).replaceText(newTitle) Espresso.closeSoftKeyboard() } - fun switchPublished() { - onView(withId(R.id.publishSwitch)).scrollTo() - onView(withId(R.id.publishSwitch)).click() + fun togglePublished() { + onView(withId(R.id.publishSwitch)).scrollTo().click() } fun deleteDiscussion() { - onView(withId(R.id.deleteText)).scrollTo() - onView(withId(R.id.deleteText)).click() - onView(withId(android.R.id.button1)).click() + onView(withId(R.id.deleteText)).scrollTo().click() + onView(withId(android.R.id.button1)).click() //button1 is actually the 'DELETE' button on the UI pop-up dialog. } - fun clickSave() { + fun clickSave() { //This method is used when editing an existing discussion. onView(withId(R.id.menuSave)).click() } + + fun clickSendNewDiscussion() { //This method is used when creating a new discussion via mobile UI. + onView(withId(R.id.menuSaveDiscussion)).click() + } + + fun editDescription(newDescription: String) { + contentRceView.perform(TypeInRCETextEditor(newDescription)) + } + } \ No newline at end of file From 0a17cd0489f719a8e21f7ce715d0b96740b94f8f Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Tue, 13 Sep 2022 16:16:37 +0200 Subject: [PATCH 083/115] [MBL-16236][Student] Multiple file sharing support (#1701) refs: MBL-16236 affects: Student release note: Added support for sharing multiple files to the app. test plan: Select multiple files in the device file explorer. Canvas should be visible in the share sheet. All the files should be visible in the file upload dialog. --- apps/student/src/main/AndroidManifest.xml | 1 + .../file/upload/FileUploadDialogFragment.kt | 46 +++++++++++-------- .../file/upload/FileUploadDialogViewModel.kt | 8 ++-- .../shareextension/ShareExtensionActivity.kt | 4 +- .../shareextension/ShareExtensionViewModel.kt | 25 ++++++---- .../com/instructure/pandautils/utils/Const.kt | 1 + .../file/upload/FileUploadViewModelTest.kt | 12 ++--- 7 files changed, 58 insertions(+), 39 deletions(-) diff --git a/apps/student/src/main/AndroidManifest.xml b/apps/student/src/main/AndroidManifest.xml index 84a04a94c4..eaf3861f3a 100644 --- a/apps/student/src/main/AndroidManifest.xml +++ b/apps/student/src/main/AndroidManifest.xml @@ -214,6 +214,7 @@ android:exported="true"> + 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 2ba7ab1c1e..c554d0fe08 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 @@ -34,7 +34,7 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import androidx.work.* +import androidx.work.WorkInfo import com.instructure.canvasapi2.models.* import com.instructure.canvasapi2.models.postmodels.FileSubmitObject import com.instructure.canvasapi2.utils.ApiPrefs @@ -43,7 +43,6 @@ import com.instructure.pandautils.databinding.FragmentFileUploadDialogBinding import com.instructure.pandautils.utils.* import dagger.hilt.android.AndroidEntryPoint import java.io.File -import kotlin.collections.ArrayList @AndroidEntryPoint class FileUploadDialogFragment : DialogFragment() { @@ -56,7 +55,7 @@ class FileUploadDialogFragment : DialogFragment() { private var canvasContext: CanvasContext by ParcelableArg(ApiPrefs.user) private var position: Int by IntArg() - private var fileSubmitUri: Uri? = null + private var fileSubmitUris: ArrayList? = arrayListOf() private var cameraImageUri: Uri? = null private var assignment: Assignment? by NullableParcelableArg() @@ -173,7 +172,7 @@ class FileUploadDialogFragment : DialogFragment() { } }) - viewModel.setData(assignment, fileSubmitUri, uploadType, canvasContext, parentFolderId, quizQuestionId, + viewModel.setData(assignment, fileSubmitUris, uploadType, canvasContext, parentFolderId, quizQuestionId, position, quizId, dialogCallback, attachmentCallback, workerCallback) } @@ -236,7 +235,7 @@ class FileUploadDialogFragment : DialogFragment() { return FileUploadDialogFragment().apply { arguments = args - fileSubmitUri = args.getParcelable(Const.URI) + fileSubmitUris = args.getParcelableArrayList(Const.URIS) uploadType = args.getSerializable(Const.UPLOAD_TYPE) as FileUploadType parentFolderId = args.getLong(Const.PARENT_FOLDER_ID, INVALID_ID) quizQuestionId = args.getLong(Const.QUIZ_ANSWER_ID, INVALID_ID) @@ -249,28 +248,28 @@ class FileUploadDialogFragment : DialogFragment() { } } - fun createBundle(submitURI: Uri?, type: FileUploadType, parentFolderId: Long?): Bundle { + fun createBundle(submitURIs: ArrayList, type: FileUploadType, parentFolderId: Long?): Bundle { val bundle = Bundle() - if (submitURI != null) bundle.putParcelable(Const.URI, submitURI) + if (submitURIs.isNotEmpty()) bundle.putParcelableArrayList(Const.URIS, submitURIs) if (parentFolderId != null) bundle.putLong(Const.PARENT_FOLDER_ID, parentFolderId) bundle.putSerializable(Const.UPLOAD_TYPE, type) return bundle } fun createMessageAttachmentsBundle(defaultFileList: ArrayList): Bundle { - val bundle = createBundle(null, FileUploadType.MESSAGE, null) + val bundle = createBundle(arrayListOf(), FileUploadType.MESSAGE, null) bundle.putParcelableArrayList(Const.FILES, defaultFileList) return bundle } fun createDiscussionsBundle(defaultFileList: ArrayList): Bundle { - val bundle = createBundle(null, FileUploadType.DISCUSSION, null) + val bundle = createBundle(arrayListOf(), FileUploadType.DISCUSSION, null) bundle.putParcelableArrayList(Const.FILES, defaultFileList) return bundle } - fun createFilesBundle(submitURI: Uri?, parentFolderId: Long?): Bundle { - return createBundle(submitURI, FileUploadType.USER, parentFolderId) + fun createFilesBundle(submitUris: ArrayList, parentFolderId: Long?): Bundle { + return createBundle(submitUris, FileUploadType.USER, parentFolderId) } fun createContextBundle(submitURI: Uri?, context: CanvasContext, parentFolderId: Long?): Bundle { @@ -282,32 +281,41 @@ class FileUploadDialogFragment : DialogFragment() { } private fun createCourseBundle(submitURI: Uri?, course: Course, parentFolderId: Long?): Bundle { - val bundle = createBundle(submitURI, FileUploadType.COURSE, parentFolderId) + val submitUris = submitURI?.let { + arrayListOf(it) + } ?: arrayListOf() + val bundle = createBundle(submitUris, FileUploadType.COURSE, parentFolderId) bundle.putParcelable(Const.CANVAS_CONTEXT, course) return bundle } private fun createGroupBundle(submitURI: Uri?, group: Group, parentFolderId: Long?): Bundle { - val bundle = createBundle(submitURI, FileUploadType.GROUP, parentFolderId) + val submitUris = submitURI?.let { + arrayListOf(it) + } ?: arrayListOf() + val bundle = createBundle(submitUris, FileUploadType.GROUP, parentFolderId) bundle.putParcelable(Const.CANVAS_CONTEXT, group) return bundle } private fun createUserBundle(submitURI: Uri?, user: User, parentFolderId: Long?): Bundle { - val bundle = createBundle(submitURI, FileUploadType.USER, parentFolderId) + val submitUris = submitURI?.let { + arrayListOf(it) + } ?: arrayListOf() + val bundle = createBundle(submitUris, FileUploadType.USER, parentFolderId) bundle.putParcelable(Const.CANVAS_CONTEXT, user) return bundle } - fun createAssignmentBundle(submitURI: Uri?, course: Course, assignment: Assignment): Bundle { - val bundle = createBundle(submitURI, FileUploadType.ASSIGNMENT, null) + fun createAssignmentBundle(submitURIs: ArrayList, course: Course, assignment: Assignment): Bundle { + val bundle = createBundle(submitURIs, FileUploadType.ASSIGNMENT, null) bundle.putParcelable(Const.CANVAS_CONTEXT, course) bundle.putParcelable(Const.ASSIGNMENT, assignment) return bundle } fun createQuizFileBundle(quizQuestionId: Long, courseId: Long, quizId: Long, position: Int): Bundle { - val bundle = createBundle(null, FileUploadType.QUIZ, null) + val bundle = createBundle(arrayListOf(), FileUploadType.QUIZ, null) bundle.putLong(Const.QUIZ_ANSWER_ID, quizQuestionId) bundle.putLong(Const.QUIZ, quizId) bundle.putLong(Const.COURSE_ID, courseId) @@ -316,7 +324,7 @@ class FileUploadDialogFragment : DialogFragment() { } fun createSubmissionCommentBundle(course: Course, assignment: Assignment, defaultFileList: java.util.ArrayList): Bundle { - val bundle = createBundle(null, FileUploadType.SUBMISSION_COMMENT, null) + val bundle = createBundle(arrayListOf(), FileUploadType.SUBMISSION_COMMENT, null) bundle.putParcelable(Const.CANVAS_CONTEXT, course) bundle.putParcelable(Const.ASSIGNMENT, assignment) bundle.putParcelableArrayList(Const.FILES, defaultFileList) @@ -324,7 +332,7 @@ class FileUploadDialogFragment : DialogFragment() { } fun createAttachmentsBundle(defaultFileList: ArrayList = ArrayList()): Bundle { - val bundle = createBundle(null, FileUploadType.MESSAGE, null) + val bundle = createBundle(arrayListOf(), FileUploadType.MESSAGE, null) bundle.putParcelableArrayList(Const.FILES, defaultFileList) return bundle } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewModel.kt index 5e5834aa81..4eb64448e7 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewModel.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/file/upload/FileUploadDialogViewModel.kt @@ -72,7 +72,7 @@ class FileUploadDialogViewModel @Inject constructor( fun setData( assignment: Assignment?, - file: Uri?, + files: ArrayList?, uploadType: FileUploadType, canvasContext: CanvasContext, parentFolderId: Long, @@ -84,10 +84,10 @@ class FileUploadDialogViewModel @Inject constructor( workerCallback: ((LiveData) -> Unit)? = null ) { this.assignment = assignment - file?.let { uri -> + files?.forEach { uri -> val submitObject = getUriContents(uri) - submitObject?.let { - this.filesToUpload = mutableListOf(FileUploadData(uri, it)) + submitObject?.let { fso -> + this.filesToUpload.add(FileUploadData(uri, fso)) } } this.uploadType = uploadType 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 d80f27017d..ad0572c49c 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 @@ -92,11 +92,11 @@ abstract class ShareExtensionActivity : AppCompatActivity() { private fun handleAction(action: ShareExtensionAction) { when (action) { is ShareExtensionAction.ShowAssignmentUploadDialog -> { - val bundle = FileUploadDialogFragment.createAssignmentBundle(action.fileUri, action.course as Course, action.assignment) + val bundle = FileUploadDialogFragment.createAssignmentBundle(action.fileUris, action.course as Course, action.assignment) showUploadDialog(bundle, action.dialogCallback) } is ShareExtensionAction.ShowMyFilesUploadDialog -> { - val bundle = FileUploadDialogFragment.createFilesBundle(action.fileUri, null) + val bundle = FileUploadDialogFragment.createFilesBundle(action.fileUris, null) showUploadDialog(bundle, action.dialogCallback) } is ShareExtensionAction.ShowToast -> { diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionViewModel.kt index fd4bd7c65f..016a526f4e 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionViewModel.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/shareextension/ShareExtensionViewModel.kt @@ -38,7 +38,7 @@ class ShareExtensionViewModel @Inject constructor( private val resources: Resources ) : ViewModel() { - var uri: Uri? = null + var uris: ArrayList? = null var uploadType = FileUploadType.USER val events: LiveData> @@ -53,17 +53,26 @@ class ShareExtensionViewModel @Inject constructor( val action = intent.action val type = intent.type - uri = if (Intent.ACTION_SEND == action && type != null) { - intent.getParcelableExtra(Intent.EXTRA_STREAM) - } else { + if (type == null) { _events.postValue(Event(ShareExtensionAction.ShowToast(resources.getString(R.string.uploadingFromSourceFailed)))) - null + return + } + when (action) { + Intent.ACTION_SEND -> { + val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) + uri?.let { + uris = arrayListOf(it) + } ?: _events.postValue(Event(ShareExtensionAction.ShowToast(resources.getString(R.string.errorOccurred)))) + } + Intent.ACTION_SEND_MULTIPLE -> { + uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) + } } } fun showUploadDialog(course: CanvasContext?, assignment: Assignment?, uploadType: FileUploadType) { this.uploadType = uploadType - uri?.let { + uris?.let { when (uploadType) { FileUploadType.USER -> _events.postValue(Event(ShareExtensionAction.ShowMyFilesUploadDialog(it, this::uploadDialogCallback))) FileUploadType.ASSIGNMENT -> { @@ -109,8 +118,8 @@ class ShareExtensionViewModel @Inject constructor( } sealed class ShareExtensionAction { - data class ShowAssignmentUploadDialog(val course: CanvasContext, val assignment: Assignment, val fileUri: Uri, val uploadType: FileUploadType, val dialogCallback: (Int) -> Unit) : ShareExtensionAction() - data class ShowMyFilesUploadDialog(val fileUri: Uri, val dialogCallback: (Int) -> Unit) : ShareExtensionAction() + data class ShowAssignmentUploadDialog(val course: CanvasContext, val assignment: Assignment, val fileUris: ArrayList, val uploadType: FileUploadType, val dialogCallback: (Int) -> Unit) : ShareExtensionAction() + data class ShowMyFilesUploadDialog(val fileUris: ArrayList, val dialogCallback: (Int) -> Unit) : ShareExtensionAction() object ShowProgressDialog : ShareExtensionAction() object ShowSuccessDialog : ShareExtensionAction() object Finish : ShareExtensionAction() diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/Const.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/Const.kt index 8d70bd66cd..2898509004 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/Const.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/Const.kt @@ -87,6 +87,7 @@ object Const { const val UNREAD = "unread" const val UPLOAD_TYPE = "uploadType" const val URI = "uri" + const val URIS = "uris" const val URL = "url" const val USER = "user" const val USER_ID = "userId" diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/file/upload/FileUploadViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/file/upload/FileUploadViewModelTest.kt index 92e89e9c96..a118f81219 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/file/upload/FileUploadViewModelTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/file/upload/FileUploadViewModelTest.kt @@ -101,7 +101,7 @@ class FileUploadViewModelTest { val viewModel = createViewModel() val course = createCourse(1L, "Course 1") val assignment = createAssignment(1L, "Assignment 1", 1L, listOf("pdf", "mp4", "docx")) - viewModel.setData(assignment, null, FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) + viewModel.setData(assignment, arrayListOf(), FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) viewModel.data.observe(lifecycleOwner) {} @@ -114,7 +114,7 @@ class FileUploadViewModelTest { val viewModel = createViewModel() val course = createCourse(1L, "Course 1") val assignment = createAssignment(1L, "Assignment 1", 1L, listOf("pdf")) - viewModel.setData(assignment, null, FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) + viewModel.setData(assignment, arrayListOf(), FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) every { fileUploadUtilsHelper.getFileSubmitObjectFromInputStream(any(), any(), any()) } returns createSubmitObject("test.pdf") @@ -132,7 +132,7 @@ class FileUploadViewModelTest { val viewModel = createViewModel() val course = createCourse(1L, "Course 1") val assignment = createAssignment(1L, "Assignment 1", 1L, listOf("pdf")) - viewModel.setData(assignment, null, FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) + viewModel.setData(assignment, arrayListOf(), FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) every { fileUploadUtilsHelper.getFileSubmitObjectFromInputStream(any(), any(), any()) } returns createSubmitObject("test.doc") @@ -150,7 +150,7 @@ class FileUploadViewModelTest { val viewModel = createViewModel() val course = createCourse(1L, "Course 1") val assignment = createAssignment(1L, "Assignment 1", 1L) - viewModel.setData(assignment, null, FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) + viewModel.setData(assignment, arrayListOf(), FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) viewModel.uploadFiles() @@ -163,7 +163,7 @@ class FileUploadViewModelTest { val viewModel = createViewModel() val course = createCourse(1L, "Course 1") val assignment = createAssignment(1L, "Assignment 1", 1L, submissionTypes = listOf("online_text_entry")) - viewModel.setData(assignment, null, FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) + viewModel.setData(assignment, arrayListOf(), FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) viewModel.addFile(uri) viewModel.uploadFiles() @@ -181,7 +181,7 @@ class FileUploadViewModelTest { every { fileUploadUtilsHelper.getFileSubmitObjectFromInputStream(any(), any(), any()) } returns submitObject - viewModel.setData(assignment, null, FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) + viewModel.setData(assignment, arrayListOf(), FileUploadType.ASSIGNMENT, course, -1L, -1L, -1, -1L) viewModel.data.observe(lifecycleOwner) {} viewModel.events.observe(lifecycleOwner) {} From 734162308a8206429e4142992d0144461fb8e4a7 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Wed, 14 Sep 2022 10:41:14 +0200 Subject: [PATCH 084/115] [MBL-16252][Student][Teacher] Revert dark mode popup refs: MBL-16252 affects: Student, Teacher release note: none --- .../ui/utils/StudentActivityTestRule.kt | 4 + .../student/activity/NavigationActivity.kt | 8 +- .../main/res/values/themes_canvastheme.xml | 1 + .../ui/utils/TeacherActivityTestRule.kt | 4 + .../teacher/activities/InitActivity.kt | 7 + apps/teacher/src/main/res/values/styles.xml | 1 + .../src/main/res/drawable/bg_bottom_sheet.xml | 25 ++++ .../main/res/drawable/ic_dark_light_theme.xml | 10 ++ .../src/main/res/values-ar/strings.xml | 7 + .../main/res/values-b+da+instk12/strings.xml | 7 + .../res/values-b+en+AU+unimelb/strings.xml | 7 + .../res/values-b+en+GB+instukhe/strings.xml | 7 + .../main/res/values-b+nb+instk12/strings.xml | 7 + .../main/res/values-b+sv+instk12/strings.xml | 7 + .../src/main/res/values-b+zh+Hans/strings.xml | 7 + .../src/main/res/values-b+zh+Hant/strings.xml | 7 + .../src/main/res/values-ca/strings.xml | 7 + .../src/main/res/values-cy/strings.xml | 7 + .../src/main/res/values-da/strings.xml | 7 + .../src/main/res/values-de/strings.xml | 7 + .../src/main/res/values-en-rAU/strings.xml | 7 + .../src/main/res/values-en-rCA/strings.xml | 7 + .../src/main/res/values-en-rCY/strings.xml | 7 + .../src/main/res/values-en-rGB/strings.xml | 7 + .../src/main/res/values-es-rES/strings.xml | 7 + .../src/main/res/values-es/strings.xml | 7 + .../src/main/res/values-fi/strings.xml | 7 + .../src/main/res/values-fr-rCA/strings.xml | 7 + .../src/main/res/values-fr/strings.xml | 7 + .../src/main/res/values-ht/strings.xml | 7 + .../src/main/res/values-is/strings.xml | 7 + .../src/main/res/values-it/strings.xml | 7 + .../src/main/res/values-ja/strings.xml | 7 + .../src/main/res/values-mi/strings.xml | 7 + .../src/main/res/values-nb/strings.xml | 7 + .../src/main/res/values-nl/strings.xml | 7 + .../src/main/res/values-pl/strings.xml | 7 + .../src/main/res/values-pt-rBR/strings.xml | 7 + .../src/main/res/values-pt-rPT/strings.xml | 7 + .../src/main/res/values-ru/strings.xml | 7 + .../src/main/res/values-sl/strings.xml | 7 + .../src/main/res/values-sv/strings.xml | 7 + .../src/main/res/values-th/strings.xml | 7 + .../src/main/res/values-vi/strings.xml | 7 + .../src/main/res/values-zh-rHK/strings.xml | 7 + .../src/main/res/values-zh/strings.xml | 7 + libs/pandares/src/main/res/values/strings.xml | 8 + .../themeselector/ThemeSelectorBottomSheet.kt | 67 +++++++++ .../pandautils/utils/ThemePrefs.kt | 4 +- .../layout/bottom_sheet_theme_selector.xml | 141 ++++++++++++++++++ .../main/res/values/themes_canvasthemes.xml | 7 + 51 files changed, 551 insertions(+), 2 deletions(-) create mode 100644 libs/pandares/src/main/res/drawable/bg_bottom_sheet.xml create mode 100644 libs/pandares/src/main/res/drawable/ic_dark_light_theme.xml create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/themeselector/ThemeSelectorBottomSheet.kt create mode 100644 libs/pandautils/src/main/res/layout/bottom_sheet_theme_selector.xml diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentActivityTestRule.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentActivityTestRule.kt index afba726dc8..bace30a97b 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentActivityTestRule.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/utils/StudentActivityTestRule.kt @@ -23,6 +23,7 @@ import com.instructure.student.util.StudentPrefs import com.instructure.espresso.InstructureActivityTestRule import com.instructure.loginapi.login.util.PreviousUsersUtils import com.instructure.pandautils.utils.PandaAppResetter +import com.instructure.pandautils.utils.ThemePrefs class StudentActivityTestRule(activityClass: Class) : InstructureActivityTestRule(activityClass) { @@ -31,6 +32,9 @@ class StudentActivityTestRule(activityClass: Class) : Instructu StudentPrefs.clearPrefs() CacheControlFlags.clearPrefs() PreviousUsersUtils.clear(context) + + // We need to set this true so the theme selector won't stop our tests. + ThemePrefs.themeSelectionShown = true } } diff --git a/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt b/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt index daba0f24cb..ed14703185 100644 --- a/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt +++ b/apps/student/src/main/java/com/instructure/student/activity/NavigationActivity.kt @@ -67,7 +67,7 @@ import com.instructure.loginapi.login.dialog.MasqueradingDialog import com.instructure.loginapi.login.tasks.LogoutTask import com.instructure.pandautils.features.help.HelpDialogFragment import com.instructure.pandautils.features.notification.preferences.PushNotificationPreferencesFragment - +import com.instructure.pandautils.features.themeselector.ThemeSelectorBottomSheet import com.instructure.pandautils.models.PushNotification import com.instructure.pandautils.receivers.PushExternalReceiver import com.instructure.pandautils.typeface.TypefaceBehavior @@ -261,6 +261,12 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. val savedBottomScreens = savedInstanceState?.getStringArrayList(BOTTOM_SCREENS_BUNDLE_KEY) restoreBottomNavState(savedBottomScreens) + + if (!ThemePrefs.themeSelectionShown) { + val themeSelector = ThemeSelectorBottomSheet() + themeSelector.show(supportFragmentManager, ThemeSelectorBottomSheet::javaClass.name) + ThemePrefs.themeSelectionShown = true + } } private fun restoreBottomNavState(savedBottomScreens: List?) { diff --git a/apps/student/src/main/res/values/themes_canvastheme.xml b/apps/student/src/main/res/values/themes_canvastheme.xml index b94010c29e..5a0ec587d4 100755 --- a/apps/student/src/main/res/values/themes_canvastheme.xml +++ b/apps/student/src/main/res/values/themes_canvastheme.xml @@ -32,6 +32,7 @@ true @style/CanvasDialogTheme_Default @style/CanvasDialogTheme_Default + @style/AppBottomSheetDialogTheme @style/PropertyInspector @style/ContextualToolbarStyle @style/AnnotationCreationToolbarIconsStyle diff --git a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherActivityTestRule.kt b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherActivityTestRule.kt index 1cdebb5045..1b41aeca71 100644 --- a/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherActivityTestRule.kt +++ b/apps/teacher/src/androidTest/java/com/instructure/teacher/ui/utils/TeacherActivityTestRule.kt @@ -21,6 +21,7 @@ import android.content.Context import com.instructure.espresso.InstructureActivityTestRule import com.instructure.loginapi.login.util.PreviousUsersUtils import com.instructure.pandautils.utils.PandaAppResetter +import com.instructure.pandautils.utils.ThemePrefs import com.instructure.teacher.utils.TeacherPrefs class TeacherActivityTestRule(activityClass: Class) : InstructureActivityTestRule(activityClass) { @@ -29,6 +30,9 @@ class TeacherActivityTestRule(activityClass: Class) : Instructur PandaAppResetter.reset(context) TeacherPrefs.safeClearPrefs() PreviousUsersUtils.clear(context) + + // We need to set this true so the theme selector won't stop our tests. + ThemePrefs.themeSelectionShown = true } } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt b/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt index 8346569e4d..8642112373 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/activities/InitActivity.kt @@ -55,6 +55,7 @@ import com.instructure.loginapi.login.tasks.LogoutTask import com.instructure.pandautils.activities.BasePresenterActivity import com.instructure.pandautils.dialogs.RatingDialog import com.instructure.pandautils.features.help.HelpDialogFragment +import com.instructure.pandautils.features.themeselector.ThemeSelectorBottomSheet import com.instructure.pandautils.models.PushNotification import com.instructure.pandautils.receivers.PushExternalReceiver import com.instructure.pandautils.typeface.TypefaceBehavior @@ -162,6 +163,12 @@ class InitActivity : BasePresenterActivity@color/textDark @style/CanvasDialogTheme_Default @style/CanvasDialogTheme_Default + @style/AppBottomSheetDialogTheme @color/backgroundLightest @style/Widget.ActionButton.Overflow diff --git a/libs/pandares/src/main/res/drawable/bg_bottom_sheet.xml b/libs/pandares/src/main/res/drawable/bg_bottom_sheet.xml new file mode 100644 index 0000000000..1c8a12bf12 --- /dev/null +++ b/libs/pandares/src/main/res/drawable/bg_bottom_sheet.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/libs/pandares/src/main/res/drawable/ic_dark_light_theme.xml b/libs/pandares/src/main/res/drawable/ic_dark_light_theme.xml new file mode 100644 index 0000000000..3dd9bc14e7 --- /dev/null +++ b/libs/pandares/src/main/res/drawable/ic_dark_light_theme.xml @@ -0,0 +1,10 @@ + + + diff --git a/libs/pandares/src/main/res/values-ar/strings.xml b/libs/pandares/src/main/res/values-ar/strings.xml index 9db8ce883b..15b0ad4877 100644 --- a/libs/pandares/src/main/res/values-ar/strings.xml +++ b/libs/pandares/src/main/res/values-ar/strings.xml @@ -1317,5 +1317,12 @@ فاتح داكن مثل الجهاز + Canvas متاح الآن في النسق الداكن + اختر نسق التطبيق + النسق الفاتح + النسق الداكن + مثل نسق الجهاز + حفظ + يمكنك تغييرها لاحقًا في إعدادات التطبيق تحديد diff --git a/libs/pandares/src/main/res/values-b+da+instk12/strings.xml b/libs/pandares/src/main/res/values-b+da+instk12/strings.xml index e3d16381ac..c975f0e076 100644 --- a/libs/pandares/src/main/res/values-b+da+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+da+instk12/strings.xml @@ -1262,5 +1262,12 @@ Lys Mørk Samme som enhed + Canvas er nu tilgængelig i mørkt tema + Vælg app-tema + Lyst tema + Mørkt tema + Samme tema som på enhed + Gem + Du kan ændre det senere i app-indstillinger Tag fat diff --git a/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml b/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml index d033761fcf..cf3e1bf89f 100644 --- a/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml +++ b/libs/pandares/src/main/res/values-b+en+AU+unimelb/strings.xml @@ -1262,5 +1262,12 @@ Light Dark Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings Grab diff --git a/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml b/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml index f58d7a871a..9b7f1fc049 100644 --- a/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml +++ b/libs/pandares/src/main/res/values-b+en+GB+instukhe/strings.xml @@ -1262,5 +1262,12 @@ Light Dark Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings Grab diff --git a/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml b/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml index e2dbf331b9..16816624d0 100644 --- a/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+nb+instk12/strings.xml @@ -1263,5 +1263,12 @@ Lys Mørk Samme som enhet + Canvas er nå tilgjengelig med mørkt tema + Velg app-tema + Lyst tema + Mørkt tema + Samme som enhetstema + Lagre + Du kan endre det senere i appinnstillinger Grip diff --git a/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml b/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml index 9adf0e613e..fd001e51fb 100644 --- a/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml +++ b/libs/pandares/src/main/res/values-b+sv+instk12/strings.xml @@ -1262,5 +1262,12 @@ Ljus Mörk Samma som enhet + Canvas finns inte tillgängligt med ett mörkt tema + Välj apptema + Ljust tema + Mörkt tema + Samma tema som enheten + Spara + Du kan ändra det sedan i appinställningarna Ta tag i diff --git a/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml b/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml index ec26a77179..b5ea856bd0 100644 --- a/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+Hans/strings.xml @@ -1248,5 +1248,12 @@ 浅色的、轻的 深色的、黑的 与设备相同 + Canvas 已推出深色主题 + 选择应用程序主题 + 浅色主题 + 深色主题 + 与设备主题相同 + 保存 + 您可以后在应用设置中更改 抓取图像 diff --git a/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml b/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml index 0dad18a366..6808be99dc 100644 --- a/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml +++ b/libs/pandares/src/main/res/values-b+zh+Hant/strings.xml @@ -1248,5 +1248,12 @@ 亮色 暗色 和裝置相同 + Canvas 現在可在暗色主題中使用 + 選擇應用程式主題 + 淡色主題 + 暗色主題 + 和裝置主題相同 + 儲存 + 您可以稍後在應用桯式設定中變更 擷取 diff --git a/libs/pandares/src/main/res/values-ca/strings.xml b/libs/pandares/src/main/res/values-ca/strings.xml index 32e3c1c4ba..84f6eaac65 100644 --- a/libs/pandares/src/main/res/values-ca/strings.xml +++ b/libs/pandares/src/main/res/values-ca/strings.xml @@ -1263,5 +1263,12 @@ Clar Fosc El mateix que el dispositiu + Ara, el Canvas està disponible en tema fosc + Trieu el tema de l’aplicació + Tema clar + Tema fosc + El mateix tema que el del dispositiu + Desa + Podeu canviar-lo més endavant a la configuració de l’aplicació Agafa diff --git a/libs/pandares/src/main/res/values-cy/strings.xml b/libs/pandares/src/main/res/values-cy/strings.xml index 85324c198c..95b5760db0 100644 --- a/libs/pandares/src/main/res/values-cy/strings.xml +++ b/libs/pandares/src/main/res/values-cy/strings.xml @@ -1262,5 +1262,12 @@ Golau Tywyll Yn un fath a’r ddyfais + Mae Canvas bellach ar gael mewn thema dywyll + Dewis thema ap + Thema golau + Thema dywyll + Yr un fath a thema’r ddyfais + Cadw + Gallwch chi ei newid yn nes ymlaen yng ngosodiadau’r ap Gafael diff --git a/libs/pandares/src/main/res/values-da/strings.xml b/libs/pandares/src/main/res/values-da/strings.xml index c5bc04f866..c6f82e0923 100644 --- a/libs/pandares/src/main/res/values-da/strings.xml +++ b/libs/pandares/src/main/res/values-da/strings.xml @@ -1262,5 +1262,12 @@ Lys Mørk Samme som enhed + Canvas er nu tilgængelig i mørkt tema + Vælg app-tema + Lyst tema + Mørkt tema + Samme tema som på enhed + Gem + Du kan ændre det senere i app-indstillinger Tag fat diff --git a/libs/pandares/src/main/res/values-de/strings.xml b/libs/pandares/src/main/res/values-de/strings.xml index 605bfcff28..16b28eec4b 100644 --- a/libs/pandares/src/main/res/values-de/strings.xml +++ b/libs/pandares/src/main/res/values-de/strings.xml @@ -1262,5 +1262,12 @@ Hell Dunkel Wie Gerät + Canvas ist jetzt in dunklem Design verfügbar + App-Design auswählen + Helles Design + Dunkles Design + Entspricht dem Gerätedesign + Speichern + Sie können es später in den App-Einstellungen ändern. Greifen diff --git a/libs/pandares/src/main/res/values-en-rAU/strings.xml b/libs/pandares/src/main/res/values-en-rAU/strings.xml index 6e9ef7a113..e6e3c9557a 100644 --- a/libs/pandares/src/main/res/values-en-rAU/strings.xml +++ b/libs/pandares/src/main/res/values-en-rAU/strings.xml @@ -1262,5 +1262,12 @@ Light Dark Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings Grab diff --git a/libs/pandares/src/main/res/values-en-rCA/strings.xml b/libs/pandares/src/main/res/values-en-rCA/strings.xml index 07291c8e2b..33b713916c 100644 --- a/libs/pandares/src/main/res/values-en-rCA/strings.xml +++ b/libs/pandares/src/main/res/values-en-rCA/strings.xml @@ -1268,6 +1268,13 @@ Dark Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings Grab diff --git a/libs/pandares/src/main/res/values-en-rCY/strings.xml b/libs/pandares/src/main/res/values-en-rCY/strings.xml index f58d7a871a..9b7f1fc049 100644 --- a/libs/pandares/src/main/res/values-en-rCY/strings.xml +++ b/libs/pandares/src/main/res/values-en-rCY/strings.xml @@ -1262,5 +1262,12 @@ Light Dark Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings Grab diff --git a/libs/pandares/src/main/res/values-en-rGB/strings.xml b/libs/pandares/src/main/res/values-en-rGB/strings.xml index 6f9d6ca4ee..a669e567ba 100644 --- a/libs/pandares/src/main/res/values-en-rGB/strings.xml +++ b/libs/pandares/src/main/res/values-en-rGB/strings.xml @@ -1262,5 +1262,12 @@ Light Dark Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings Grab diff --git a/libs/pandares/src/main/res/values-es-rES/strings.xml b/libs/pandares/src/main/res/values-es-rES/strings.xml index ed083dd091..6d241f41d2 100644 --- a/libs/pandares/src/main/res/values-es-rES/strings.xml +++ b/libs/pandares/src/main/res/values-es-rES/strings.xml @@ -1264,5 +1264,12 @@ Iluminado Oscuro Coincidir con el dispositivo + Canvas está ahora disponible en tema oscuro + Elegir el tema de la aplicación + Tema claro + Tema oscuro + Coincidir con el tema del dispositivo + Guardar + Se puede cambiar después en los ajustes de la aplicación Captar diff --git a/libs/pandares/src/main/res/values-es/strings.xml b/libs/pandares/src/main/res/values-es/strings.xml index 2e96201470..d3060859f7 100644 --- a/libs/pandares/src/main/res/values-es/strings.xml +++ b/libs/pandares/src/main/res/values-es/strings.xml @@ -1262,5 +1262,12 @@ Claro Oscuro Igual que el dispositivo + Canvas está ahora disponible en tema oscuro + Elegir el tema de la aplicación + Tema claro + Tema oscuro + Igual que el tema del dispositivo + Guardar + Puede cambiarlo más tarde en las configuraciones de la aplicación Capturar diff --git a/libs/pandares/src/main/res/values-fi/strings.xml b/libs/pandares/src/main/res/values-fi/strings.xml index 4f851a7531..42163d106c 100644 --- a/libs/pandares/src/main/res/values-fi/strings.xml +++ b/libs/pandares/src/main/res/values-fi/strings.xml @@ -1262,5 +1262,12 @@ Valo Tumma Sama kuin laite + Canvas on nyt saatavilla tummana teema + Valitse sovelluksen teema + Vaalea teema + Tumma teema + Sama kuin laitteen teema + Tallenna + Voit vaihtaa sen myöhemmin sovellusasetuksissa Nappaa diff --git a/libs/pandares/src/main/res/values-fr-rCA/strings.xml b/libs/pandares/src/main/res/values-fr-rCA/strings.xml index 4a790e24c9..4aa2cfbd87 100644 --- a/libs/pandares/src/main/res/values-fr-rCA/strings.xml +++ b/libs/pandares/src/main/res/values-fr-rCA/strings.xml @@ -1262,5 +1262,12 @@ Clair Foncé Identique au dispositif + Canvas est désormais disponible en thème sombre + Choisissez le thème de l’application + Thème clair + Thème sombre + Identique au thème du dispositif + Enregistrer + Vous pouvez le changer plus tard dans les paramètres de l’application Attraper diff --git a/libs/pandares/src/main/res/values-fr/strings.xml b/libs/pandares/src/main/res/values-fr/strings.xml index 66a82833e0..485ec478e4 100644 --- a/libs/pandares/src/main/res/values-fr/strings.xml +++ b/libs/pandares/src/main/res/values-fr/strings.xml @@ -1262,5 +1262,12 @@ Clair Foncé Identique à l’appareil + Canvas est désormais disponible en thème sombre + Sélectionnez le thème de l’application + Thème clair + Thème sombre + Identique au thème de l’appareil + Enregistrer + Vous pourrez modifier le thème ultérieurement dans les paramètres de l’application Attraper diff --git a/libs/pandares/src/main/res/values-ht/strings.xml b/libs/pandares/src/main/res/values-ht/strings.xml index e47be074bb..9e39f808a0 100644 --- a/libs/pandares/src/main/res/values-ht/strings.xml +++ b/libs/pandares/src/main/res/values-ht/strings.xml @@ -1262,5 +1262,12 @@ Limyè Nwa Menm ak aparèy la + Canvas disponib kounye a an mòd sonm + Chwazi app pou chanje mòd la + Mòd klere + Mòd sonm + Menm ak mòd aparèy la + Anrejistre + Ou ka chanje l apre nan paramèt app la. Sezi diff --git a/libs/pandares/src/main/res/values-is/strings.xml b/libs/pandares/src/main/res/values-is/strings.xml index 0156a983f0..91bb520a1e 100644 --- a/libs/pandares/src/main/res/values-is/strings.xml +++ b/libs/pandares/src/main/res/values-is/strings.xml @@ -1262,5 +1262,12 @@ Ljóst Dökkt Sama og tæki + Canvas er nú í boði með dökku þema + Veldu þema smáforrits + Ljóst þema + Dökkt þema + Sama og þema tækis + Vista + Þú getur breytt því síðar í stillingum smáforrits Grípa diff --git a/libs/pandares/src/main/res/values-it/strings.xml b/libs/pandares/src/main/res/values-it/strings.xml index cc15565b1e..67b958657e 100644 --- a/libs/pandares/src/main/res/values-it/strings.xml +++ b/libs/pandares/src/main/res/values-it/strings.xml @@ -1262,5 +1262,12 @@ Chiaro Scuro Come il dispositivo + Canvas è ora disponibile in tema scuso + Scegli tema app + Tema chiaro + Tema scuro + Come il tema dispositivo + Salva + Puoi modificarlo successivamente nelle impostazioni dell’app Afferra diff --git a/libs/pandares/src/main/res/values-ja/strings.xml b/libs/pandares/src/main/res/values-ja/strings.xml index 20f0ebfd14..99d2b39b07 100644 --- a/libs/pandares/src/main/res/values-ja/strings.xml +++ b/libs/pandares/src/main/res/values-ja/strings.xml @@ -1248,5 +1248,12 @@ 明るい 暗い デバイスと同じ + Canvasがダークテーマで利用可能に + アプリのテーマを選ぶ + ライトテーマ + ダークテーマ + デバイスのテーマと同じ + 保存 + 後でアプリの設定から変更可能 捕捉 diff --git a/libs/pandares/src/main/res/values-mi/strings.xml b/libs/pandares/src/main/res/values-mi/strings.xml index 4c259f072b..55767377ad 100644 --- a/libs/pandares/src/main/res/values-mi/strings.xml +++ b/libs/pandares/src/main/res/values-mi/strings.xml @@ -1262,5 +1262,12 @@ Marama Pōuri Rite tonu ka rite ki pūrere + Canvas ko inaianei e wātea ana i te pouri kaupapa + Whiriwhiria taupānga kaupapa + Maama kaupapa + Pouriuri kaupapa + Rite tonu ka rite ki pūrere kaupapa + Tiaki + Ka taea e koe te whakarereke i muri mai i te taupānga tautuhinga Kōrapurapu diff --git a/libs/pandares/src/main/res/values-nb/strings.xml b/libs/pandares/src/main/res/values-nb/strings.xml index 84a8208a14..2d3199d921 100644 --- a/libs/pandares/src/main/res/values-nb/strings.xml +++ b/libs/pandares/src/main/res/values-nb/strings.xml @@ -1263,5 +1263,12 @@ Lys Mørk Samme som enhet + Canvas er nå tilgjengelig med mørkt tema + Velg app-tema + Lyst tema + Mørkt tema + Samme som enhetstema + Lagre + Du kan endre det senere i appinnstillinger Grip diff --git a/libs/pandares/src/main/res/values-nl/strings.xml b/libs/pandares/src/main/res/values-nl/strings.xml index 373d3015c1..482387492b 100644 --- a/libs/pandares/src/main/res/values-nl/strings.xml +++ b/libs/pandares/src/main/res/values-nl/strings.xml @@ -1262,5 +1262,12 @@ Licht Donker Hetzelfde als apparaat + Canvas is niet beschikbaar in donker thema + Kies app-thema + Licht thema + Donker thema + Hetzelfde als apparaatthema + Opslaan + Je kunt dit later wijzigen in de app-instellingen. Pakken diff --git a/libs/pandares/src/main/res/values-pl/strings.xml b/libs/pandares/src/main/res/values-pl/strings.xml index 0bab7939fd..36da65abbf 100644 --- a/libs/pandares/src/main/res/values-pl/strings.xml +++ b/libs/pandares/src/main/res/values-pl/strings.xml @@ -1290,5 +1290,12 @@ Jasny Ciemny Taki sam jak urządzenia + Dla Canvas jest teraz dostępny ciemny motyw + Wybierz motyw aplikacji + Jasny motyw + Ciemny motyw + Taki sam jak motyw urządzenia + Zapisz + Można zmienić go później w Ustawieniach aplikacji Chwyć diff --git a/libs/pandares/src/main/res/values-pt-rBR/strings.xml b/libs/pandares/src/main/res/values-pt-rBR/strings.xml index 83370fba79..c0f7d685d4 100644 --- a/libs/pandares/src/main/res/values-pt-rBR/strings.xml +++ b/libs/pandares/src/main/res/values-pt-rBR/strings.xml @@ -1262,5 +1262,12 @@ Claro Escuro Igual ao dispositivo + O Canvas agora está disponível no tema escuro + Escolha o tema do aplicativo + Tema claro + Tema escuro + Igual ao tema do dispositivo + Salvar + Você pode alterá-lo mais tarde nas configurações do aplicativo Pegar diff --git a/libs/pandares/src/main/res/values-pt-rPT/strings.xml b/libs/pandares/src/main/res/values-pt-rPT/strings.xml index 84f99375af..42e243f7a1 100644 --- a/libs/pandares/src/main/res/values-pt-rPT/strings.xml +++ b/libs/pandares/src/main/res/values-pt-rPT/strings.xml @@ -1262,5 +1262,12 @@ Claro Escuro O mesmo que dispositivo + O ecrã está agora disponível em tema escuro + Escolha o tema da aplicação + Tema claro + Tema escuro + O mesmo tema do dispositivo + Guardar + Pode alterá-lo mais tarde nas definições da aplicação Agarrar diff --git a/libs/pandares/src/main/res/values-ru/strings.xml b/libs/pandares/src/main/res/values-ru/strings.xml index e8d3cd3c2d..44e54a176e 100644 --- a/libs/pandares/src/main/res/values-ru/strings.xml +++ b/libs/pandares/src/main/res/values-ru/strings.xml @@ -1290,5 +1290,12 @@ Светлая Темная Аналогично устройству + Canvas теперь доступно в темной теме + Выбрать тему приложения + Светлая тема + Темная тема + Аналогично теме устройства + Сохранить + Вы можете изменить этот параметр позднее в настройках приложения Захват diff --git a/libs/pandares/src/main/res/values-sl/strings.xml b/libs/pandares/src/main/res/values-sl/strings.xml index 3c6ccf18ba..3d8364be0e 100644 --- a/libs/pandares/src/main/res/values-sl/strings.xml +++ b/libs/pandares/src/main/res/values-sl/strings.xml @@ -1262,5 +1262,12 @@ Svetlo Temno Enako kot naprava + Sistem Canvas je zdaj na voljo v temni preobleki + Izberite preobleko aplikacije + Svetla preobleka + Temna preobleka + Enako kot preobleka na napravi + Shrani + To lahko pozneje spremenite v nastavitvah aplikacije Zgrabi diff --git a/libs/pandares/src/main/res/values-sv/strings.xml b/libs/pandares/src/main/res/values-sv/strings.xml index 888ceb3dc0..c165c9eaa5 100644 --- a/libs/pandares/src/main/res/values-sv/strings.xml +++ b/libs/pandares/src/main/res/values-sv/strings.xml @@ -1262,5 +1262,12 @@ Ljus Mörk Samma som enhet + Canvas finns inte tillgängligt med ett mörkt tema + Välj apptema + Ljust tema + Mörkt tema + Samma tema som enheten + Spara + Du kan ändra det sedan i appinställningarna Ta tag i diff --git a/libs/pandares/src/main/res/values-th/strings.xml b/libs/pandares/src/main/res/values-th/strings.xml index c44253501b..a75508fd00 100644 --- a/libs/pandares/src/main/res/values-th/strings.xml +++ b/libs/pandares/src/main/res/values-th/strings.xml @@ -1262,5 +1262,12 @@ สว่าง มืด เหมือนกันกับอุปกรณ์ + Canvas พร้อมใช้งานในธีมมืดแล้ว + เลือกธีมของแอพ + ธีมสว่าง + ธีมมืด + ธีมเหมือนกับอุปกรณ์ + บันทึก + คุณสามารถแก้ไขได้ในภายหลังจากค่าปรับตั้งแอพ คว้าไว้ diff --git a/libs/pandares/src/main/res/values-vi/strings.xml b/libs/pandares/src/main/res/values-vi/strings.xml index f0ab481b35..7fbcc90ae8 100644 --- a/libs/pandares/src/main/res/values-vi/strings.xml +++ b/libs/pandares/src/main/res/values-vi/strings.xml @@ -1263,5 +1263,12 @@ Sáng Tối Tương tự như thiết bị + Canvas hiện nay đã có sẵn ở chủ đề tối + Lựa chọn chủ đề ứng dụng + Chủ đề sáng + Chủ đề tối + Tương tự như chủ đề thiết bị + Lưu + Bạn có thể thay đổi sau trong cài đặt ứng dụng Nắm lấy diff --git a/libs/pandares/src/main/res/values-zh-rHK/strings.xml b/libs/pandares/src/main/res/values-zh-rHK/strings.xml index 0dad18a366..6808be99dc 100644 --- a/libs/pandares/src/main/res/values-zh-rHK/strings.xml +++ b/libs/pandares/src/main/res/values-zh-rHK/strings.xml @@ -1248,5 +1248,12 @@ 亮色 暗色 和裝置相同 + Canvas 現在可在暗色主題中使用 + 選擇應用程式主題 + 淡色主題 + 暗色主題 + 和裝置主題相同 + 儲存 + 您可以稍後在應用桯式設定中變更 擷取 diff --git a/libs/pandares/src/main/res/values-zh/strings.xml b/libs/pandares/src/main/res/values-zh/strings.xml index ec26a77179..b5ea856bd0 100644 --- a/libs/pandares/src/main/res/values-zh/strings.xml +++ b/libs/pandares/src/main/res/values-zh/strings.xml @@ -1248,5 +1248,12 @@ 浅色的、轻的 深色的、黑的 与设备相同 + Canvas 已推出深色主题 + 选择应用程序主题 + 浅色主题 + 深色主题 + 与设备主题相同 + 保存 + 您可以后在应用设置中更改 抓取图像 diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index cfa06b55f6..6f8d1fb058 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -1267,6 +1267,14 @@ Dark Same as device + Canvas is now available in dark theme + Choose app theme + Light theme + Dark theme + Same as device theme + Save + You can change it later in app settings + Grab File Upload diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/themeselector/ThemeSelectorBottomSheet.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/themeselector/ThemeSelectorBottomSheet.kt new file mode 100644 index 0000000000..b4f46cfcbb --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/themeselector/ThemeSelectorBottomSheet.kt @@ -0,0 +1,67 @@ +/* + * 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.pandautils.features.themeselector + +import android.content.DialogInterface +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.widget.CompoundButtonCompat +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.instructure.pandautils.R +import com.instructure.pandautils.utils.AppTheme +import com.instructure.pandautils.utils.ThemePrefs +import com.instructure.pandautils.utils.ViewStyler +import com.instructure.pandautils.utils.onClick +import kotlinx.android.synthetic.main.bottom_sheet_theme_selector.* + +class ThemeSelectorBottomSheet : BottomSheetDialogFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.bottom_sheet_theme_selector, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val radioButtonColor = ViewStyler.makeColorStateListForRadioGroup(requireContext().getColor(R.color.textDarkest), requireContext().getColor(R.color.textInfo)) + CompoundButtonCompat.setButtonTintList(buttonLightTheme, radioButtonColor) + CompoundButtonCompat.setButtonTintList(buttonDarkTheme, radioButtonColor) + CompoundButtonCompat.setButtonTintList(buttonDeviceTheme, radioButtonColor) + + saveButton.onClick { + val appTheme = when { + buttonLightTheme.isChecked -> AppTheme.LIGHT + buttonDarkTheme.isChecked -> AppTheme.DARK + else -> AppTheme.SYSTEM + } + setAppTheme(appTheme) + } + } + + private fun setAppTheme(appTheme: AppTheme) { + AppCompatDelegate.setDefaultNightMode(appTheme.nightModeType) + ThemePrefs.appTheme = appTheme.ordinal + dismiss() + } + +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt index 29549cdf8b..bea672ec29 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ThemePrefs.kt @@ -54,7 +54,9 @@ object ThemePrefs : PrefManager("CanvasTheme") { var appTheme by IntPref(defaultValue = 0) - override fun keepBaseProps() = listOf(::appTheme) + var themeSelectionShown by BooleanPref() + + override fun keepBaseProps() = listOf(::appTheme, ::themeSelectionShown) override fun onClearPrefs() { } diff --git a/libs/pandautils/src/main/res/layout/bottom_sheet_theme_selector.xml b/libs/pandautils/src/main/res/layout/bottom_sheet_theme_selector.xml new file mode 100644 index 0000000000..4204d580a9 --- /dev/null +++ b/libs/pandautils/src/main/res/layout/bottom_sheet_theme_selector.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + +