From f7325a347c992840314c95162518dd54e7a8ce3d Mon Sep 17 00:00:00 2001 From: "kristof.nemere" Date: Mon, 22 Sep 2025 11:25:11 +0200 Subject: [PATCH 01/58] Release Teacher 2.1.0 (83) --- 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 e876ef6541..d1fd288a7d 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -35,8 +35,8 @@ android { defaultConfig { minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 82 - versionName = '2.0.1' + versionCode = 83 + versionName = '2.1.0' vectorDrawables.useSupportLibrary = true testInstrumentationRunner 'com.instructure.teacher.ui.espresso.TeacherHiltTestRunner' testInstrumentationRunnerArguments disableAnalytics: 'true' From b98ae4805f11eb496be14c4eb784df082e336291 Mon Sep 17 00:00:00 2001 From: "kristof.nemere" Date: Mon, 22 Sep 2025 11:26:10 +0200 Subject: [PATCH 02/58] Release Parent 4.6.0 (62) --- apps/parent/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/parent/build.gradle b/apps/parent/build.gradle index d8c7168f16..047bcbd08e 100644 --- a/apps/parent/build.gradle +++ b/apps/parent/build.gradle @@ -40,8 +40,8 @@ android { applicationId "com.instructure.parentapp" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode 61 - versionName "4.5.0" + versionCode 62 + versionName "4.6.0" buildConfigField "boolean", "IS_TESTING", "false" testInstrumentationRunner 'com.instructure.parentapp.ui.espresso.ParentHiltTestRunner' From f0ef6eee3e4a8eca1d6ffcf5c3eced8a504956bf Mon Sep 17 00:00:00 2001 From: "kristof.nemere" Date: Fri, 26 Sep 2025 13:40:27 +0200 Subject: [PATCH 03/58] Rubric score input fix --- .../grade/rubric/SpeedGraderRubricScreen.kt | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/rubric/SpeedGraderRubricScreen.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/rubric/SpeedGraderRubricScreen.kt index e07d94873b..511145b25f 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/rubric/SpeedGraderRubricScreen.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/speedgrader/grade/rubric/SpeedGraderRubricScreen.kt @@ -111,7 +111,9 @@ fun SpeedGraderRubricContent(uiState: SpeedGraderRubricUiState) { .padding(vertical = 12.dp, horizontal = 16.dp) ) { Text( - modifier = Modifier.padding(bottom = 14.dp).testTag("speedGraderRubricsLabel"), + modifier = Modifier + .padding(bottom = 14.dp) + .testTag("speedGraderRubricsLabel"), text = stringResource(R.string.rubricsTitle), fontSize = 16.sp, fontWeight = FontWeight.SemiBold, @@ -316,8 +318,19 @@ private fun RubricCriterion( if (expanded) { CanvasDivider() } - - var enteredPoint by remember(assessment) { mutableStateOf(assessment?.points) } + var textFieldScore by remember(assessment) { + mutableStateOf(assessment?.points?.stringValueWithoutTrailingZeros.orEmpty()) + } + var enteredPoint by remember(assessment) { + mutableStateOf(assessment?.points) + } + LaunchedEffect(textFieldScore) { + val scoreAsDouble = textFieldScore.toDoubleOrNull() + if (scoreAsDouble != enteredPoint) { + enteredPoint = scoreAsDouble + onPointChanged(enteredPoint.orDefault(), rubricCriterion.id) + } + } Row( modifier = Modifier .fillMaxWidth() @@ -332,11 +345,8 @@ private fun RubricCriterion( Spacer(modifier = Modifier.weight(1f)) BasicTextFieldWithHintDecoration( modifier = Modifier.padding(end = 8.dp), - value = enteredPoint?.stringValueWithoutTrailingZeros.orEmpty(), - onValueChange = { point -> - enteredPoint = point.toDoubleOrNull() - onPointChanged(enteredPoint.orDefault(), rubricCriterion.id) - }, + value = textFieldScore, + onValueChange = { textFieldScore = it }, hint = stringResource(R.string.rubricScoreHint), textColor = LocalCourseColor.current, hintColor = colorResource(R.color.textPlaceholder), From b31ca9d707ead87381281176de76e64875bbcc96 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Mon, 3 Nov 2025 11:32:34 +0100 Subject: [PATCH 04/58] Version bump --- 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 29113262da..6c81f7f6cc 100644 --- a/apps/teacher/build.gradle +++ b/apps/teacher/build.gradle @@ -55,8 +55,8 @@ android { defaultConfig { minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode = 82 - versionName = '2.0.1' + versionCode = 84 + versionName = '2.2.0' vectorDrawables.useSupportLibrary = true testInstrumentationRunner 'com.instructure.teacher.espresso.TeacherHiltTestRunner' testInstrumentationRunnerArguments disableAnalytics: 'true' From 4de084394299cf7bcff28247016bb9a851dfa6fa Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Mon, 3 Nov 2025 11:40:34 +0100 Subject: [PATCH 05/58] Version bump --- apps/parent/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/parent/build.gradle b/apps/parent/build.gradle index 1736c5549c..acfadaac45 100644 --- a/apps/parent/build.gradle +++ b/apps/parent/build.gradle @@ -41,8 +41,8 @@ android { applicationId "com.instructure.parentapp" minSdkVersion Versions.MIN_SDK targetSdkVersion Versions.TARGET_SDK - versionCode 61 - versionName "4.5.0" + versionCode 63 + versionName "4.7.0" buildConfigField "boolean", "IS_TESTING", "false" testInstrumentationRunner 'com.instructure.parentapp.ui.espresso.ParentHiltTestRunner' From 8ce3eb30d528165c75df6f367db32cdf48c47a09 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Mon, 3 Nov 2025 13:57:36 +0100 Subject: [PATCH 06/58] [MBL-19399][Student] To Do List UI Implementation (#3353) refs: MBL-19399 affects: Student release note: Added new To Do List view for students to see upcoming assignments and tasks * Feature skeleton and items UI. * Fetching data, mapping UI items. * DCP and today indicator. * Sticky header. * Text size changes. * Added bottom bar badge. * Pandas * Complein status. * Unit tests. * Changed dates to the correct default. * Added remote config flag. * Handle correct context name and removed unused imports. * Fixed RouterUtilsTest --- .../student/activity/CallbackActivity.kt | 34 + .../student/activity/NavigationActivity.kt | 32 +- .../student/di/feature/ToDoListModule.kt | 36 + .../todolist/StudentToDoListRouter.kt | 43 + .../features/todolist/ToDoListFragment.kt | 117 +++ ...ListFragment.kt => OldToDoListFragment.kt} | 8 +- .../navigation/DefaultNavigationBehavior.kt | 3 +- .../ElementaryNavigationBehavior.kt | 3 +- .../student/navigation/NavigationBehavior.kt | 13 + .../student/router/RouteMatcher.kt | 12 +- .../student/router/RouteResolver.kt | 4 +- .../student/widget/todo/ToDoWidgetUpdater.kt | 58 +- .../student/test/util/RouterUtilsTest.kt | 10 + .../canvasapi2/utils/RemoteConfigUtils.kt | 1 + libs/pandares/src/main/res/values/strings.xml | 9 + .../features/todolist/ToDoListRepository.kt | 81 ++ .../features/todolist/ToDoListRouter.kt | 25 + .../features/todolist/ToDoListScreen.kt | 755 ++++++++++++++++++ .../features/todolist/ToDoListUiState.kt | 60 ++ .../features/todolist/ToDoListViewModel.kt | 168 ++++ .../pandautils/utils/PlannerItemExtensions.kt | 58 ++ .../src/main/res/drawable/ic_panda_bottom.xml | 74 ++ .../src/main/res/drawable/ic_panda_top.xml | 45 ++ .../todolist/ToDoListRepositoryTest.kt | 410 ++++++++++ .../todolist/ToDoListViewModelTest.kt | 423 ++++++++++ .../utils/PlannerItemExtensionsTest.kt | 459 +++++++++++ 26 files changed, 2866 insertions(+), 75 deletions(-) create mode 100644 apps/student/src/main/java/com/instructure/student/di/feature/ToDoListModule.kt create mode 100644 apps/student/src/main/java/com/instructure/student/features/todolist/StudentToDoListRouter.kt create mode 100644 apps/student/src/main/java/com/instructure/student/features/todolist/ToDoListFragment.kt rename apps/student/src/main/java/com/instructure/student/fragment/{ToDoListFragment.kt => OldToDoListFragment.kt} (97%) create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRepository.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRouter.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListScreen.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListUiState.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListViewModel.kt create mode 100644 libs/pandautils/src/main/res/drawable/ic_panda_bottom.xml create mode 100644 libs/pandautils/src/main/res/drawable/ic_panda_top.xml create mode 100644 libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListRepositoryTest.kt create mode 100644 libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListViewModelTest.kt create mode 100644 libs/pandautils/src/test/java/com/instructure/pandautils/utils/PlannerItemExtensionsTest.kt 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 fe20ece105..d1715f8ae9 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 @@ -20,6 +20,7 @@ package com.instructure.student.activity import android.os.Bundle import com.google.firebase.crashlytics.FirebaseCrashlytics import com.instructure.canvasapi2.StatusCallback +import com.instructure.canvasapi2.apis.PlannerAPI import com.instructure.canvasapi2.apis.UserAPI import com.instructure.canvasapi2.builders.RestParams import com.instructure.canvasapi2.managers.FeaturesManager @@ -41,8 +42,12 @@ import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.ApiType import com.instructure.canvasapi2.utils.LinkHeaders import com.instructure.canvasapi2.utils.Logger +import com.instructure.canvasapi2.utils.RemoteConfigParam +import com.instructure.canvasapi2.utils.RemoteConfigUtils +import com.instructure.canvasapi2.utils.depaginate import com.instructure.canvasapi2.utils.pageview.PandataInfo import com.instructure.canvasapi2.utils.pageview.PandataManager +import com.instructure.canvasapi2.utils.toApiString import com.instructure.canvasapi2.utils.weave.StatusCallbackError import com.instructure.canvasapi2.utils.weave.awaitApi import com.instructure.canvasapi2.utils.weave.catch @@ -65,6 +70,7 @@ import com.instructure.student.util.StudentPrefs import com.instructure.student.widget.WidgetLogger import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job +import org.threeten.bp.LocalDate import retrofit2.Call import retrofit2.Response import sdk.pendo.io.Pendo @@ -85,6 +91,9 @@ abstract class CallbackActivity : ParentActivity(), OnUnreadCountInvalidated, No @Inject lateinit var userApi: UserAPI.UsersInterface + @Inject + lateinit var plannerApi: PlannerAPI.PlannerInterface + @Inject lateinit var widgetLogger: WidgetLogger @@ -94,6 +103,7 @@ abstract class CallbackActivity : ParentActivity(), OnUnreadCountInvalidated, No abstract fun updateUnreadCount(unreadCount: Int) abstract fun increaseUnreadCount(increaseBy: Int) abstract fun updateNotificationCount(notificationCount: Int) + abstract fun updateToDoCount(toDoCount: Int) abstract fun initialCoreDataLoadingComplete() override fun onCreate(savedInstanceState: Bundle?) { @@ -178,6 +188,10 @@ abstract class CallbackActivity : ParentActivity(), OnUnreadCountInvalidated, No getUnreadNotificationCount() + if (RemoteConfigUtils.getBoolean(RemoteConfigParam.TODO_REDESIGN)) { + getToDoCount() + } + initialCoreDataLoadingComplete() } catch { initialCoreDataLoadingComplete() @@ -206,6 +220,26 @@ abstract class CallbackActivity : ParentActivity(), OnUnreadCountInvalidated, No } } + private suspend fun getToDoCount() { + // TODO Implement correct filtering in MBL-19401 + val now = LocalDate.now().atStartOfDay() + val startDate = now.minusDays(7).toApiString().orEmpty() + val endDate = now.plusDays(7).toApiString().orEmpty() + + val restParams = RestParams(isForceReadFromNetwork = true, usePerPageQueryParam = true) + val plannerItems = plannerApi.getPlannerItems( + startDate = startDate, + endDate = endDate, + contextCodes = emptyList(), + restParams = restParams + ).depaginate { nextUrl -> + plannerApi.nextPagePlannerItems(nextUrl, restParams) + } + + val todoCount = plannerItems.dataOrNull?.count().orDefault() + updateToDoCount(todoCount) + } + private fun getUnreadNotificationCount() { UnreadCountManager.getUnreadNotificationCount(object : StatusCallback>() { override fun onResponse(data: Call>, response: Response>) { 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 94d384b33d..f39b6315ad 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 @@ -62,6 +62,8 @@ import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.Logger import com.instructure.canvasapi2.utils.MasqueradeHelper import com.instructure.canvasapi2.utils.Pronouns +import com.instructure.canvasapi2.utils.RemoteConfigParam +import com.instructure.canvasapi2.utils.RemoteConfigUtils import com.instructure.canvasapi2.utils.weave.WeaveJob import com.instructure.canvasapi2.utils.weave.awaitApi import com.instructure.canvasapi2.utils.weave.catch @@ -134,10 +136,11 @@ import com.instructure.student.events.UserUpdatedEvent import com.instructure.student.features.files.list.FileListFragment import com.instructure.student.features.modules.progression.CourseModuleProgressionFragment import com.instructure.student.features.navigation.NavigationRepository +import com.instructure.student.features.todolist.ToDoListFragment import com.instructure.student.fragment.BookmarksFragment import com.instructure.student.fragment.DashboardFragment import com.instructure.student.fragment.NotificationListFragment -import com.instructure.student.fragment.ToDoListFragment +import com.instructure.student.fragment.OldToDoListFragment import com.instructure.student.mobius.assignmentDetails.submission.picker.PickerSubmissionUploadEffectHandler import com.instructure.student.mobius.assignmentDetails.submissionDetails.content.emptySubmission.ui.SubmissionDetailsEmptyContentFragment import com.instructure.student.navigation.AccountMenuItem @@ -537,7 +540,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. } AppShortcutManager.APP_SHORTCUT_CALENDAR -> selectBottomNavFragment( CalendarFragment::class.java) - AppShortcutManager.APP_SHORTCUT_TODO -> selectBottomNavFragment(ToDoListFragment::class.java) + AppShortcutManager.APP_SHORTCUT_TODO -> selectBottomNavFragment(navigationBehavior.todoFragmentClass) AppShortcutManager.APP_SHORTCUT_NOTIFICATIONS -> selectBottomNavFragment(NotificationListFragment::class.java) AppShortcutManager.APP_SHORTCUT_INBOX -> { if (ApiPrefs.isStudentView) { @@ -789,7 +792,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. when (item.itemId) { R.id.bottomNavigationHome -> selectBottomNavFragment(navigationBehavior.homeFragmentClass) R.id.bottomNavigationCalendar -> selectBottomNavFragment(CalendarFragment::class.java) - R.id.bottomNavigationToDo -> selectBottomNavFragment(ToDoListFragment::class.java) + R.id.bottomNavigationToDo -> selectBottomNavFragment(navigationBehavior.todoFragmentClass) R.id.bottomNavigationNotifications -> selectBottomNavFragment(NotificationListFragment::class.java) R.id.bottomNavigationInbox -> { if (ApiPrefs.isStudentView) { @@ -812,7 +815,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. R.id.bottomNavigationHome -> abortReselect = currentFragmentClass.isAssignableFrom(navigationBehavior.homeFragmentClass) R.id.bottomNavigationCalendar -> abortReselect = currentFragmentClass.isAssignableFrom( CalendarFragment::class.java) - R.id.bottomNavigationToDo -> abortReselect = currentFragmentClass.isAssignableFrom(ToDoListFragment::class.java) + R.id.bottomNavigationToDo -> abortReselect = currentFragmentClass.isAssignableFrom(navigationBehavior.todoFragmentClass) R.id.bottomNavigationNotifications -> abortReselect = currentFragmentClass.isAssignableFrom(NotificationListFragment::class.java) R.id.bottomNavigationInbox -> abortReselect = currentFragmentClass.isAssignableFrom(InboxFragment::class.java) } @@ -822,7 +825,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. when (item.itemId) { R.id.bottomNavigationHome -> selectBottomNavFragment(navigationBehavior.homeFragmentClass) R.id.bottomNavigationCalendar -> selectBottomNavFragment(CalendarFragment::class.java) - R.id.bottomNavigationToDo -> selectBottomNavFragment(ToDoListFragment::class.java) + R.id.bottomNavigationToDo -> selectBottomNavFragment(navigationBehavior.todoFragmentClass) R.id.bottomNavigationNotifications -> selectBottomNavFragment(NotificationListFragment::class.java) R.id.bottomNavigationInbox -> { if (ApiPrefs.isStudentView) { @@ -875,6 +878,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. is EventFragment -> setBottomBarItemSelected(R.id.bottomNavigationCalendar) //To-do is ToDoListFragment -> setBottomBarItemSelected(R.id.bottomNavigationToDo) + is OldToDoListFragment -> setBottomBarItemSelected(R.id.bottomNavigationToDo) //Notifications is NotificationListFragment-> { setBottomBarItemSelected(if(fragment.isCourseOrGroup()) R.id.bottomNavigationHome @@ -1265,6 +1269,10 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. updateBottomBarBadge(R.id.bottomNavigationNotifications, notificationCount, R.plurals.a11y_notificationsUnreadCount) } + override fun updateToDoCount(toDoCount: Int) { + updateBottomBarBadge(R.id.bottomNavigationToDo, toDoCount, R.plurals.a11y_todoBadgeCount) + } + private fun updateBottomBarBadge(@IdRes menuItemId: Int, count: Int, @PluralsRes quantityContentDescription: Int? = null) = with(binding) { if (count > 0) { bottomBar.getOrCreateBadge(menuItemId).number = count @@ -1306,9 +1314,17 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. val route = CalendarFragment.makeRoute() CalendarFragment.newInstance(route) } - ToDoListFragment::class.java.name -> { - val route = ToDoListFragment.makeRoute(ApiPrefs.user!!) - ToDoListFragment.newInstance(route) + navigationBehavior.todoFragmentClass.name -> { + val route = if (RemoteConfigUtils.getBoolean(RemoteConfigParam.TODO_REDESIGN)) { + ToDoListFragment.makeRoute(ApiPrefs.user!!) + } else { + OldToDoListFragment.makeRoute(ApiPrefs.user!!) + } + if (RemoteConfigUtils.getBoolean(RemoteConfigParam.TODO_REDESIGN)) { + ToDoListFragment.newInstance(route) + } else { + OldToDoListFragment.newInstance(route) + } } NotificationListFragment::class.java.name -> { val route = NotificationListFragment.makeRoute(ApiPrefs.user!!) diff --git a/apps/student/src/main/java/com/instructure/student/di/feature/ToDoListModule.kt b/apps/student/src/main/java/com/instructure/student/di/feature/ToDoListModule.kt new file mode 100644 index 0000000000..29cbb5ffb3 --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/di/feature/ToDoListModule.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 - 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.di.feature + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import com.instructure.pandautils.features.todolist.ToDoListRouter +import com.instructure.student.features.todolist.StudentToDoListRouter +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.FragmentComponent + +@Module +@InstallIn(FragmentComponent::class) +class ToDoListModule { + + @Provides + fun provideToDoListRouter(activity: FragmentActivity, fragment: Fragment): ToDoListRouter { + return StudentToDoListRouter(activity, fragment) + } +} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/todolist/StudentToDoListRouter.kt b/apps/student/src/main/java/com/instructure/student/features/todolist/StudentToDoListRouter.kt new file mode 100644 index 0000000000..8e1ec238a0 --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/features/todolist/StudentToDoListRouter.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 - 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.todolist + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import com.instructure.pandautils.features.todolist.ToDoListRouter +import com.instructure.student.activity.NavigationActivity + +class StudentToDoListRouter( + private val activity: FragmentActivity, + private val fragment: Fragment +) : ToDoListRouter { + + override fun openNavigationDrawer() { + (activity as? NavigationActivity)?.openNavigationDrawer() + } + + override fun attachNavigationDrawer() { + val toDoListFragment = fragment as? ToDoListFragment + if (toDoListFragment != null) { + (activity as? NavigationActivity)?.attachNavigationDrawer(toDoListFragment, null) + } + } + + override fun openToDoItem(itemId: String) { + // TODO: Implement navigation to specific to-do item based on item type + } +} diff --git a/apps/student/src/main/java/com/instructure/student/features/todolist/ToDoListFragment.kt b/apps/student/src/main/java/com/instructure/student/features/todolist/ToDoListFragment.kt new file mode 100644 index 0000000000..9797b598d3 --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/features/todolist/ToDoListFragment.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2025 - 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.todolist + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.instructure.canvasapi2.models.CanvasContext +import com.instructure.canvasapi2.utils.pageview.PageView +import com.instructure.interactions.FragmentInteractions +import com.instructure.interactions.Navigation +import com.instructure.interactions.router.Route +import com.instructure.pandautils.analytics.SCREEN_VIEW_TO_DO_LIST +import com.instructure.pandautils.analytics.ScreenView +import com.instructure.pandautils.base.BaseCanvasFragment +import com.instructure.pandautils.compose.CanvasTheme +import com.instructure.pandautils.features.todolist.ToDoListRouter +import com.instructure.pandautils.features.todolist.ToDoListScreen +import com.instructure.pandautils.features.todolist.ToDoListViewModel +import com.instructure.pandautils.features.todolist.ToDoListViewModelAction +import com.instructure.pandautils.interfaces.NavigationCallbacks +import com.instructure.pandautils.utils.ThemePrefs +import com.instructure.pandautils.utils.ViewStyler +import com.instructure.pandautils.utils.collectOneOffEvents +import com.instructure.pandautils.utils.makeBundle +import com.instructure.pandautils.utils.withArgs +import com.instructure.student.R +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@PageView +@ScreenView(SCREEN_VIEW_TO_DO_LIST) +@AndroidEntryPoint +class ToDoListFragment : BaseCanvasFragment(), FragmentInteractions, NavigationCallbacks { + + private val viewModel: ToDoListViewModel by viewModels() + + @Inject + lateinit var toDoListRouter: ToDoListRouter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + applyTheme() + viewLifecycleOwner.lifecycleScope.collectOneOffEvents(viewModel.events, ::handleAction) + + return ComposeView(requireActivity()).apply { + setContent { + CanvasTheme { + val uiState by viewModel.uiState.collectAsState() + + ToDoListScreen( + uiState = uiState, + actionHandler = viewModel::handleAction, + navigationIconClick = { toDoListRouter.openNavigationDrawer() } + ) + } + } + } + } + + override val navigation: Navigation? + get() = activity as? Navigation + + override fun title(): String = getString(R.string.Todo) + + override fun applyTheme() { + ViewStyler.setStatusBarDark(requireActivity(), ThemePrefs.primaryColor) + toDoListRouter.attachNavigationDrawer() + } + + override fun getFragment(): Fragment = this + + private fun handleAction(action: ToDoListViewModelAction) { + when (action) { + is ToDoListViewModelAction.OpenToDoItem -> toDoListRouter.openToDoItem(action.itemId) + } + } + + override fun onHandleBackPressed(): Boolean { + return false + } + + companion object { + fun makeRoute(canvasContext: CanvasContext): Route = Route(ToDoListFragment::class.java, canvasContext, Bundle()) + + private fun validateRoute(route: Route) = route.canvasContext != null + + fun newInstance(route: Route): ToDoListFragment? { + if (!validateRoute(route)) return null + return ToDoListFragment().withArgs(route.canvasContext!!.makeBundle()) + } + } +} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/OldToDoListFragment.kt similarity index 97% rename from apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt rename to apps/student/src/main/java/com/instructure/student/fragment/OldToDoListFragment.kt index 983872fe1b..c87f39ba29 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/ToDoListFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/OldToDoListFragment.kt @@ -59,7 +59,7 @@ import javax.inject.Inject @ScreenView(SCREEN_VIEW_TO_DO_LIST) @PageView @AndroidEntryPoint -class ToDoListFragment : ParentFragment() { +class OldToDoListFragment : ParentFragment() { private val binding by viewBinding(FragmentListTodoBinding::bind) private lateinit var recyclerViewBinding: PandaRecyclerRefreshLayoutBinding @@ -250,13 +250,13 @@ class ToDoListFragment : ParentFragment() { } companion object { - fun makeRoute(canvasContext: CanvasContext): Route = Route(ToDoListFragment::class.java, canvasContext, Bundle()) + fun makeRoute(canvasContext: CanvasContext): Route = Route(OldToDoListFragment::class.java, canvasContext, Bundle()) private fun validateRoute(route: Route) = route.canvasContext != null - fun newInstance(route: Route): ToDoListFragment? { + fun newInstance(route: Route): OldToDoListFragment? { if (!validateRoute(route)) return null - return ToDoListFragment().withArgs(route.canvasContext!!.makeBundle()) + return OldToDoListFragment().withArgs(route.canvasContext!!.makeBundle()) } } diff --git a/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt index 124ee3612c..6a84f493a2 100644 --- a/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt +++ b/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt @@ -26,14 +26,13 @@ import com.instructure.student.R import com.instructure.student.fragment.DashboardFragment import com.instructure.student.fragment.NotificationListFragment import com.instructure.student.fragment.ParentFragment -import com.instructure.student.fragment.ToDoListFragment class DefaultNavigationBehavior(private val apiPrefs: ApiPrefs) : NavigationBehavior { override val bottomNavBarFragments: List> = listOf( DashboardFragment::class.java, CalendarFragment::class.java, - ToDoListFragment::class.java, + todoFragmentClass, NotificationListFragment::class.java, getInboxBottomBarFragment(apiPrefs) ) diff --git a/apps/student/src/main/java/com/instructure/student/navigation/ElementaryNavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/ElementaryNavigationBehavior.kt index 9492060f65..6f2e276459 100644 --- a/apps/student/src/main/java/com/instructure/student/navigation/ElementaryNavigationBehavior.kt +++ b/apps/student/src/main/java/com/instructure/student/navigation/ElementaryNavigationBehavior.kt @@ -25,7 +25,6 @@ import com.instructure.pandautils.utils.CanvasFont import com.instructure.student.R import com.instructure.student.fragment.NotificationListFragment import com.instructure.student.fragment.ParentFragment -import com.instructure.student.fragment.ToDoListFragment import com.instructure.student.mobius.elementary.ElementaryDashboardFragment class ElementaryNavigationBehavior(private val apiPrefs: ApiPrefs) : NavigationBehavior { @@ -33,7 +32,7 @@ class ElementaryNavigationBehavior(private val apiPrefs: ApiPrefs) : NavigationB override val bottomNavBarFragments: List> = listOf( ElementaryDashboardFragment::class.java, CalendarFragment::class.java, - ToDoListFragment::class.java, + todoFragmentClass, NotificationListFragment::class.java, getInboxBottomBarFragment(apiPrefs) ) diff --git a/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt index 7ac1999403..4f6a29c8c8 100644 --- a/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt +++ b/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt @@ -20,10 +20,14 @@ import androidx.annotation.MenuRes import androidx.fragment.app.Fragment import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.RemoteConfigParam +import com.instructure.canvasapi2.utils.RemoteConfigUtils import com.instructure.interactions.router.Route import com.instructure.pandautils.features.inbox.list.InboxFragment import com.instructure.pandautils.utils.CanvasFont import com.instructure.student.activity.NothingToSeeHereFragment +import com.instructure.student.features.todolist.ToDoListFragment +import com.instructure.student.fragment.OldToDoListFragment import com.instructure.student.fragment.ParentFragment interface NavigationBehavior { @@ -41,6 +45,15 @@ interface NavigationBehavior { val canvasFont: CanvasFont + val todoFragmentClass: Class + get() { + return if (RemoteConfigUtils.getBoolean(RemoteConfigParam.TODO_REDESIGN)) { + ToDoListFragment::class.java + } else { + OldToDoListFragment::class.java + } + } + @get:MenuRes val bottomBarMenu: Int 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 4b9c97bf8f..12a77f8045 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 @@ -33,6 +33,8 @@ import com.instructure.canvasapi2.models.FileFolder import com.instructure.canvasapi2.models.Tab import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.Logger +import com.instructure.canvasapi2.utils.RemoteConfigParam +import com.instructure.canvasapi2.utils.RemoteConfigUtils import com.instructure.canvasapi2.utils.weave.catch import com.instructure.canvasapi2.utils.weave.tryLaunch import com.instructure.interactions.router.BaseRouteMatcher @@ -80,15 +82,16 @@ import com.instructure.student.features.pages.list.PageListFragment import com.instructure.student.features.people.details.PeopleDetailsFragment import com.instructure.student.features.people.list.PeopleListFragment import com.instructure.student.features.quiz.list.QuizListFragment +import com.instructure.student.features.todolist.ToDoListFragment import com.instructure.student.fragment.AnnouncementListFragment import com.instructure.student.fragment.BasicQuizViewFragment import com.instructure.student.fragment.CourseSettingsFragment import com.instructure.student.fragment.DashboardFragment import com.instructure.student.fragment.InternalWebviewFragment import com.instructure.student.fragment.NotificationListFragment +import com.instructure.student.fragment.OldToDoListFragment import com.instructure.student.fragment.ProfileSettingsFragment import com.instructure.student.fragment.StudioWebViewFragment -import com.instructure.student.fragment.ToDoListFragment import com.instructure.student.fragment.UnsupportedFeatureFragment import com.instructure.student.fragment.UnsupportedTabFragment import com.instructure.student.fragment.ViewHtmlFragment @@ -343,7 +346,12 @@ object RouteMatcher : BaseRouteMatcher() { routes.add(Route("/todos/:${ToDoFragment.PLANNABLE_ID}", ToDoFragment::class.java)) // To Do List - routes.add(Route("/todolist", ToDoListFragment::class.java).copy(canvasContext = ApiPrefs.user)) + val todoListFragmentClass = if (RemoteConfigUtils.getBoolean(RemoteConfigParam.TODO_REDESIGN)) { + ToDoListFragment::class.java + } else { + OldToDoListFragment::class.java + } + routes.add(Route("/todolist", todoListFragmentClass).copy(canvasContext = ApiPrefs.user)) // Syllabus routes.add(Route(courseOrGroup("/:${RouterParams.COURSE_ID}/assignments/syllabus"), SyllabusRepositoryFragment::class.java)) diff --git a/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt b/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt index 0052ea522e..8b665681fd 100644 --- a/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt +++ b/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt @@ -45,6 +45,7 @@ import com.instructure.student.features.pages.list.PageListFragment import com.instructure.student.features.people.details.PeopleDetailsFragment import com.instructure.student.features.people.list.PeopleListFragment import com.instructure.student.features.quiz.list.QuizListFragment +import com.instructure.student.features.todolist.ToDoListFragment import com.instructure.student.fragment.AccountPreferencesFragment import com.instructure.student.fragment.AnnouncementListFragment import com.instructure.student.fragment.AssignmentBasicFragment @@ -57,7 +58,7 @@ import com.instructure.student.fragment.InternalWebviewFragment import com.instructure.student.fragment.NotificationListFragment import com.instructure.student.fragment.ProfileSettingsFragment import com.instructure.student.fragment.StudioWebViewFragment -import com.instructure.student.fragment.ToDoListFragment +import com.instructure.student.fragment.OldToDoListFragment import com.instructure.student.fragment.UnknownItemFragment import com.instructure.student.fragment.UnsupportedFeatureFragment import com.instructure.student.fragment.UnsupportedTabFragment @@ -114,6 +115,7 @@ object RouteResolver { return when { cls.isA() -> DashboardFragment.newInstance(route) cls.isA() -> ElementaryDashboardFragment.newInstance(route) + cls.isA() -> OldToDoListFragment.newInstance(route) cls.isA() -> ToDoListFragment.newInstance(route) cls.isA() -> NotificationListFragment.newInstance(route) cls.isA() -> InboxFragment.newInstance(route) diff --git a/apps/student/src/main/java/com/instructure/student/widget/todo/ToDoWidgetUpdater.kt b/apps/student/src/main/java/com/instructure/student/widget/todo/ToDoWidgetUpdater.kt index 272cd376f3..4f8d3f64de 100644 --- a/apps/student/src/main/java/com/instructure/student/widget/todo/ToDoWidgetUpdater.kt +++ b/apps/student/src/main/java/com/instructure/student/widget/todo/ToDoWidgetUpdater.kt @@ -23,16 +23,15 @@ import com.instructure.canvasapi2.models.PlannableType import com.instructure.canvasapi2.models.PlannerItem import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.DataResult -import com.instructure.canvasapi2.utils.DateHelper import com.instructure.canvasapi2.utils.Failure import com.instructure.canvasapi2.utils.toApiString -import com.instructure.canvasapi2.utils.toDate import com.instructure.pandautils.utils.courseOrUserColor +import com.instructure.pandautils.utils.getContextNameForPlannerItem +import com.instructure.pandautils.utils.getDateTextForPlannerItem import com.instructure.pandautils.utils.getIconForPlannerItem import com.instructure.pandautils.utils.getTagForPlannerItem import com.instructure.pandautils.utils.orDefault import com.instructure.pandautils.utils.toLocalDate -import com.instructure.student.R import com.instructure.student.widget.glance.WidgetState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -119,59 +118,6 @@ class ToDoWidgetUpdater( tag = getTagForPlannerItem(context) ) - private fun PlannerItem.getContextNameForPlannerItem(context: Context, courses: List): String { - val courseCode = courses.find { it.id == canvasContext.id }?.courseCode - return when (plannableType) { - PlannableType.PLANNER_NOTE -> { - if (contextName.isNullOrEmpty()) { - context.getString(R.string.userCalendarToDo) - } else { - context.getString(R.string.courseToDo, courseCode) - } - } - - else -> { - if (canvasContext is Course) { - courseCode.orEmpty() - } else { - contextName.orEmpty() - } - } - } - } - - private fun PlannerItem.getDateTextForPlannerItem(context: Context): String? { - return when (plannableType) { - PlannableType.PLANNER_NOTE -> { - plannable.todoDate.toDate()?.let { - DateHelper.getFormattedTime(context, it) - } - } - - PlannableType.CALENDAR_EVENT -> { - val startDate = plannable.startAt - val endDate = plannable.endAt - if (startDate != null && endDate != null) { - val startText = DateHelper.getFormattedTime(context, startDate).orEmpty() - val endText = DateHelper.getFormattedTime(context, endDate).orEmpty() - - when { - plannable.allDay == true -> context.getString(R.string.widgetAllDay) - startDate == endDate -> startText - else -> context.getString(R.string.widgetFromTo, startText, endText) - } - } else null - } - - else -> { - plannable.dueAt?.let { - val timeText = DateHelper.getFormattedTime(context, it).orEmpty() - context.getString(R.string.widgetDueDate, timeText) - } - } - } - } - private fun PlannerItem.getUrl(): String { val url = when (plannableType) { PlannableType.CALENDAR_EVENT -> { diff --git a/apps/student/src/test/java/com/instructure/student/test/util/RouterUtilsTest.kt b/apps/student/src/test/java/com/instructure/student/test/util/RouterUtilsTest.kt index 32710d6e65..57f4ea4d9d 100644 --- a/apps/student/src/test/java/com/instructure/student/test/util/RouterUtilsTest.kt +++ b/apps/student/src/test/java/com/instructure/student/test/util/RouterUtilsTest.kt @@ -21,6 +21,7 @@ import androidx.fragment.app.FragmentActivity import androidx.test.ext.junit.runners.AndroidJUnit4 import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.RemoteConfigUtils import com.instructure.interactions.router.Route import com.instructure.interactions.router.RouteContext import com.instructure.interactions.router.RouterParams @@ -46,8 +47,11 @@ import com.instructure.student.mobius.assignmentDetails.submissionDetails.ui.Sub import com.instructure.student.mobius.conferences.conference_list.ui.ConferenceListRepositoryFragment import com.instructure.student.mobius.syllabus.ui.SyllabusRepositoryFragment import com.instructure.student.router.RouteMatcher +import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject import junit.framework.TestCase +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -56,6 +60,12 @@ class RouterUtilsTest : TestCase() { private val activity: FragmentActivity = mockk(relaxed = true) + @Before + fun setup() { + mockkObject(RemoteConfigUtils) + every { RemoteConfigUtils.getString(any()) } returns "false" + } + @Test fun testCanRouteInternally_misc() { // Home diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/RemoteConfigUtils.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/RemoteConfigUtils.kt index 76618fbdef..621281f55f 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/RemoteConfigUtils.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/RemoteConfigUtils.kt @@ -17,6 +17,7 @@ enum class RemoteConfigParam(val rc_name: String, val safeValueAsString: String) TEST_LONG("test_long", "42"), TEST_STRING("test_string", "hey there"), SPEEDGRADER_V2("speedgrader_v2", "true"), + TODO_REDESIGN("todo_redesign", "false") } /** diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index 9fee624f81..f20aadf279 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -1375,6 +1375,11 @@ %s unread notifications + + %s to do item + %s to do items + + No annotation selected Email Notifications @@ -2179,4 +2184,8 @@ Scored Less than / %s pts Scored %1$s - %2$s Multiple filters + Filter + There was an error loading your to-do items. Please check your connection and try again. + No To Dos for now! + It looks like a great time to rest, relax, and recharge. diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRepository.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRepository.kt new file mode 100644 index 0000000000..3ef9e3904e --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRepository.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +import com.instructure.canvasapi2.apis.CourseAPI +import com.instructure.canvasapi2.apis.PlannerAPI +import com.instructure.canvasapi2.builders.RestParams +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.PlannableType +import com.instructure.canvasapi2.models.PlannerItem +import com.instructure.canvasapi2.models.PlannerOverride +import com.instructure.canvasapi2.utils.DataResult +import com.instructure.canvasapi2.utils.depaginate +import javax.inject.Inject + +class ToDoListRepository @Inject constructor( + private val plannerApi: PlannerAPI.PlannerInterface, + private val courseApi: CourseAPI.CoursesInterface +) { + suspend fun getPlannerItems( + startDate: String, + endDate: String, + forceRefresh: Boolean + ): DataResult> { + val restParams = RestParams(isForceReadFromNetwork = forceRefresh, usePerPageQueryParam = true) + return plannerApi.getPlannerItems( + startDate = startDate, + endDate = endDate, + contextCodes = emptyList(), + restParams = restParams + ).depaginate { nextUrl -> + plannerApi.nextPagePlannerItems(nextUrl, restParams) + } + } + + suspend fun getCourses(forceRefresh: Boolean): DataResult> { + val restParams = RestParams(isForceReadFromNetwork = forceRefresh) + return courseApi.getFirstPageCourses(restParams).depaginate { nextUrl -> + courseApi.next(nextUrl, restParams) + } + } + + suspend fun updatePlannerOverride( + plannerOverrideId: Long, + markedComplete: Boolean + ): DataResult { + val restParams = RestParams(isForceReadFromNetwork = true) + return plannerApi.updatePlannerOverride( + plannerOverrideId = plannerOverrideId, + complete = markedComplete, + params = restParams + ) + } + + suspend fun createPlannerOverride( + plannableId: Long, + plannableType: PlannableType, + markedComplete: Boolean + ): DataResult { + val restParams = RestParams(isForceReadFromNetwork = true) + val override = PlannerOverride( + plannableId = plannableId, + plannableType = plannableType, + markedComplete = markedComplete + ) + return plannerApi.createPlannerOverride(override, restParams) + } +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRouter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRouter.kt new file mode 100644 index 0000000000..020b03a395 --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRouter.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +interface ToDoListRouter { + + fun openNavigationDrawer() + + fun attachNavigationDrawer() + + fun openToDoItem(itemId: String) +} diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListScreen.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListScreen.kt new file mode 100644 index 0000000000..ca2e86382f --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListScreen.kt @@ -0,0 +1,755 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Checkbox +import androidx.compose.material.CheckboxDefaults +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInParent +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.instructure.canvasapi2.models.CanvasContext +import com.instructure.canvasapi2.utils.ContextKeeper +import com.instructure.pandautils.R +import com.instructure.pandautils.compose.CanvasTheme +import com.instructure.pandautils.compose.composables.CanvasDivider +import com.instructure.pandautils.compose.composables.CanvasThemedAppBar +import com.instructure.pandautils.compose.composables.EmptyContent +import com.instructure.pandautils.compose.composables.ErrorContent +import com.instructure.pandautils.compose.composables.Loading +import com.instructure.pandautils.compose.modifiers.conditional +import com.instructure.pandautils.utils.ThemePrefs +import com.instructure.pandautils.utils.courseOrUserColor +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale +import kotlin.math.roundToInt + +private data class StickyHeaderState( + val item: ToDoItemUiState?, + val yOffset: Float, + val isVisible: Boolean +) + +private data class DateBadgeData( + val dayOfWeek: String, + val day: Int, + val month: String, + val isToday: Boolean, + val dateTextColor: Color +) + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ToDoListScreen( + uiState: ToDoListUiState, + actionHandler: (ToDoListActionHandler) -> Unit, + modifier: Modifier = Modifier, + navigationIconClick: () -> Unit = {} +) { + val pullRefreshState = rememberPullRefreshState( + refreshing = uiState.isRefreshing, + onRefresh = { actionHandler(ToDoListActionHandler.Refresh) } + ) + + Scaffold( + backgroundColor = colorResource(R.color.backgroundLightest), + topBar = { + CanvasThemedAppBar( + title = stringResource(id = R.string.Todo), + navIconRes = R.drawable.ic_hamburger, + navIconContentDescription = stringResource(id = R.string.navigation_drawer_open), + navigationActionClick = navigationIconClick, + actions = { + IconButton(onClick = { actionHandler(ToDoListActionHandler.FilterClicked) }) { + Icon( + painter = painterResource(id = R.drawable.ic_filter_outline), + contentDescription = stringResource(id = R.string.a11y_contentDescriptionToDoFilter) + ) + } + } + ) + }, + modifier = modifier + ) { padding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .pullRefresh(pullRefreshState) + ) { + when { + uiState.isLoading -> { + Loading(modifier = Modifier.align(Alignment.Center)) + } + + uiState.isError -> { + ErrorContent( + errorMessage = stringResource(id = R.string.errorLoadingToDos), + retryClick = { actionHandler(ToDoListActionHandler.Refresh) }, + modifier = Modifier.fillMaxSize() + ) + } + + uiState.itemsByDate.isEmpty() -> { + EmptyContent( + emptyTitle = stringResource(id = R.string.noToDosForNow), + emptyMessage = stringResource(id = R.string.noToDosForNowSubtext), + imageRes = R.drawable.ic_no_events, + modifier = Modifier.fillMaxSize() + ) + } + + else -> { + ToDoListContent( + itemsByDate = uiState.itemsByDate, + actionHandler = actionHandler + ) + } + } + + PullRefreshIndicator( + refreshing = uiState.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + backgroundColor = colorResource(id = R.color.backgroundLightest), + contentColor = Color(ThemePrefs.brandColor) + ) + } + } +} + +@Composable +private fun ToDoListContent( + itemsByDate: Map>, + actionHandler: (ToDoListActionHandler) -> Unit, + modifier: Modifier = Modifier +) { + val dateGroups = itemsByDate.entries.toList() + val listState = rememberLazyListState() + val itemPositions = remember { mutableStateMapOf() } + val itemSizes = remember { mutableStateMapOf() } + val density = LocalDensity.current + var listHeight by remember { mutableIntStateOf(0) } + + val stickyHeaderState = rememberStickyHeaderState( + dateGroups = dateGroups, + listState = listState, + itemPositions = itemPositions, + density = density + ) + + // Calculate content height from last item's position + size + val listContentHeight by remember { + derivedStateOf { + if (dateGroups.isEmpty()) return@derivedStateOf 0 + val lastGroup = dateGroups.last() + val lastItem = lastGroup.value.last() + val lastItemPosition = itemPositions[lastItem.id] ?: return@derivedStateOf 0 + val lastItemSize = itemSizes[lastItem.id] ?: return@derivedStateOf 0 + (lastItemPosition + lastItemSize).toInt() + } + } + + // Calculate if there's enough space for pandas (at least 140dp) + val availableSpacePx = listHeight - listContentHeight + val minSpaceForPandasPx = with(density) { 140.dp.toPx() } + val showPandas = listHeight > 0 && listContentHeight > 0 && + availableSpacePx >= minSpaceForPandasPx && + itemsByDate.isNotEmpty() + + Box(modifier = modifier.fillMaxSize()) { + LazyColumn( + state = listState, + modifier = Modifier + .fillMaxSize() + .onGloballyPositioned { coordinates -> + listHeight = coordinates.size.height + } + ) { + dateGroups.forEachIndexed { groupIndex, (date, items) -> + items.forEachIndexed { index, item -> + item(key = item.id) { + ToDoItem( + item = item, + showDateBadge = index == 0, + hideDate = index == 0 && stickyHeaderState.isVisible && stickyHeaderState.item?.id == item.id, + onCheckedChange = { actionHandler(ToDoListActionHandler.ToggleItemChecked(item.id)) }, + onClick = { actionHandler(ToDoListActionHandler.ItemClicked(item.id)) }, + modifier = Modifier.onGloballyPositioned { coordinates -> + itemPositions[item.id] = coordinates.positionInParent().y + itemSizes[item.id] = coordinates.size.height + } + ) + } + } + + // Add divider between date groups, or after last group if pandas are showing + if (groupIndex < dateGroups.size - 1 || showPandas) { + item(key = "divider_$date") { + val isLastDivider = groupIndex == dateGroups.size - 1 + CanvasDivider( + modifier = Modifier + .fillMaxWidth() + .height(0.5.dp) + .conditional(!isLastDivider) { + padding(horizontal = 16.dp) + } + ) + } + } + } + } + + // Panda illustrations + if (showPandas) { + PandaIllustrations(contentHeightPx = listContentHeight.toFloat()) + } + + // Sticky header overlay + stickyHeaderState.item?.let { item -> + if (stickyHeaderState.isVisible) { + StickyDateBadge( + item = item, + yOffset = stickyHeaderState.yOffset + ) + } + } + } +} + +@Composable +private fun StickyDateBadge( + item: ToDoItemUiState, + yOffset: Float +) { + val dateBadgeData = rememberDateBadgeData(item.date) + + Box( + modifier = Modifier + .offset { IntOffset(0, yOffset.roundToInt()) } + .padding(start = 12.dp, top = 8.dp, bottom = 8.dp) + ) { + Box( + modifier = Modifier + .width(44.dp) + .padding(end = 12.dp) + .background(colorResource(id = R.color.backgroundLightest)), + contentAlignment = Alignment.TopCenter + ) { + DateBadge(dateBadgeData) + } + } +} + +@Composable +private fun ToDoItem( + item: ToDoItemUiState, + showDateBadge: Boolean, + onCheckedChange: () -> Unit, + onClick: () -> Unit, + modifier: Modifier = Modifier, + hideDate: Boolean = false +) { + val dateBadgeData = rememberDateBadgeData(item.date) + + Row( + modifier = modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(start = 12.dp, end = 16.dp, top = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.Top + ) { + Box( + modifier = Modifier + .width(44.dp) + .padding(end = 12.dp), + contentAlignment = Alignment.TopCenter + ) { + if (showDateBadge && !hideDate) { + DateBadge(dateBadgeData) + } + } + + Row( + modifier = Modifier.weight(1f), + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + val contextColor = Color(item.canvasContext.courseOrUserColor) + Row(verticalAlignment = Alignment.Top) { + Icon( + painter = painterResource(id = item.iconRes), + contentDescription = null, + tint = contextColor, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + CanvasDivider( + modifier = Modifier + .width(0.5.dp) + .height(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = item.contextLabel, + fontSize = 14.sp, + color = contextColor, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + } + + Text( + text = item.title, + fontSize = 16.sp, + color = colorResource(id = R.color.textDarkest), + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + + item.tag?.let { + Text( + text = it, + fontSize = 14.sp, + color = colorResource(id = R.color.textDark), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + item.dateLabel?.let { + Text( + text = it, + fontSize = 14.sp, + color = colorResource(id = R.color.textDark), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + + Spacer(modifier = Modifier.width(16.dp)) + + Checkbox( + checked = item.isChecked, + onCheckedChange = { onCheckedChange() }, + colors = CheckboxDefaults.colors( + checkedColor = Color(ThemePrefs.brandColor), + uncheckedColor = colorResource(id = R.color.textDark) + ) + ) + } + } +} + +@Composable +private fun rememberDateBadgeData(date: Date): DateBadgeData { + val calendar = remember(date) { + Calendar.getInstance().apply { time = date } + } + + val dayOfWeek = remember(date) { + SimpleDateFormat("EEE", Locale.getDefault()).format(date) + } + + val day = remember(date) { + calendar.get(Calendar.DAY_OF_MONTH) + } + + val month = remember(date) { + SimpleDateFormat("MMM", Locale.getDefault()).format(date) + } + + val isToday = remember(date) { + val today = Calendar.getInstance() + calendar.get(Calendar.YEAR) == today.get(Calendar.YEAR) && + calendar.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR) + } + + val dateTextColor = if (isToday) { + Color(ThemePrefs.brandColor) + } else { + colorResource(id = R.color.textDark) + } + + return DateBadgeData(dayOfWeek, day, month, isToday, dateTextColor) +} + +@Composable +private fun DateBadge(dateBadgeData: DateBadgeData) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = dateBadgeData.dayOfWeek, + fontSize = 12.sp, + color = dateBadgeData.dateTextColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Box( + contentAlignment = Alignment.Center, + modifier = if (dateBadgeData.isToday) { + Modifier + .size(32.dp) + .border(width = 1.dp, color = dateBadgeData.dateTextColor, shape = CircleShape) + } else { + Modifier + } + ) { + Text( + text = dateBadgeData.day.toString(), + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + color = dateBadgeData.dateTextColor + ) + } + Text( + text = dateBadgeData.month, + fontSize = 10.sp, + color = dateBadgeData.dateTextColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +private fun rememberStickyHeaderState( + dateGroups: List>>, + listState: LazyListState, + itemPositions: Map, + density: Density +): StickyHeaderState { + return remember { + derivedStateOf { + calculateStickyHeaderState(dateGroups, listState, itemPositions, density) + } + }.value +} + +private fun calculateStickyHeaderState( + dateGroups: List>>, + listState: LazyListState, + itemPositions: Map, + density: Density +): StickyHeaderState { + val firstVisibleItemIndex = listState.firstVisibleItemIndex + val firstVisibleItemScrollOffset = listState.firstVisibleItemScrollOffset + + // Find which date group's first item has been scrolled past + var currentGroupIndex = -1 + var itemCount = 0 + + for ((groupIndex, group) in dateGroups.withIndex()) { + val groupItemCount = group.value.size + if (firstVisibleItemIndex < itemCount + groupItemCount) { + currentGroupIndex = groupIndex + break + } + itemCount += groupItemCount + if (groupIndex < dateGroups.size - 1) 1 else 0 // +1 for divider + } + + if (currentGroupIndex == -1 || currentGroupIndex >= dateGroups.size) { + return StickyHeaderState(null, 0f, false) + } + + val currentGroup = dateGroups[currentGroupIndex] + val firstItemOfCurrentGroup = currentGroup.value.first() + + // Check if the first item has scrolled up even slightly + val shouldShowSticky = if (firstVisibleItemIndex > 0) { + true + } else { + firstVisibleItemScrollOffset > 0 + } + + // Calculate offset for animation when next group approaches + var yOffset = 0f + if (currentGroupIndex < dateGroups.size - 1) { + val nextGroup = dateGroups[currentGroupIndex + 1] + val nextGroupFirstItem = nextGroup.value.first() + val nextItemPosition = itemPositions[nextGroupFirstItem.id] ?: Float.MAX_VALUE + + // Calculate date badge height by converting sp and dp values to pixels + // Date badge components: + // - dayOfWeek text: 12.sp + // - day text (in 32.dp box): 12.sp (bold) + // - month text: 10.sp + // - All text heights together: 22.sp + // - item bottom padding: 8.dp + val textHeightPx = with(density) { 22.sp.toPx() } + val circleHeightPx = with(density) { 32.dp.toPx() } + val paddingPx = with(density) { 8.dp.toPx() } + val stickyHeaderHeightPx = textHeightPx + circleHeightPx + paddingPx + + if (nextItemPosition < stickyHeaderHeightPx && nextItemPosition > 0) { + yOffset = nextItemPosition - stickyHeaderHeightPx + } + } + + return StickyHeaderState( + item = if (shouldShowSticky) firstItemOfCurrentGroup else null, + yOffset = yOffset, + isVisible = shouldShowSticky + ) +} + +@Composable +private fun PandaIllustrations(contentHeightPx: Float) { + val density = LocalDensity.current + + Box(modifier = Modifier.fillMaxSize()) { + // Top-right panda - positioned at top-right, below content + Icon( + painter = painterResource(id = R.drawable.ic_panda_top), + contentDescription = null, + modifier = Modifier + .width(180.dp) + .height(137.dp) + .align(Alignment.TopEnd) + .offset(x = (-24).dp, y = with(density) { (contentHeightPx - 2.5.dp.toPx()).toDp() }), + tint = Color.Unspecified + ) + + // Bottom-left panda - positioned at bottom-left + Icon( + painter = painterResource(id = R.drawable.ic_panda_bottom), + contentDescription = null, + modifier = Modifier + .width(114.dp) + .height(137.dp) + .align(Alignment.BottomStart) + .offset(x = 24.dp, y = 30.5.dp), + tint = Color.Unspecified + ) + } +} + +@Preview(name = "Light Mode", showBackground = true) +@Preview(name = "Dark Mode", showBackground = true, uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES) +@Composable +fun ToDoListScreenPreview() { + ContextKeeper.appContext = LocalContext.current + val calendar = Calendar.getInstance() + CanvasTheme { + ToDoListScreen( + uiState = ToDoListUiState( + itemsByDate = mapOf( + Date(10) to listOf( + ToDoItemUiState( + id = "1", + title = "Short title", + date = calendar.apply { set(2024, 9, 22, 7, 59) }.time, + dateLabel = "7:59 AM", + contextLabel = "COURSE", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.ASSIGNMENT, + iconRes = R.drawable.ic_assignment, + isChecked = false + ), + ToDoItemUiState( + id = "2", + title = "Levitate an object without crushing it, bonus points if you don't scratch the paint", + date = calendar.apply { set(2024, 9, 22, 11, 59) }.time, + dateLabel = "11:59 AM", + contextLabel = "Introduction to Advanced Galactic Force Manipulation and Control Techniques for Beginners", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.QUIZ, + iconRes = R.drawable.ic_quiz, + isChecked = false + ), + ToDoItemUiState( + id = "3", + title = "Identify which emotions lead to Jedi calmness vs. a full Darth Vader office meltdown situation", + date = calendar.apply { set(2024, 9, 22, 14, 30) }.time, + dateLabel = "2:30 PM", + contextLabel = "FORC 101", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.ASSIGNMENT, + iconRes = R.drawable.ic_assignment, + isChecked = true + ), + ToDoItemUiState( + id = "4", + title = "Peer review discussion post", + date = calendar.apply { set(2024, 9, 22, 16, 0) }.time, + dateLabel = "4:00 PM", + tag = "Peer Reviews for Exploring Emotional Mastery", + contextLabel = "Advanced Force Psychology", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.SUB_ASSIGNMENT, + iconRes = R.drawable.ic_discussion, + isChecked = false + ) + ), + Date(1000) to listOf( + ToDoItemUiState( + id = "5", + title = "Essay - Why Force-choking co-workers is frowned upon in most galactic workplaces", + date = calendar.apply { set(2024, 9, 23, 19, 0) }.time, + dateLabel = "7:00 PM", + contextLabel = "Professional Jedi Ethics and Workplace Communication", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.DISCUSSION, + iconRes = R.drawable.ic_discussion, + isChecked = false + ), + ToDoItemUiState( + id = "6", + title = "Personal meditation practice", + date = calendar.apply { set(2024, 9, 23, 20, 0) }.time, + dateLabel = "8:00 PM", + contextLabel = "My Notes", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.PLANNER_NOTE, + iconRes = R.drawable.ic_todo, + isChecked = false + ), + ToDoItemUiState( + id = "7", + title = "Q", + date = calendar.apply { set(2024, 9, 23, 23, 59) }.time, + dateLabel = "11:59 PM", + contextLabel = "PHY", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.PLANNER_NOTE, + iconRes = R.drawable.ic_todo, + isChecked = false + ) + ), + Date(2000) to listOf( + ToDoItemUiState( + id = "9", + title = "Lightsaber maintenance workshop", + date = calendar.apply { set(2024, 9, 24, 10, 0) }.time, + dateLabel = "10:00 AM", + contextLabel = "Equipment & Safety", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.CALENDAR_EVENT, + iconRes = R.drawable.ic_calendar, + isChecked = false + ) + ) + ) + ), + actionHandler = {} + ) + } +} + +@Preview(name = "With Pandas Light Mode", showBackground = true) +@Preview(name = "With Pandas Dark Mode", showBackground = true, uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES) +@Composable +fun ToDoListScreenWithPandasPreview() { + ContextKeeper.appContext = LocalContext.current + val calendar = Calendar.getInstance() + CanvasTheme { + ToDoListScreen( + uiState = ToDoListUiState( + itemsByDate = mapOf( + Date(10) to listOf( + ToDoItemUiState( + id = "1", + title = "Complete Force training assignment", + date = calendar.apply { set(2024, 9, 22, 7, 59) }.time, + dateLabel = "7:59 AM", + contextLabel = "FORC 101", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.ASSIGNMENT, + iconRes = R.drawable.ic_assignment, + isChecked = false + ), + ToDoItemUiState( + id = "2", + title = "Read chapter on Jedi meditation techniques", + date = calendar.apply { set(2024, 9, 22, 11, 59) }.time, + dateLabel = "11:59 AM", + contextLabel = "Introduction to Advanced Force Manipulation", + canvasContext = CanvasContext.defaultCanvasContext(), + itemType = ToDoItemType.QUIZ, + iconRes = R.drawable.ic_quiz, + isChecked = false + ) + ) + ) + ), + actionHandler = {} + ) + } +} + +@Preview(name = "Empty Light Mode", showBackground = true) +@Preview(name = "Empty Dark Mode", showBackground = true, uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES) +@Composable +fun ToDoListScreenEmptyPreview() { + ContextKeeper.appContext = LocalContext.current + CanvasTheme { + ToDoListScreen( + uiState = ToDoListUiState(), + actionHandler = {} + ) + } +} diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListUiState.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListUiState.kt new file mode 100644 index 0000000000..03f7481d46 --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListUiState.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +import com.instructure.canvasapi2.models.CanvasContext +import com.instructure.pandautils.R +import java.util.Date + +data class ToDoListUiState( + val isLoading: Boolean = false, + val isError: Boolean = false, + val isRefreshing: Boolean = false, + val itemsByDate: Map> = emptyMap() +) + +data class ToDoItemUiState( + val id: String, + val title: String, + val date: Date, + val dateLabel: String?, + val contextLabel: String, + val canvasContext: CanvasContext, + val itemType: ToDoItemType, + val isChecked: Boolean = false, + val iconRes: Int = R.drawable.ic_calendar, + val tag: String? = null +) + +enum class ToDoItemType { + ASSIGNMENT, + SUB_ASSIGNMENT, + QUIZ, + DISCUSSION, + CALENDAR_EVENT, + PLANNER_NOTE +} + +sealed class ToDoListViewModelAction { + data class OpenToDoItem(val itemId: String) : ToDoListViewModelAction() +} + +sealed class ToDoListActionHandler { + data object Refresh : ToDoListActionHandler() + data class ToggleItemChecked(val itemId: String) : ToDoListActionHandler() + data class ItemClicked(val itemId: String) : ToDoListActionHandler() + data object FilterClicked : ToDoListActionHandler() +} diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListViewModel.kt new file mode 100644 index 0000000000..fe424743c0 --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListViewModel.kt @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +import android.content.Context +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.PlannableType +import com.instructure.canvasapi2.models.PlannerItem +import com.instructure.canvasapi2.utils.DateHelper +import com.instructure.canvasapi2.utils.isInvited +import com.instructure.canvasapi2.utils.toApiString +import com.instructure.pandautils.utils.getContextNameForPlannerItem +import com.instructure.pandautils.utils.getDateTextForPlannerItem +import com.instructure.pandautils.utils.getIconForPlannerItem +import com.instructure.pandautils.utils.getTagForPlannerItem +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.threeten.bp.LocalDate +import javax.inject.Inject + +@HiltViewModel +class ToDoListViewModel @Inject constructor( + @ApplicationContext private val context: Context, + private val repository: ToDoListRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(ToDoListUiState()) + val uiState = _uiState.asStateFlow() + + private val _events = Channel() + val events = _events.receiveAsFlow() + + init { + loadData() + } + + private fun loadData(forceRefresh: Boolean = false) { + viewModelScope.launch { + try { + _uiState.update { it.copy(isLoading = !forceRefresh, isRefreshing = forceRefresh, isError = false) } + + val now = LocalDate.now().atStartOfDay() + val startDate = now.minusDays(7).toApiString().orEmpty() + val endDate = now.plusDays(7).toApiString().orEmpty() + + val courses = repository.getCourses(forceRefresh).dataOrThrow + val plannerItems = repository.getPlannerItems(startDate, endDate, forceRefresh).dataOrThrow + + // Filter courses - exclude access restricted, invited + val filteredCourses = courses.filter { + !it.accessRestrictedByDate && !it.isInvited() + } + val courseMap = filteredCourses.associateBy { it.id } + + // Filter planner items - exclude announcements, assessment requests + val filteredItems = plannerItems + .filter { it.plannableType != PlannableType.ANNOUNCEMENT && it.plannableType != PlannableType.ASSESSMENT_REQUEST } + .sortedBy { it.comparisonDate } + + // Group items by date + val itemsByDate = filteredItems + .groupBy { DateHelper.getCleanDate(it.comparisonDate.time) } + .mapValues { (_, items) -> + items.map { plannerItem -> + mapToUiState(plannerItem, courseMap) + } + } + + _uiState.update { + it.copy( + isLoading = false, + isRefreshing = false, + isError = false, + itemsByDate = itemsByDate + ) + } + } catch (e: Exception) { + e.printStackTrace() + _uiState.update { + it.copy( + isLoading = false, + isRefreshing = false, + isError = true + ) + } + } + } + } + + private fun mapToUiState(plannerItem: PlannerItem, courseMap: Map): ToDoItemUiState { + val itemType = when (plannerItem.plannableType) { + PlannableType.ASSIGNMENT -> ToDoItemType.ASSIGNMENT + PlannableType.SUB_ASSIGNMENT -> ToDoItemType.SUB_ASSIGNMENT + PlannableType.QUIZ -> ToDoItemType.QUIZ + PlannableType.DISCUSSION_TOPIC -> ToDoItemType.DISCUSSION + PlannableType.CALENDAR_EVENT -> ToDoItemType.CALENDAR_EVENT + PlannableType.PLANNER_NOTE -> ToDoItemType.PLANNER_NOTE + else -> ToDoItemType.CALENDAR_EVENT + } + + return ToDoItemUiState( + id = plannerItem.plannable.id.toString(), + title = plannerItem.plannable.title, + date = plannerItem.plannableDate, + dateLabel = plannerItem.getDateTextForPlannerItem(context), + contextLabel = plannerItem.getContextNameForPlannerItem(context, courseMap.values), + canvasContext = plannerItem.canvasContext, + itemType = itemType, + isChecked = isComplete(plannerItem), + iconRes = plannerItem.getIconForPlannerItem(), + tag = plannerItem.getTagForPlannerItem(context) + ) + } + + private fun isComplete(plannerItem: PlannerItem): Boolean { + return if (plannerItem.plannableType == PlannableType.ASSIGNMENT + || plannerItem.plannableType == PlannableType.DISCUSSION_TOPIC + || plannerItem.plannableType == PlannableType.SUB_ASSIGNMENT + ) { + plannerItem.submissionState?.submitted == true + } else { + plannerItem.plannerOverride?.markedComplete == true + } + } + + fun handleAction(action: ToDoListActionHandler) { + when (action) { + is ToDoListActionHandler.ItemClicked -> { + viewModelScope.launch { + _events.send(ToDoListViewModelAction.OpenToDoItem(action.itemId)) + } + } + + is ToDoListActionHandler.Refresh -> { + loadData(forceRefresh = true) + } + + is ToDoListActionHandler.ToggleItemChecked -> { + // TODO: Implement toggle checked - will be implemented in future story + } + + is ToDoListActionHandler.FilterClicked -> { + // TODO: Implement filter - will be implemented in future story + } + } + } +} diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/PlannerItemExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/PlannerItemExtensions.kt index 8019185672..3f5c7540d4 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/PlannerItemExtensions.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/PlannerItemExtensions.kt @@ -18,9 +18,12 @@ package com.instructure.pandautils.utils import android.content.Context import androidx.annotation.DrawableRes +import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.PlannableType import com.instructure.canvasapi2.models.PlannerItem import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.DateHelper +import com.instructure.canvasapi2.utils.toDate import com.instructure.pandautils.R fun PlannerItem.todoHtmlUrl(apiPrefs: ApiPrefs): String { @@ -39,6 +42,61 @@ fun PlannerItem.getIconForPlannerItem(): Int { } } +fun PlannerItem.getDateTextForPlannerItem(context: Context): String? { + return when (plannableType) { + PlannableType.PLANNER_NOTE -> { + plannable.todoDate.toDate()?.let { + DateHelper.getFormattedTime(context, it) + } + } + + PlannableType.CALENDAR_EVENT -> { + val startDate = plannable.startAt + val endDate = plannable.endAt + if (startDate != null && endDate != null) { + val startText = DateHelper.getFormattedTime(context, startDate).orEmpty() + val endText = DateHelper.getFormattedTime(context, endDate).orEmpty() + + when { + plannable.allDay == true -> context.getString(R.string.widgetAllDay) + startDate == endDate -> startText + else -> context.getString(R.string.widgetFromTo, startText, endText) + } + } else null + } + + else -> { + plannable.dueAt?.let { + val timeText = DateHelper.getFormattedTime(context, it).orEmpty() + context.getString(R.string.widgetDueDate, timeText) + } + } + } +} + +fun PlannerItem.getContextNameForPlannerItem(context: Context, courses: Collection): String { + val course = courses.find { it.id == canvasContext.id } + val hasNickname = course?.originalName != null + val courseTitle = if (hasNickname) course.name else course?.courseCode + return when (plannableType) { + PlannableType.PLANNER_NOTE -> { + if (contextName.isNullOrEmpty()) { + context.getString(R.string.userCalendarToDo) + } else { + context.getString(R.string.courseToDo, courseTitle ?: contextName) + } + } + + else -> { + if (canvasContext is Course) { + courseTitle.orEmpty() + } else { + contextName.orEmpty() + } + } + } +} + fun PlannerItem.getTagForPlannerItem(context: Context): String? { return if (plannable.subAssignmentTag == Const.REPLY_TO_TOPIC) { context.getString(R.string.reply_to_topic) diff --git a/libs/pandautils/src/main/res/drawable/ic_panda_bottom.xml b/libs/pandautils/src/main/res/drawable/ic_panda_bottom.xml new file mode 100644 index 0000000000..e7a042bc9d --- /dev/null +++ b/libs/pandautils/src/main/res/drawable/ic_panda_bottom.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + diff --git a/libs/pandautils/src/main/res/drawable/ic_panda_top.xml b/libs/pandautils/src/main/res/drawable/ic_panda_top.xml new file mode 100644 index 0000000000..f3605adfc9 --- /dev/null +++ b/libs/pandautils/src/main/res/drawable/ic_panda_top.xml @@ -0,0 +1,45 @@ + + + + + + + + + + diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListRepositoryTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListRepositoryTest.kt new file mode 100644 index 0000000000..41aca37f2b --- /dev/null +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListRepositoryTest.kt @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +import com.instructure.canvasapi2.apis.CourseAPI +import com.instructure.canvasapi2.apis.PlannerAPI +import com.instructure.canvasapi2.builders.RestParams +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.Plannable +import com.instructure.canvasapi2.models.PlannableType +import com.instructure.canvasapi2.models.PlannerItem +import com.instructure.canvasapi2.models.PlannerOverride +import com.instructure.canvasapi2.utils.DataResult +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.util.Date + +class ToDoListRepositoryTest { + + private val plannerApi: PlannerAPI.PlannerInterface = mockk(relaxed = true) + private val courseApi: CourseAPI.CoursesInterface = mockk(relaxed = true) + + private lateinit var repository: ToDoListRepository + + @Before + fun setup() { + repository = ToDoListRepository(plannerApi, courseApi) + } + + // getPlannerItems tests + @Test + fun `getPlannerItems returns success with data`() = runTest { + val startDate = "2025-01-01" + val endDate = "2025-01-31" + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment 1"), + createPlannerItem(id = 2L, title = "Assignment 2") + ) + + coEvery { + plannerApi.getPlannerItems( + startDate = startDate, + endDate = endDate, + contextCodes = emptyList(), + restParams = any() + ) + } returns DataResult.Success(plannerItems) + + val result = repository.getPlannerItems(startDate, endDate, forceRefresh = false) + + assertTrue(result is DataResult.Success) + assertEquals(2, result.dataOrNull?.size) + assertEquals("Assignment 1", result.dataOrNull?.get(0)?.plannable?.title) + } + + @Test + fun `getPlannerItems returns failure when API call fails`() = runTest { + val startDate = "2025-01-01" + val endDate = "2025-01-31" + + coEvery { + plannerApi.getPlannerItems( + startDate = startDate, + endDate = endDate, + contextCodes = emptyList(), + restParams = any() + ) + } returns DataResult.Fail() + + val result = repository.getPlannerItems(startDate, endDate, forceRefresh = false) + + assertTrue(result is DataResult.Fail) + } + + @Test + fun `getPlannerItems uses correct RestParams when forceRefresh is true`() = runTest { + val startDate = "2025-01-01" + val endDate = "2025-01-31" + + coEvery { + plannerApi.getPlannerItems( + startDate = any(), + endDate = any(), + contextCodes = any(), + restParams = any() + ) + } returns DataResult.Success(emptyList()) + + repository.getPlannerItems(startDate, endDate, forceRefresh = true) + + coVerify { + plannerApi.getPlannerItems( + startDate = startDate, + endDate = endDate, + contextCodes = emptyList(), + restParams = match { it.isForceReadFromNetwork && it.usePerPageQueryParam } + ) + } + } + + @Test + fun `getPlannerItems uses correct RestParams when forceRefresh is false`() = runTest { + val startDate = "2025-01-01" + val endDate = "2025-01-31" + + coEvery { + plannerApi.getPlannerItems( + startDate = any(), + endDate = any(), + contextCodes = any(), + restParams = any() + ) + } returns DataResult.Success(emptyList()) + + repository.getPlannerItems(startDate, endDate, forceRefresh = false) + + coVerify { + plannerApi.getPlannerItems( + startDate = startDate, + endDate = endDate, + contextCodes = emptyList(), + restParams = match { !it.isForceReadFromNetwork && it.usePerPageQueryParam } + ) + } + } + + // getCourses tests + @Test + fun `getCourses returns success with data`() = runTest { + val courses = listOf( + Course(id = 1L, name = "Course 1", courseCode = "CS101"), + Course(id = 2L, name = "Course 2", courseCode = "MATH201") + ) + + coEvery { + courseApi.getFirstPageCourses(any()) + } returns DataResult.Success(courses) + + val result = repository.getCourses(forceRefresh = false) + + assertTrue(result is DataResult.Success) + assertEquals(2, result.dataOrNull?.size) + assertEquals("Course 1", result.dataOrNull?.get(0)?.name) + } + + @Test + fun `getCourses returns failure when API call fails`() = runTest { + coEvery { + courseApi.getFirstPageCourses(any()) + } returns DataResult.Fail() + + val result = repository.getCourses(forceRefresh = false) + + assertTrue(result is DataResult.Fail) + } + + @Test + fun `getCourses uses correct RestParams when forceRefresh is true`() = runTest { + coEvery { + courseApi.getFirstPageCourses(any()) + } returns DataResult.Success(emptyList()) + + repository.getCourses(forceRefresh = true) + + coVerify { + courseApi.getFirstPageCourses( + match { it.isForceReadFromNetwork } + ) + } + } + + @Test + fun `getCourses uses correct RestParams when forceRefresh is false`() = runTest { + coEvery { + courseApi.getFirstPageCourses(any()) + } returns DataResult.Success(emptyList()) + + repository.getCourses(forceRefresh = false) + + coVerify { + courseApi.getFirstPageCourses( + match { !it.isForceReadFromNetwork } + ) + } + } + + // updatePlannerOverride tests + @Test + fun `updatePlannerOverride returns success with updated override`() = runTest { + val overrideId = 123L + val override = PlannerOverride( + id = overrideId, + plannableId = 1L, + plannableType = PlannableType.ASSIGNMENT, + markedComplete = true + ) + + coEvery { + plannerApi.updatePlannerOverride( + plannerOverrideId = overrideId, + complete = true, + params = any() + ) + } returns DataResult.Success(override) + + val result = repository.updatePlannerOverride(overrideId, markedComplete = true) + + assertTrue(result is DataResult.Success) + assertEquals(overrideId, result.dataOrNull?.id) + assertEquals(true, result.dataOrNull?.markedComplete) + } + + @Test + fun `updatePlannerOverride returns failure when API call fails`() = runTest { + val overrideId = 123L + + coEvery { + plannerApi.updatePlannerOverride( + plannerOverrideId = overrideId, + complete = false, + params = any() + ) + } returns DataResult.Fail() + + val result = repository.updatePlannerOverride(overrideId, markedComplete = false) + + assertTrue(result is DataResult.Fail) + } + + @Test + fun `updatePlannerOverride always uses forceRefresh`() = runTest { + val overrideId = 123L + + coEvery { + plannerApi.updatePlannerOverride( + plannerOverrideId = any(), + complete = any(), + params = any() + ) + } returns DataResult.Success(mockk(relaxed = true)) + + repository.updatePlannerOverride(overrideId, markedComplete = true) + + coVerify { + plannerApi.updatePlannerOverride( + plannerOverrideId = overrideId, + complete = true, + params = match { it.isForceReadFromNetwork } + ) + } + } + + // createPlannerOverride tests + @Test + fun `createPlannerOverride returns success with created override`() = runTest { + val plannableId = 456L + val plannableType = PlannableType.ASSIGNMENT + val override = PlannerOverride( + id = 789L, + plannableId = plannableId, + plannableType = plannableType, + markedComplete = true + ) + + coEvery { + plannerApi.createPlannerOverride( + plannerOverride = any(), + params = any() + ) + } returns DataResult.Success(override) + + val result = repository.createPlannerOverride( + plannableId = plannableId, + plannableType = plannableType, + markedComplete = true + ) + + assertTrue(result is DataResult.Success) + assertEquals(plannableId, result.dataOrNull?.plannableId) + assertEquals(plannableType, result.dataOrNull?.plannableType) + assertEquals(true, result.dataOrNull?.markedComplete) + } + + @Test + fun `createPlannerOverride returns failure when API call fails`() = runTest { + coEvery { + plannerApi.createPlannerOverride( + plannerOverride = any(), + params = any() + ) + } returns DataResult.Fail() + + val result = repository.createPlannerOverride( + plannableId = 456L, + plannableType = PlannableType.QUIZ, + markedComplete = false + ) + + assertTrue(result is DataResult.Fail) + } + + @Test + fun `createPlannerOverride passes correct parameters to API`() = runTest { + val plannableId = 456L + val plannableType = PlannableType.DISCUSSION_TOPIC + + coEvery { + plannerApi.createPlannerOverride( + plannerOverride = any(), + params = any() + ) + } returns DataResult.Success(mockk(relaxed = true)) + + repository.createPlannerOverride( + plannableId = plannableId, + plannableType = plannableType, + markedComplete = false + ) + + coVerify { + plannerApi.createPlannerOverride( + plannerOverride = match { + it.plannableId == plannableId && + it.plannableType == plannableType && !it.markedComplete + }, + params = match { it.isForceReadFromNetwork } + ) + } + } + + @Test + fun `createPlannerOverride always uses forceRefresh`() = runTest { + coEvery { + plannerApi.createPlannerOverride( + plannerOverride = any(), + params = any() + ) + } returns DataResult.Success(mockk(relaxed = true)) + + repository.createPlannerOverride( + plannableId = 456L, + plannableType = PlannableType.PLANNER_NOTE, + markedComplete = true + ) + + coVerify { + plannerApi.createPlannerOverride( + plannerOverride = any(), + params = match { it.isForceReadFromNetwork } + ) + } + } + + // Helper function to create test PlannerItem + private fun createPlannerItem( + id: Long, + title: String, + plannableType: PlannableType = PlannableType.ASSIGNMENT + ): PlannerItem { + return PlannerItem( + courseId = 1L, + groupId = null, + userId = null, + contextType = "Course", + contextName = "Test Course", + plannableType = plannableType, + plannable = Plannable( + id = id, + title = title, + courseId = 1L, + groupId = null, + userId = null, + pointsPossible = null, + dueAt = Date(), + assignmentId = null, + todoDate = null, + startAt = null, + endAt = null, + details = null, + allDay = null, + subAssignmentTag = null + ), + plannableDate = Date(), + htmlUrl = null, + submissionState = null, + newActivity = null, + plannerOverride = null, + plannableItemDetails = null + ) + } +} \ No newline at end of file diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListViewModelTest.kt new file mode 100644 index 0000000000..e466f0fc25 --- /dev/null +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListViewModelTest.kt @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +import android.content.Context +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.Plannable +import com.instructure.canvasapi2.models.PlannableType +import com.instructure.canvasapi2.models.PlannerItem +import com.instructure.canvasapi2.models.SubmissionState +import com.instructure.canvasapi2.utils.ContextKeeper +import com.instructure.canvasapi2.utils.DataResult +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.util.Date + +@OptIn(ExperimentalCoroutinesApi::class) +class ToDoListViewModelTest { + + private val testDispatcher = UnconfinedTestDispatcher() + private val context: Context = mockk(relaxed = true) + private val repository: ToDoListRepository = mockk(relaxed = true) + + @Before + fun setUp() { + Dispatchers.setMain(testDispatcher) + ContextKeeper.appContext = context + } + + @After + fun tearDown() { + Dispatchers.resetMain() + unmockkAll() + } + + @Test + fun `ViewModel init loads data successfully`() = runTest { + val courses = listOf( + Course(id = 1L, name = "Course 1", courseCode = "CS101"), + Course(id = 2L, name = "Course 2", courseCode = "MATH201") + ) + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment 1", courseId = 1L), + createPlannerItem(id = 2L, title = "Quiz 1", courseId = 2L, plannableType = PlannableType.QUIZ) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(courses) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + assertFalse(uiState.isLoading) + assertFalse(uiState.isRefreshing) + assertFalse(uiState.isError) + assertEquals(2, uiState.itemsByDate.values.flatten().size) + } + + @Test + fun `ViewModel filters out announcements`() = runTest { + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment 1", plannableType = PlannableType.ASSIGNMENT), + createPlannerItem(id = 2L, title = "Announcement", plannableType = PlannableType.ANNOUNCEMENT) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + val allItems = uiState.itemsByDate.values.flatten() + + assertEquals(1, allItems.size) + assertEquals("Assignment 1", allItems.first().title) + } + + @Test + fun `ViewModel filters out assessment requests`() = runTest { + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment 1", plannableType = PlannableType.ASSIGNMENT), + createPlannerItem(id = 2L, title = "Assessment Request", plannableType = PlannableType.ASSESSMENT_REQUEST) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + val allItems = uiState.itemsByDate.values.flatten() + + assertEquals(1, allItems.size) + assertEquals("Assignment 1", allItems.first().title) + } + + @Test + fun `ViewModel filters out access restricted courses`() = runTest { + val courses = listOf( + Course(id = 1L, name = "Available Course", courseCode = "CS101", accessRestrictedByDate = false), + Course(id = 2L, name = "Restricted Course", courseCode = "CS102", accessRestrictedByDate = true) + ) + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment 1", courseId = 1L), + createPlannerItem(id = 2L, title = "Assignment 2", courseId = 2L) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(courses) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + every { context.getString(any(), any()) } returns "CS101" + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + val allItems = uiState.itemsByDate.values.flatten() + + // Only assignment from non-restricted course should be present with context label + assertEquals(2, allItems.size) + // First item should have context label from course map + assertTrue(allItems.any { it.title == "Assignment 1" }) + } + + @Test + fun `ViewModel filters out invited courses`() = runTest { + val courses = listOf( + Course(id = 1L, name = "Enrolled Course", courseCode = "CS101", enrollments = mutableListOf()), + Course(id = 2L, name = "Invited Course", courseCode = "CS102", enrollments = mutableListOf(mockk { + every { enrollmentState } returns "invited" + })) + ) + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment 1", courseId = 1L), + createPlannerItem(id = 2L, title = "Assignment 2", courseId = 2L) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(courses) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + // Should have both assignments, but invited course won't be in course map + assertEquals(2, uiState.itemsByDate.values.flatten().size) + } + + @Test + fun `ViewModel handles error state`() = runTest { + coEvery { repository.getCourses(any()) } returns DataResult.Fail() + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Fail() + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + assertTrue(uiState.isError) + assertFalse(uiState.isLoading) + assertFalse(uiState.isRefreshing) + } + + @Test + fun `ViewModel handles exception during load`() = runTest { + coEvery { repository.getCourses(any()) } throws RuntimeException("Test error") + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + assertTrue(uiState.isError) + assertFalse(uiState.isLoading) + assertFalse(uiState.isRefreshing) + } + + @Test + fun `ViewModel groups items by date`() = runTest { + val date1 = Date(1704067200000L) // Jan 1, 2024 + val date2 = Date(1704153600000L) // Jan 2, 2024 + + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment 1", plannableDate = date1), + createPlannerItem(id = 2L, title = "Assignment 2", plannableDate = date1), + createPlannerItem(id = 3L, title = "Assignment 3", plannableDate = date2) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + assertEquals(2, uiState.itemsByDate.keys.size) + } + + @Test + fun `ViewModel maps item types correctly`() = runTest { + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment", plannableType = PlannableType.ASSIGNMENT), + createPlannerItem(id = 2L, title = "Quiz", plannableType = PlannableType.QUIZ), + createPlannerItem(id = 3L, title = "Discussion", plannableType = PlannableType.DISCUSSION_TOPIC), + createPlannerItem(id = 4L, title = "Calendar Event", plannableType = PlannableType.CALENDAR_EVENT), + createPlannerItem(id = 5L, title = "Planner Note", plannableType = PlannableType.PLANNER_NOTE), + createPlannerItem(id = 6L, title = "Sub Assignment", plannableType = PlannableType.SUB_ASSIGNMENT) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + val allItems = uiState.itemsByDate.values.flatten() + + assertEquals(6, allItems.size) + assertEquals(ToDoItemType.ASSIGNMENT, allItems.find { it.title == "Assignment" }?.itemType) + assertEquals(ToDoItemType.QUIZ, allItems.find { it.title == "Quiz" }?.itemType) + assertEquals(ToDoItemType.DISCUSSION, allItems.find { it.title == "Discussion" }?.itemType) + assertEquals(ToDoItemType.CALENDAR_EVENT, allItems.find { it.title == "Calendar Event" }?.itemType) + assertEquals(ToDoItemType.PLANNER_NOTE, allItems.find { it.title == "Planner Note" }?.itemType) + assertEquals(ToDoItemType.SUB_ASSIGNMENT, allItems.find { it.title == "Sub Assignment" }?.itemType) + } + + @Test + fun `ViewModel sets isChecked true for submitted assignments`() = runTest { + val plannerItem = createPlannerItem( + id = 1L, + title = "Submitted Assignment", + plannableType = PlannableType.ASSIGNMENT, + submitted = true + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + val item = uiState.itemsByDate.values.flatten().first() + + assertTrue(item.isChecked) + } + + @Test + fun `ViewModel sets isChecked false for unsubmitted assignments`() = runTest { + val plannerItem = createPlannerItem( + id = 1L, + title = "Unsubmitted Assignment", + plannableType = PlannableType.ASSIGNMENT, + submitted = false + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + val item = uiState.itemsByDate.values.flatten().first() + + assertFalse(item.isChecked) + } + + // handleAction tests + @Test + fun `handleAction ItemClicked sends OpenToDoItem event`() = runTest { + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(emptyList()) + + val viewModel = getViewModel() + val events = mutableListOf() + + backgroundScope.launch(testDispatcher) { + viewModel.events.toList(events) + } + + viewModel.handleAction(ToDoListActionHandler.ItemClicked("123")) + + assertEquals(1, events.size) + assertTrue(events.first() is ToDoListViewModelAction.OpenToDoItem) + assertEquals("123", (events.first() as ToDoListViewModelAction.OpenToDoItem).itemId) + } + + @Test + fun `handleAction Refresh triggers data reload with forceRefresh`() = runTest { + val courses = listOf(Course(id = 1L, name = "Course 1", courseCode = "CS101")) + val initialPlannerItems = listOf(createPlannerItem(id = 1L, title = "Assignment 1")) + val refreshedPlannerItems = listOf( + createPlannerItem(id = 1L, title = "Assignment 1"), + createPlannerItem(id = 2L, title = "Assignment 2") + ) + + coEvery { repository.getCourses(false) } returns DataResult.Success(courses) + coEvery { repository.getPlannerItems(any(), any(), false) } returns DataResult.Success(initialPlannerItems) + coEvery { repository.getCourses(true) } returns DataResult.Success(courses) + coEvery { repository.getPlannerItems(any(), any(), true) } returns DataResult.Success(refreshedPlannerItems) + + val viewModel = getViewModel() + + // Verify initial data + val initialUiState = viewModel.uiState.value + assertEquals(1, initialUiState.itemsByDate.values.flatten().size) + assertEquals("Assignment 1", initialUiState.itemsByDate.values.flatten().first().title) + + // Trigger refresh + viewModel.handleAction(ToDoListActionHandler.Refresh) + + // Verify refreshed data + val refreshedUiState = viewModel.uiState.value + assertEquals(2, refreshedUiState.itemsByDate.values.flatten().size) + assertTrue(refreshedUiState.itemsByDate.values.flatten().any { it.title == "Assignment 2" }) + } + + @Test + fun `Empty planner items returns empty state`() = runTest { + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(emptyList()) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + assertFalse(uiState.isLoading) + assertFalse(uiState.isError) + assertTrue(uiState.itemsByDate.isEmpty()) + } + + @Test + fun `Items are sorted by comparison date`() = runTest { + val date1 = Date(1704067200000L) // Earlier date + val date2 = Date(1704153600000L) // Later date + + val plannerItems = listOf( + createPlannerItem(id = 2L, title = "Later Assignment", plannableDate = date2), + createPlannerItem(id = 1L, title = "Earlier Assignment", plannableDate = date1) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + val dates = uiState.itemsByDate.keys.toList() + + // Dates should be sorted (earlier date first) + assertTrue(dates.size == 2) + } + + // Helper functions + private fun getViewModel(): ToDoListViewModel { + return ToDoListViewModel(context, repository) + } + + private fun createPlannerItem( + id: Long, + title: String, + courseId: Long? = null, + plannableType: PlannableType = PlannableType.ASSIGNMENT, + plannableDate: Date = Date(), + submitted: Boolean = false + ): PlannerItem { + return PlannerItem( + courseId = courseId, + groupId = null, + userId = null, + contextType = if (courseId != null) "Course" else null, + contextName = null, + plannableType = plannableType, + plannable = Plannable( + id = id, + title = title, + courseId = courseId, + groupId = null, + userId = null, + pointsPossible = null, + dueAt = plannableDate, + assignmentId = null, + todoDate = null, + startAt = null, + endAt = null, + details = null, + allDay = null, + subAssignmentTag = null + ), + plannableDate = plannableDate, + htmlUrl = null, + submissionState = if (submitted) SubmissionState(submitted = true) else null, + newActivity = null, + plannerOverride = null, + plannableItemDetails = null + ) + } +} \ No newline at end of file diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/utils/PlannerItemExtensionsTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/utils/PlannerItemExtensionsTest.kt new file mode 100644 index 0000000000..6536e2ecba --- /dev/null +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/utils/PlannerItemExtensionsTest.kt @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2025 - 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.pandautils.utils + +import android.content.Context +import com.instructure.canvasapi2.models.Course +import com.instructure.canvasapi2.models.Plannable +import com.instructure.canvasapi2.models.PlannableType +import com.instructure.canvasapi2.models.PlannerItem +import com.instructure.canvasapi2.models.PlannerItemDetails +import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.DateHelper +import com.instructure.canvasapi2.utils.toApiString +import com.instructure.pandautils.R +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkAll +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import java.util.Calendar +import java.util.Date + +class PlannerItemExtensionsTest { + + private val context: Context = mockk(relaxed = true) + + @Before + fun setup() { + mockkObject(ApiPrefs) + every { ApiPrefs.fullDomain } returns "https://test.instructure.com" + + mockkObject(DateHelper) + } + + @After + fun tearDown() { + unmockkAll() + } + + companion object { + // Static dates for predictable testing + private val TEST_DATE = createDate(2025, Calendar.JANUARY, 15, 14, 30) // Jan 15, 2025 at 2:30 PM + private val TEST_DATE_2 = createDate(2025, Calendar.JANUARY, 15, 15, 30) // Jan 15, 2025 at 3:30 PM + + private fun createDate(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0): Date { + return Calendar.getInstance().apply { + set(year, month, day, hour, minute, 0) + set(Calendar.MILLISECOND, 0) + }.time + } + } + + // todoHtmlUrl tests + @Test + fun `todoHtmlUrl returns correct URL`() { + val plannerItem = createPlannerItem(plannableId = 12345L) + + val result = plannerItem.todoHtmlUrl(ApiPrefs) + + assertEquals("https://test.instructure.com/todos/12345", result) + } + + // getIconForPlannerItem tests + @Test + fun `getIconForPlannerItem returns assignment icon for ASSIGNMENT type`() { + val plannerItem = createPlannerItem(plannableType = PlannableType.ASSIGNMENT) + + val result = plannerItem.getIconForPlannerItem() + + assertEquals(R.drawable.ic_assignment, result) + } + + @Test + fun `getIconForPlannerItem returns quiz icon for QUIZ type`() { + val plannerItem = createPlannerItem(plannableType = PlannableType.QUIZ) + + val result = plannerItem.getIconForPlannerItem() + + assertEquals(R.drawable.ic_quiz, result) + } + + @Test + fun `getIconForPlannerItem returns calendar icon for CALENDAR_EVENT type`() { + val plannerItem = createPlannerItem(plannableType = PlannableType.CALENDAR_EVENT) + + val result = plannerItem.getIconForPlannerItem() + + assertEquals(R.drawable.ic_calendar, result) + } + + @Test + fun `getIconForPlannerItem returns discussion icon for DISCUSSION_TOPIC type`() { + val plannerItem = createPlannerItem(plannableType = PlannableType.DISCUSSION_TOPIC) + + val result = plannerItem.getIconForPlannerItem() + + assertEquals(R.drawable.ic_discussion, result) + } + + @Test + fun `getIconForPlannerItem returns discussion icon for SUB_ASSIGNMENT type`() { + val plannerItem = createPlannerItem(plannableType = PlannableType.SUB_ASSIGNMENT) + + val result = plannerItem.getIconForPlannerItem() + + assertEquals(R.drawable.ic_discussion, result) + } + + @Test + fun `getIconForPlannerItem returns todo icon for PLANNER_NOTE type`() { + val plannerItem = createPlannerItem(plannableType = PlannableType.PLANNER_NOTE) + + val result = plannerItem.getIconForPlannerItem() + + assertEquals(R.drawable.ic_todo, result) + } + + @Test + fun `getIconForPlannerItem returns calendar icon for unknown type`() { + val plannerItem = createPlannerItem(plannableType = PlannableType.ANNOUNCEMENT) + + val result = plannerItem.getIconForPlannerItem() + + assertEquals(R.drawable.ic_calendar, result) + } + + // getDateTextForPlannerItem tests + @Test + fun `getDateTextForPlannerItem returns formatted time for PLANNER_NOTE with todoDate`() { + val plannable = createPlannable(todoDate = TEST_DATE.toApiString()) + val plannerItem = createPlannerItem( + plannableType = PlannableType.PLANNER_NOTE, + plannable = plannable + ) + + every { DateHelper.getFormattedTime(context, TEST_DATE) } returns "2:30 PM" + + val result = plannerItem.getDateTextForPlannerItem(context) + + assertEquals("2:30 PM", result) + } + + @Test + fun `getDateTextForPlannerItem returns null for PLANNER_NOTE without todoDate`() { + val plannable = createPlannable(todoDate = null) + val plannerItem = createPlannerItem( + plannableType = PlannableType.PLANNER_NOTE, + plannable = plannable + ) + + val result = plannerItem.getDateTextForPlannerItem(context) + + assertNull(result) + } + + @Test + fun `getDateTextForPlannerItem returns all day text for all-day CALENDAR_EVENT`() { + val plannable = createPlannable( + startAt = TEST_DATE, + endAt = TEST_DATE, + allDay = true + ) + val plannerItem = createPlannerItem( + plannableType = PlannableType.CALENDAR_EVENT, + plannable = plannable + ) + + every { context.getString(R.string.widgetAllDay) } returns "All Day" + + val result = plannerItem.getDateTextForPlannerItem(context) + + assertEquals("All Day", result) + } + + @Test + fun `getDateTextForPlannerItem returns single time for CALENDAR_EVENT with same start and end`() { + val plannable = createPlannable( + startAt = TEST_DATE, + endAt = TEST_DATE, + allDay = false + ) + val plannerItem = createPlannerItem( + plannableType = PlannableType.CALENDAR_EVENT, + plannable = plannable + ) + + every { DateHelper.getFormattedTime(context, TEST_DATE) } returns "2:30 PM" + + val result = plannerItem.getDateTextForPlannerItem(context) + + assertEquals("2:30 PM", result) + } + + @Test + fun `getDateTextForPlannerItem returns time range for CALENDAR_EVENT with different times`() { + val plannable = createPlannable( + startAt = TEST_DATE, + endAt = TEST_DATE_2, + allDay = false + ) + val plannerItem = createPlannerItem( + plannableType = PlannableType.CALENDAR_EVENT, + plannable = plannable + ) + + every { DateHelper.getFormattedTime(context, TEST_DATE) } returns "2:30 PM" + every { DateHelper.getFormattedTime(context, TEST_DATE_2) } returns "3:30 PM" + every { context.getString(R.string.widgetFromTo, "2:30 PM", "3:30 PM") } returns "2:30 PM - 3:30 PM" + + val result = plannerItem.getDateTextForPlannerItem(context) + + assertEquals("2:30 PM - 3:30 PM", result) + } + + @Test + fun `getDateTextForPlannerItem returns null for CALENDAR_EVENT without dates`() { + val plannable = createPlannable( + startAt = null, + endAt = null + ) + val plannerItem = createPlannerItem( + plannableType = PlannableType.CALENDAR_EVENT, + plannable = plannable + ) + + val result = plannerItem.getDateTextForPlannerItem(context) + + assertNull(result) + } + + @Test + fun `getDateTextForPlannerItem returns due date text for ASSIGNMENT with dueAt`() { + val plannable = createPlannable(dueAt = TEST_DATE) + val plannerItem = createPlannerItem( + plannableType = PlannableType.ASSIGNMENT, + plannable = plannable + ) + + every { DateHelper.getFormattedTime(context, TEST_DATE) } returns "2:30 PM" + every { context.getString(R.string.widgetDueDate, "2:30 PM") } returns "Due: 2:30 PM" + + val result = plannerItem.getDateTextForPlannerItem(context) + + assertEquals("Due: 2:30 PM", result) + } + + @Test + fun `getDateTextForPlannerItem returns null for ASSIGNMENT without dueAt`() { + val plannable = createPlannable(dueAt = null) + val plannerItem = createPlannerItem( + plannableType = PlannableType.ASSIGNMENT, + plannable = plannable + ) + + val result = plannerItem.getDateTextForPlannerItem(context) + + assertNull(result) + } + + // getContextNameForPlannerItem tests + @Test + fun `getContextNameForPlannerItem returns User To-Do for PLANNER_NOTE without contextName`() { + val plannerItem = createPlannerItem( + plannableType = PlannableType.PLANNER_NOTE, + contextName = null + ) + + every { context.getString(R.string.userCalendarToDo) } returns "User To-Do" + + val result = plannerItem.getContextNameForPlannerItem(context, emptyList()) + + assertEquals("User To-Do", result) + } + + @Test + fun `getContextNameForPlannerItem returns course todo for PLANNER_NOTE with contextName`() { + val course = Course(id = 123L, courseCode = "CS101") + val plannerItem = createPlannerItem( + plannableType = PlannableType.PLANNER_NOTE, + courseId = 123L, + contextName = "Computer Science" + ) + + every { context.getString(R.string.courseToDo, "CS101") } returns "CS101 To-Do" + + val result = plannerItem.getContextNameForPlannerItem(context, listOf(course)) + + assertEquals("CS101 To-Do", result) + } + + @Test + fun `getContextNameForPlannerItem returns course code for Course context`() { + val course = Course(id = 123L, courseCode = "CS101") + val plannerItem = createPlannerItem( + plannableType = PlannableType.ASSIGNMENT, + courseId = 123L + ) + + val result = plannerItem.getContextNameForPlannerItem(context, listOf(course)) + + assertEquals("CS101", result) + } + + @Test + fun `getContextNameForPlannerItem returns empty string for Course context without matching course`() { + val course = Course(id = 999L, courseCode = "CS101") + val plannerItem = createPlannerItem( + plannableType = PlannableType.ASSIGNMENT, + courseId = 123L + ) + + val result = plannerItem.getContextNameForPlannerItem(context, listOf(course)) + + assertEquals("", result) + } + + @Test + fun `getContextNameForPlannerItem returns contextName for non-Course context`() { + val plannerItem = createPlannerItem( + plannableType = PlannableType.ASSIGNMENT, + userId = 456L, + contextName = "Personal" + ) + + val result = plannerItem.getContextNameForPlannerItem(context, emptyList()) + + assertEquals("Personal", result) + } + + // getTagForPlannerItem tests + @Test + fun `getTagForPlannerItem returns reply to topic for REPLY_TO_TOPIC tag`() { + val plannable = createPlannable(subAssignmentTag = Const.REPLY_TO_TOPIC) + val plannerItem = createPlannerItem(plannable = plannable) + + every { context.getString(R.string.reply_to_topic) } returns "Reply to Topic" + + val result = plannerItem.getTagForPlannerItem(context) + + assertEquals("Reply to Topic", result) + } + + @Test + fun `getTagForPlannerItem returns additional replies for REPLY_TO_ENTRY with count`() { + val details = PlannerItemDetails(replyRequiredCount = 3) + val plannable = createPlannable(subAssignmentTag = Const.REPLY_TO_ENTRY) + val plannerItem = createPlannerItem( + plannable = plannable, + plannableItemDetails = details + ) + + every { context.getString(R.string.additional_replies, 3) } returns "3 Additional Replies" + + val result = plannerItem.getTagForPlannerItem(context) + + assertEquals("3 Additional Replies", result) + } + + @Test + fun `getTagForPlannerItem returns null for REPLY_TO_ENTRY without count`() { + val plannable = createPlannable(subAssignmentTag = Const.REPLY_TO_ENTRY) + val plannerItem = createPlannerItem( + plannable = plannable, + plannableItemDetails = null + ) + + val result = plannerItem.getTagForPlannerItem(context) + + assertNull(result) + } + + @Test + fun `getTagForPlannerItem returns null for no subAssignmentTag`() { + val plannable = createPlannable(subAssignmentTag = null) + val plannerItem = createPlannerItem(plannable = plannable) + + val result = plannerItem.getTagForPlannerItem(context) + + assertNull(result) + } + + // Helper functions to create test objects with default values + private fun createPlannable( + id: Long = 1L, + title: String = "Test", + courseId: Long? = null, + groupId: Long? = null, + userId: Long? = null, + pointsPossible: Double? = null, + dueAt: Date? = null, + assignmentId: Long? = null, + todoDate: String? = null, + startAt: Date? = null, + endAt: Date? = null, + details: String? = null, + allDay: Boolean? = null, + subAssignmentTag: String? = null + ): Plannable { + return Plannable( + id = id, + title = title, + courseId = courseId, + groupId = groupId, + userId = userId, + pointsPossible = pointsPossible, + dueAt = dueAt, + assignmentId = assignmentId, + todoDate = todoDate, + startAt = startAt, + endAt = endAt, + details = details, + allDay = allDay, + subAssignmentTag = subAssignmentTag + ) + } + + private fun createPlannerItem( + plannableId: Long = 1L, + plannableType: PlannableType = PlannableType.ASSIGNMENT, + plannable: Plannable = createPlannable(id = plannableId), + courseId: Long? = null, + userId: Long? = null, + contextName: String? = null, + plannableItemDetails: PlannerItemDetails? = null + ): PlannerItem { + return PlannerItem( + courseId = courseId, + groupId = null, + userId = userId, + contextType = if (courseId != null) "Course" else null, + contextName = contextName, + plannableType = plannableType, + plannable = plannable, + plannableDate = Date(), + htmlUrl = null, + submissionState = null, + newActivity = null, + plannerOverride = null, + plannableItemDetails = plannableItemDetails + ) + } +} \ No newline at end of file From ce7d449e9c28c9b2b05d54a05e5378e25282defe Mon Sep 17 00:00:00 2001 From: Tamas Kozmer Date: Tue, 4 Nov 2025 18:07:41 +0100 Subject: [PATCH 07/58] Fix Syllabus issues. --- .../teacher/features/syllabus/ui/SyllabusFragment.kt | 6 ++++++ .../teacher/features/syllabus/ui/SyllabusTabAdapter.kt | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/ui/SyllabusFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/ui/SyllabusFragment.kt index dd808b4416..14bcceeac3 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/ui/SyllabusFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/features/syllabus/ui/SyllabusFragment.kt @@ -16,6 +16,7 @@ */ package com.instructure.teacher.features.syllabus.ui +import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import com.instructure.canvasapi2.models.Course @@ -54,6 +55,11 @@ abstract class SyllabusFragment : MobiusFragment) : PagerAdapter() { private fun isSyllabusPosition(position: Int) = position == SYLLABUS_TAB_POSITION private fun setupWebView(webView: CanvasWebView) { - val activity = (webView.context as? FragmentActivity) - activity?.let { webView.addVideoClient(it) } + val activity = webView.context.getFragmentActivity() + webView.addVideoClient(activity) webView.canvasWebViewClientCallback = object : CanvasWebView.CanvasWebViewClientCallback { override fun openMediaFromWebView(mime: String, url: String, filename: String) { RouteMatcher.openMedia(activity, url) @@ -83,7 +83,7 @@ class SyllabusTabAdapter(private val titles: List) : PagerAdapter() { } override fun launchInternalWebViewFragment(url: String) { - activity?.startActivity(InternalWebViewActivity.createIntent(webView.context, url, "", true)) + activity.startActivity(InternalWebViewActivity.createIntent(webView.context, url, "", true)) } } From c12f8237be6c206be5952c24f84ab17f964d7770 Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:25:19 +0100 Subject: [PATCH 08/58] Actions PR pipeline (#3368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Optimize PR pipeline workflow with dynamic matrix and performance improvements This commit significantly optimizes the GitHub Actions PR pipeline to reduce build times and runner costs while improving developer experience. Key optimizations: - Dynamic matrix generation: Only builds apps mentioned in PR body, eliminating wasted runner time from jobs that skip all steps - Enhanced Gradle caching: Added build cache and more granular cache keys for better hit rates (saves 2-5 minutes per build) - Gradle performance flags: Enabled parallel execution, configuration cache, and optimized JVM settings (saves 3-7 minutes per build) - Shallow clone: Added fetch-depth: 1 to all checkouts (saves 30-60 seconds) - Removed unit-test dependency: UI/E2E tests now only depend on build job, allowing parallel execution (saves 5-10 minutes) - Batch artifact uploads: Combined 3 separate uploads into 1 per app with compression and retention settings - Firebase CLI caching: Cache npm global packages to speed up Firebase CLI installation (saves 30-45 seconds) - Sticky QR code comments: Firebase distribution comments now update in place rather than creating duplicates, with timestamps and commit links - Consolidated Firebase App ID setup: Replaced 3 conditional steps with 1 case statement - Fixed bugs: Removed jq dependencies, fixed e2e-tests job name, updated artifact download logic Expected performance gains: - Per PR (single app): 8-12 minutes faster - Per PR (all 3 apps): 10-15 minutes faster - Runner minute savings: 30-40% reduction - Improved cache hit rates and faster subsequent builds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Fix hashFiles syntax error in pr-pipeline workflow The hashFiles() function was incorrectly using multiple separate calls which caused parsing errors in GitHub Actions. Consolidated all glob patterns into a single hashFiles() call and simplified restore-keys. This fixes the build failures where the workflow would fail with: 'hashFiles('**/*.gradle*, ...) failed. Fail to hash files under directory' * Fix hashFiles glob patterns in workflow Changes: - Split invalid '**/*.gradle*' into explicit '**/*.gradle' and '**/*.gradle.kts' - Changed 'gradle.properties' to '**/gradle.properties' to catch all instances - Fixed buildSrc path from '**/buildSrc/**/*.kt' to 'apps/buildSrc/src/**/*.kt' All patterns now match actual files in the repository: - 24 .gradle files - 8 .gradle.kts files - 2 gradle-wrapper.properties files - 3 gradle.properties files - 4 Kotlin files in apps/buildSrc/src/ This resolves the hashFiles parsing error in GitHub Actions. * Fix hashFiles to use separate calls for each pattern GitHub Actions hashFiles() doesn't properly handle multiple comma-separated patterns in a single call. Split into separate hashFiles() calls: - hashFiles('**/*.gradle') for Groovy build files - hashFiles('**/*.gradle.kts') for Kotlin build files This approach avoids the parsing issues and generates proper cache keys. * Use official Gradle hashFiles pattern from GitHub Actions docs Using the exact pattern from actions/cache documentation: hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') This is the recommended pattern for Gradle projects and should work correctly with GitHub Actions' hashFiles function. * Simplify hashFiles to single pattern for gradle-wrapper.properties Use only gradle-wrapper.properties for cache key to avoid hashFiles errors. This is the most critical file for Gradle cache invalidation as it determines the Gradle version. Testing with minimal pattern to isolate the hashFiles issue. * Remove TimingsListener from student build.gradle The TimingsListener is incompatible with Gradle's configuration cache (--configuration-cache flag). Removing it allows us to use configuration cache for faster builds. The listener is no longer used and can be safely removed. * Remove --configuration-cache flag from all Gradle commands The project has multiple configuration cache incompatibilities: - Task.project access at execution time in dataseedingapi/build.gradle - Project serialization issues in various tasks - Custom build listeners (TimingsListener was removed but issues remain) Removing --configuration-cache to allow builds to succeed. We still get benefits from --build-cache, --parallel, and other optimizations. * Add caching, shallow clones, and Gradle performance tuning Optimizations added: 1. Shallow git clones (fetch-depth: 1) - Faster checkout 2. Enhanced Gradle caching: - Cache Gradle packages (~/.gradle/caches, ~/.gradle/wrapper) - Cache Gradle build cache (build-cache-* and .gradle directory) 3. Gradle performance tuning flags: - --build-cache: Use Gradle build cache - --parallel: Run tasks in parallel - --max-workers=4: Limit parallel workers - --no-daemon: Disable daemon for CI - -Dorg.gradle.jvmargs="-Xmx4g": Increase heap size - -Dkotlin.compiler.execution.strategy=in-process: Faster Kotlin compilation Also removed TimingsListener from student/build.gradle (incompatible with future config cache support) * Split test matrix jobs into individual jobs for independent retry Replace 3 matrix-based test jobs with 12 individual jobs: Unit Tests (3 jobs): - parent-unit-tests - student-unit-tests - teacher-unit-tests UI Tests (6 jobs): - parent-portrait-ui-tests - parent-landscape-ui-tests - student-portrait-ui-tests - student-landscape-ui-tests - teacher-portrait-ui-tests - teacher-landscape-ui-tests E2E Tests (3 jobs): - parent-e2e-tests - student-e2e-tests - teacher-e2e-tests Benefits: - Each test job can be retried independently - Flaky tests don't block other tests - Better visibility in GitHub Actions UI - Clearer dependencies (no matrix complexity) - Easier debugging with explicit job names All jobs include performance optimizations: - Gradle flags: --build-cache, --parallel, --max-workers=4 - Shallow clones: fetch-depth: 1 - Enhanced caching for Gradle packages and build cache * Fix hashFiles pattern in unit test jobs to use working syntax * Update test job conditions: UI tests always run, E2E tests run when checkbox present in PR body * Fix unit test Gradle command syntax - use multiline format * Fix PR pipeline: unit tests, heap size, and sticky QR comments - Fix unit test Gradle commands by using multiline format - Increase JVM heap size to 6GB (matching apps/gradle.properties) - Restore sticky QR code comments with hidden HTML identifiers - Remove jq dependency with pure bash URL encoding - Configure UI tests to run automatically on PR open/sync - Configure E2E tests to run when "Run E2E test suite" is in PR body 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Add security improvements to PR pipeline workflow - Add file permissions (chmod 600) for sensitive files (keystore, service account) - Add secret validation with clear error messages - Add error handling for Firebase distribution operations - Add URL extraction validation - Add cleanup steps for sensitive files with always() condition - Applied to build job and parent-portrait-ui-tests job 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Complete security hardening for all test jobs Apply security improvements to all remaining test jobs: - Add secret validation with error messages for all test jobs - Add chmod 600 for service account keys across all jobs - Add cleanup steps with always() condition for all test jobs Security improvements now applied to: - 6 UI test jobs (portrait + landscape for parent/student/teacher) - 3 E2E test jobs (parent/student/teacher) All critical security issues from AI review are now resolved. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Enhance Claude code review workflow with update tracking and focused feedback - On PR synchronize events, Claude now reads and updates its previous reviews instead of creating new ones - Implemented checkbox-based progress tracking for identified issues - Inline comments now reserved for change requests only; positive feedback stays in summary - Added GitHub MCP tools for review management: list_reviews, get_review, list_review_comments, update_review_comment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- .github/workflows/claude-code-review.yml | 33 +- .github/workflows/pr-pipeline.yml | 1115 ++++++++++++++++++++++ apps/student/build.gradle | 3 - 3 files changed, 1145 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/pr-pipeline.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 1f33912b41..d756544331 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -39,6 +39,7 @@ jobs: prompt: | REPO: ${{ github.repository }} PR NUMBER: ${{ github.event.pull_request.number }} + EVENT ACTION: ${{ github.event.action }} Please review this pull request and provide inline feedback using the GitHub review system. @@ -52,11 +53,37 @@ jobs: Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. Instructions: + ${{ github.event.action == 'synchronize' && ' + ## SYNCHRONIZE EVENT - UPDATE EXISTING REVIEW + This is an update to an existing PR. You must: + 1. Use the GitHub MCP tools to fetch your previous reviews on this PR + 2. Fetch the latest PR diff and identify what has changed since your last review + 3. Update your EXISTING review comments - DO NOT create a new review summary + 4. Use checkboxes to track progress on previously identified issues: + - [ ] Unresolved issue + - [x] Resolved issue + 5. For each previously identified issue: + - If it has been addressed: Mark the checkbox as complete [x] and add a note + - If it is still present: Keep the checkbox unchecked [ ] + - If new issues are found: Add new checkboxes [ ] + 6. Update inline comments: + - Resolve or update threads that have been addressed + - Add new inline comments ONLY for new issues that require changes + - Do NOT add inline comments for positive changes or improvements + 7. Keep all positive feedback in the summary section only + + DO NOT create a new review from scratch. Update the existing one.' || ' + ## NEW REVIEW EVENT + This is a new PR or initial review. You must: 1. Use the GitHub MCP tools to fetch the PR diff - 2. Add inline comments using the appropriate MCP tools for each specific piece of feedback on particular lines - 3. Submit the review with event type 'COMMENT' (not 'REQUEST_CHANGES') to publish as non-blocking feedback + 2. Create a review summary with checkboxes for any issues found: + - [ ] Issue description and location + 3. Add inline comments ONLY for specific code that needs changes + 4. DO NOT add inline comments for positive feedback - include positive feedback in the summary section only + 5. Submit the review with event type COMMENT (not REQUEST_CHANGES) to publish as non-blocking feedback + ' }} # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options - claude_args: '--allowedTools "mcp__github__create_pending_pull_request_review,mcp__github__add_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff"' + claude_args: '--allowedTools "mcp__github__create_pending_pull_request_review,mcp__github__add_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff,mcp__github__list_reviews,mcp__github__get_review,mcp__github__list_review_comments,mcp__github__update_review_comment,mcp__github__create_or_update_pull_request_review_comment"' diff --git a/.github/workflows/pr-pipeline.yml b/.github/workflows/pr-pipeline.yml new file mode 100644 index 0000000000..5faf5418c9 --- /dev/null +++ b/.github/workflows/pr-pipeline.yml @@ -0,0 +1,1115 @@ +name: Pull Request + +on: + pull_request: + types: [opened, synchronize, labeled] + branches-ignore: + - 'release/**' + +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build: + name: ${{ matrix.app-type-lower }}-build + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && ( + contains(github.event.pull_request.body, 'Parent') || + contains(github.event.pull_request.body, 'Student') || + contains(github.event.pull_request.body, 'Teacher') + ) + strategy: + fail-fast: true + matrix: + include: + - app-type: Parent + app-type-lower: parent + should_run: ${{ contains(github.event.pull_request.body, 'Parent') }} + - app-type: Student + app-type-lower: student + should_run: ${{ contains(github.event.pull_request.body, 'Student') }} + - app-type: Teacher + app-type-lower: teacher + should_run: ${{ contains(github.event.pull_request.body, 'Teacher') }} + steps: + - name: Dump Pull Request Event Context + run: | + echo "body: ${{ github.event.pull_request.body }}" + echo "${{ toJson(github.event.pull_request) }}" + + - name: Checkout repository + if: ${{ matrix.should_run }} + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 1 + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Set up JDK 17 + if: ${{ matrix.should_run }} + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Gradle packages + if: ${{ matrix.should_run }} + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Cache Gradle Build Cache + if: ${{ matrix.should_run }} + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches/build-cache-* + .gradle + key: ${{ runner.os }}-gradle-build-cache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-gradle-build-cache- + + - name: Decode Release Keystore + if: ${{ matrix.should_run }} + run: | + echo "${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }}" | base64 --decode > release.jks + chmod 600 release.jks + + - name: Setup Service account + if: ${{ matrix.should_run }} + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: FIREBASE_SERVICE_ACCOUNT_KEY is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Build Release Notes + if: ${{ matrix.should_run }} + id: get_release_notes + run: | + echo "RELEASE_NOTES<> $GITHUB_OUTPUT + echo "${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Install Firebase CLI + if: ${{ matrix.should_run }} + run: npm install -g firebase-tools + + - name: Setup Parent Firebase App Id + if: ${{ matrix.should_run && matrix.app-type == 'Parent' }} + run: | + if [ -z "${{ secrets.FIREBASE_ANDROID_PARENT_APP_ID }}" ]; then + echo "Error: FIREBASE_ANDROID_PARENT_APP_ID is not set" + exit 1 + fi + echo "${{ secrets.FIREBASE_ANDROID_PARENT_APP_ID }}" > firebase_app_id.txt + + - name: Setup Student Firebase App Id + if: ${{ matrix.should_run && matrix.app-type == 'Student' }} + run: | + if [ -z "${{ secrets.FIREBASE_ANDROID_STUDENT_APP_ID }}" ]; then + echo "Error: FIREBASE_ANDROID_STUDENT_APP_ID is not set" + exit 1 + fi + echo "${{ secrets.FIREBASE_ANDROID_STUDENT_APP_ID }}" > firebase_app_id.txt + + - name: Setup Teacher Firebase App Id + if: ${{ matrix.should_run && matrix.app-type == 'Teacher' }} + run: | + if [ -z "${{ secrets.FIREBASE_ANDROID_TEACHER_APP_ID }}" ]; then + echo "Error: FIREBASE_ANDROID_TEACHER_APP_ID is not set" + exit 1 + fi + echo "${{ secrets.FIREBASE_ANDROID_TEACHER_APP_ID }}" > firebase_app_id.txt + + # Building Artifacts + - name: Build debug and test APKs + if: ${{ matrix.should_run }} + run: | + ./gradle/gradlew -p apps :${{ matrix.app-type-lower }}:assembleQaDebug \ + :${{ matrix.app-type-lower }}:assembleQaDebugAndroidTest \ + :${{ matrix.app-type-lower }}:assembleDevDebugMinify \ + --build-cache \ + --parallel \ + --max-workers=4 \ + --no-daemon \ + -Dorg.gradle.jvmargs="-Xmx6g -XX:+HeapDumpOnOutOfMemoryError" \ + -Dkotlin.compiler.execution.strategy=in-process \ + -Pandroid.injected.signing.store.file=$(pwd)/release.jks + + # Uploading Artifacts to GitHub + - name: Upload QA debug APK + if: ${{ matrix.should_run }} + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.app-type-lower }}-qa-debug.apk + path: apps/${{ matrix.app-type-lower }}/build/outputs/apk/qa/debug/${{ matrix.app-type-lower }}-qa-debug.apk + + - name: Upload QA test APK + if: ${{ matrix.should_run }} + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.app-type-lower }}-qa-debug-androidTest.apk + path: apps/${{ matrix.app-type-lower }}/build/outputs/apk/androidTest/qa/debug/${{ matrix.app-type-lower }}-qa-debug-androidTest.apk + + - name: Upload Dev debug APK + if: ${{ matrix.should_run }} + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.app-type-lower }}-dev-debugMinify.apk + path: apps/${{ matrix.app-type-lower }}/build/outputs/apk/dev/debugMinify/${{ matrix.app-type-lower }}-dev-debugMinify.apk + + # Uploading Artifacts to Firebase App Distribution + - name: Distribute app to Firebase App Distribution + if: ${{ matrix.should_run }} + env: + GOOGLE_APPLICATION_CREDENTIALS: ${{ github.workspace }}/service-account-key.json + run: | + firebase --version + FIREBASE_APP_ID=$(cat firebase_app_id.txt) + + if ! firebase appdistribution:distribute "apps/${{ matrix.app-type-lower }}/build/outputs/apk/dev/debugMinify/${{ matrix.app-type-lower }}-dev-debugMinify.apk" \ + --app "$FIREBASE_APP_ID" \ + --release-notes "${{ steps.get_release_notes.outputs.RELEASE_NOTES }}" \ + --groups "Testers" > result.txt 2>&1; then + echo "Firebase distribution failed:" + cat result.txt + exit 1 + fi + cat result.txt + + - name: Prepare Comment Body + if: ${{ matrix.should_run }} + id: prepare_comment + run: | + INSTALL_URL=$(grep -o 'https://appdistribution\.firebase[^[:space:]]*' result.txt | head -1) + + if [ -z "$INSTALL_URL" ]; then + echo "Error: Could not extract install URL from Firebase output" + cat result.txt + exit 1 + fi + + INSTALL_URL_ESCAPED=$(printf '%s' "$INSTALL_URL" | sed 's/:/%3A/g; s/\//%2F/g; s/?/%3F/g; s/=/%3D/g; s/&/%26/g') + { + echo "body<" + echo "

${{ matrix.app-type }} Install Page

" + echo "EOF" + } >> $GITHUB_OUTPUT + + - name: Find Previous Comment + if: ${{ matrix.should_run }} + id: find_comment + uses: peter-evans/find-comment@v2 + with: + issue-number: ${{ github.event.number }} + comment-author: 'github-actions[bot]' + body-includes: '' + + - name: Create or Update Comment + if: ${{ matrix.should_run }} + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find_comment.outputs.comment-id }} + issue-number: ${{ github.event.number }} + body: ${{ steps.prepare_comment.outputs.body }} + edit-mode: replace + + - name: Cleanup sensitive files + if: always() && matrix.should_run + run: | + rm -f release.jks + rm -f service-account-key.json + rm -f firebase_app_id.txt + rm -f result.txt + + submodule-build-and-test: + name: submodule-build-and-test + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && ( + contains(github.event.pull_request.body, 'Parent') || + contains(github.event.pull_request.body, 'Student') || + contains(github.event.pull_request.body, 'Teacher') + ) + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # Building Artifacts + - name: Build test and app APKs + run: | + ./gradle/gradlew -p apps :pandautils:assembleDebugAndroidTest + mv ./libs/pandautils/build/outputs/apk/androidTest/debug/pandautils-debug-androidTest.apk ./libs/pandautils/pandautils-test.apk + ./gradle/gradlew -p apps :pandautils:assembleDebugAndroidTest -DtestApplicationId=com.instructure.pandautils + mv ./libs/pandautils/build/outputs/apk/androidTest/debug/pandautils-debug-androidTest.apk ./libs/pandautils/pandautils-app.apk + + - name: Run submodule unit tests + run: | + ./gradle/gradlew -p apps testDebugUnitTest -x :dataseedingapi:test -x :teacher:test -x :student:test + + # Uploading Artifacts to GitHub + - name: Upload test APK + uses: actions/upload-artifact@v4 + with: + name: pandautils-test.apk + path: libs/pandautils/pandautils-test.apk + + - name: Upload app APK + uses: actions/upload-artifact@v4 + with: + name: pandautils-app.apk + path: libs/pandautils/pandautils-app.apk + + parent-unit-tests: + name: parent-unit-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Parent') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 1 + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Run unit tests + run: | + ./gradle/gradlew -p apps :parent:testDevDebugUnitTest \ + --build-cache \ + --parallel \ + --max-workers=4 \ + --no-daemon \ + -Dorg.gradle.jvmargs="-Xmx6g -XX:+HeapDumpOnOutOfMemoryError" \ + -Dkotlin.compiler.execution.strategy=in-process + + student-unit-tests: + name: student-unit-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Student') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 1 + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Run unit tests + run: | + ./gradle/gradlew -p apps :student:testDevDebugUnitTest \ + --build-cache \ + --parallel \ + --max-workers=4 \ + --no-daemon \ + -Dorg.gradle.jvmargs="-Xmx6g -XX:+HeapDumpOnOutOfMemoryError" \ + -Dkotlin.compiler.execution.strategy=in-process + + teacher-unit-tests: + name: teacher-unit-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Teacher') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 1 + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Run unit tests + run: | + ./gradle/gradlew -p apps :teacher:testDevDebugUnitTest \ + --build-cache \ + --parallel \ + --max-workers=4 \ + --no-daemon \ + -Dorg.gradle.jvmargs="-Xmx6g -XX:+HeapDumpOnOutOfMemoryError" \ + -Dkotlin.compiler.execution.strategy=in-process + + parent-portrait-ui-tests: + name: parent-portrait-ui-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Parent') + needs: [build, parent-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/parent/flank.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "parent-qa-debug.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/qa/debug + mv parent-qa-debug.apk/parent-qa-debug.apk apps/parent/build/outputs/apk/qa/debug/ + rm -rf parent-qa-debug.apk + fi + if [ -d "parent-qa-debug-androidTest.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/androidTest/qa/debug + mv parent-qa-debug-androidTest.apk/parent-qa-debug-androidTest.apk apps/parent/build/outputs/apk/androidTest/qa/debug/ + rm -rf parent-qa-debug-androidTest.apk + fi + if [ -d "parent-dev-debugMinify.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/dev/debugMinify + mv parent-dev-debugMinify.apk/parent-dev-debugMinify.apk apps/parent/build/outputs/apk/dev/debugMinify/ + rm -rf parent-dev-debugMinify.apk + fi + + - name: Run Flank UI tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash parent results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + parent-landscape-ui-tests: + name: parent-landscape-ui-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Parent') + needs: [build, parent-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/parent/flank_landscape.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "parent-qa-debug.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/qa/debug + mv parent-qa-debug.apk/parent-qa-debug.apk apps/parent/build/outputs/apk/qa/debug/ + rm -rf parent-qa-debug.apk + fi + if [ -d "parent-qa-debug-androidTest.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/androidTest/qa/debug + mv parent-qa-debug-androidTest.apk/parent-qa-debug-androidTest.apk apps/parent/build/outputs/apk/androidTest/qa/debug/ + rm -rf parent-qa-debug-androidTest.apk + fi + if [ -d "parent-dev-debugMinify.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/dev/debugMinify + mv parent-dev-debugMinify.apk/parent-dev-debugMinify.apk apps/parent/build/outputs/apk/dev/debugMinify/ + rm -rf parent-dev-debugMinify.apk + fi + + - name: Run Flank UI tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash parent results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + student-portrait-ui-tests: + name: student-portrait-ui-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Student') + needs: [build, student-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/student/flank.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "student-qa-debug.apk" ]; then + mkdir -p apps/student/build/outputs/apk/qa/debug + mv student-qa-debug.apk/student-qa-debug.apk apps/student/build/outputs/apk/qa/debug/ + rm -rf student-qa-debug.apk + fi + if [ -d "student-qa-debug-androidTest.apk" ]; then + mkdir -p apps/student/build/outputs/apk/androidTest/qa/debug + mv student-qa-debug-androidTest.apk/student-qa-debug-androidTest.apk apps/student/build/outputs/apk/androidTest/qa/debug/ + rm -rf student-qa-debug-androidTest.apk + fi + if [ -d "student-dev-debugMinify.apk" ]; then + mkdir -p apps/student/build/outputs/apk/dev/debugMinify + mv student-dev-debugMinify.apk/student-dev-debugMinify.apk apps/student/build/outputs/apk/dev/debugMinify/ + rm -rf student-dev-debugMinify.apk + fi + + - name: Run Flank UI tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash student results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + student-landscape-ui-tests: + name: student-landscape-ui-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Student') + needs: [build, student-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/student/flank_landscape.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "student-qa-debug.apk" ]; then + mkdir -p apps/student/build/outputs/apk/qa/debug + mv student-qa-debug.apk/student-qa-debug.apk apps/student/build/outputs/apk/qa/debug/ + rm -rf student-qa-debug.apk + fi + if [ -d "student-qa-debug-androidTest.apk" ]; then + mkdir -p apps/student/build/outputs/apk/androidTest/qa/debug + mv student-qa-debug-androidTest.apk/student-qa-debug-androidTest.apk apps/student/build/outputs/apk/androidTest/qa/debug/ + rm -rf student-qa-debug-androidTest.apk + fi + if [ -d "student-dev-debugMinify.apk" ]; then + mkdir -p apps/student/build/outputs/apk/dev/debugMinify + mv student-dev-debugMinify.apk/student-dev-debugMinify.apk apps/student/build/outputs/apk/dev/debugMinify/ + rm -rf student-dev-debugMinify.apk + fi + + - name: Run Flank UI tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash student results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + teacher-portrait-ui-tests: + name: teacher-portrait-ui-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Teacher') + needs: [build, teacher-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/teacher/flank.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "teacher-qa-debug.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/qa/debug + mv teacher-qa-debug.apk/teacher-qa-debug.apk apps/teacher/build/outputs/apk/qa/debug/ + rm -rf teacher-qa-debug.apk + fi + if [ -d "teacher-qa-debug-androidTest.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/androidTest/qa/debug + mv teacher-qa-debug-androidTest.apk/teacher-qa-debug-androidTest.apk apps/teacher/build/outputs/apk/androidTest/qa/debug/ + rm -rf teacher-qa-debug-androidTest.apk + fi + if [ -d "teacher-dev-debugMinify.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/dev/debugMinify + mv teacher-dev-debugMinify.apk/teacher-dev-debugMinify.apk apps/teacher/build/outputs/apk/dev/debugMinify/ + rm -rf teacher-dev-debugMinify.apk + fi + + - name: Run Flank UI tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash teacher results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + teacher-landscape-ui-tests: + name: teacher-landscape-ui-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Teacher') + needs: [build, teacher-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/teacher/flank_landscape.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "teacher-qa-debug.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/qa/debug + mv teacher-qa-debug.apk/teacher-qa-debug.apk apps/teacher/build/outputs/apk/qa/debug/ + rm -rf teacher-qa-debug.apk + fi + if [ -d "teacher-qa-debug-androidTest.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/androidTest/qa/debug + mv teacher-qa-debug-androidTest.apk/teacher-qa-debug-androidTest.apk apps/teacher/build/outputs/apk/androidTest/qa/debug/ + rm -rf teacher-qa-debug-androidTest.apk + fi + if [ -d "teacher-dev-debugMinify.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/dev/debugMinify + mv teacher-dev-debugMinify.apk/teacher-dev-debugMinify.apk apps/teacher/build/outputs/apk/dev/debugMinify/ + rm -rf teacher-dev-debugMinify.apk + fi + + - name: Run Flank UI tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash teacher results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + parent-e2e-tests: + name: parent-e2e-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Parent') && contains(github.event.pull_request.body, 'Run E2E test suite') + needs: [build, parent-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/parent/flank_e2e.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "parent-qa-debug.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/qa/debug + mv parent-qa-debug.apk/parent-qa-debug.apk apps/parent/build/outputs/apk/qa/debug/ + rm -rf parent-qa-debug.apk + fi + if [ -d "parent-qa-debug-androidTest.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/androidTest/qa/debug + mv parent-qa-debug-androidTest.apk/parent-qa-debug-androidTest.apk apps/parent/build/outputs/apk/androidTest/qa/debug/ + rm -rf parent-qa-debug-androidTest.apk + fi + if [ -d "parent-dev-debugMinify.apk" ]; then + mkdir -p apps/parent/build/outputs/apk/dev/debugMinify + mv parent-dev-debugMinify.apk/parent-dev-debugMinify.apk apps/parent/build/outputs/apk/dev/debugMinify/ + rm -rf parent-dev-debugMinify.apk + fi + + - name: Run Flank E2E tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash parent results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + student-e2e-tests: + name: student-e2e-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Student') && contains(github.event.pull_request.body, 'Run E2E test suite') + needs: [build, student-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/student/flank_e2e.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "student-qa-debug.apk" ]; then + mkdir -p apps/student/build/outputs/apk/qa/debug + mv student-qa-debug.apk/student-qa-debug.apk apps/student/build/outputs/apk/qa/debug/ + rm -rf student-qa-debug.apk + fi + if [ -d "student-qa-debug-androidTest.apk" ]; then + mkdir -p apps/student/build/outputs/apk/androidTest/qa/debug + mv student-qa-debug-androidTest.apk/student-qa-debug-androidTest.apk apps/student/build/outputs/apk/androidTest/qa/debug/ + rm -rf student-qa-debug-androidTest.apk + fi + if [ -d "student-dev-debugMinify.apk" ]; then + mkdir -p apps/student/build/outputs/apk/dev/debugMinify + mv student-dev-debugMinify.apk/student-dev-debugMinify.apk apps/student/build/outputs/apk/dev/debugMinify/ + rm -rf student-dev-debugMinify.apk + fi + + - name: Run Flank E2E tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash student results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + teacher-e2e-tests: + name: teacher-e2e-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Teacher') && contains(github.event.pull_request.body, 'Run E2E test suite') + needs: [build, teacher-unit-tests] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: GCLOUD_KEY secret is not set" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Setup Flank config + run: cp ./apps/teacher/flank_e2e.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + if [ -d "teacher-qa-debug.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/qa/debug + mv teacher-qa-debug.apk/teacher-qa-debug.apk apps/teacher/build/outputs/apk/qa/debug/ + rm -rf teacher-qa-debug.apk + fi + if [ -d "teacher-qa-debug-androidTest.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/androidTest/qa/debug + mv teacher-qa-debug-androidTest.apk/teacher-qa-debug-androidTest.apk apps/teacher/build/outputs/apk/androidTest/qa/debug/ + rm -rf teacher-qa-debug-androidTest.apk + fi + if [ -d "teacher-dev-debugMinify.apk" ]; then + mkdir -p apps/teacher/build/outputs/apk/dev/debugMinify + mv teacher-dev-debugMinify.apk/teacher-dev-debugMinify.apk apps/teacher/build/outputs/apk/dev/debugMinify/ + rm -rf teacher-dev-debugMinify.apk + fi + + - name: Run Flank E2E tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' + + - name: Report test results to Splunk + run: ./apps/postProcessTestRun.bash teacher results/`ls results` + env: + SPLUNK_MOBILE_TOKEN: ${{ secrets.SPLUNK_MOBILE_TOKEN }} + OBSERVE_MOBILE_TOKEN: ${{ secrets.OBSERVE_MOBILE_TOKEN }} + BITRISE_TRIGGERED_WORKFLOW_ID: ${{ github.workflow }} + BITRISE_GIT_BRANCH: ${{ github.ref_name }} + BITRISE_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Cleanup sensitive files + if: always() + run: rm -f service-account-key.json + + submodule-ui-tests: + name: submodule-ui-tests + runs-on: ubuntu-latest + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && ( + contains(github.event.pull_request.body, 'Parent') || + contains(github.event.pull_request.body, 'Student') || + contains(github.event.pull_request.body, 'Teacher') + ) + needs: submodule-build-and-test + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download test APK artifact + uses: actions/download-artifact@v4 + with: + name: pandautils-test.apk + path: . + + - name: Download app APK artifact + uses: actions/download-artifact@v4 + with: + name: pandautils-app.apk + path: . + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.GCLOUD_KEY }} + run: echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + + - name: Setup Flank config + run: cp ./libs/pandautils/flank.yml ./flank.yml + + - name: Copy APKs to expected locations + run: | + mkdir -p libs/pandautils + mv pandautils-test.apk libs/pandautils/ + mv pandautils-app.apk libs/pandautils/ + + - name: Run Flank E2E tests + uses: Flank/flank@v23.10.1 + with: + version: 'v23.07.0' + platform: 'android' + service_account: './service-account-key.json' + flank_configuration_file: './flank.yml' \ No newline at end of file diff --git a/apps/student/build.gradle b/apps/student/build.gradle index 0f87999743..c756f47af9 100644 --- a/apps/student/build.gradle +++ b/apps/student/build.gradle @@ -338,9 +338,6 @@ dependencies { implementation Libs.GLANCE_APPWIDGET_PREVIEW } -// Comment out this line if the reporting logic starts going wonky. -gradle.addListener new TimingsListener(project) - apply plugin: 'com.google.gms.google-services' if (coverageEnabled) { From a349f015b6bcc53bf41936f5c89e924b2ffa0514 Mon Sep 17 00:00:00 2001 From: Tamas Kozmer <72397075+tamaskozmer@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:22:46 +0100 Subject: [PATCH 09/58] [MBL-19400][Student] To Do List - Implement actions (swipe, checkbox) (#3367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude noreply@anthropic.com refs: MBL-19400 affects: Student release note: Added swipe-to-complete and checkbox toggle functionality to To Do List with undo support * Swipe actin UI. * Swipe action * Refactored viewModel injection and screen architecture so the content of the screen could be reused. * Undo feature. * Checkbox action. * Offline and haptics. * Corrected calendar user colors to be in sync with the to do. * Fixed badge issues. * Tests. * Fixed remote config params for devDebugMinify build. * Fade in/out text. * Improve haptic feedback and fade-in animation for swipe gestures - Apply ease-in cubic easing to swipe indicator fade-in for more natural animation - Add GESTURE_START haptic feedback when user begins dragging - Add GESTURE_END haptic feedback when swipe animation completes - Keep TOGGLE_ON/OFF for checkbox interactions - All haptics fall back to CONTEXT_CLICK on API < 34 - Provides better tactile feedback flow: start → drag → end * Improved error logging. * Add DefaultToDoListRouter for Teacher and Parent apps - Created DefaultToDoListRouter with no-op implementations - Added ToDoListRouter provider to Teacher ToDoModule - Added ToDoListRouter provider to Parent ToDoModule - Fixes build issues for Teacher and Parent apps since ToDoListFragment is in common code * fixed tests * CR fixes. * Show snackbar for marking as undone as well. * Test fixes. --- .../parentapp/di/feature/ToDoModule.kt | 7 + .../ui/interaction/LoginInteractionTest.kt | 2 +- .../student/activity/CallbackActivity.kt | 3 +- .../student/activity/NavigationActivity.kt | 9 +- .../settings/StudentSettingsBehaviour.kt | 2 +- .../todolist/StudentToDoListRouter.kt | 1 + .../student/navigation/NavigationBehavior.kt | 2 +- .../student/router/RouteMatcher.kt | 2 +- .../student/router/RouteResolver.kt | 2 +- .../com/instructure/teacher/di/ToDoModule.kt | 7 + .../mockcanvas/endpoints/ApiEndpoint.kt | 1 - .../mockcanvas/endpoints/CareerEndpoints.kt | 2 +- libs/pandares/src/main/res/values/strings.xml | 7 + .../composables/SelectContextScreen.kt | 12 +- .../calendar/composables/CalendarEvents.kt | 9 +- .../filter/CalendarFilterViewModel.kt | 5 +- .../todolist/DefaultToDoListRouter.kt | 35 ++ .../features/todolist/OnToDoCountChanged.kt | 20 + .../features/todolist/ToDoListFragment.kt | 54 +-- .../features/todolist/ToDoListRepository.kt | 5 + .../features/todolist/ToDoListScreen.kt | 354 +++++++++++++--- .../features/todolist/ToDoListUiState.kt | 30 +- .../features/todolist/ToDoListViewModel.kt | 193 +++++++-- .../pandautils/utils/PlannerItemExtensions.kt | 11 + .../pandautils/utils/ViewExtensions.kt | 28 ++ .../todolist/ToDoListViewModelTest.kt | 378 ++++++++++++++++-- 26 files changed, 998 insertions(+), 183 deletions(-) create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/DefaultToDoListRouter.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/OnToDoCountChanged.kt rename {apps/student/src/main/java/com/instructure/student => libs/pandautils/src/main/java/com/instructure/pandautils}/features/todolist/ToDoListFragment.kt (58%) diff --git a/apps/parent/src/main/java/com/instructure/parentapp/di/feature/ToDoModule.kt b/apps/parent/src/main/java/com/instructure/parentapp/di/feature/ToDoModule.kt index e665f3eaeb..ac1bfddfa6 100644 --- a/apps/parent/src/main/java/com/instructure/parentapp/di/feature/ToDoModule.kt +++ b/apps/parent/src/main/java/com/instructure/parentapp/di/feature/ToDoModule.kt @@ -20,6 +20,8 @@ package com.instructure.parentapp.di.feature import androidx.fragment.app.FragmentActivity import com.instructure.pandautils.features.calendartodo.details.ToDoViewModelBehavior import com.instructure.pandautils.features.calendartodo.details.ToDoRouter +import com.instructure.pandautils.features.todolist.DefaultToDoListRouter +import com.instructure.pandautils.features.todolist.ToDoListRouter import com.instructure.parentapp.features.calendartodo.ParentToDoRouter import com.instructure.parentapp.util.navigation.Navigation import dagger.Module @@ -36,6 +38,11 @@ class ToDoModule { fun provideToDoRouter(activity: FragmentActivity, navigation: Navigation): ToDoRouter { return ParentToDoRouter(activity, navigation) } + + @Provides + fun provideToDoListRouter(): ToDoListRouter { + return DefaultToDoListRouter() + } } @Module diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt index 4720757b45..beafbbc822 100644 --- a/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/interaction/LoginInteractionTest.kt @@ -38,7 +38,7 @@ class LoginInteractionTest : StudentTest() { if(isTabletDevice()) loginFindSchoolPage.assertHintText(R.string.schoolInstructureCom) else loginFindSchoolPage.assertHintText(R.string.loginHint) - loginFindSchoolPage.enterDomain("harv") + loginFindSchoolPage.enterDomain("harvest") loginFindSchoolPage.assertSchoolSearchResults("City Harvest Church (Singapore)") } 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 d1715f8ae9..9dcbff4e6d 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,6 +60,7 @@ import com.instructure.pandautils.utils.FeatureFlagProvider import com.instructure.pandautils.utils.LocaleUtils import com.instructure.pandautils.utils.SHA256 import com.instructure.pandautils.utils.ThemePrefs +import com.instructure.pandautils.utils.isComplete import com.instructure.pandautils.utils.orDefault import com.instructure.pandautils.utils.toast import com.instructure.student.BuildConfig @@ -236,7 +237,7 @@ abstract class CallbackActivity : ParentActivity(), OnUnreadCountInvalidated, No plannerApi.nextPagePlannerItems(nextUrl, restParams) } - val todoCount = plannerItems.dataOrNull?.count().orDefault() + val todoCount = plannerItems.dataOrNull?.count { !it.isComplete() }.orDefault() updateToDoCount(todoCount) } 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 f39b6315ad..f7ad3fa378 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 @@ -91,6 +91,8 @@ import com.instructure.pandautils.features.notification.preferences.PushNotifica import com.instructure.pandautils.features.offline.sync.OfflineSyncHelper import com.instructure.pandautils.features.reminder.AlarmScheduler import com.instructure.pandautils.features.settings.SettingsFragment +import com.instructure.pandautils.features.todolist.OnToDoCountChanged +import com.instructure.pandautils.features.todolist.ToDoListFragment import com.instructure.pandautils.interfaces.NavigationCallbacks import com.instructure.pandautils.models.PushNotification import com.instructure.pandautils.receivers.PushExternalReceiver @@ -136,7 +138,6 @@ import com.instructure.student.events.UserUpdatedEvent import com.instructure.student.features.files.list.FileListFragment import com.instructure.student.features.modules.progression.CourseModuleProgressionFragment import com.instructure.student.features.navigation.NavigationRepository -import com.instructure.student.features.todolist.ToDoListFragment import com.instructure.student.fragment.BookmarksFragment import com.instructure.student.fragment.DashboardFragment import com.instructure.student.fragment.NotificationListFragment @@ -174,7 +175,7 @@ private const val BOTTOM_SCREENS_BUNDLE_KEY = "bottomScreens" @AndroidEntryPoint @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog.OnMasqueradingSet, - FullScreenInteractions, ActivityCompat.OnRequestPermissionsResultCallback by PermissionReceiver() { + FullScreenInteractions, ActivityCompat.OnRequestPermissionsResultCallback by PermissionReceiver(), OnToDoCountChanged { private val binding by viewBinding(ActivityNavigationBinding::inflate) private lateinit var navigationDrawerBinding: NavigationDrawerBinding @@ -1273,6 +1274,10 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. updateBottomBarBadge(R.id.bottomNavigationToDo, toDoCount, R.plurals.a11y_todoBadgeCount) } + override fun onToDoCountChanged(count: Int) { + updateToDoCount(count) + } + private fun updateBottomBarBadge(@IdRes menuItemId: Int, count: Int, @PluralsRes quantityContentDescription: Int? = null) = with(binding) { if (count > 0) { bottomBar.getOrCreateBadge(menuItemId).number = count diff --git a/apps/student/src/main/java/com/instructure/student/features/settings/StudentSettingsBehaviour.kt b/apps/student/src/main/java/com/instructure/student/features/settings/StudentSettingsBehaviour.kt index 3ef8fbc4d9..f735e4312f 100644 --- a/apps/student/src/main/java/com/instructure/student/features/settings/StudentSettingsBehaviour.kt +++ b/apps/student/src/main/java/com/instructure/student/features/settings/StudentSettingsBehaviour.kt @@ -45,7 +45,7 @@ class StudentSettingsBehaviour( if (apiPrefs.canvasForElementary) { preferencesList.add(1, SettingsItem.HOMEROOM_VIEW) } - if (BuildConfig.DEBUG) { + if (BuildConfig.IS_DEBUG) { preferencesList.add(SettingsItem.ACCOUNT_PREFERENCES) preferencesList.add(SettingsItem.FEATURE_FLAGS) preferencesList.add(SettingsItem.REMOTE_CONFIG) diff --git a/apps/student/src/main/java/com/instructure/student/features/todolist/StudentToDoListRouter.kt b/apps/student/src/main/java/com/instructure/student/features/todolist/StudentToDoListRouter.kt index 8e1ec238a0..546b18af37 100644 --- a/apps/student/src/main/java/com/instructure/student/features/todolist/StudentToDoListRouter.kt +++ b/apps/student/src/main/java/com/instructure/student/features/todolist/StudentToDoListRouter.kt @@ -18,6 +18,7 @@ package com.instructure.student.features.todolist import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity +import com.instructure.pandautils.features.todolist.ToDoListFragment import com.instructure.pandautils.features.todolist.ToDoListRouter import com.instructure.student.activity.NavigationActivity diff --git a/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt index 4f6a29c8c8..1f5fa1b168 100644 --- a/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt +++ b/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt @@ -26,7 +26,7 @@ import com.instructure.interactions.router.Route import com.instructure.pandautils.features.inbox.list.InboxFragment import com.instructure.pandautils.utils.CanvasFont import com.instructure.student.activity.NothingToSeeHereFragment -import com.instructure.student.features.todolist.ToDoListFragment +import com.instructure.pandautils.features.todolist.ToDoListFragment import com.instructure.student.fragment.OldToDoListFragment import com.instructure.student.fragment.ParentFragment 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 12a77f8045..be5807afc7 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 @@ -82,7 +82,7 @@ import com.instructure.student.features.pages.list.PageListFragment import com.instructure.student.features.people.details.PeopleDetailsFragment import com.instructure.student.features.people.list.PeopleListFragment import com.instructure.student.features.quiz.list.QuizListFragment -import com.instructure.student.features.todolist.ToDoListFragment +import com.instructure.pandautils.features.todolist.ToDoListFragment import com.instructure.student.fragment.AnnouncementListFragment import com.instructure.student.fragment.BasicQuizViewFragment import com.instructure.student.fragment.CourseSettingsFragment diff --git a/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt b/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt index 8b665681fd..f1ffe01a5a 100644 --- a/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt +++ b/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt @@ -45,7 +45,7 @@ import com.instructure.student.features.pages.list.PageListFragment import com.instructure.student.features.people.details.PeopleDetailsFragment import com.instructure.student.features.people.list.PeopleListFragment import com.instructure.student.features.quiz.list.QuizListFragment -import com.instructure.student.features.todolist.ToDoListFragment +import com.instructure.pandautils.features.todolist.ToDoListFragment import com.instructure.student.fragment.AccountPreferencesFragment import com.instructure.student.fragment.AnnouncementListFragment import com.instructure.student.fragment.AssignmentBasicFragment diff --git a/apps/teacher/src/main/java/com/instructure/teacher/di/ToDoModule.kt b/apps/teacher/src/main/java/com/instructure/teacher/di/ToDoModule.kt index 49984b9398..19ae28e1a9 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/di/ToDoModule.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/di/ToDoModule.kt @@ -20,6 +20,8 @@ package com.instructure.teacher.di import androidx.fragment.app.FragmentActivity import com.instructure.pandautils.features.calendartodo.details.ToDoViewModelBehavior import com.instructure.pandautils.features.calendartodo.details.ToDoRouter +import com.instructure.pandautils.features.todolist.DefaultToDoListRouter +import com.instructure.pandautils.features.todolist.ToDoListRouter import com.instructure.teacher.features.calendartodo.TeacherToDoRouter import dagger.Module import dagger.Provides @@ -35,6 +37,11 @@ class ToDoModule { fun provideToDoRouter(activity: FragmentActivity): ToDoRouter { return TeacherToDoRouter(activity) } + + @Provides + fun provideToDoListRouter(): ToDoListRouter { + return DefaultToDoListRouter() + } } @Module 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 ef196dfbc5..5a2194d4c6 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 @@ -18,7 +18,6 @@ package com.instructure.canvas.espresso.mockcanvas.endpoints import android.util.Log import com.google.gson.Gson -import com.instructure.canvas.espresso.mockCanvas.endpoints.CareerEndpoint import com.instructure.canvas.espresso.mockcanvas.Endpoint import com.instructure.canvas.espresso.mockcanvas.addDiscussionTopicToCourse import com.instructure.canvas.espresso.mockcanvas.addPlannable diff --git a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockcanvas/endpoints/CareerEndpoints.kt b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockcanvas/endpoints/CareerEndpoints.kt index 28f6dd32a1..acfcfc0e4d 100644 --- a/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockcanvas/endpoints/CareerEndpoints.kt +++ b/automation/espresso/src/main/kotlin/com/instructure/canvas/espresso/mockcanvas/endpoints/CareerEndpoints.kt @@ -14,7 +14,7 @@ * along with this program. If not, see . * */ -package com.instructure.canvas.espresso.mockCanvas.endpoints +package com.instructure.canvas.espresso.mockcanvas.endpoints import com.instructure.canvas.espresso.mockcanvas.Endpoint import com.instructure.canvas.espresso.mockcanvas.utils.Segment diff --git a/libs/pandares/src/main/res/values/strings.xml b/libs/pandares/src/main/res/values/strings.xml index f20aadf279..20259688d5 100644 --- a/libs/pandares/src/main/res/values/strings.xml +++ b/libs/pandares/src/main/res/values/strings.xml @@ -2186,6 +2186,13 @@ Multiple filters Filter There was an error loading your to-do items. Please check your connection and try again. + There was an error updating the to-do item. Please check your connection and try again. No To Dos for now! It looks like a great time to rest, relax, and recharge. + Done + Undo + %s marked as done + %s marked as not done + Undo + This action cannot be performed offline diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/SelectContextScreen.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/SelectContextScreen.kt index 502258936c..dcff8f4c1e 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/SelectContextScreen.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/SelectContextScreen.kt @@ -48,12 +48,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.models.Course -import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.utils.ContextKeeper import com.instructure.pandautils.R import com.instructure.pandautils.compose.CanvasTheme -import com.instructure.pandautils.utils.ThemePrefs -import com.instructure.pandautils.utils.color +import com.instructure.pandautils.utils.courseOrUserColor import com.instructure.pandautils.utils.isCourse import com.instructure.pandautils.utils.isGroup import com.instructure.pandautils.utils.isUser @@ -188,13 +186,7 @@ private fun SelectContextItem( modifier: Modifier = Modifier ) { val context = LocalContext.current - val color = Color( - if (canvasContext is User) { - ThemePrefs.brandColor - } else { - canvasContext.color - } - ) + val color = Color(canvasContext.courseOrUserColor) Row( modifier = modifier .defaultMinSize(minHeight = 54.dp) diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/calendar/composables/CalendarEvents.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/calendar/composables/CalendarEvents.kt index 894a8b9697..447df04416 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/calendar/composables/CalendarEvents.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/calendar/composables/CalendarEvents.kt @@ -65,7 +65,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.instructure.canvasapi2.models.CanvasContext -import com.instructure.canvasapi2.models.User import com.instructure.canvasapi2.utils.ContextKeeper import com.instructure.pandautils.R import com.instructure.pandautils.compose.composables.ErrorContent @@ -75,7 +74,7 @@ import com.instructure.pandautils.features.calendar.CalendarEventsPageUiState import com.instructure.pandautils.features.calendar.CalendarEventsUiState import com.instructure.pandautils.features.calendar.EventUiState import com.instructure.pandautils.utils.ThemePrefs -import com.instructure.pandautils.utils.color +import com.instructure.pandautils.utils.courseOrUserColor import com.jakewharton.threetenabp.AndroidThreeTen private const val PAGE_COUNT = 1000 @@ -184,11 +183,7 @@ fun CalendarEventsPage( @Composable fun CalendarEventItem(eventUiState: EventUiState, onEventClick: (Long) -> Unit, modifier: Modifier = Modifier) { - val contextColor = if (eventUiState.canvasContext is User) { - Color(ThemePrefs.brandColor) - } else { - Color(eventUiState.canvasContext.color) - } + val contextColor = Color(eventUiState.canvasContext.courseOrUserColor) Row( modifier .clickable { onEventClick(eventUiState.plannableId) } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/calendar/filter/CalendarFilterViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/calendar/filter/CalendarFilterViewModel.kt index 292d524db2..dcc4b1e145 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/calendar/filter/CalendarFilterViewModel.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/calendar/filter/CalendarFilterViewModel.kt @@ -23,8 +23,7 @@ import com.instructure.canvasapi2.utils.DataResult import com.instructure.pandautils.R import com.instructure.pandautils.features.calendar.CalendarRepository import com.instructure.pandautils.room.calendar.entities.CalendarFilterEntity -import com.instructure.pandautils.utils.ThemePrefs -import com.instructure.pandautils.utils.color +import com.instructure.pandautils.utils.courseOrUserColor import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow @@ -89,7 +88,7 @@ class CalendarFilterViewModel @Inject constructor( } private fun createFilterItemsUiState(type: CanvasContext.Type) = canvasContexts[type]?.map { - val color = if (type == CanvasContext.Type.USER) ThemePrefs.brandColor else it.color + val color = it.courseOrUserColor CalendarFilterItemUiState(it.contextId, it.name.orEmpty(), contextIdFilters.contains(it.contextId), color) } ?: emptyList() diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/DefaultToDoListRouter.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/DefaultToDoListRouter.kt new file mode 100644 index 0000000000..01e8550b48 --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/DefaultToDoListRouter.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +/** + * Default implementation of ToDoListRouter with no-op implementations. + * Used when the app doesn't need custom routing behavior. + */ +class DefaultToDoListRouter : ToDoListRouter { + + override fun openNavigationDrawer() { + // No-op implementation + } + + override fun attachNavigationDrawer() { + // No-op implementation + } + + override fun openToDoItem(itemId: String) { + // No-op implementation + } +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/OnToDoCountChanged.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/OnToDoCountChanged.kt new file mode 100644 index 0000000000..87d20371cf --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/OnToDoCountChanged.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2025 - 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.pandautils.features.todolist + +interface OnToDoCountChanged { + fun onToDoCountChanged(count: Int) +} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/todolist/ToDoListFragment.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListFragment.kt similarity index 58% rename from apps/student/src/main/java/com/instructure/student/features/todolist/ToDoListFragment.kt rename to libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListFragment.kt index 9797b598d3..0d291327b1 100644 --- a/apps/student/src/main/java/com/instructure/student/features/todolist/ToDoListFragment.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListFragment.kt @@ -1,51 +1,27 @@ -/* - * Copyright (C) 2025 - 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.todolist +package com.instructure.pandautils.features.todolist +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.utils.pageview.PageView import com.instructure.interactions.FragmentInteractions import com.instructure.interactions.Navigation import com.instructure.interactions.router.Route +import com.instructure.pandautils.R import com.instructure.pandautils.analytics.SCREEN_VIEW_TO_DO_LIST import com.instructure.pandautils.analytics.ScreenView import com.instructure.pandautils.base.BaseCanvasFragment import com.instructure.pandautils.compose.CanvasTheme -import com.instructure.pandautils.features.todolist.ToDoListRouter -import com.instructure.pandautils.features.todolist.ToDoListScreen -import com.instructure.pandautils.features.todolist.ToDoListViewModel -import com.instructure.pandautils.features.todolist.ToDoListViewModelAction import com.instructure.pandautils.interfaces.NavigationCallbacks import com.instructure.pandautils.utils.ThemePrefs import com.instructure.pandautils.utils.ViewStyler -import com.instructure.pandautils.utils.collectOneOffEvents import com.instructure.pandautils.utils.makeBundle import com.instructure.pandautils.utils.withArgs -import com.instructure.student.R import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -54,28 +30,30 @@ import javax.inject.Inject @AndroidEntryPoint class ToDoListFragment : BaseCanvasFragment(), FragmentInteractions, NavigationCallbacks { - private val viewModel: ToDoListViewModel by viewModels() - @Inject lateinit var toDoListRouter: ToDoListRouter + private var onToDoCountChanged: OnToDoCountChanged? = null + + override fun onAttach(context: Context) { + super.onAttach(context) + onToDoCountChanged = context as? OnToDoCountChanged + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { applyTheme() - viewLifecycleOwner.lifecycleScope.collectOneOffEvents(viewModel.events, ::handleAction) return ComposeView(requireActivity()).apply { setContent { CanvasTheme { - val uiState by viewModel.uiState.collectAsState() - ToDoListScreen( - uiState = uiState, - actionHandler = viewModel::handleAction, - navigationIconClick = { toDoListRouter.openNavigationDrawer() } + navigationIconClick = { toDoListRouter.openNavigationDrawer() }, + openToDoItem = { itemId -> toDoListRouter.openToDoItem(itemId) }, + onToDoCountChanged = { count -> onToDoCountChanged?.onToDoCountChanged(count) } ) } } @@ -94,12 +72,6 @@ class ToDoListFragment : BaseCanvasFragment(), FragmentInteractions, NavigationC override fun getFragment(): Fragment = this - private fun handleAction(action: ToDoListViewModelAction) { - when (action) { - is ToDoListViewModelAction.OpenToDoItem -> toDoListRouter.openToDoItem(action.itemId) - } - } - override fun onHandleBackPressed(): Boolean { return false } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRepository.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRepository.kt index 3ef9e3904e..a64ee2b5fd 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRepository.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListRepository.kt @@ -15,6 +15,7 @@ */ package com.instructure.pandautils.features.todolist +import com.instructure.canvasapi2.CanvasRestAdapter import com.instructure.canvasapi2.apis.CourseAPI import com.instructure.canvasapi2.apis.PlannerAPI import com.instructure.canvasapi2.builders.RestParams @@ -78,4 +79,8 @@ class ToDoListRepository @Inject constructor( ) return plannerApi.createPlannerOverride(override, restParams) } + + fun invalidateCachedResponses() { + CanvasRestAdapter.clearCacheUrls("planner") + } } \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListScreen.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListScreen.kt index ca2e86382f..b2d54c9798 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListScreen.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListScreen.kt @@ -15,10 +15,15 @@ */ package com.instructure.pandautils.features.todolist +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -39,24 +44,36 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Scaffold +import androidx.compose.material.Snackbar +import androidx.compose.material.SnackbarDuration +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState +import androidx.compose.material.SnackbarResult import androidx.compose.material.Text import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -67,6 +84,7 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.utils.ContextKeeper import com.instructure.pandautils.R @@ -79,12 +97,19 @@ import com.instructure.pandautils.compose.composables.Loading import com.instructure.pandautils.compose.modifiers.conditional import com.instructure.pandautils.utils.ThemePrefs import com.instructure.pandautils.utils.courseOrUserColor +import com.instructure.pandautils.utils.performGestureHapticFeedback +import com.instructure.pandautils.utils.performToggleHapticFeedback +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale +import kotlin.math.abs import kotlin.math.roundToInt +private const val SWIPE_THRESHOLD_DP = 150 + private data class StickyHeaderState( val item: ToDoItemUiState?, val yOffset: Float, @@ -102,14 +127,53 @@ private data class DateBadgeData( @OptIn(ExperimentalMaterialApi::class) @Composable fun ToDoListScreen( - uiState: ToDoListUiState, - actionHandler: (ToDoListActionHandler) -> Unit, - modifier: Modifier = Modifier, - navigationIconClick: () -> Unit = {} + navigationIconClick: () -> Unit, + openToDoItem: (String) -> Unit, + onToDoCountChanged: (Int) -> Unit, + modifier: Modifier = Modifier ) { + val viewModel = hiltViewModel() + val uiState by viewModel.uiState.collectAsState() + val snackbarHostState = remember { SnackbarHostState() } + val context = LocalContext.current + + LaunchedEffect(uiState.snackbarMessage) { + uiState.snackbarMessage?.let { message -> + snackbarHostState.showSnackbar(message) + uiState.onSnackbarDismissed() + } + } + + LaunchedEffect(uiState.confirmationSnackbarData) { + uiState.confirmationSnackbarData?.let { item -> + val messageRes = if (item.markedAsDone) { + R.string.todoMarkedAsDone + } else { + R.string.todoMarkedAsNotDone + } + val message = context.getString(messageRes, item.title) + val result = snackbarHostState.showSnackbar( + message = message, + actionLabel = context.getString(R.string.todoMarkedAsDoneSnackbarUndo), + duration = SnackbarDuration.Long + ) + if (result == SnackbarResult.ActionPerformed) { + uiState.onUndoMarkAsDoneUndoneAction() + } else { + uiState.onMarkedAsDoneSnackbarDismissed() + } + } + } + + LaunchedEffect(uiState.toDoCount) { + uiState.toDoCount?.let { count -> + onToDoCountChanged(count) + } + } + val pullRefreshState = rememberPullRefreshState( refreshing = uiState.isRefreshing, - onRefresh = { actionHandler(ToDoListActionHandler.Refresh) } + onRefresh = uiState.onRefresh ) Scaffold( @@ -121,7 +185,7 @@ fun ToDoListScreen( navIconContentDescription = stringResource(id = R.string.navigation_drawer_open), navigationActionClick = navigationIconClick, actions = { - IconButton(onClick = { actionHandler(ToDoListActionHandler.FilterClicked) }) { + IconButton(onClick = { /* TODO: Implement filter - will be implemented in future story */ }) { Icon( painter = painterResource(id = R.drawable.ic_filter_outline), contentDescription = stringResource(id = R.string.a11y_contentDescriptionToDoFilter) @@ -130,6 +194,14 @@ fun ToDoListScreen( } ) }, + snackbarHost = { + SnackbarHost(snackbarHostState) { data -> + Snackbar( + snackbarData = data, + actionColor = Color(ThemePrefs.textButtonColor) + ) + } + }, modifier = modifier ) { padding -> Box( @@ -138,35 +210,10 @@ fun ToDoListScreen( .padding(padding) .pullRefresh(pullRefreshState) ) { - when { - uiState.isLoading -> { - Loading(modifier = Modifier.align(Alignment.Center)) - } - - uiState.isError -> { - ErrorContent( - errorMessage = stringResource(id = R.string.errorLoadingToDos), - retryClick = { actionHandler(ToDoListActionHandler.Refresh) }, - modifier = Modifier.fillMaxSize() - ) - } - - uiState.itemsByDate.isEmpty() -> { - EmptyContent( - emptyTitle = stringResource(id = R.string.noToDosForNow), - emptyMessage = stringResource(id = R.string.noToDosForNowSubtext), - imageRes = R.drawable.ic_no_events, - modifier = Modifier.fillMaxSize() - ) - } - - else -> { - ToDoListContent( - itemsByDate = uiState.itemsByDate, - actionHandler = actionHandler - ) - } - } + ToDoListContent( + uiState = uiState, + onOpenToDoItem = openToDoItem + ) PullRefreshIndicator( refreshing = uiState.isRefreshing, @@ -181,8 +228,46 @@ fun ToDoListScreen( @Composable private fun ToDoListContent( + uiState: ToDoListUiState, + onOpenToDoItem: (String) -> Unit, + modifier: Modifier = Modifier +) { + when { + uiState.isLoading -> { + Loading(modifier = modifier.fillMaxSize()) + } + + uiState.isError -> { + ErrorContent( + errorMessage = stringResource(id = R.string.errorLoadingToDos), + retryClick = uiState.onRefresh, + modifier = modifier.fillMaxSize() + ) + } + + uiState.itemsByDate.isEmpty() -> { + EmptyContent( + emptyTitle = stringResource(id = R.string.noToDosForNow), + emptyMessage = stringResource(id = R.string.noToDosForNowSubtext), + imageRes = R.drawable.ic_no_events, + modifier = modifier.fillMaxSize() + ) + } + + else -> { + ToDoItemsList( + itemsByDate = uiState.itemsByDate, + onItemClicked = onOpenToDoItem, + modifier = modifier + ) + } + } +} + +@Composable +private fun ToDoItemsList( itemsByDate: Map>, - actionHandler: (ToDoListActionHandler) -> Unit, + onItemClicked: (String) -> Unit, modifier: Modifier = Modifier ) { val dateGroups = itemsByDate.entries.toList() @@ -234,8 +319,8 @@ private fun ToDoListContent( item = item, showDateBadge = index == 0, hideDate = index == 0 && stickyHeaderState.isVisible && stickyHeaderState.item?.id == item.id, - onCheckedChange = { actionHandler(ToDoListActionHandler.ToggleItemChecked(item.id)) }, - onClick = { actionHandler(ToDoListActionHandler.ItemClicked(item.id)) }, + onCheckedChange = { item.onCheckboxToggle(!item.isChecked) }, + onClick = { onItemClicked(item.id) }, modifier = Modifier.onGloballyPositioned { coordinates -> itemPositions[item.id] = coordinates.positionInParent().y itemSizes[item.id] = coordinates.size.height @@ -310,12 +395,187 @@ private fun ToDoItem( onClick: () -> Unit, modifier: Modifier = Modifier, hideDate: Boolean = false +) { + val coroutineScope = rememberCoroutineScope() + val animatedOffsetX = remember { Animatable(0f) } + var itemWidth by remember { mutableFloatStateOf(0f) } + val density = LocalDensity.current + val view = LocalView.current + + val swipeThreshold = with(density) { SWIPE_THRESHOLD_DP.dp.toPx() } + + fun animateToCenter() { + coroutineScope.launch { + animatedOffsetX.animateTo( + targetValue = 0f, + animationSpec = tween(durationMillis = 300) + ) + } + } + + fun handleSwipeEnd() { + coroutineScope.launch { + val currentOffset = animatedOffsetX.value + val absOffset = abs(currentOffset) + if (absOffset >= swipeThreshold) { + val targetOffset = if (currentOffset > 0) itemWidth else -itemWidth + animatedOffsetX.animateTo( + targetValue = targetOffset, + animationSpec = tween(durationMillis = 200) + ) + + // Gesture end haptic feedback + view.performGestureHapticFeedback(isStart = false) + delay(300) + animateToCenter() + item.onSwipeToDone() + } else { + animateToCenter() + } + } + } + + Box( + modifier = modifier + .fillMaxWidth() + .onGloballyPositioned { coordinates -> + itemWidth = coordinates.size.width.toFloat() + } + .pointerInput(Unit) { + detectHorizontalDragGestures( + onDragStart = { + // Gesture start haptic feedback when user begins dragging + view.performGestureHapticFeedback(isStart = true) + }, + onDragEnd = { handleSwipeEnd() }, + onDragCancel = { + animateToCenter() + }, + onHorizontalDrag = { _, dragAmount -> + coroutineScope.launch { + val newOffset = (animatedOffsetX.value + dragAmount).coerceIn(-itemWidth, itemWidth) + animatedOffsetX.snapTo(newOffset) + } + } + ) + } + ) { + SwipeBackground( + isChecked = item.isChecked, + offsetX = animatedOffsetX.value + ) + + ToDoItemContent( + item = item, + showDateBadge = showDateBadge, + hideDate = hideDate, + onCheckedChange = onCheckedChange, + onClick = onClick, + modifier = Modifier.offset { IntOffset(animatedOffsetX.value.roundToInt(), 0) } + ) + } +} + +@Composable +private fun BoxScope.SwipeBackground(isChecked: Boolean, offsetX: Float) { + val backgroundColor = if (isChecked) { + colorResource(R.color.backgroundDark) + } else { + colorResource(R.color.backgroundSuccess) + } + + val text = if (isChecked) { + stringResource(id = R.string.todoSwipeUndo) + } else { + stringResource(id = R.string.todoSwipeDone) + } + + val icon = if (isChecked) { + R.drawable.ic_reply + } else { + R.drawable.ic_checkmark_lined + } + + // Calculate alpha based on swipe progress with ease-in curve + val density = LocalDensity.current + val swipeThreshold = with(density) { SWIPE_THRESHOLD_DP.dp.toPx() } + val progress = (abs(offsetX) / swipeThreshold).coerceIn(0f, 1f) + // Apply ease-in cubic easing for gradual fade-in that accelerates near threshold + val alpha = progress * progress * progress + + Box( + modifier = Modifier + .matchParentSize() + .background(backgroundColor) + ) { + if (offsetX > 0) { + Row( + modifier = Modifier + .align(Alignment.CenterStart) + .padding(start = 16.dp) + .alpha(alpha), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + Icon( + painter = painterResource(id = icon), + contentDescription = null, + tint = colorResource(R.color.textLightest), + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + color = colorResource(R.color.textLightest) + ) + } + } + + if (offsetX < 0) { + Row( + modifier = Modifier + .align(Alignment.CenterEnd) + .padding(end = 16.dp) + .alpha(alpha), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + Text( + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + color = colorResource(R.color.textLightest) + ) + Spacer(modifier = Modifier.width(12.dp)) + Icon( + painter = painterResource(id = icon), + contentDescription = null, + tint = colorResource(R.color.textLightest), + modifier = Modifier.size(24.dp) + ) + } + } + } +} + +@Composable +private fun ToDoItemContent( + item: ToDoItemUiState, + showDateBadge: Boolean, + hideDate: Boolean, + onCheckedChange: () -> Unit, + onClick: () -> Unit, + modifier: Modifier = Modifier ) { val dateBadgeData = rememberDateBadgeData(item.date) + val view = LocalView.current Row( modifier = modifier .fillMaxWidth() + .background(colorResource(id = R.color.backgroundLightest)) .clickable(onClick = onClick) .padding(start = 12.dp, end = 16.dp, top = 8.dp, bottom = 8.dp), verticalAlignment = Alignment.Top @@ -394,7 +654,11 @@ private fun ToDoItem( Checkbox( checked = item.isChecked, - onCheckedChange = { onCheckedChange() }, + onCheckedChange = { + // Determine if marking as done or undone based on the new checked state + view.performToggleHapticFeedback(it) + onCheckedChange() + }, colors = CheckboxDefaults.colors( checkedColor = Color(ThemePrefs.brandColor), uncheckedColor = colorResource(id = R.color.textDark) @@ -595,7 +859,7 @@ fun ToDoListScreenPreview() { ContextKeeper.appContext = LocalContext.current val calendar = Calendar.getInstance() CanvasTheme { - ToDoListScreen( + ToDoListContent( uiState = ToDoListUiState( itemsByDate = mapOf( Date(10) to listOf( @@ -695,7 +959,7 @@ fun ToDoListScreenPreview() { ) ) ), - actionHandler = {} + onOpenToDoItem = {} ) } } @@ -707,7 +971,7 @@ fun ToDoListScreenWithPandasPreview() { ContextKeeper.appContext = LocalContext.current val calendar = Calendar.getInstance() CanvasTheme { - ToDoListScreen( + ToDoListContent( uiState = ToDoListUiState( itemsByDate = mapOf( Date(10) to listOf( @@ -736,7 +1000,7 @@ fun ToDoListScreenWithPandasPreview() { ) ) ), - actionHandler = {} + onOpenToDoItem = {} ) } } @@ -747,9 +1011,9 @@ fun ToDoListScreenWithPandasPreview() { fun ToDoListScreenEmptyPreview() { ContextKeeper.appContext = LocalContext.current CanvasTheme { - ToDoListScreen( + ToDoListContent( uiState = ToDoListUiState(), - actionHandler = {} + onOpenToDoItem = {} ) } } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListUiState.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListUiState.kt index 03f7481d46..4b10f3855a 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListUiState.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListUiState.kt @@ -23,7 +23,20 @@ data class ToDoListUiState( val isLoading: Boolean = false, val isError: Boolean = false, val isRefreshing: Boolean = false, - val itemsByDate: Map> = emptyMap() + val itemsByDate: Map> = emptyMap(), + val snackbarMessage: String? = null, + val onSnackbarDismissed: () -> Unit = {}, + val confirmationSnackbarData: ConfirmationSnackbarData? = null, + val onUndoMarkAsDoneUndoneAction: () -> Unit = {}, + val onMarkedAsDoneSnackbarDismissed: () -> Unit = {}, + val onRefresh: () -> Unit = {}, + val toDoCount: Int? = null +) + +data class ConfirmationSnackbarData( + val itemId: String, + val title: String, + val markedAsDone: Boolean ) data class ToDoItemUiState( @@ -36,7 +49,9 @@ data class ToDoItemUiState( val itemType: ToDoItemType, val isChecked: Boolean = false, val iconRes: Int = R.drawable.ic_calendar, - val tag: String? = null + val tag: String? = null, + val onSwipeToDone: () -> Unit = {}, + val onCheckboxToggle: (Boolean) -> Unit = {} ) enum class ToDoItemType { @@ -47,14 +62,3 @@ enum class ToDoItemType { CALENDAR_EVENT, PLANNER_NOTE } - -sealed class ToDoListViewModelAction { - data class OpenToDoItem(val itemId: String) : ToDoListViewModelAction() -} - -sealed class ToDoListActionHandler { - data object Refresh : ToDoListActionHandler() - data class ToggleItemChecked(val itemId: String) : ToDoListActionHandler() - data class ItemClicked(val itemId: String) : ToDoListActionHandler() - data object FilterClicked : ToDoListActionHandler() -} diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListViewModel.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListViewModel.kt index fe424743c0..bbd40ad710 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListViewModel.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/features/todolist/ToDoListViewModel.kt @@ -18,38 +18,49 @@ package com.instructure.pandautils.features.todolist import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.PlannableType import com.instructure.canvasapi2.models.PlannerItem import com.instructure.canvasapi2.utils.DateHelper import com.instructure.canvasapi2.utils.isInvited import com.instructure.canvasapi2.utils.toApiString +import com.instructure.pandautils.R +import com.instructure.pandautils.utils.NetworkStateProvider import com.instructure.pandautils.utils.getContextNameForPlannerItem import com.instructure.pandautils.utils.getDateTextForPlannerItem import com.instructure.pandautils.utils.getIconForPlannerItem import com.instructure.pandautils.utils.getTagForPlannerItem +import com.instructure.pandautils.utils.isComplete +import com.instructure.pandautils.utils.orDefault import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.threeten.bp.LocalDate +import java.util.Date import javax.inject.Inject @HiltViewModel class ToDoListViewModel @Inject constructor( @ApplicationContext private val context: Context, - private val repository: ToDoListRepository + private val repository: ToDoListRepository, + private val networkStateProvider: NetworkStateProvider, + private val firebaseCrashlytics: FirebaseCrashlytics ) : ViewModel() { - private val _uiState = MutableStateFlow(ToDoListUiState()) + private val _uiState = MutableStateFlow( + ToDoListUiState( + onSnackbarDismissed = { clearSnackbarMessage() }, + onUndoMarkAsDoneUndoneAction = { handleUndoMarkAsDoneUndone() }, + onMarkedAsDoneSnackbarDismissed = { clearMarkedAsDoneItem() }, + onRefresh = { handleRefresh() } + )) val uiState = _uiState.asStateFlow() - private val _events = Channel() - val events = _events.receiveAsFlow() + private val plannerItemsMap = mutableMapOf() init { loadData() @@ -78,6 +89,10 @@ class ToDoListViewModel @Inject constructor( .filter { it.plannableType != PlannableType.ANNOUNCEMENT && it.plannableType != PlannableType.ASSESSMENT_REQUEST } .sortedBy { it.comparisonDate } + // Store planner items for later reference + plannerItemsMap.clear() + filteredItems.forEach { plannerItemsMap[it.plannable.id.toString()] = it } + // Group items by date val itemsByDate = filteredItems .groupBy { DateHelper.getCleanDate(it.comparisonDate.time) } @@ -87,16 +102,20 @@ class ToDoListViewModel @Inject constructor( } } + val toDoCount = calculateToDoCount(itemsByDate) + _uiState.update { it.copy( isLoading = false, isRefreshing = false, isError = false, - itemsByDate = itemsByDate + itemsByDate = itemsByDate, + toDoCount = toDoCount ) } } catch (e: Exception) { e.printStackTrace() + firebaseCrashlytics.recordException(e) _uiState.update { it.copy( isLoading = false, @@ -119,50 +138,166 @@ class ToDoListViewModel @Inject constructor( else -> ToDoItemType.CALENDAR_EVENT } + val itemId = plannerItem.plannable.id.toString() + return ToDoItemUiState( - id = plannerItem.plannable.id.toString(), + id = itemId, title = plannerItem.plannable.title, date = plannerItem.plannableDate, dateLabel = plannerItem.getDateTextForPlannerItem(context), contextLabel = plannerItem.getContextNameForPlannerItem(context, courseMap.values), canvasContext = plannerItem.canvasContext, itemType = itemType, - isChecked = isComplete(plannerItem), + isChecked = plannerItem.isComplete(), iconRes = plannerItem.getIconForPlannerItem(), - tag = plannerItem.getTagForPlannerItem(context) + tag = plannerItem.getTagForPlannerItem(context), + onSwipeToDone = { handleSwipeToDone(itemId) }, + onCheckboxToggle = { isChecked -> handleCheckboxToggle(itemId, isChecked) } ) } - private fun isComplete(plannerItem: PlannerItem): Boolean { - return if (plannerItem.plannableType == PlannableType.ASSIGNMENT - || plannerItem.plannableType == PlannableType.DISCUSSION_TOPIC - || plannerItem.plannableType == PlannableType.SUB_ASSIGNMENT - ) { - plannerItem.submissionState?.submitted == true - } else { - plannerItem.plannerOverride?.markedComplete == true + private fun handleSwipeToDone(itemId: String) { + viewModelScope.launch { + if (!networkStateProvider.isOnline()) { + _uiState.update { + it.copy(snackbarMessage = context.getString(R.string.todoActionOffline)) + } + return@launch + } + + val plannerItem = plannerItemsMap[itemId] ?: return@launch + val currentIsChecked = plannerItem.isComplete() + val newIsChecked = !currentIsChecked + + val success = updateItemCompleteState(itemId, newIsChecked) + + // Show marked-as-done snackbar only when marking as done (not when undoing) + if (success) { + _uiState.update { + it.copy( + confirmationSnackbarData = ConfirmationSnackbarData( + itemId = itemId, + title = plannerItem.plannable.title, + markedAsDone = newIsChecked + ) + ) + } + } + } + } + + private fun handleUndoMarkAsDoneUndone() { + viewModelScope.launch { + val markedAsDoneItem = _uiState.value.confirmationSnackbarData ?: return@launch + val itemId = markedAsDoneItem.itemId + + // Clear the snackbar immediately + _uiState.update { it.copy(confirmationSnackbarData = null) } + + updateItemCompleteState(itemId, !markedAsDoneItem.markedAsDone) } } - fun handleAction(action: ToDoListActionHandler) { - when (action) { - is ToDoListActionHandler.ItemClicked -> { - viewModelScope.launch { - _events.send(ToDoListViewModelAction.OpenToDoItem(action.itemId)) + private fun handleCheckboxToggle(itemId: String, isChecked: Boolean) { + viewModelScope.launch { + if (!networkStateProvider.isOnline()) { + _uiState.update { + it.copy(snackbarMessage = context.getString(R.string.todoActionOffline)) } + return@launch } - is ToDoListActionHandler.Refresh -> { - loadData(forceRefresh = true) + val plannerItem = plannerItemsMap[itemId] ?: return@launch + + val success = updateItemCompleteState(itemId, isChecked) + + // Show marked-as-done snackbar only when checking the box + if (success) { + _uiState.update { + it.copy( + confirmationSnackbarData = ConfirmationSnackbarData( + itemId = itemId, + title = plannerItem.plannable.title, + markedAsDone = isChecked + ) + ) + } + } + } + } + + private suspend fun updateItemCompleteState(itemId: String, newIsChecked: Boolean): Boolean { + val plannerItem = plannerItemsMap[itemId] ?: return false + val currentIsChecked = plannerItem.isComplete() + + // Optimistically update UI + updateItemCheckedState(itemId, newIsChecked) + + return try { + // Update or create planner override + val plannerOverrideResult = if (plannerItem.plannerOverride?.id != null) { + repository.updatePlannerOverride( + plannerOverrideId = plannerItem.plannerOverride?.id.orDefault(), + markedComplete = newIsChecked + ).dataOrThrow + } else { + repository.createPlannerOverride( + plannableId = plannerItem.plannable.id, + plannableType = plannerItem.plannableType, + markedComplete = newIsChecked + ).dataOrThrow } - is ToDoListActionHandler.ToggleItemChecked -> { - // TODO: Implement toggle checked - will be implemented in future story + // Update the stored planner item with new override state + val updatedPlannerItem = plannerItem.copy(plannerOverride = plannerOverrideResult) + plannerItemsMap[itemId] = updatedPlannerItem + + // Invalidate planner cache + repository.invalidateCachedResponses() + + true + } catch (e: Exception) { + e.printStackTrace() + firebaseCrashlytics.recordException(e) + // Revert the optimistic update + updateItemCheckedState(itemId, currentIsChecked) + // Show error snackbar + _uiState.update { + it.copy(snackbarMessage = context.getString(R.string.errorUpdatingToDo)) } + false + } + } - is ToDoListActionHandler.FilterClicked -> { - // TODO: Implement filter - will be implemented in future story + private fun updateItemCheckedState(itemId: String, isChecked: Boolean) { + _uiState.update { state -> + val updatedItemsByDate = state.itemsByDate.mapValues { (_, items) -> + items.map { item -> + if (item.id == itemId) { + item.copy(isChecked = isChecked) + } else { + item + } + } } + val toDoCount = calculateToDoCount(updatedItemsByDate) + state.copy(itemsByDate = updatedItemsByDate, toDoCount = toDoCount) } } + + private fun calculateToDoCount(itemsByDate: Map>): Int { + return itemsByDate.values.flatten().count { !it.isChecked } + } + + private fun handleRefresh() { + loadData(forceRefresh = true) + } + + private fun clearSnackbarMessage() { + _uiState.update { it.copy(snackbarMessage = null) } + } + + private fun clearMarkedAsDoneItem() { + _uiState.update { it.copy(confirmationSnackbarData = null) } + } } diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/PlannerItemExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/PlannerItemExtensions.kt index 3f5c7540d4..b9d132142e 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/PlannerItemExtensions.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/PlannerItemExtensions.kt @@ -109,3 +109,14 @@ fun PlannerItem.getTagForPlannerItem(context: Context): String? { null } } + +fun PlannerItem.isComplete(): Boolean { + return plannerOverride?.markedComplete ?: if (plannableType == PlannableType.ASSIGNMENT + || plannableType == PlannableType.DISCUSSION_TOPIC + || plannableType == PlannableType.SUB_ASSIGNMENT + ) { + submissionState?.submitted == true + } else { + false + } +} diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ViewExtensions.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ViewExtensions.kt index c764887a1e..a651bad475 100644 --- a/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ViewExtensions.kt +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/utils/ViewExtensions.kt @@ -35,10 +35,12 @@ import android.graphics.Rect import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Build import android.text.Editable import android.text.TextWatcher import android.util.AttributeSet import android.util.TypedValue +import android.view.HapticFeedbackConstants import android.view.Menu import android.view.MenuItem import android.view.TouchDelegate @@ -990,3 +992,29 @@ fun View.showSnackbar( snackbar.show() snackbar.view.requestAccessibilityFocus(1000) } + +/** + * Performs haptic feedback with appropriate constants based on API level. + * Uses TOGGLE_ON/TOGGLE_OFF on API 34+ for marking done/undone, falls back to CONTEXT_CLICK on older versions. + */ +fun View.performToggleHapticFeedback(toggleOn: Boolean) { + val hapticConstant = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + if (toggleOn) HapticFeedbackConstants.TOGGLE_ON else HapticFeedbackConstants.TOGGLE_OFF + } else { + HapticFeedbackConstants.CONTEXT_CLICK + } + performHapticFeedback(hapticConstant) +} + +/** + * Performs haptic feedback for gesture start/end with appropriate constants based on API level. + * Uses GESTURE_START/GESTURE_END on API 34+, falls back to CONTEXT_CLICK on older versions. + */ +fun View.performGestureHapticFeedback(isStart: Boolean) { + val hapticConstant = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + if (isStart) HapticFeedbackConstants.GESTURE_START else HapticFeedbackConstants.GESTURE_END + } else { + HapticFeedbackConstants.CONTEXT_CLICK + } + performHapticFeedback(hapticConstant) +} \ No newline at end of file diff --git a/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListViewModelTest.kt b/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListViewModelTest.kt index e466f0fc25..f9753e7abe 100644 --- a/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListViewModelTest.kt +++ b/libs/pandautils/src/test/java/com/instructure/pandautils/features/todolist/ToDoListViewModelTest.kt @@ -16,21 +16,25 @@ package com.instructure.pandautils.features.todolist import android.content.Context +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.models.Plannable import com.instructure.canvasapi2.models.PlannableType import com.instructure.canvasapi2.models.PlannerItem +import com.instructure.canvasapi2.models.PlannerOverride import com.instructure.canvasapi2.models.SubmissionState import com.instructure.canvasapi2.utils.ContextKeeper import com.instructure.canvasapi2.utils.DataResult +import com.instructure.pandautils.R +import com.instructure.pandautils.utils.NetworkStateProvider import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll +import io.mockk.verify import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest @@ -49,6 +53,8 @@ class ToDoListViewModelTest { private val testDispatcher = UnconfinedTestDispatcher() private val context: Context = mockk(relaxed = true) private val repository: ToDoListRepository = mockk(relaxed = true) + private val networkStateProvider: NetworkStateProvider = mockk(relaxed = true) + private val firebaseCrashlytics: FirebaseCrashlytics = mockk(relaxed = true) @Before fun setUp() { @@ -290,28 +296,9 @@ class ToDoListViewModelTest { assertFalse(item.isChecked) } - // handleAction tests + // Callback tests @Test - fun `handleAction ItemClicked sends OpenToDoItem event`() = runTest { - coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) - coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(emptyList()) - - val viewModel = getViewModel() - val events = mutableListOf() - - backgroundScope.launch(testDispatcher) { - viewModel.events.toList(events) - } - - viewModel.handleAction(ToDoListActionHandler.ItemClicked("123")) - - assertEquals(1, events.size) - assertTrue(events.first() is ToDoListViewModelAction.OpenToDoItem) - assertEquals("123", (events.first() as ToDoListViewModelAction.OpenToDoItem).itemId) - } - - @Test - fun `handleAction Refresh triggers data reload with forceRefresh`() = runTest { + fun `onRefresh callback triggers data reload with forceRefresh`() = runTest { val courses = listOf(Course(id = 1L, name = "Course 1", courseCode = "CS101")) val initialPlannerItems = listOf(createPlannerItem(id = 1L, title = "Assignment 1")) val refreshedPlannerItems = listOf( @@ -332,7 +319,7 @@ class ToDoListViewModelTest { assertEquals("Assignment 1", initialUiState.itemsByDate.values.flatten().first().title) // Trigger refresh - viewModel.handleAction(ToDoListActionHandler.Refresh) + viewModel.uiState.value.onRefresh() // Verify refreshed data val refreshedUiState = viewModel.uiState.value @@ -376,9 +363,350 @@ class ToDoListViewModelTest { assertTrue(dates.size == 2) } + // Todo count tests + @Test + fun `ViewModel calculates todo count correctly on initial load`() = runTest { + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Unchecked 1", submitted = false), + createPlannerItem(id = 2L, title = "Checked", submitted = true), + createPlannerItem(id = 3L, title = "Unchecked 2", submitted = false) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + assertEquals(2, uiState.toDoCount) + } + + @Test + fun `ViewModel emits zero todo count when all items are checked`() = runTest { + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Checked 1", submitted = true), + createPlannerItem(id = 2L, title = "Checked 2", submitted = true) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + assertEquals(0, uiState.toDoCount) + } + + @Test + fun `ViewModel emits todo count when all items are unchecked`() = runTest { + val plannerItems = listOf( + createPlannerItem(id = 1L, title = "Unchecked 1", submitted = false), + createPlannerItem(id = 2L, title = "Unchecked 2", submitted = false), + createPlannerItem(id = 3L, title = "Unchecked 3", submitted = false) + ) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(plannerItems) + + val viewModel = getViewModel() + + val uiState = viewModel.uiState.value + + assertEquals(3, uiState.toDoCount) + } + + // Checkbox toggle tests + @Test + fun `Checkbox toggle successfully marks item as done`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.createPlannerOverride(any(), any(), any()) } returns DataResult.Success(plannerOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(true) + + val uiState = viewModel.uiState.value + + assertTrue(uiState.itemsByDate.values.flatten().first().isChecked) + assertEquals("Assignment", uiState.confirmationSnackbarData?.title) + assertTrue(uiState.confirmationSnackbarData?.markedAsDone == true) + coVerify { repository.createPlannerOverride(1L, PlannableType.ASSIGNMENT, true) } + } + + @Test + fun `Checkbox toggle successfully marks item as undone`() = runTest { + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false).copy( + plannerOverride = plannerOverride + ) + val updatedOverride = plannerOverride.copy(markedComplete = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.updatePlannerOverride(any(), any()) } returns DataResult.Success(updatedOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(false) + + val uiState = viewModel.uiState.value + + assertFalse(uiState.itemsByDate.values.flatten().first().isChecked) + coVerify { repository.updatePlannerOverride(100L, false) } + } + + @Test + fun `Checkbox toggle shows offline snackbar when device is offline`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + every { networkStateProvider.isOnline() } returns false + every { context.getString(R.string.todoActionOffline) } returns "This action cannot be performed offline" + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(true) + + val uiState = viewModel.uiState.value + + assertFalse(uiState.itemsByDate.values.flatten().first().isChecked) + assertEquals("This action cannot be performed offline", uiState.snackbarMessage) + } + + @Test + fun `Checkbox toggle reverts on failure`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.createPlannerOverride(any(), any(), any()) } returns DataResult.Fail() + every { networkStateProvider.isOnline() } returns true + every { context.getString(R.string.errorUpdatingToDo) } returns "Error updating to-do" + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(true) + + val uiState = viewModel.uiState.value + + assertFalse(uiState.itemsByDate.values.flatten().first().isChecked) + assertEquals("Error updating to-do", uiState.snackbarMessage) + } + + // Swipe to done tests + @Test + fun `Swipe to done successfully marks item as done when unchecked`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.createPlannerOverride(any(), any(), any()) } returns DataResult.Success(plannerOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onSwipeToDone() + + val uiState = viewModel.uiState.value + + assertTrue(uiState.itemsByDate.values.flatten().first().isChecked) + assertEquals("Assignment", uiState.confirmationSnackbarData?.title) + assertTrue(uiState.confirmationSnackbarData?.markedAsDone == true) + } + + @Test + fun `Swipe to done successfully marks item as undone when checked`() = runTest { + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false).copy( + plannerOverride = plannerOverride + ) + val updatedOverride = plannerOverride.copy(markedComplete = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.updatePlannerOverride(any(), any()) } returns DataResult.Success(updatedOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onSwipeToDone() + + val uiState = viewModel.uiState.value + + assertFalse(uiState.itemsByDate.values.flatten().first().isChecked) + } + + @Test + fun `Swipe to done shows offline snackbar when device is offline`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + every { networkStateProvider.isOnline() } returns false + every { context.getString(R.string.todoActionOffline) } returns "This action cannot be performed offline" + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onSwipeToDone() + + val uiState = viewModel.uiState.value + + assertFalse(uiState.itemsByDate.values.flatten().first().isChecked) + assertEquals("This action cannot be performed offline", uiState.snackbarMessage) + } + + // Cache invalidation tests + @Test + fun `Cache is invalidated after successfully creating planner override`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.createPlannerOverride(any(), any(), any()) } returns DataResult.Success(plannerOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(true) + + verify { repository.invalidateCachedResponses() } + } + + @Test + fun `Cache is invalidated after successfully updating planner override`() = runTest { + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false).copy( + plannerOverride = plannerOverride + ) + val updatedOverride = plannerOverride.copy(markedComplete = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.updatePlannerOverride(any(), any()) } returns DataResult.Success(updatedOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(false) + + verify { repository.invalidateCachedResponses() } + } + + @Test + fun `Cache is not invalidated when planner override update fails`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.createPlannerOverride(any(), any(), any()) } returns DataResult.Fail() + every { networkStateProvider.isOnline() } returns true + every { context.getString(R.string.errorUpdatingToDo) } returns "Error updating to-do" + + val viewModel = getViewModel() + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(true) + + verify(exactly = 0) { repository.invalidateCachedResponses() } + } + + // Undo tests + @Test + fun `Undo mark as done successfully reverts item to unchecked`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + val revertedOverride = plannerOverride.copy(markedComplete = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.createPlannerOverride(1L, PlannableType.ASSIGNMENT, true) } returns DataResult.Success(plannerOverride) + coEvery { repository.updatePlannerOverride(100L, false) } returns DataResult.Success(revertedOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + // First mark as done + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(true) + + // Verify marked as done + assertTrue(viewModel.uiState.value.itemsByDate.values.flatten().first().isChecked) + + // Now undo + viewModel.uiState.value.onUndoMarkAsDoneUndoneAction() + + val uiState = viewModel.uiState.value + + assertFalse(uiState.itemsByDate.values.flatten().first().isChecked) + assertEquals(null, uiState.confirmationSnackbarData) + } + + @Test + fun `Todo count updates when item is marked as done`() = runTest { + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false) + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.createPlannerOverride(any(), any(), any()) } returns DataResult.Success(plannerOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + assertEquals(1, viewModel.uiState.value.toDoCount) + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(true) + + assertEquals(0, viewModel.uiState.value.toDoCount) + } + + @Test + fun `Todo count updates when item is marked as undone`() = runTest { + val plannerOverride = PlannerOverride(id = 100L, plannableId = 1L, plannableType = PlannableType.ASSIGNMENT, markedComplete = true) + val plannerItem = createPlannerItem(id = 1L, title = "Assignment", submitted = false).copy( + plannerOverride = plannerOverride + ) + val updatedOverride = plannerOverride.copy(markedComplete = false) + + coEvery { repository.getCourses(any()) } returns DataResult.Success(emptyList()) + coEvery { repository.getPlannerItems(any(), any(), any()) } returns DataResult.Success(listOf(plannerItem)) + coEvery { repository.updatePlannerOverride(any(), any()) } returns DataResult.Success(updatedOverride) + every { networkStateProvider.isOnline() } returns true + + val viewModel = getViewModel() + + assertEquals(0, viewModel.uiState.value.toDoCount) + + val item = viewModel.uiState.value.itemsByDate.values.flatten().first() + item.onCheckboxToggle(false) + + assertEquals(1, viewModel.uiState.value.toDoCount) + } + // Helper functions private fun getViewModel(): ToDoListViewModel { - return ToDoListViewModel(context, repository) + return ToDoListViewModel(context, repository, networkStateProvider, firebaseCrashlytics) } private fun createPlannerItem( From 6f4add1b0375ab98afc7c62bd599bd213fcc89be Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:38:24 +0100 Subject: [PATCH 10/58] Fix PR pipeline (#3370) --- .github/workflows/build-app.yml | 189 +++++++++++++++ .github/workflows/claude-code-review.yml | 32 +-- .github/workflows/pr-pipeline.yml | 286 +++++------------------ 3 files changed, 261 insertions(+), 246 deletions(-) create mode 100644 .github/workflows/build-app.yml diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml new file mode 100644 index 0000000000..7fad64b118 --- /dev/null +++ b/.github/workflows/build-app.yml @@ -0,0 +1,189 @@ +name: Build App + +on: + workflow_call: + inputs: + app-type: + description: 'App type (Parent, Student, or Teacher)' + required: true + type: string + app-type-lower: + description: 'App type in lowercase (parent, student, or teacher)' + required: true + type: string + firebase-app-id-secret: + description: 'Name of the Firebase App ID secret' + required: true + type: string + secrets: + ACCESS_TOKEN: + required: true + ANDROID_RELEASE_KEYSTORE_B64: + required: true + FIREBASE_SERVICE_ACCOUNT_KEY: + required: true + FIREBASE_APP_ID: + required: true + +jobs: + build: + name: ${{ inputs.app-type-lower }}-build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 1 + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Cache Gradle Build Cache + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches/build-cache-* + .gradle + key: ${{ runner.os }}-gradle-build-cache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-gradle-build-cache- + + - name: Decode Release Keystore + run: | + echo "${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }}" | base64 --decode > release.jks + chmod 600 release.jks + + - name: Setup Service account + env: + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} + run: | + if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then + echo "Error: Firebase service account key is not configured" + exit 1 + fi + echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json + chmod 600 service-account-key.json + + - name: Build Release Notes + id: get_release_notes + run: | + echo "RELEASE_NOTES<> $GITHUB_OUTPUT + echo "${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Install Firebase CLI + run: npm install -g firebase-tools + + - name: Setup Firebase App Id + run: | + if [ -z "${{ secrets.FIREBASE_APP_ID }}" ]; then + echo "Error: Firebase App ID is not configured" + exit 1 + fi + echo "${{ secrets.FIREBASE_APP_ID }}" > firebase_app_id.txt + + - name: Build debug and test APKs + run: | + ./gradle/gradlew -p apps :${{ inputs.app-type-lower }}:assembleQaDebug \ + :${{ inputs.app-type-lower }}:assembleQaDebugAndroidTest \ + :${{ inputs.app-type-lower }}:assembleDevDebugMinify \ + --build-cache \ + --parallel \ + --max-workers=4 \ + --no-daemon \ + -Dorg.gradle.jvmargs="-Xmx6g -XX:+HeapDumpOnOutOfMemoryError" \ + -Dkotlin.compiler.execution.strategy=in-process \ + -Pandroid.injected.signing.store.file=$(pwd)/release.jks + + - name: Upload QA debug APK + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.app-type-lower }}-qa-debug.apk + path: apps/${{ inputs.app-type-lower }}/build/outputs/apk/qa/debug/${{ inputs.app-type-lower }}-qa-debug.apk + + - name: Upload QA test APK + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.app-type-lower }}-qa-debug-androidTest.apk + path: apps/${{ inputs.app-type-lower }}/build/outputs/apk/androidTest/qa/debug/${{ inputs.app-type-lower }}-qa-debug-androidTest.apk + + - name: Upload Dev debug APK + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.app-type-lower }}-dev-debugMinify.apk + path: apps/${{ inputs.app-type-lower }}/build/outputs/apk/dev/debugMinify/${{ inputs.app-type-lower }}-dev-debugMinify.apk + + - name: Distribute app to Firebase App Distribution + env: + GOOGLE_APPLICATION_CREDENTIALS: ${{ github.workspace }}/service-account-key.json + run: | + firebase --version + FIREBASE_APP_ID="$(cat firebase_app_id.txt)" + + if ! firebase appdistribution:distribute "apps/${{ inputs.app-type-lower }}/build/outputs/apk/dev/debugMinify/${{ inputs.app-type-lower }}-dev-debugMinify.apk" \ + --app "$FIREBASE_APP_ID" \ + --release-notes "${{ steps.get_release_notes.outputs.RELEASE_NOTES }}" \ + --groups "Testers" > result.txt 2>&1; then + echo "Firebase distribution failed:" + cat result.txt + exit 1 + fi + cat result.txt + + - name: Prepare Comment Body + id: prepare_comment + run: | + INSTALL_URL=$(grep -o 'https://appdistribution\.firebase[^[:space:]]*' result.txt | head -1) + + if [ -z "$INSTALL_URL" ]; then + echo "Error: Could not extract install URL from Firebase output" + cat result.txt + exit 1 + fi + + INSTALL_URL_ESCAPED=$(printf '%s' "$INSTALL_URL" | sed 's/:/%3A/g; s/\//%2F/g; s/?/%3F/g; s/=/%3D/g; s/&/%26/g') + { + echo "body<" + echo "

${{ inputs.app-type }} Install Page

" + echo "EOF" + } >> $GITHUB_OUTPUT + + - name: Find Previous Comment + id: find_comment + uses: peter-evans/find-comment@v2 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '' + + - name: Create or Update Comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find_comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: ${{ steps.prepare_comment.outputs.body }} + edit-mode: replace + + - name: Cleanup sensitive files + if: always() + run: | + rm -f release.jks + rm -f service-account-key.json + rm -f firebase_app_id.txt + rm -f result.txt diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index d756544331..13fa64b9d2 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -58,21 +58,23 @@ jobs: This is an update to an existing PR. You must: 1. Use the GitHub MCP tools to fetch your previous reviews on this PR 2. Fetch the latest PR diff and identify what has changed since your last review - 3. Update your EXISTING review comments - DO NOT create a new review summary - 4. Use checkboxes to track progress on previously identified issues: - - [ ] Unresolved issue - - [x] Resolved issue - 5. For each previously identified issue: - - If it has been addressed: Mark the checkbox as complete [x] and add a note - - If it is still present: Keep the checkbox unchecked [ ] - - If new issues are found: Add new checkboxes [ ] - 6. Update inline comments: - - Resolve or update threads that have been addressed - - Add new inline comments ONLY for new issues that require changes - - Do NOT add inline comments for positive changes or improvements - 7. Keep all positive feedback in the summary section only + 3. Find your previous review summary comment (the one that starts with "## PR Review Summary" or similar) + 4. Post a NEW PR comment (not a review) with an update status that includes: + - Reference to your previous review + - Progress update using checkboxes: + - [x] Previously identified issues that have been resolved + - [ ] Previously identified issues still present + - [ ] New issues found in this update + - Brief summary of what changed + - Any new concerns or positive feedback + 5. For inline review comments: + - Resolve threads where the issue has been fixed + - Update existing review comment threads if partially addressed + - Add new inline review comments ONLY for new issues that require changes + - Do NOT add inline comments for positive feedback + 6. DO NOT create a new review summary - only post a progress update comment - DO NOT create a new review from scratch. Update the existing one.' || ' + Use mcp__github__create_or_update_issue_comment to post the update.' || ' ## NEW REVIEW EVENT This is a new PR or initial review. You must: 1. Use the GitHub MCP tools to fetch the PR diff @@ -85,5 +87,5 @@ jobs: # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options - claude_args: '--allowedTools "mcp__github__create_pending_pull_request_review,mcp__github__add_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff,mcp__github__list_reviews,mcp__github__get_review,mcp__github__list_review_comments,mcp__github__update_review_comment,mcp__github__create_or_update_pull_request_review_comment"' + claude_args: '--allowedTools "mcp__github__create_pending_pull_request_review,mcp__github__add_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff,mcp__github__list_reviews,mcp__github__get_review,mcp__github__list_review_comments,mcp__github__update_review_comment,mcp__github__create_or_update_pull_request_review_comment,mcp__github__create_or_update_issue_comment,mcp__github__list_issue_comments,mcp__github__resolve_review_thread"' diff --git a/.github/workflows/pr-pipeline.yml b/.github/workflows/pr-pipeline.yml index 5faf5418c9..6d7b837445 100644 --- a/.github/workflows/pr-pipeline.yml +++ b/.github/workflows/pr-pipeline.yml @@ -11,232 +11,56 @@ concurrency: cancel-in-progress: true jobs: - build: - name: ${{ matrix.app-type-lower }}-build - runs-on: ubuntu-latest + parent-build: if: >- ( (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) - ) && ( - contains(github.event.pull_request.body, 'Parent') || - contains(github.event.pull_request.body, 'Student') || - contains(github.event.pull_request.body, 'Teacher') - ) - strategy: - fail-fast: true - matrix: - include: - - app-type: Parent - app-type-lower: parent - should_run: ${{ contains(github.event.pull_request.body, 'Parent') }} - - app-type: Student - app-type-lower: student - should_run: ${{ contains(github.event.pull_request.body, 'Student') }} - - app-type: Teacher - app-type-lower: teacher - should_run: ${{ contains(github.event.pull_request.body, 'Teacher') }} - steps: - - name: Dump Pull Request Event Context - run: | - echo "body: ${{ github.event.pull_request.body }}" - echo "${{ toJson(github.event.pull_request) }}" - - - name: Checkout repository - if: ${{ matrix.should_run }} - uses: actions/checkout@v4 - with: - submodules: 'recursive' - fetch-depth: 1 - token: ${{ secrets.ACCESS_TOKEN }} - - - name: Set up JDK 17 - if: ${{ matrix.should_run }} - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Cache Gradle packages - if: ${{ matrix.should_run }} - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Cache Gradle Build Cache - if: ${{ matrix.should_run }} - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches/build-cache-* - .gradle - key: ${{ runner.os }}-gradle-build-cache-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-gradle-build-cache- - - - name: Decode Release Keystore - if: ${{ matrix.should_run }} - run: | - echo "${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }}" | base64 --decode > release.jks - chmod 600 release.jks - - - name: Setup Service account - if: ${{ matrix.should_run }} - env: - FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} - run: | - if [ -z "${FIREBASE_SERVICE_ACCOUNT_KEY}" ]; then - echo "Error: FIREBASE_SERVICE_ACCOUNT_KEY is not set" - exit 1 - fi - echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > service-account-key.json - chmod 600 service-account-key.json - - - name: Build Release Notes - if: ${{ matrix.should_run }} - id: get_release_notes - run: | - echo "RELEASE_NOTES<> $GITHUB_OUTPUT - echo "${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Install Firebase CLI - if: ${{ matrix.should_run }} - run: npm install -g firebase-tools - - - name: Setup Parent Firebase App Id - if: ${{ matrix.should_run && matrix.app-type == 'Parent' }} - run: | - if [ -z "${{ secrets.FIREBASE_ANDROID_PARENT_APP_ID }}" ]; then - echo "Error: FIREBASE_ANDROID_PARENT_APP_ID is not set" - exit 1 - fi - echo "${{ secrets.FIREBASE_ANDROID_PARENT_APP_ID }}" > firebase_app_id.txt - - - name: Setup Student Firebase App Id - if: ${{ matrix.should_run && matrix.app-type == 'Student' }} - run: | - if [ -z "${{ secrets.FIREBASE_ANDROID_STUDENT_APP_ID }}" ]; then - echo "Error: FIREBASE_ANDROID_STUDENT_APP_ID is not set" - exit 1 - fi - echo "${{ secrets.FIREBASE_ANDROID_STUDENT_APP_ID }}" > firebase_app_id.txt - - - name: Setup Teacher Firebase App Id - if: ${{ matrix.should_run && matrix.app-type == 'Teacher' }} - run: | - if [ -z "${{ secrets.FIREBASE_ANDROID_TEACHER_APP_ID }}" ]; then - echo "Error: FIREBASE_ANDROID_TEACHER_APP_ID is not set" - exit 1 - fi - echo "${{ secrets.FIREBASE_ANDROID_TEACHER_APP_ID }}" > firebase_app_id.txt - - # Building Artifacts - - name: Build debug and test APKs - if: ${{ matrix.should_run }} - run: | - ./gradle/gradlew -p apps :${{ matrix.app-type-lower }}:assembleQaDebug \ - :${{ matrix.app-type-lower }}:assembleQaDebugAndroidTest \ - :${{ matrix.app-type-lower }}:assembleDevDebugMinify \ - --build-cache \ - --parallel \ - --max-workers=4 \ - --no-daemon \ - -Dorg.gradle.jvmargs="-Xmx6g -XX:+HeapDumpOnOutOfMemoryError" \ - -Dkotlin.compiler.execution.strategy=in-process \ - -Pandroid.injected.signing.store.file=$(pwd)/release.jks - - # Uploading Artifacts to GitHub - - name: Upload QA debug APK - if: ${{ matrix.should_run }} - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.app-type-lower }}-qa-debug.apk - path: apps/${{ matrix.app-type-lower }}/build/outputs/apk/qa/debug/${{ matrix.app-type-lower }}-qa-debug.apk - - - name: Upload QA test APK - if: ${{ matrix.should_run }} - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.app-type-lower }}-qa-debug-androidTest.apk - path: apps/${{ matrix.app-type-lower }}/build/outputs/apk/androidTest/qa/debug/${{ matrix.app-type-lower }}-qa-debug-androidTest.apk - - - name: Upload Dev debug APK - if: ${{ matrix.should_run }} - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.app-type-lower }}-dev-debugMinify.apk - path: apps/${{ matrix.app-type-lower }}/build/outputs/apk/dev/debugMinify/${{ matrix.app-type-lower }}-dev-debugMinify.apk - - # Uploading Artifacts to Firebase App Distribution - - name: Distribute app to Firebase App Distribution - if: ${{ matrix.should_run }} - env: - GOOGLE_APPLICATION_CREDENTIALS: ${{ github.workspace }}/service-account-key.json - run: | - firebase --version - FIREBASE_APP_ID=$(cat firebase_app_id.txt) - - if ! firebase appdistribution:distribute "apps/${{ matrix.app-type-lower }}/build/outputs/apk/dev/debugMinify/${{ matrix.app-type-lower }}-dev-debugMinify.apk" \ - --app "$FIREBASE_APP_ID" \ - --release-notes "${{ steps.get_release_notes.outputs.RELEASE_NOTES }}" \ - --groups "Testers" > result.txt 2>&1; then - echo "Firebase distribution failed:" - cat result.txt - exit 1 - fi - cat result.txt - - - name: Prepare Comment Body - if: ${{ matrix.should_run }} - id: prepare_comment - run: | - INSTALL_URL=$(grep -o 'https://appdistribution\.firebase[^[:space:]]*' result.txt | head -1) - - if [ -z "$INSTALL_URL" ]; then - echo "Error: Could not extract install URL from Firebase output" - cat result.txt - exit 1 - fi - - INSTALL_URL_ESCAPED=$(printf '%s' "$INSTALL_URL" | sed 's/:/%3A/g; s/\//%2F/g; s/?/%3F/g; s/=/%3D/g; s/&/%26/g') - { - echo "body<" - echo "

${{ matrix.app-type }} Install Page

" - echo "EOF" - } >> $GITHUB_OUTPUT - - - name: Find Previous Comment - if: ${{ matrix.should_run }} - id: find_comment - uses: peter-evans/find-comment@v2 - with: - issue-number: ${{ github.event.number }} - comment-author: 'github-actions[bot]' - body-includes: '' - - - name: Create or Update Comment - if: ${{ matrix.should_run }} - uses: peter-evans/create-or-update-comment@v4 - with: - comment-id: ${{ steps.find_comment.outputs.comment-id }} - issue-number: ${{ github.event.number }} - body: ${{ steps.prepare_comment.outputs.body }} - edit-mode: replace - - - name: Cleanup sensitive files - if: always() && matrix.should_run - run: | - rm -f release.jks - rm -f service-account-key.json - rm -f firebase_app_id.txt - rm -f result.txt + ) && contains(github.event.pull_request.body, 'Parent') + uses: ./.github/workflows/build-app.yml + with: + app-type: Parent + app-type-lower: parent + firebase-app-id-secret: FIREBASE_ANDROID_PARENT_APP_ID + secrets: + ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + ANDROID_RELEASE_KEYSTORE_B64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }} + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} + FIREBASE_APP_ID: ${{ secrets.FIREBASE_ANDROID_PARENT_APP_ID }} + + student-build: + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Student') + uses: ./.github/workflows/build-app.yml + with: + app-type: Student + app-type-lower: student + firebase-app-id-secret: FIREBASE_ANDROID_STUDENT_APP_ID + secrets: + ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + ANDROID_RELEASE_KEYSTORE_B64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }} + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} + FIREBASE_APP_ID: ${{ secrets.FIREBASE_ANDROID_STUDENT_APP_ID }} + + teacher-build: + if: >- + ( + (github.event.action == 'opened' || github.event.action == 'synchronize') || + (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) + ) && contains(github.event.pull_request.body, 'Teacher') + uses: ./.github/workflows/build-app.yml + with: + app-type: Teacher + app-type-lower: teacher + firebase-app-id-secret: FIREBASE_ANDROID_TEACHER_APP_ID + secrets: + ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + ANDROID_RELEASE_KEYSTORE_B64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }} + FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} + FIREBASE_APP_ID: ${{ secrets.FIREBASE_ANDROID_TEACHER_APP_ID }} submodule-build-and-test: name: submodule-build-and-test @@ -432,7 +256,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Parent') - needs: [build, parent-unit-tests] + needs: [parent-build, parent-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -503,7 +327,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Parent') - needs: [build, parent-unit-tests] + needs: [parent-build, parent-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -574,7 +398,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Student') - needs: [build, student-unit-tests] + needs: [student-build, student-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -645,7 +469,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Student') - needs: [build, student-unit-tests] + needs: [student-build, student-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -716,7 +540,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Teacher') - needs: [build, teacher-unit-tests] + needs: [teacher-build, teacher-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -787,7 +611,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Teacher') - needs: [build, teacher-unit-tests] + needs: [teacher-build, teacher-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -858,7 +682,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Parent') && contains(github.event.pull_request.body, 'Run E2E test suite') - needs: [build, parent-unit-tests] + needs: [parent-build, parent-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -929,7 +753,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Student') && contains(github.event.pull_request.body, 'Run E2E test suite') - needs: [build, student-unit-tests] + needs: [student-build, student-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -1000,7 +824,7 @@ jobs: (github.event.action == 'opened' || github.event.action == 'synchronize') || (github.event.action == 'labeled' && (contains(github.event.pull_request.labels.*.name, 'run-ui-tests') || contains(github.event.pull_request.labels.*.name, 'run-e2e-tests'))) ) && contains(github.event.pull_request.body, 'Teacher') && contains(github.event.pull_request.body, 'Run E2E test suite') - needs: [build, teacher-unit-tests] + needs: [teacher-build, teacher-unit-tests] steps: - name: Checkout repository uses: actions/checkout@v4 From 4915d188a23bf415c02e048ba4025827b4fe6a92 Mon Sep 17 00:00:00 2001 From: domonkosadam <53480952+domonkosadam@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:29:32 +0100 Subject: [PATCH 11/58] [CLX-3127][Horizon] Learner dashboard landscape mode (#3364) refs: CLX-3127 affects: Horizon release note: none --- .../features/dashboard/DashboardScreen.kt | 129 +++++++--- .../DashboardPaginatedWidgetCardState.kt | 24 +- .../DashboardAnnouncementBannerWidget.kt | 24 +- .../widget/course/DashboardCourseSection.kt | 12 +- .../widget/course/DashboardCourseViewModel.kt | 38 +-- .../widget/course/DashboardMapper.kt | 3 - .../course/card/DashboardCourseCardContent.kt | 234 +++++++++++------- .../course/card/DashboardCourseCardLoading.kt | 129 ---------- .../course/card/DashboardCourseCardState.kt | 32 ++- .../myprogress/DashboardMyProgressWidget.kt | 34 +-- .../card/DashboardMyProgressCardState.kt | 6 +- .../DashboardSkillHighlightsWidget.kt | 3 +- .../DashboardSkillHighlightsCardContent.kt | 59 +++-- .../card/DashboardSkillHighlightsCardState.kt | 17 +- .../DashboardSkillOverviewWidget.kt | 4 +- .../card/DashboardSkillOverviewCardState.kt | 6 +- .../timespent/DashboardTimeSpentWidget.kt | 21 +- .../card/DashboardTimeSpentCardContent.kt | 2 + .../card/DashboardTimeSpentCardState.kt | 6 +- .../horizon/horizonui/Extensions.kt | 7 +- 20 files changed, 396 insertions(+), 394 deletions(-) delete mode 100644 libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardLoading.kt diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/DashboardScreen.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/DashboardScreen.kt index 57b515c1d0..7934525554 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/DashboardScreen.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/DashboardScreen.kt @@ -22,13 +22,19 @@ import android.content.pm.PackageManager import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -76,6 +82,7 @@ import com.instructure.horizon.horizonui.foundation.HorizonColors import com.instructure.horizon.horizonui.foundation.HorizonElevation import com.instructure.horizon.horizonui.foundation.HorizonSpace import com.instructure.horizon.horizonui.foundation.SpaceSize +import com.instructure.horizon.horizonui.isWideLayout import com.instructure.horizon.horizonui.molecules.Badge import com.instructure.horizon.horizonui.molecules.BadgeContent import com.instructure.horizon.horizonui.molecules.BadgeType @@ -191,42 +198,7 @@ fun DashboardScreen(uiState: DashboardUiState, mainNavController: NavHostControl refreshStateFlow ) HorizonSpace(SpaceSize.SPACE_16) - val pagerState = rememberPagerState{ 3 } - AnimatedHorizontalPager( - pagerState, - sizeAnimationRange = 0f, - contentPadding = PaddingValues(horizontal = 24.dp), - pageSpacing = 12.dp, - verticalAlignment = Alignment.CenterVertically, - ) { index, modifier -> - when (index) { - 0 -> { - DashboardMyProgressWidget( - shouldRefresh, - refreshStateFlow, - modifier.padding(bottom = 16.dp) - ) - } - 1 -> { - DashboardTimeSpentWidget( - shouldRefresh, - refreshStateFlow, - modifier.padding(bottom = 16.dp) - ) - } - 2 -> { - DashboardSkillOverviewWidget( - homeNavController, - shouldRefresh, - refreshStateFlow, - modifier.padding(bottom = 16.dp) - ) - } - else -> { - - } - } - } + NumericWidgetRow(shouldRefresh, refreshStateFlow, homeNavController) DashboardSkillHighlightsWidget( homeNavController, shouldRefresh, @@ -303,6 +275,91 @@ private fun HomeScreenTopBar(uiState: DashboardUiState, mainNavController: NavCo } } +@Composable +private fun NumericWidgetRow( + shouldRefresh: Boolean, + refreshStateFlow: MutableStateFlow>, + homeNavController: NavHostController +) { + BoxWithConstraints { + val pagerState = rememberPagerState { 3 } + if (this.isWideLayout) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier + .horizontalScroll(rememberScrollState()) + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(bottom = 12.dp) + ) { + DashboardMyProgressWidget( + shouldRefresh, + refreshStateFlow, + Modifier.width(IntrinsicSize.Max) + ) + DashboardTimeSpentWidget( + shouldRefresh, + refreshStateFlow, + Modifier.width(IntrinsicSize.Max) + ) + DashboardSkillOverviewWidget( + homeNavController, + shouldRefresh, + refreshStateFlow, + Modifier.width(IntrinsicSize.Max) + ) + } + } else { + AnimatedHorizontalPager( + pagerState, + sizeAnimationRange = 0f, + beyondViewportPageCount = 3, + contentPadding = PaddingValues(horizontal = 24.dp), + pageSpacing = 12.dp, + verticalAlignment = Alignment.CenterVertically, + ) { index, modifier -> + when (index) { + 0 -> { + DashboardMyProgressWidget( + shouldRefresh, + refreshStateFlow, + modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) + } + + 1 -> { + DashboardTimeSpentWidget( + shouldRefresh, + refreshStateFlow, + modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) + } + + 2 -> { + DashboardSkillOverviewWidget( + homeNavController, + shouldRefresh, + refreshStateFlow, + modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) + } + + else -> { + + } + } + } + } + } +} + @Composable private fun NotificationPermissionRequest() { val context = LocalContext.current diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/DashboardPaginatedWidgetCardState.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/DashboardPaginatedWidgetCardState.kt index f843a694a0..31dd37a7a8 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/DashboardPaginatedWidgetCardState.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/DashboardPaginatedWidgetCardState.kt @@ -6,7 +6,14 @@ import java.util.Date data class DashboardPaginatedWidgetCardState( val items: List = emptyList(), val isLoading: Boolean = false, -) +) { + companion object { + val Loading = DashboardPaginatedWidgetCardState( + items = listOf(DashboardPaginatedWidgetCardItemState.Loading), + isLoading = true + ) + } +} data class DashboardPaginatedWidgetCardItemState( val chipState: DashboardPaginatedWidgetCardChipState? = null, @@ -15,7 +22,20 @@ data class DashboardPaginatedWidgetCardItemState( val date: Date? = null, val title: String? = null, val route: DashboardPaginatedWidgetCardButtonRoute? = null -) +) { + companion object { + val Loading = DashboardPaginatedWidgetCardItemState( + chipState = DashboardPaginatedWidgetCardChipState( + label = "Announcement", + color = StatusChipColor.Sky + ), + title = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Announcement title shown here.", + source = "Institution or Course Name Here", + date = Date(), + route = DashboardPaginatedWidgetCardButtonRoute.MainRoute("") + ) + } +} data class DashboardPaginatedWidgetCardChipState( val label: String, diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/announcement/DashboardAnnouncementBannerWidget.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/announcement/DashboardAnnouncementBannerWidget.kt index d949af71e9..4e4da8f785 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/announcement/DashboardAnnouncementBannerWidget.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/announcement/DashboardAnnouncementBannerWidget.kt @@ -22,21 +22,15 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController -import com.instructure.horizon.R import com.instructure.horizon.features.dashboard.DashboardItemState import com.instructure.horizon.features.dashboard.widget.DashboardPaginatedWidgetCard -import com.instructure.horizon.features.dashboard.widget.DashboardPaginatedWidgetCardButtonRoute -import com.instructure.horizon.features.dashboard.widget.DashboardPaginatedWidgetCardChipState -import com.instructure.horizon.features.dashboard.widget.DashboardPaginatedWidgetCardItemState +import com.instructure.horizon.features.dashboard.widget.DashboardPaginatedWidgetCardState import com.instructure.horizon.features.dashboard.widget.announcement.card.DashboardAnnouncementBannerCardError -import com.instructure.horizon.horizonui.molecules.StatusChipColor import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update -import java.util.Date @Composable fun DashboardAnnouncementBannerWidget( @@ -71,21 +65,7 @@ fun DashboardAnnouncementBannerSection( when (state.state) { DashboardItemState.LOADING -> { DashboardPaginatedWidgetCard( - state.cardState.copy( - items = listOf( - DashboardPaginatedWidgetCardItemState( - chipState = DashboardPaginatedWidgetCardChipState( - label = stringResource(R.string.notificationsAnnouncementCategoryLabel), - color = StatusChipColor.Sky - ), - title = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Announcement title shown here.", - source = "Institution or Course Name Here", - date = Date(), - route = DashboardPaginatedWidgetCardButtonRoute.MainRoute("") - ) - ), - isLoading = true - ), + DashboardPaginatedWidgetCardState.Loading, mainNavController, homeNavController, ) diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardCourseSection.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardCourseSection.kt index baafacd090..af258919d8 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardCourseSection.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardCourseSection.kt @@ -41,7 +41,6 @@ import com.instructure.horizon.features.dashboard.widget.DashboardPaginatedWidge import com.instructure.horizon.features.dashboard.widget.course.card.CardClickAction import com.instructure.horizon.features.dashboard.widget.course.card.DashboardCourseCardContent import com.instructure.horizon.features.dashboard.widget.course.card.DashboardCourseCardError -import com.instructure.horizon.features.dashboard.widget.course.card.DashboardCourseCardLoading import com.instructure.horizon.features.dashboard.widget.course.card.DashboardCourseCardState import com.instructure.horizon.features.home.HomeNavigationRoute import com.instructure.horizon.horizonui.foundation.HorizonColors @@ -82,7 +81,12 @@ fun DashboardCourseSection( ) { when(state.state) { DashboardItemState.LOADING -> { - DashboardCourseCardLoading(Modifier.padding(horizontal = 24.dp)) + DashboardCourseCardContent( + DashboardCourseCardState.Loading, + { handleClickAction(it, mainNavController, homeNavController) }, + true, + modifier = Modifier.padding(horizontal = 24.dp) + ) } DashboardItemState.ERROR -> { DashboardCourseCardError({state.onRefresh {} }, Modifier.padding(horizontal = 24.dp)) @@ -160,7 +164,9 @@ private fun DashboardCourseItem( modifier = modifier.fillMaxWidth() ){ DashboardCourseCardContent( - cardState, { handleClickAction(it, mainNavController, homeNavController) } + cardState, + { handleClickAction(it, mainNavController, homeNavController) }, + false ) } } diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardCourseViewModel.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardCourseViewModel.kt index bbcc31c2c3..5a839f4e05 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardCourseViewModel.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardCourseViewModel.kt @@ -25,10 +25,9 @@ import com.instructure.canvasapi2.utils.weave.tryLaunch import com.instructure.horizon.features.dashboard.DashboardEvent import com.instructure.horizon.features.dashboard.DashboardEventHandler import com.instructure.horizon.features.dashboard.DashboardItemState +import com.instructure.horizon.features.dashboard.widget.DashboardPaginatedWidgetCardState import com.instructure.horizon.features.dashboard.widget.course.card.CardClickAction import com.instructure.horizon.features.dashboard.widget.course.card.DashboardCourseCardModuleItemState -import com.instructure.horizon.features.dashboard.widget.course.card.DashboardCourseCardState -import com.instructure.horizon.features.dashboard.widget.DashboardPaginatedWidgetCardState import com.instructure.horizon.model.LearningObjectType import com.instructure.journey.type.ProgramProgressCourseEnrollmentStatus import com.instructure.pandautils.utils.formatIsoDuration @@ -110,22 +109,7 @@ class DashboardCourseViewModel @Inject constructor( nextModuleForCourse = { courseId -> fetchNextModuleState(courseId, forceNetwork) }, - ).map { state -> - if (state.buttonState?.onClickAction is CardClickAction.Action) { - state.copy(buttonState = state.buttonState.copy( - onClickAction = CardClickAction.Action { - viewModelScope.tryLaunch { - updateCourseButtonState(state, isLoading = true) - state.buttonState.action() - onRefresh() - updateCourseButtonState(state, isLoading = false) - } catch { - updateCourseButtonState(state, isLoading = false) - } - }, - )) - } else state - } + ) val programCardStates = programs .filter { program -> program.sortedRequirements.none { it.enrollmentStatus == ProgramProgressCourseEnrollmentStatus.ENROLLED } } @@ -156,22 +140,4 @@ class DashboardCourseViewModel @Inject constructor( onClickAction = CardClickAction.NavigateToModuleItem(courseId, nextModuleItem.id) ) } - - private fun updateCourseButtonState(state: DashboardCourseCardState, isLoading: Boolean) { - _uiState.update { - it.copy( - courses = it.courses.map { originalState -> - if (originalState.title == state.title && originalState.parentPrograms == state.parentPrograms) { - originalState.copy( - buttonState = originalState.buttonState?.copy( - isLoading = isLoading - ) - ) - } else { - originalState - } - } - ) - } - } } \ No newline at end of file diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardMapper.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardMapper.kt index 2eb876877c..f75bd8c07a 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardMapper.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/DashboardMapper.kt @@ -97,7 +97,6 @@ private fun GetCoursesQuery.Enrollment.mapCompleted(context: Context, programs: description = context.getString(R.string.dashboardCompletedCourseDetails), progress = this.course?.usersConnection?.nodes?.firstOrNull()?.courseProgression?.requirements?.completionPercentage ?: 0.0, moduleItem = null, - buttonState = null, onClickAction = CardClickAction.NavigateToCourse(this.course?.id?.toLongOrNull() ?: -1L) ) } @@ -124,8 +123,6 @@ private suspend fun GetCoursesQuery.Enrollment.mapActive( description = null, progress = this.course?.usersConnection?.nodes?.firstOrNull()?.courseProgression?.requirements?.completionPercentage ?: 0.0, moduleItem = nextModuleForCourse(this.course?.id?.toLongOrNull()), - buttonState = null, onClickAction = CardClickAction.NavigateToCourse(this.course?.id?.toLongOrNull() ?: -1L), - lastAccessed = this.lastActivityAt ) } \ No newline at end of file diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardContent.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardContent.kt index 85cb418b6f..4e3e31b58d 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardContent.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardContent.kt @@ -21,6 +21,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow @@ -31,6 +32,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -70,11 +72,7 @@ import com.instructure.horizon.horizonui.foundation.HorizonCornerRadius import com.instructure.horizon.horizonui.foundation.HorizonSpace import com.instructure.horizon.horizonui.foundation.HorizonTypography import com.instructure.horizon.horizonui.foundation.SpaceSize -import com.instructure.horizon.horizonui.molecules.ButtonColor -import com.instructure.horizon.horizonui.molecules.ButtonHeight -import com.instructure.horizon.horizonui.molecules.ButtonIconPosition -import com.instructure.horizon.horizonui.molecules.ButtonWidth -import com.instructure.horizon.horizonui.molecules.LoadingButton +import com.instructure.horizon.horizonui.isWideLayout import com.instructure.horizon.horizonui.molecules.Pill import com.instructure.horizon.horizonui.molecules.PillCase import com.instructure.horizon.horizonui.molecules.PillSize @@ -82,8 +80,6 @@ import com.instructure.horizon.horizonui.molecules.PillStyle import com.instructure.horizon.horizonui.molecules.PillType import com.instructure.horizon.horizonui.molecules.ProgressBarSmall import com.instructure.horizon.horizonui.molecules.ProgressBarStyle -import com.instructure.horizon.horizonui.molecules.StatusChip -import com.instructure.horizon.horizonui.molecules.StatusChipState import com.instructure.horizon.model.LearningObjectType import com.instructure.pandautils.utils.localisedFormatMonthDay import java.util.Date @@ -93,6 +89,7 @@ import kotlin.math.roundToInt fun DashboardCourseCardContent( state: DashboardCourseCardState, handleOnClickAction: (CardClickAction?) -> Unit, + isLoading: Boolean, modifier: Modifier = Modifier ) { DashboardCard(modifier) { @@ -103,51 +100,115 @@ fun DashboardCourseCardContent( handleOnClickAction(state.onClickAction) } ) { - if (state.imageState != null) { - CourseImage(state.imageState) - } - Column( - modifier = Modifier - .padding(horizontal = 24.dp) - ) { - if (state.chipState != null) { - Spacer(Modifier.height(24.dp)) - CardChip(state.chipState) - } - if (!state.parentPrograms.isNullOrEmpty()) { - Spacer(Modifier.height(16.dp)) - ProgramsText(state.parentPrograms, handleOnClickAction) - } - if (!state.title.isNullOrEmpty()) { - Spacer(Modifier.height(16.dp)) - TitleText(state.title) - } - if (state.progress != null) { - Spacer(Modifier.height(12.dp)) - CourseProgress(state.progress) - } - if (!state.description.isNullOrEmpty()) { - Spacer(Modifier.height(16.dp)) - DescriptionText(state.description) - } - if (state.moduleItem != null) { - Spacer(Modifier.height(16.dp)) - ModuleItemCard(state.moduleItem, handleOnClickAction) - } - if (state.buttonState != null) { - Spacer(Modifier.height(16.dp)) - DashboardCardButton(state.buttonState, handleOnClickAction) + BoxWithConstraints { + if (this.isWideLayout) { + DashboardCourseCardWideContent(state, isLoading, handleOnClickAction) + } else { + DashboardCourseCardCompactContent(state, isLoading, handleOnClickAction) } } - Spacer(Modifier.height(24.dp)) + } + } +} + +@Composable +private fun DashboardCourseCardCompactContent( + state: DashboardCourseCardState, + isLoading: Boolean, + handleOnClickAction: (CardClickAction?) -> Unit, +) { + Column( + modifier = Modifier.fillMaxWidth() + ){ + if (state.imageState != null) { + CourseImage(state.imageState, isLoading, Modifier.fillMaxWidth()) + } + Column( + modifier = Modifier + .padding(horizontal = 24.dp) + ) { + if (!state.parentPrograms.isNullOrEmpty()) { + Spacer(Modifier.height(16.dp)) + ProgramsText(state.parentPrograms, isLoading, handleOnClickAction) + } + if (!state.title.isNullOrEmpty()) { + Spacer(Modifier.height(16.dp)) + TitleText(state.title, isLoading) + } + if (state.progress != null) { + Spacer(Modifier.height(12.dp)) + CourseProgress(state.progress, isLoading) + } + if (!state.description.isNullOrEmpty()) { + Spacer(Modifier.height(16.dp)) + DescriptionText(state.description, isLoading) + } + if (state.moduleItem != null) { + Spacer(Modifier.height(16.dp)) + ModuleItemCard(state.moduleItem, isLoading, handleOnClickAction) + } + } + HorizonSpace(SpaceSize.SPACE_24) + } +} + +@Composable +private fun DashboardCourseCardWideContent( + state: DashboardCourseCardState, + isLoading: Boolean, + handleOnClickAction: (CardClickAction?) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ){ + if (state.imageState != null) { + CourseImage( + state.imageState, + isLoading, + Modifier + .width(320.dp) + .padding(start = 24.dp, top = 24.dp, bottom = 24.dp, end = 16.dp) + .clip(HorizonCornerRadius.level2) + ) + } + Column( + modifier = Modifier + .padding(end = 24.dp, bottom = 24.dp) + ) { + if (!state.parentPrograms.isNullOrEmpty()) { + Spacer(Modifier.height(16.dp)) + ProgramsText(state.parentPrograms, isLoading, handleOnClickAction) + } + if (!state.title.isNullOrEmpty()) { + Spacer(Modifier.height(16.dp)) + TitleText(state.title, isLoading) + } + if (state.progress != null) { + Spacer(Modifier.height(12.dp)) + CourseProgress(state.progress, isLoading) + } + if (!state.description.isNullOrEmpty()) { + Spacer(Modifier.height(16.dp)) + DescriptionText(state.description, isLoading) + } + if (state.moduleItem != null) { + Spacer(Modifier.height(16.dp)) + ModuleItemCard(state.moduleItem, isLoading, handleOnClickAction) + } } } } @OptIn(ExperimentalGlideComposeApi::class) @Composable -private fun CourseImage(state: DashboardCourseCardImageState) { - var isLoading by rememberSaveable { mutableStateOf(true) } +private fun CourseImage( + state: DashboardCourseCardImageState, + isLoading: Boolean, + modifier: Modifier = Modifier +) { + var isImageLoading by rememberSaveable { mutableStateOf(true) } if (!state.imageUrl.isNullOrEmpty()) { GlideImage( state.imageUrl, @@ -161,7 +222,7 @@ private fun CourseImage(state: DashboardCourseCardImageState) { target: Target, isFirstResource: Boolean ): Boolean { - isLoading = false + isImageLoading = false return false } @@ -172,25 +233,24 @@ private fun CourseImage(state: DashboardCourseCardImageState) { dataSource: DataSource, isFirstResource: Boolean ): Boolean { - isLoading = false + isImageLoading = false return false } }) }, - modifier = Modifier - .fillMaxWidth() + modifier = modifier .aspectRatio(1.69f) - .shimmerEffect(isLoading) + .shimmerEffect(isLoading || isImageLoading) ) } else { if (state.showPlaceholder) { Box( contentAlignment = Alignment.Center, - modifier = Modifier - .fillMaxWidth() + modifier = modifier .aspectRatio(1.69f) .background(HorizonColors.Surface.institution().copy(alpha = 0.1f)) + .shimmerEffect(isLoading || isImageLoading) ) { Icon( painterResource(R.drawable.book_2_filled), @@ -203,21 +263,10 @@ private fun CourseImage(state: DashboardCourseCardImageState) { } } -@Composable -private fun CardChip(state: DashboardCourseCardChipState) { - StatusChip( - StatusChipState( - label = state.label, - color = state.color, - fill = true, - iconRes = null - ) - ) -} - @Composable private fun ProgramsText( programs: List, + isLoading: Boolean, handleOnClickAction: (CardClickAction?) -> Unit, ) { val programsAnnotated = buildAnnotatedString { @@ -248,36 +297,54 @@ private fun ProgramsText( } Text( - modifier = Modifier.semantics(mergeDescendants = true) {}, + text = fullText, style = HorizonTypography.p1, - text = fullText + modifier = Modifier + .semantics(mergeDescendants = true) {} + .shimmerEffect(isLoading) ) } @Composable -private fun TitleText(title: String) { +private fun TitleText( + title: String, + isLoading: Boolean, +) { Text( text = title, style = HorizonTypography.h4, color = HorizonColors.Text.title(), maxLines = 2, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + modifier = Modifier.shimmerEffect(isLoading) ) } @Composable -private fun DescriptionText(description: String) { +private fun DescriptionText( + description: String, + isLoading: Boolean, +) { Text( text = description, style = HorizonTypography.p1, color = HorizonColors.Text.body(), + modifier = Modifier.shimmerEffect(isLoading) ) } @Composable -private fun CourseProgress(progress: Double) { +private fun CourseProgress( + progress: Double, + isLoading: Boolean, +) { Row( verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.shimmerEffect( + isLoading, + backgroundColor = HorizonColors.Surface.institution().copy(0.1f), + shimmerColor = HorizonColors.Surface.institution().copy(0.05f), + ) ) { ProgressBarSmall( progress = progress, @@ -300,6 +367,7 @@ private fun CourseProgress(progress: Double) { @Composable private fun ModuleItemCard( state: DashboardCourseCardModuleItemState, + isLoading: Boolean, handleOnClickAction: (CardClickAction?) -> Unit, ) { Box( @@ -313,6 +381,11 @@ private fun ModuleItemCard( ) .clip(HorizonCornerRadius.level2) .clickable { handleOnClickAction(state.onClickAction) } + .shimmerEffect( + isLoading, + backgroundColor = HorizonColors.Surface.institution().copy(0.1f), + shimmerColor = HorizonColors.Surface.institution().copy(0.05f), + ) .padding(16.dp) ) { Column { @@ -361,23 +434,6 @@ private fun ModuleItemCard( } } -@Composable -private fun DashboardCardButton( - state: DashboardCourseCardButtonState, - handleOnClickAction: (CardClickAction?) -> Unit -) { - LoadingButton( - label = state.label, - height = ButtonHeight.SMALL, - width = ButtonWidth.RELATIVE, - color = ButtonColor.Black, - iconPosition = ButtonIconPosition.NoIcon, - onClick = { handleOnClickAction(state.onClickAction) }, - contentAlignment = Alignment.Center, - loading = state.isLoading, - ) -} - @Composable @Preview private fun DashboardCourseCardWithModulePreview() { @@ -402,11 +458,7 @@ private fun DashboardCourseCardWithModulePreview() { estimatedDuration = "5 mins", onClickAction = CardClickAction.Action({}) ), - buttonState = DashboardCourseCardButtonState( - label = "Go to Course", - onClickAction = CardClickAction.Action({}) - ), onClickAction = CardClickAction.Action({}) ) - DashboardCourseCardContent(state, {}) + DashboardCourseCardContent(state, {}, false) } \ No newline at end of file diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardLoading.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardLoading.kt deleted file mode 100644 index f13dd1fa93..0000000000 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardLoading.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2025 - 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.horizon.features.dashboard.widget.course.card - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.semantics.clearAndSetSemantics -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.instructure.horizon.R -import com.instructure.horizon.features.dashboard.DashboardCard -import com.instructure.horizon.horizonui.animation.shimmerEffect -import com.instructure.horizon.horizonui.foundation.HorizonColors -import com.instructure.horizon.horizonui.foundation.HorizonCornerRadius -import com.instructure.horizon.horizonui.foundation.HorizonSpace -import com.instructure.horizon.horizonui.foundation.SpaceSize - -@Composable -fun DashboardCourseCardLoading( - modifier: Modifier = Modifier, -) { - val context = LocalContext.current - DashboardCard( - modifier - .clearAndSetSemantics { - contentDescription = context.getString( - R.string.a11y_dashboardWidgetLoadingContentDescription, - context.getString(R.string.a11y_dashboardCoursesSectionTitle) - ) - }, - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding() - ) { - Box( - Modifier - .fillMaxWidth() - .aspectRatio(1.69f) - .shimmerEffect( - true, - shape = HorizonCornerRadius.level0, - ) - ) - Column( - Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp) - ) { - HorizonSpace(SpaceSize.SPACE_8) - - Box( - Modifier - .fillMaxWidth() - .height(50.dp) - .shimmerEffect(true) - ) - - HorizonSpace(SpaceSize.SPACE_8) - - Box( - Modifier - .fillMaxWidth() - .height(25.dp) - .shimmerEffect( - true, - ) - ) - - HorizonSpace(SpaceSize.SPACE_8) - - Box( - Modifier - .fillMaxWidth() - .height(25.dp) - .shimmerEffect( - true, - shape = HorizonCornerRadius.level6, - backgroundColor = HorizonColors.Surface.institution().copy(alpha = 0.1f) - ) - ) - - HorizonSpace(SpaceSize.SPACE_8) - - Box( - Modifier - .fillMaxWidth() - .height(100.dp) - .shimmerEffect( - true, - shape = HorizonCornerRadius.level2, - backgroundColor = HorizonColors.Surface.institution().copy(alpha = 0.1f) - ) - ) - - HorizonSpace(SpaceSize.SPACE_24) - } - } - } -} - -@Composable -@Preview -private fun DashboardCourseCardLoadingPreview() { - DashboardCourseCardLoading() -} \ No newline at end of file diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardState.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardState.kt index 75e83a51f4..a74d6120ae 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardState.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/course/card/DashboardCourseCardState.kt @@ -1,21 +1,31 @@ package com.instructure.horizon.features.dashboard.widget.course.card -import com.instructure.horizon.horizonui.molecules.StatusChipColor import com.instructure.horizon.model.LearningObjectType import java.util.Date data class DashboardCourseCardState( - val chipState: DashboardCourseCardChipState? = null, val parentPrograms: List? = null, val imageState: DashboardCourseCardImageState? = null, val title: String? = null, val description: String? = null, val progress: Double? = null, val moduleItem: DashboardCourseCardModuleItemState? = null, - val buttonState: DashboardCourseCardButtonState? = null, val onClickAction: CardClickAction? = null, - val lastAccessed: Date? = null, -) +) { + companion object { + val Loading = DashboardCourseCardState( + parentPrograms = listOf(DashboardCourseCardParentProgramState("Loading Program", "1", CardClickAction.Action {})), + imageState = DashboardCourseCardImageState(imageUrl = "url"), + title = "Loading Course Title", + progress = 20.0, + moduleItem = DashboardCourseCardModuleItemState( + moduleItemTitle = "Loading Module Item", + moduleItemType = LearningObjectType.PAGE, + onClickAction = CardClickAction.Action({}) + ) + ) + } +} data class DashboardCourseCardParentProgramState( val programName: String, @@ -31,18 +41,6 @@ data class DashboardCourseCardModuleItemState( val onClickAction: CardClickAction, ) -data class DashboardCourseCardButtonState( - val label: String, - val onClickAction: CardClickAction, - val isLoading: Boolean = false, - val action: suspend () -> Unit = { }, -) - -data class DashboardCourseCardChipState( - val label: String, - val color: StatusChipColor, -) - data class DashboardCourseCardImageState( val imageUrl: String? = null, val showPlaceholder: Boolean = false, diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/myprogress/DashboardMyProgressWidget.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/myprogress/DashboardMyProgressWidget.kt index 6ac669b2e5..b823bcb1ef 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/myprogress/DashboardMyProgressWidget.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/myprogress/DashboardMyProgressWidget.kt @@ -16,13 +16,10 @@ */ package com.instructure.horizon.features.dashboard.widget.myprogress -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel @@ -30,6 +27,7 @@ import com.instructure.horizon.R import com.instructure.horizon.features.dashboard.DashboardItemState import com.instructure.horizon.features.dashboard.widget.DashboardWidgetCardError import com.instructure.horizon.features.dashboard.widget.myprogress.card.DashboardMyProgressCardContent +import com.instructure.horizon.features.dashboard.widget.myprogress.card.DashboardMyProgressCardState import com.instructure.horizon.horizonui.foundation.HorizonColors import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -62,16 +60,11 @@ fun DashboardMyProgressSection( ) { when (state.state) { DashboardItemState.LOADING -> { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxWidth() - ) { - DashboardMyProgressCardContent( - state.cardState, - true, - modifier - ) - } + DashboardMyProgressCardContent( + DashboardMyProgressCardState.Loading, + true, + modifier + ) } DashboardItemState.ERROR -> { DashboardWidgetCardError( @@ -84,16 +77,11 @@ fun DashboardMyProgressSection( ) } DashboardItemState.SUCCESS -> { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxWidth(), - ) { - DashboardMyProgressCardContent( - state.cardState, - false, - modifier - ) - } + DashboardMyProgressCardContent( + state.cardState, + false, + modifier + ) } } } diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/myprogress/card/DashboardMyProgressCardState.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/myprogress/card/DashboardMyProgressCardState.kt index 892724666b..1c4b53ba0a 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/myprogress/card/DashboardMyProgressCardState.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/myprogress/card/DashboardMyProgressCardState.kt @@ -18,4 +18,8 @@ package com.instructure.horizon.features.dashboard.widget.myprogress.card data class DashboardMyProgressCardState( val moduleCountCompleted: Int = 0 -) +) { + companion object { + val Loading = DashboardMyProgressCardState(5) + } +} diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/DashboardSkillHighlightsWidget.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/DashboardSkillHighlightsWidget.kt index f4ba223bee..cd3d4d0cb8 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/DashboardSkillHighlightsWidget.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/DashboardSkillHighlightsWidget.kt @@ -30,6 +30,7 @@ import com.instructure.horizon.R import com.instructure.horizon.features.dashboard.DashboardItemState import com.instructure.horizon.features.dashboard.widget.DashboardWidgetCardError import com.instructure.horizon.features.dashboard.widget.skillhighlights.card.DashboardSkillHighlightsCardContent +import com.instructure.horizon.features.dashboard.widget.skillhighlights.card.DashboardSkillHighlightsCardState import com.instructure.horizon.horizonui.foundation.HorizonColors import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -65,7 +66,7 @@ fun DashboardSkillHighlightsSection( when (state.state) { DashboardItemState.LOADING -> { DashboardSkillHighlightsCardContent( - state.cardState, + DashboardSkillHighlightsCardState.Loading, homeNavController, true, modifier.padding(horizontal = 24.dp), diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/card/DashboardSkillHighlightsCardContent.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/card/DashboardSkillHighlightsCardContent.kt index 1a74cf7c34..f3844d12d0 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/card/DashboardSkillHighlightsCardContent.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/card/DashboardSkillHighlightsCardContent.kt @@ -19,12 +19,15 @@ package com.instructure.horizon.features.dashboard.widget.skillhighlights.card import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext @@ -44,6 +47,7 @@ import com.instructure.horizon.horizonui.foundation.HorizonColors import com.instructure.horizon.horizonui.foundation.HorizonSpace import com.instructure.horizon.horizonui.foundation.HorizonTypography import com.instructure.horizon.horizonui.foundation.SpaceSize +import com.instructure.horizon.horizonui.isWideLayout import com.instructure.horizon.horizonui.molecules.Pill import com.instructure.horizon.horizonui.molecules.PillCase import com.instructure.horizon.horizonui.molecules.PillSize @@ -84,20 +88,47 @@ fun DashboardSkillHighlightsCardContent( } } else { HorizonSpace(SpaceSize.SPACE_8) - Column( - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - state.skills.forEach { skill -> - SkillCard( - skill, - skill.proficiencyLevel.opacity(), - homeNavController, - modifier = Modifier.shimmerEffect( - isLoading, - backgroundColor = HorizonColors.PrimitivesGreen.green12().copy(alpha = 0.8f), - shimmerColor = HorizonColors.PrimitivesGreen.green12().copy(alpha = 0.5f) - ) - ) + + BoxWithConstraints { + if (this.isWideLayout) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + state.skills.forEach { skill -> + SkillCard( + skill, + skill.proficiencyLevel.opacity(), + homeNavController, + modifier = Modifier + .weight(1f) + .shimmerEffect( + isLoading, + backgroundColor = HorizonColors.PrimitivesGreen.green12().copy(alpha = 0.8f), + shimmerColor = HorizonColors.PrimitivesGreen.green12().copy(alpha = 0.5f) + ) + ) + } + } + } else { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + state.skills.forEach { skill -> + SkillCard( + skill, + skill.proficiencyLevel.opacity(), + homeNavController, + modifier = Modifier + .fillMaxWidth() + .shimmerEffect( + isLoading, + backgroundColor = HorizonColors.PrimitivesGreen.green12().copy(alpha = 0.8f), + shimmerColor = HorizonColors.PrimitivesGreen.green12().copy(alpha = 0.5f) + ) + ) + } + } } } } diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/card/DashboardSkillHighlightsCardState.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/card/DashboardSkillHighlightsCardState.kt index be8da4680e..0d70e27ac6 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/card/DashboardSkillHighlightsCardState.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skillhighlights/card/DashboardSkillHighlightsCardState.kt @@ -21,12 +21,25 @@ import com.instructure.horizon.R data class DashboardSkillHighlightsCardState( val skills: List = emptyList() -) +) { + companion object { + val Loading = DashboardSkillHighlightsCardState( + skills = List(3) { SkillHighlight.Loading } + ) + } +} data class SkillHighlight( val name: String, val proficiencyLevel: SkillHighlightProficiencyLevel -) +) { + companion object { + val Loading = SkillHighlight( + name = "Lorem Ipsum Dolor", + proficiencyLevel = SkillHighlightProficiencyLevel.BEGINNER + ) + } +} enum class SkillHighlightProficiencyLevel( @StringRes val skillProficiencyLevelRes: Int, diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skilloverview/DashboardSkillOverviewWidget.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skilloverview/DashboardSkillOverviewWidget.kt index 8b46c1d732..2e21839cb3 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skilloverview/DashboardSkillOverviewWidget.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skilloverview/DashboardSkillOverviewWidget.kt @@ -16,6 +16,7 @@ */ package com.instructure.horizon.features.dashboard.widget.skilloverview +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -28,6 +29,7 @@ import com.instructure.horizon.R import com.instructure.horizon.features.dashboard.DashboardItemState import com.instructure.horizon.features.dashboard.widget.DashboardWidgetCardError import com.instructure.horizon.features.dashboard.widget.skilloverview.card.DashboardSkillOverviewCardContent +import com.instructure.horizon.features.dashboard.widget.skilloverview.card.DashboardSkillOverviewCardState import com.instructure.horizon.horizonui.foundation.HorizonColors import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -63,7 +65,7 @@ fun DashboardSkillOverviewSection( when (state.state) { DashboardItemState.LOADING -> { DashboardSkillOverviewCardContent( - state.cardState, + DashboardSkillOverviewCardState.Loading, homeNavController, true, modifier diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skilloverview/card/DashboardSkillOverviewCardState.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skilloverview/card/DashboardSkillOverviewCardState.kt index 8fabe83d8b..4fc816becc 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skilloverview/card/DashboardSkillOverviewCardState.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/skilloverview/card/DashboardSkillOverviewCardState.kt @@ -18,4 +18,8 @@ package com.instructure.horizon.features.dashboard.widget.skilloverview.card data class DashboardSkillOverviewCardState( val completedSkillCount: Int = 0 -) +) { + companion object { + val Loading = DashboardSkillOverviewCardState(5) + } +} diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/DashboardTimeSpentWidget.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/DashboardTimeSpentWidget.kt index 7d15c923fe..00b3b3b662 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/DashboardTimeSpentWidget.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/DashboardTimeSpentWidget.kt @@ -16,13 +16,10 @@ */ package com.instructure.horizon.features.dashboard.widget.timespent -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel @@ -30,6 +27,7 @@ import com.instructure.horizon.R import com.instructure.horizon.features.dashboard.DashboardItemState import com.instructure.horizon.features.dashboard.widget.DashboardWidgetCardError import com.instructure.horizon.features.dashboard.widget.timespent.card.DashboardTimeSpentCardContent +import com.instructure.horizon.features.dashboard.widget.timespent.card.DashboardTimeSpentCardState import com.instructure.horizon.horizonui.foundation.HorizonColors import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -62,7 +60,11 @@ fun DashboardTimeSpentSection( ) { when (state.state) { DashboardItemState.LOADING -> { - DashboardTimeSpentCardContent(state.cardState, true, modifier) + DashboardTimeSpentCardContent( + DashboardTimeSpentCardState.Loading, + true, + modifier + ) } DashboardItemState.ERROR -> { DashboardWidgetCardError( @@ -75,12 +77,11 @@ fun DashboardTimeSpentSection( ) } DashboardItemState.SUCCESS -> { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxWidth() - ) { - DashboardTimeSpentCardContent(state.cardState, false, modifier) - } + DashboardTimeSpentCardContent( + state.cardState, + false, + modifier + ) } } } diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/card/DashboardTimeSpentCardContent.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/card/DashboardTimeSpentCardContent.kt index 41c2e4a935..89cd0a7d58 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/card/DashboardTimeSpentCardContent.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/card/DashboardTimeSpentCardContent.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -133,6 +134,7 @@ fun DashboardTimeSpentCardContent( modifier = Modifier .shimmerEffect(isLoading) .focusable() + .widthIn(min = 100.dp) ) } } diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/card/DashboardTimeSpentCardState.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/card/DashboardTimeSpentCardState.kt index 198c52b23d..e1d0b4dd04 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/card/DashboardTimeSpentCardState.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/dashboard/widget/timespent/card/DashboardTimeSpentCardState.kt @@ -22,7 +22,11 @@ data class DashboardTimeSpentCardState( val courses: List = emptyList(), val selectedCourseId: Long? = null, val onCourseSelected: (String?) -> Unit = {} -) +) { + companion object { + val Loading = DashboardTimeSpentCardState(hours = 5, minutes = 30,) + } +} data class CourseOption( val id: Long, diff --git a/libs/horizon/src/main/java/com/instructure/horizon/horizonui/Extensions.kt b/libs/horizon/src/main/java/com/instructure/horizon/horizonui/Extensions.kt index dea6e9850e..fbf7c96c04 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/horizonui/Extensions.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/horizonui/Extensions.kt @@ -16,11 +16,13 @@ package com.instructure.horizon.horizonui import android.content.Context +import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.ui.semantics.LiveRegionMode import androidx.compose.ui.semantics.SemanticsPropertyReceiver import androidx.compose.ui.semantics.liveRegion import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.unit.dp import com.instructure.horizon.R fun SemanticsPropertyReceiver.expandable(context: Context, expanded: Boolean) { @@ -40,4 +42,7 @@ fun SemanticsPropertyReceiver.selectable(context: Context, selected: Boolean) { stateDescription = if (selected) selectedStateDesc else unselectedStateDesc liveRegion = LiveRegionMode.Assertive -} \ No newline at end of file +} + +val BoxWithConstraintsScope.isWideLayout + get() = this.maxWidth >= 400.dp \ No newline at end of file From 01d9af037d7cd1bf987ed52c63feb73220714458 Mon Sep 17 00:00:00 2001 From: Kristof Nemere <109959688+kristofnemere@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:44:26 +0100 Subject: [PATCH 12/58] [MBL-17287][Teacher] Add dashboard card reordering functionality refs: MBL-17287 affects: Teacher release note: Teachers can now reorder their dashboard cards by dragging them. --- .../teacher/activities/InitActivity.kt | 3 + .../factory/DashboardPresenterFactory.kt | 9 +- .../teacher/fragments/DashboardFragment.kt | 111 +++++++++++++++++- .../teacher/presenters/DashboardPresenter.kt | 44 ++++++- 4 files changed, 162 insertions(+), 5 deletions(-) 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 84ca934b28..9b706c2d02 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 @@ -158,6 +158,9 @@ class InitActivity : BasePresenterActivity + // Cancel any active drag on dashboard before switching tabs + (supportFragmentManager.findFragmentByTag(DashboardFragment::class.java.simpleName) as? DashboardFragment)?.cancelCardDrag() + selectedTab = when (item.itemId) { R.id.tab_courses -> { addCoursesFragment() diff --git a/apps/teacher/src/main/java/com/instructure/teacher/factory/DashboardPresenterFactory.kt b/apps/teacher/src/main/java/com/instructure/teacher/factory/DashboardPresenterFactory.kt index 9f6f773622..f44d4755ca 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/factory/DashboardPresenterFactory.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/factory/DashboardPresenterFactory.kt @@ -17,10 +17,15 @@ package com.instructure.teacher.factory +import com.instructure.canvasapi2.apis.UserAPI +import com.instructure.pandautils.utils.NetworkStateProvider import com.instructure.teacher.presenters.DashboardPresenter import com.instructure.teacher.viewinterface.CoursesView import com.instructure.pandautils.blueprint.PresenterFactory -class DashboardPresenterFactory : PresenterFactory { - override fun create() = DashboardPresenter() +class DashboardPresenterFactory( + private val userApi: UserAPI.UsersInterface, + private val networkStateProvider: NetworkStateProvider +) : PresenterFactory { + override fun create() = DashboardPresenter(userApi, networkStateProvider) } diff --git a/apps/teacher/src/main/java/com/instructure/teacher/fragments/DashboardFragment.kt b/apps/teacher/src/main/java/com/instructure/teacher/fragments/DashboardFragment.kt index b1e2c5db58..93f0a6e499 100644 --- a/apps/teacher/src/main/java/com/instructure/teacher/fragments/DashboardFragment.kt +++ b/apps/teacher/src/main/java/com/instructure/teacher/fragments/DashboardFragment.kt @@ -20,10 +20,15 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.view.MenuItem +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_CANCEL import android.view.View +import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView +import com.instructure.canvasapi2.apis.UserAPI import com.instructure.canvasapi2.models.Course import com.instructure.canvasapi2.utils.ApiPrefs import com.instructure.canvasapi2.utils.pageview.PageView @@ -33,7 +38,18 @@ import com.instructure.pandautils.binding.viewBinding import com.instructure.pandautils.features.dashboard.edit.EditDashboardFragment import com.instructure.pandautils.features.dashboard.notifications.DashboardNotificationsFragment import com.instructure.pandautils.fragments.BaseSyncFragment -import com.instructure.pandautils.utils.* +import com.instructure.pandautils.utils.Const +import com.instructure.pandautils.utils.NetworkStateProvider +import com.instructure.pandautils.utils.ThemePrefs +import com.instructure.pandautils.utils.Utils +import com.instructure.pandautils.utils.ViewStyler +import com.instructure.pandautils.utils.fadeAnimationWithAction +import com.instructure.pandautils.utils.getDrawableCompat +import com.instructure.pandautils.utils.requestAccessibilityFocus +import com.instructure.pandautils.utils.setGone +import com.instructure.pandautils.utils.setVisible +import com.instructure.pandautils.utils.setupAsBackButton +import com.instructure.pandautils.utils.toast import com.instructure.teacher.R import com.instructure.teacher.activities.InitActivity import com.instructure.teacher.adapters.CoursesAdapter @@ -49,16 +65,26 @@ import com.instructure.teacher.utils.RecyclerViewUtils import com.instructure.teacher.utils.TeacherPrefs import com.instructure.teacher.utils.setupMenu import com.instructure.teacher.viewinterface.CoursesView +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import javax.inject.Inject private const val LIST_SPAN_COUNT = 1 @PageView @ScreenView(SCREEN_VIEW_DASHBOARD) +@AndroidEntryPoint class DashboardFragment : BaseSyncFragment(), CoursesView { + @Inject + lateinit var userApi: UserAPI.UsersInterface + + @Inject + lateinit var networkStateProvider: NetworkStateProvider + private val binding by viewBinding(FragmentDashboardBinding::bind) private lateinit var mGridLayoutManager: GridLayoutManager @@ -82,7 +108,7 @@ class DashboardFragment : BaseSyncFragment(Course::class.java) { +class DashboardPresenter( + private val userApi: UserAPI.UsersInterface, + private val networkStateProvider: NetworkStateProvider +) : SyncPresenter(Course::class.java) { private var dashboardJob: Job? = null @@ -113,4 +123,36 @@ class DashboardPresenter : SyncPresenter(Course::class.java override fun areContentsTheSame(item1: Course, item2: Course): Boolean { return item1.contextId.hashCode() == item2.contextId.hashCode() } + + fun moveCourse(fromPosition: Int, toPosition: Int) { + if (fromPosition < 0 + || toPosition < 0 + || fromPosition >= data.size() + || toPosition >= data.size() + || fromPosition == toPosition + ) return + val courses = data.toList().toMutableList() + val movedCourse = courses.removeAt(fromPosition) + courses.add(toPosition, movedCourse) + data.clear() + data.addOrUpdate(courses) + } + + suspend fun saveDashboardPositions(): DataResult { + val courses = data.toList() + val positions = courses + .mapIndexed { index, course -> Pair(course.contextId, index) } + .toMap() + val dashboardPositions = DashboardPositions(positions) + + val result = userApi.updateDashboardPositions(dashboardPositions, RestParams(isForceReadFromNetwork = true)) + if (result is DataResult.Success) { + CanvasRestAdapter.clearCacheUrls("dashboard/dashboard_cards") + } + return result + } + + fun isOnline(): Boolean { + return networkStateProvider.isOnline() + } } From d6dc9fcfb72f30f4374250b4c31ea1631c7cc1b3 Mon Sep 17 00:00:00 2001 From: domonkosadam <53480952+domonkosadam@users.noreply.github.com> Date: Tue, 11 Nov 2025 08:41:34 +0100 Subject: [PATCH 13/58] [CLX-3200][Horizon] ModuleItemSequence screen notebook changes (#3369) refs: CLX-3200 affects: Horizon release note: none --- .../horizon/espresso/TestModule.kt | 5 + .../DashboardTimeSpentWidgetUiTest.kt | 14 +-- .../learn/course/note/CourseNotesScreen.kt | 3 +- .../ModuleItemSequenceScreen.kt | 35 +++---- .../ModuleItemSequenceUiState.kt | 2 - .../ModuleItemSequenceViewModel.kt | 5 - .../content/page/PageDetailsContentScreen.kt | 50 +++------- .../features/notebook/NotebookDialog.kt | 44 --------- .../features/notebook/NotebookScreen.kt | 50 ++++++---- .../features/notebook/NotebookUiState.kt | 1 + .../features/notebook/NotebookViewModel.kt | 40 ++++---- .../ComposeNotesHighlightingCanvasWebView.kt | 18 +--- .../webview/JSTextSelectionInterface.kt | 36 +------ .../navigation/NotebookDialogNavigation.kt | 93 ------------------- .../notebook/navigation/NotebookNavigation.kt | 32 ++++++- .../notebook/navigation/NotebookRoute.kt | 40 +++++++- .../horizon/horizonui/Extensions.kt | 2 +- libs/horizon/src/main/res/values/strings.xml | 4 +- .../notebook/NotebookViewModelTest.kt | 40 ++------ 19 files changed, 175 insertions(+), 339 deletions(-) delete mode 100644 libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookDialog.kt delete mode 100644 libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookDialogNavigation.kt diff --git a/libs/horizon/src/androidTest/java/com/instructure/horizon/espresso/TestModule.kt b/libs/horizon/src/androidTest/java/com/instructure/horizon/espresso/TestModule.kt index 9997f8b0a6..e55f15d48a 100644 --- a/libs/horizon/src/androidTest/java/com/instructure/horizon/espresso/TestModule.kt +++ b/libs/horizon/src/androidTest/java/com/instructure/horizon/espresso/TestModule.kt @@ -298,4 +298,9 @@ object HorizonTestModule { fun provideToDoViewModelBehavior(): com.instructure.pandautils.features.calendartodo.details.ToDoViewModelBehavior { throw NotImplementedError("This is a test module. Implementation not required.") } + + @Provides + fun provideToDoListRouter(): com.instructure.pandautils.features.todolist.ToDoListRouter { + throw NotImplementedError("This is a test module. Implementation not required.") + } } \ No newline at end of file diff --git a/libs/horizon/src/androidTest/java/com/instructure/horizon/ui/features/dashboard/widget/timespent/DashboardTimeSpentWidgetUiTest.kt b/libs/horizon/src/androidTest/java/com/instructure/horizon/ui/features/dashboard/widget/timespent/DashboardTimeSpentWidgetUiTest.kt index a1929c52d4..8c04bc3888 100644 --- a/libs/horizon/src/androidTest/java/com/instructure/horizon/ui/features/dashboard/widget/timespent/DashboardTimeSpentWidgetUiTest.kt +++ b/libs/horizon/src/androidTest/java/com/instructure/horizon/ui/features/dashboard/widget/timespent/DashboardTimeSpentWidgetUiTest.kt @@ -237,13 +237,7 @@ class DashboardTimeSpentWidgetUiTest { fun testSuccessStateWithZeroHoursZeroMinutesDisplaysEmpty() { val state = DashboardTimeSpentUiState( state = DashboardItemState.SUCCESS, - cardState = DashboardTimeSpentCardState( - hours = 0, - minutes = 0, - courses = listOf( - CourseOption(id = 1L, name = "Course 1") - ) - ) + cardState = DashboardTimeSpentCardState() ) composeTestRule.setContent { @@ -251,12 +245,12 @@ class DashboardTimeSpentWidgetUiTest { } // Verify zero hours is not displayed - composeTestRule.onNodeWithText("0").assertDoesNotExist() + composeTestRule.onNodeWithText("0", useUnmergedTree = true).assertDoesNotExist() // Verify single course text is not displayed - composeTestRule.onNodeWithText("hours in your course").assertDoesNotExist() + composeTestRule.onNodeWithText("hours", useUnmergedTree = true).assertDoesNotExist() // Verify empty state message is displayed - composeTestRule.onNodeWithText("This widget will update once data becomes available.").assertIsDisplayed() + composeTestRule.onNodeWithText("This widget will update once data becomes available.", useUnmergedTree = true).assertIsDisplayed() } } diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/learn/course/note/CourseNotesScreen.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/learn/course/note/CourseNotesScreen.kt index f58171c751..0f84853fe1 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/learn/course/note/CourseNotesScreen.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/learn/course/note/CourseNotesScreen.kt @@ -38,7 +38,8 @@ fun CourseNotesScreen( val state by viewModel.uiState.collectAsState() LaunchedEffect(courseId) { - viewModel.updateCourseId(courseId) + viewModel.updateFilters(courseId) + viewModel.updateScreenState(true, false) } Box( diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceScreen.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceScreen.kt index a526ca2f46..8095ded43e 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceScreen.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceScreen.kt @@ -43,7 +43,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -101,7 +100,7 @@ import com.instructure.horizon.features.moduleitemsequence.content.lti.ExternalT import com.instructure.horizon.features.moduleitemsequence.content.page.PageDetailsContentScreen import com.instructure.horizon.features.moduleitemsequence.content.page.PageDetailsViewModel import com.instructure.horizon.features.moduleitemsequence.progress.ProgressScreen -import com.instructure.horizon.features.notebook.NotebookBottomDialog +import com.instructure.horizon.features.notebook.navigation.NotebookRoute import com.instructure.horizon.horizonui.foundation.HorizonColors import com.instructure.horizon.horizonui.foundation.HorizonCornerRadius import com.instructure.horizon.horizonui.foundation.HorizonElevation @@ -122,14 +121,12 @@ import com.instructure.horizon.horizonui.molecules.PillType import com.instructure.horizon.horizonui.molecules.Spinner import com.instructure.horizon.horizonui.molecules.SpinnerSize import com.instructure.horizon.horizonui.platform.LoadingStateWrapper -import com.instructure.horizon.navigation.MainNavigationRoute import com.instructure.pandautils.compose.modifiers.conditional import com.instructure.pandautils.utils.Const import com.instructure.pandautils.utils.ThemePrefs import com.instructure.pandautils.utils.ViewStyler import com.instructure.pandautils.utils.getActivityOrNull import com.instructure.pandautils.utils.orDefault -import kotlinx.coroutines.launch import kotlin.math.abs @Composable @@ -153,7 +150,18 @@ fun ModuleItemSequenceScreen(mainNavController: NavHostController, uiState: Modu onPreviousClick = uiState.onPreviousClick, onAssignmentToolsClick = uiState.onAssignmentToolsClick, onAiAssistClick = { uiState.updateShowAiAssist(true) }, - onNotebookClick = { uiState.updateShowNotebook(true) }, + onNotebookClick = { + mainNavController.navigate( + NotebookRoute.Notebook.route( + uiState.courseId.toString(), + uiState.objectTypeAndId.first, + uiState.objectTypeAndId.second, + true, + false, + true + ) + ) + }, notebookEnabled = uiState.notebookButtonEnabled, aiAssistEnabled = uiState.aiAssistButtonEnabled, hasUnreadComments = uiState.hasUnreadComments @@ -166,22 +174,6 @@ fun ModuleItemSequenceScreen(mainNavController: NavHostController, uiState: Modu onDismiss = { uiState.updateShowAiAssist(false) }, ) } - if (uiState.showNotebook) { - NotebookBottomDialog( - uiState.courseId, - uiState.objectTypeAndId, - { snackbarMessage, onDismiss -> - scope.launch { - if (snackbarMessage != null) { - val result = snackbarHostState.showSnackbar(snackbarMessage) - if (result == SnackbarResult.Dismissed) { - onDismiss() - } - } - } }, - { uiState.updateShowNotebook(false) } - ) - } ModuleItemSequenceContent(uiState = uiState, mainNavController = mainNavController, onBackPressed = { mainNavController.popBackStack() }) @@ -646,7 +638,6 @@ private fun ModuleItemSequenceScreenPreview() { moduleItemContent = ModuleItemContent.Assignment(courseId = 1, assignmentId = 1L) ), updateShowAiAssist = {}, - updateShowNotebook = {}, ) ) } diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceUiState.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceUiState.kt index 0fee607201..e2fe68f661 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceUiState.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceUiState.kt @@ -36,10 +36,8 @@ data class ModuleItemSequenceUiState( val assignmentToolsOpened: () -> Unit = {}, val showAiAssist: Boolean = false, val aiAssistButtonEnabled: Boolean = false, - val showNotebook: Boolean = false, val notebookButtonEnabled: Boolean = false, val updateShowAiAssist: (Boolean) -> Unit, - val updateShowNotebook: (Boolean) -> Unit, val objectTypeAndId: Pair = Pair("", ""), val updateObjectTypeAndId: (Pair) -> Unit = {}, val hasUnreadComments: Boolean = false, diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceViewModel.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceViewModel.kt index 0afcccbe11..a9189d1e99 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceViewModel.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/ModuleItemSequenceViewModel.kt @@ -85,7 +85,6 @@ class ModuleItemSequenceViewModel @Inject constructor( onAssignmentToolsClick = ::onAssignmentToolsClicked, assignmentToolsOpened = ::assignmentToolsOpened, updateShowAiAssist = ::updateShowAiAssist, - updateShowNotebook = ::updateShowNotebook, updateObjectTypeAndId = ::updateNotebookObjectTypeAndId, updateAiAssistContext = ::updateAiAssistContext, ) @@ -550,10 +549,6 @@ class ModuleItemSequenceViewModel @Inject constructor( _uiState.update { it.copy(showAiAssist = show) } } - private fun updateShowNotebook(show: Boolean) { - _uiState.update { it.copy(showNotebook = show) } - } - private fun updateNotebookObjectTypeAndId(objectTypeAndId: Pair) { _uiState.update { it.copy( diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/content/page/PageDetailsContentScreen.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/content/page/PageDetailsContentScreen.kt index 25e20ddd92..b63a2ec51b 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/content/page/PageDetailsContentScreen.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/moduleitemsequence/content/page/PageDetailsContentScreen.kt @@ -30,9 +30,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController -import com.instructure.canvasapi2.managers.graphql.horizon.redwood.NoteHighlightedData -import com.instructure.canvasapi2.managers.graphql.horizon.redwood.NoteHighlightedDataRange -import com.instructure.canvasapi2.managers.graphql.horizon.redwood.NoteHighlightedDataTextPosition import com.instructure.horizon.features.aiassistant.common.model.AiAssistContextSource import com.instructure.horizon.features.notebook.common.webview.ComposeNotesHighlightingCanvasWebView import com.instructure.horizon.features.notebook.common.webview.NotesCallback @@ -114,40 +111,21 @@ fun PageDetailsContentScreen( ) }, onNoteAdded = { selectedText, noteType, startContainer, startOffset, endContainer, endOffset, textSelectionStart, textSelectionEnd -> - if (noteType == null) { - mainNavController.navigate( - NotebookRoute.AddNotebook( - courseId = uiState.courseId.toString(), - objectType = "Page", - objectId = uiState.pageId.toString(), - highlightedTextStartOffset = startOffset, - highlightedTextEndOffset = endOffset, - highlightedTextStartContainer = startContainer, - highlightedTextEndContainer = endContainer, - highlightedText = selectedText, - textSelectionStart = textSelectionStart, - textSelectionEnd = textSelectionEnd, - noteType = noteType - ) - ) - } else { - uiState.addNote( - NoteHighlightedData( - selectedText = selectedText, - range = NoteHighlightedDataRange( - startOffset = startOffset, - endOffset = endOffset, - startContainer = startContainer, - endContainer = endContainer - ), - textPosition = NoteHighlightedDataTextPosition( - start = textSelectionStart, - end = textSelectionEnd - ) - ), - noteType + mainNavController.navigate( + NotebookRoute.AddNotebook( + courseId = uiState.courseId.toString(), + objectType = "Page", + objectId = uiState.pageId.toString(), + highlightedTextStartOffset = startOffset, + highlightedTextEndOffset = endOffset, + highlightedTextStartContainer = startContainer, + highlightedTextEndContainer = endContainer, + highlightedText = selectedText, + textSelectionStart = textSelectionStart, + textSelectionEnd = textSelectionEnd, + noteType = noteType ) - } + ) } ) ) diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookDialog.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookDialog.kt deleted file mode 100644 index 87954dd93b..0000000000 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookDialog.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2025 - 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.horizon.features.notebook - -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import com.instructure.horizon.features.notebook.navigation.NotebookDialogNavigation -import com.instructure.horizon.horizonui.foundation.HorizonColors - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun NotebookBottomDialog( - courseId: Long, - objectFilter: Pair, - onShowSnackbar: (String?, () -> Unit) -> Unit, - onDismiss: () -> Unit -) { - val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - - ModalBottomSheet( - containerColor = HorizonColors.Surface.pagePrimary(), - onDismissRequest = { onDismiss() }, - dragHandle = null, - sheetState = bottomSheetState, - ) { - NotebookDialogNavigation(courseId, objectFilter, onDismiss, onShowSnackbar) - } -} \ No newline at end of file diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookScreen.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookScreen.kt index fdf3723dae..ae23b33e01 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookScreen.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -38,6 +39,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat import androidx.navigation.NavHostController import com.instructure.canvasapi2.managers.graphql.horizon.redwood.NoteHighlightedData import com.instructure.canvasapi2.managers.graphql.horizon.redwood.NoteHighlightedDataRange @@ -51,6 +53,7 @@ import com.instructure.horizon.features.notebook.common.composable.NotebookPill import com.instructure.horizon.features.notebook.common.composable.NotebookTypeSelect import com.instructure.horizon.features.notebook.common.model.Note import com.instructure.horizon.features.notebook.common.model.NotebookType +import com.instructure.horizon.features.notebook.navigation.NotebookRoute import com.instructure.horizon.horizonui.foundation.HorizonColors import com.instructure.horizon.horizonui.foundation.HorizonCornerRadius import com.instructure.horizon.horizonui.foundation.HorizonElevation @@ -64,6 +67,8 @@ import com.instructure.horizon.horizonui.molecules.IconButtonSize import com.instructure.horizon.horizonui.molecules.Spinner import com.instructure.horizon.navigation.MainNavigationRoute import com.instructure.pandautils.compose.modifiers.conditional +import com.instructure.pandautils.utils.ViewStyler +import com.instructure.pandautils.utils.getActivityOrNull import com.instructure.pandautils.utils.localisedFormat import java.util.Date @@ -71,9 +76,12 @@ import java.util.Date fun NotebookScreen( mainNavController: NavHostController, state: NotebookUiState, - onDismiss: (() -> Unit)? = null, - onNoteSelected: ((Note) -> Unit)? = null, ) { + val activity = LocalContext.current.getActivityOrNull() + LaunchedEffect(Unit) { + if (activity != null) ViewStyler.setStatusBarColor(activity, ContextCompat.getColor(activity, R.color.surface_pagePrimary)) + } + val scrollState = rememberLazyListState() Scaffold( containerColor = HorizonColors.Surface.pagePrimary(), @@ -87,15 +95,6 @@ fun NotebookScreen( ) } ) - } else if (onDismiss != null) { - NotebookAppBar( - onClose = { onDismiss() }, - modifier = Modifier.conditional(scrollState.canScrollBackward) { - horizonShadow( - elevation = HorizonElevation.level2, - ) - } - ) } }, ) { padding -> @@ -140,13 +139,30 @@ fun NotebookScreen( items(state.notes) { note -> Column { NoteContent(note) { - onNoteSelected?.invoke(note) ?: mainNavController.navigate( - MainNavigationRoute.ModuleItemSequence( - courseId = note.courseId, - moduleItemAssetType = note.objectType.value, - moduleItemAssetId = note.objectId, + if (state.navigateToEdit) { + mainNavController.navigate( + NotebookRoute.EditNotebook( + noteId = note.id, + highlightedTextStartOffset = note.highlightedText.range.startOffset, + highlightedTextEndOffset = note.highlightedText.range.endOffset, + highlightedTextStartContainer = note.highlightedText.range.startContainer, + highlightedTextEndContainer = note.highlightedText.range.endContainer, + textSelectionStart = note.highlightedText.textPosition.start, + textSelectionEnd = note.highlightedText.textPosition.end, + highlightedText = note.highlightedText.selectedText, + noteType = note.type.name, + userComment = note.userText + ) + ) + } else { + mainNavController.navigate( + MainNavigationRoute.ModuleItemSequence( + courseId = note.courseId, + moduleItemAssetType = note.objectType.value, + moduleItemAssetId = note.objectId, + ) ) - ) + } } if (state.notes.lastOrNull() != note) { diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookUiState.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookUiState.kt index 2fb4184155..f7a914c076 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookUiState.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookUiState.kt @@ -31,4 +31,5 @@ data class NotebookUiState( val updateContent: (Long?, Pair?) -> Unit, val showTopBar: Boolean = false, val showFilters: Boolean = false, + val navigateToEdit: Boolean = false ) \ No newline at end of file diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookViewModel.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookViewModel.kt index 262e6aacd5..046215adee 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookViewModel.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/NotebookViewModel.kt @@ -16,12 +16,14 @@ */ package com.instructure.horizon.features.notebook +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.instructure.canvasapi2.utils.weave.catch import com.instructure.canvasapi2.utils.weave.tryLaunch import com.instructure.horizon.features.notebook.common.model.NotebookType import com.instructure.horizon.features.notebook.common.model.mapToNotes +import com.instructure.horizon.features.notebook.navigation.NotebookRoute import com.instructure.redwood.QueryNotesQuery import com.instructure.redwood.type.OrderDirection import dagger.hilt.android.lifecycle.HiltViewModel @@ -33,24 +35,30 @@ import javax.inject.Inject @HiltViewModel class NotebookViewModel @Inject constructor( private val repository: NotebookRepository, + savedStateHandle: SavedStateHandle, ): ViewModel() { private var cursorId: String? = null private var pageInfo: QueryNotesQuery.PageInfo? = null - private var courseId: Long? = null - private var objectTypeAndId: Pair? = null + private var courseId: Long? = savedStateHandle.get(NotebookRoute.Notebook.COURSE_ID)?.toLongOrNull() + private var objectTypeAndId: Pair? = getObjectTypeAndId(savedStateHandle) + private var showTopBar: Boolean = savedStateHandle.get(NotebookRoute.Notebook.SHOW_TOP_BAR) ?: false + private var showFilters: Boolean = savedStateHandle.get(NotebookRoute.Notebook.SHOW_FILTERS) ?: false + private var navigateToEdit: Boolean = savedStateHandle.get(NotebookRoute.Notebook.NAVIGATE_TO_EDIT) ?: false private val _uiState = MutableStateFlow(NotebookUiState( loadPreviousPage = ::getPreviousPage, loadNextPage = ::getNextPage, onFilterSelected = ::onFilterSelected, - updateContent = ::updateContent + updateContent = ::updateContent, + showTopBar = showTopBar, + showFilters = showFilters, + navigateToEdit = navigateToEdit, )) val uiState = _uiState.asStateFlow() init { loadData() - updateScreenState() } private fun loadData( @@ -117,27 +125,23 @@ class NotebookViewModel @Inject constructor( this.objectTypeAndId = objectTypeAndId loadData() } - updateScreenState() } - fun updateCourseId(courseId: Long?) { + fun updateFilters(courseId: Long? = null, objectTypeAndId: Pair? = null) { if (courseId != this.courseId) { this.courseId = courseId + this.objectTypeAndId = objectTypeAndId loadData() } - updateScreenState() } - private fun updateScreenState() { - if (courseId != null) { - _uiState.update { it.copy(showTopBar = false) } - if (objectTypeAndId != null) { - _uiState.update { it.copy(showFilters = false) } - } else { - _uiState.update { it.copy(showFilters = true) } - } - } else { - _uiState.update { it.copy(showTopBar = true, showFilters = true) } - } + fun updateScreenState(showFilters: Boolean = false, showTopBar: Boolean = false) { + _uiState.update { it.copy(showTopBar = showTopBar, showFilters = showFilters) } + } + + private fun getObjectTypeAndId(savedStateHandle: SavedStateHandle): Pair? { + val objectType = savedStateHandle.get(NotebookRoute.Notebook.OBJECT_TYPE) ?: return null + val objectId = savedStateHandle.get(NotebookRoute.Notebook.OBJECT_ID) ?: return null + return Pair(objectType, objectId) } } \ No newline at end of file diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/common/webview/ComposeNotesHighlightingCanvasWebView.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/common/webview/ComposeNotesHighlightingCanvasWebView.kt index 1acc60c200..8d9da9ca46 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/common/webview/ComposeNotesHighlightingCanvasWebView.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/common/webview/ComposeNotesHighlightingCanvasWebView.kt @@ -121,7 +121,7 @@ fun ComposeNotesHighlightingCanvasWebView( ) if (notes.none { intersects(it.highlightedText.textPosition.start to it.highlightedText.textPosition.end, selectedTextStart to selectedTextEnd) }){ add( - ActionMenuItem(2, context.getString(R.string.notesActionMenuAddImportantNote)) { + ActionMenuItem(2, context.getString(R.string.notesActionMenuMarkImportantNote)) { notesCallback.onNoteAdded( selectedText, NotebookType.Important.name, @@ -135,7 +135,7 @@ fun ComposeNotesHighlightingCanvasWebView( } ) add( - ActionMenuItem(3, context.getString(R.string.notesActionMenuAddConfusingNote)) { + ActionMenuItem(3, context.getString(R.string.notesActionMenuMarkConfusingNote)) { notesCallback.onNoteAdded( selectedText, NotebookType.Confusing.name, @@ -148,20 +148,6 @@ fun ComposeNotesHighlightingCanvasWebView( ) } ) - add( - ActionMenuItem(4, context.getString(R.string.notesActionMenuAddNote)) { - notesCallback.onNoteAdded( - selectedText, - null, - selectedTextRangeStartContainer, - selectedTextRangeStartOffset, - selectedTextRangeEndContainer, - selectedTextRangeEndOffset, - selectedTextStart, - selectedTextEnd - ) - } - ) } } } diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/common/webview/JSTextSelectionInterface.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/common/webview/JSTextSelectionInterface.kt index 03e51f8867..6dd7d6dc9e 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/common/webview/JSTextSelectionInterface.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/common/webview/JSTextSelectionInterface.kt @@ -89,8 +89,6 @@ class JSTextSelectionInterface( } companion object { - private const val flagBase64Source = "url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTkiIHZpZXdCb3g9IjAgMCAxNiAxOSIgZmlsbD0iI0ZGRkZGRiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMiA5Ljc2OTI1VjE3Ljc1QzIgMTcuOTYyNSAxLjkyODA4IDE4LjE0MDYgMS43ODQyNSAxOC4yODQzQzEuNjQwNDIgMTguNDI4MSAxLjQ2MjI1IDE4LjUgMS4yNDk3NSAxOC41QzEuMDM3MDggMTguNSAwLjg1OSAxOC40MjgxIDAuNzE1NSAxOC4yODQzQzAuNTcxODMzIDE4LjE0MDYgMC41IDE3Ljk2MjUgMC41IDE3Ljc1VjEuNDA0QzAuNSAxLjE0NzgzIDAuNTg2NjY3IDAuOTMzMTY3IDAuNzYgMC43NjAwMDFDMC45MzMxNjcgMC41ODY2NjcgMS4xNDc4MyAwLjUgMS40MDQgMC41SDE0LjExMTVDMTQuMjc1NyAwLjUgMTQuNDIzNSAwLjUzOCAxNC41NTUgMC42MTRDMTQuNjg2NyAwLjY4OTgzMyAxNC43OTEyIDAuNzg4NTAxIDE0Ljg2ODUgMC45MTAwMDFDMTQuOTQ1OCAxLjAzMTUgMTQuOTkzOCAxLjE2NjY3IDE1LjAxMjUgMS4zMTU1QzE1LjAzMSAxLjQ2NDE3IDE1LjAwNjEgMS42MTU4MyAxNC45Mzc4IDEuNzcwNUwxMy40NTIgNS4xMzQ3NUwxNC45Mzc4IDguNDk4NzVDMTUuMDA2MSA4LjY1MzQyIDE1LjAzMSA4LjgwNTA4IDE1LjAxMjUgOC45NTM3NUMxNC45OTM4IDkuMTAyNTggMTQuOTQ1OCA5LjIzNzc1IDE0Ljg2ODUgOS4zNTkyNUMxNC43OTEyIDkuNDgwNzUgMTQuNjg2NyA5LjU3OTQyIDE0LjU1NSA5LjY1NTI1QzE0LjQyMzUgOS43MzEyNSAxNC4yNzU3IDkuNzY5MjUgMTQuMTExNSA5Ljc2OTI1SDJaTTIgOC4yNjkyNUgxMy4yMzI3TDEyLjE1IDUuODY1NUMxMi4wNDM3IDUuNjM4IDExLjk5MDUgNS4zOTQyNSAxMS45OTA1IDUuMTM0MjVDMTEuOTkwNSA0Ljg3NDI1IDEyLjA0MzcgNC42MzA3NSAxMi4xNSA0LjQwMzc1TDEzLjIzMjcgMkgyVjguMjY5MjVaIiBmaWxsPSIjRkZGRkZGIi8+PC9zdmc+Cg==)" - private const val questionBase64Source = "url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI0ZGRkZGRiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTQuOTU1OCA4LjAxNzE5QzE0Ljk1NTggNy4xOTE1MyAxNC42Nzk5IDYuNTI4MzYgMTQuMTI4IDYuMDI3NjlDMTMuNTc2IDUuNTI3MTkgMTIuODQ3NSA1LjI3Njk0IDExLjk0MjMgNS4yNzY5NEMxMS4zNjkzIDUuMjc2OTQgMTAuODYxMyA1LjM5Mzg2IDEwLjQxODMgNS42Mjc2OUM5Ljk3NTI5IDUuODYxNjkgOS41OTI4OCA2LjIxMzk0IDkuMjcxMDQgNi42ODQ0NEM5LjA4MTM4IDYuOTUyNDQgOC44Mjc4OCA3LjExMDc4IDguNTEwNTQgNy4xNTk0NEM4LjE5MzM4IDcuMjA4MjggNy45MTYxMiA3LjEzMDc4IDcuNjc4NzkgNi45MjY5NEM3LjUwMzI5IDYuNzc0MjggNy40MDMwNCA2LjU4Mjk0IDcuMzc4MDQgNi4zNTI5NEM3LjM1MzA0IDYuMTIyNzggNy40MDY1NCA1LjkwNDQ0IDcuNTM4NTQgNS42OTc5NEM4LjA0NjIxIDQuOTMwMTEgOC42NzQxMyA0LjM0NjE5IDkuNDIyMjkgMy45NDYxOUMxMC4xNzAzIDMuNTQ2MTkgMTEuMDEwMyAzLjM0NjE5IDExLjk0MjMgMy4zNDYxOUMxMy40MzA4IDMuMzQ2MTkgMTQuNjQyNCAzLjc3MjQ0IDE1LjU3NyA0LjYyNDk0QzE2LjUxMTUgNS40Nzc0NCAxNi45Nzg4IDYuNTg3NjkgMTYuOTc4OCA3Ljk1NTY5QzE2Ljk3ODggOC42ODAwMyAxNi44MjM3IDkuMzQzNzggMTYuNTEzNSA5Ljk0Njk0QzE2LjIwMzIgMTAuNTUwMyAxNS42NzQ0IDExLjE5OTQgMTQuOTI3IDExLjg5NDJDMTQuMjI3IDEyLjUyODkgMTMuNzQ5NSAxMy4wNDM5IDEzLjQ5NDMgMTMuNDM5NEMxMy4yMzkxIDEzLjgzNDkgMTMuMDkxIDE0LjI3OTQgMTMuMDUgMTQuNzcyOUMxMy4wMDkgMTUuMDU3NiAxMi44OTExIDE1LjI5NDggMTIuNjk2MyAxNS40ODQ0QzEyLjUwMTMgMTUuNjc0MyAxMi4yNjY2IDE1Ljc2OTIgMTEuOTkyMyAxNS43NjkyQzExLjcxOCAxNS43NjkyIDExLjQ4MzQgMTUuNjc1MyAxMS4yODg1IDE1LjQ4NzRDMTEuMDkzNyAxNS4yOTk2IDEwLjk5NjMgMTUuMDY4NSAxMC45OTYzIDE0Ljc5NDJDMTAuOTk2MyAxNC4xMjUgMTEuMTQ5MSAxMy41MTMyIDExLjQ1NDggMTIuOTU4N0MxMS43NjA2IDEyLjQwNDIgMTIuMjcyNSAxMS44MDc3IDEyLjk5MDUgMTEuMTY5MkMxMy43NTcyIDEwLjQ5NjIgMTQuMjc2NCA5LjkzNDYxIDE0LjU0OCA5LjQ4NDQ0QzE0LjgxOTkgOS4wMzQ0NCAxNC45NTU4IDguNTQ1MzYgMTQuOTU1OCA4LjAxNzE5Wk0xMS45NDIzIDIxLjQ5OTlDMTEuNTMzMyAyMS40OTk5IDExLjE4MSAyMS4zNTIyIDEwLjg4NTUgMjEuMDU2N0MxMC41OSAyMC43NjEyIDEwLjQ0MjMgMjAuNDA4OSAxMC40NDIzIDE5Ljk5OTlDMTAuNDQyMyAxOS41OTA5IDEwLjU5IDE5LjIzODcgMTAuODg1NSAxOC45NDMyQzExLjE4MSAxOC42NDc3IDExLjUzMzMgMTguNDk5OSAxMS45NDIzIDE4LjQ5OTlDMTIuMzUxMyAxOC40OTk5IDEyLjcwMzUgMTguNjQ3NyAxMi45OTkgMTguOTQzMkMxMy4yOTQ1IDE5LjIzODcgMTMuNDQyMyAxOS41OTA5IDEzLjQ0MjMgMTkuOTk5OUMxMy40NDIzIDIwLjQwODkgMTMuMjk0NSAyMC43NjEyIDEyLjk5OSAyMS4wNTY3QzEyLjcwMzUgMjEuMzUyMiAxMi4zNTEzIDIxLjQ5OTkgMTEuOTQyMyAyMS40OTk5WiIgZmlsbD0iI0ZGRkZGRiIvPjwvc3ZnPgo=)" private const val JS_INTERFACE_NAME = "TextSelectionInterface" private const val JS_CODE_FROM_WEB = """ let highlightCss = ` @@ -100,46 +98,14 @@ let highlightCss = ` text-decoration-color: rgba(14, 104, 179, 1); position: relative; } - .highlighted-important::before { - content: ""; - display: block; - position: absolute; - top: -6px; - left: -6px; - width: 12px; - height: 12px; - border-radius: 50%; - z-index: 10; - background-color: rgba(14, 104, 179, 1); - background-image: ${flagBase64Source}; - background-size: 60%; - background-repeat: no-repeat; - background-position: center; - } .highlighted-confusing { background-color: rgba(199, 31, 35, 0.2); text-decoration: underline; text-decoration-color: rgba(199, 31, 35, 1); - + text-decoration-style: dashed; position: relative; } - .highlighted-confusing::before { - content: ""; - display: block; - position: absolute; - top: -6px; - left: -6px; - width: 12px; - height: 12px; - border-radius: 50%; - z-index: 10; - background-color: rgba(199, 31, 35, 1); - background-image: ${questionBase64Source}; - background-size: 80%; - background-repeat: no-repeat; - background-position: center; - } ` const styleSheet = document.createElement("style"); styleSheet.innerText = highlightCss; diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookDialogNavigation.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookDialogNavigation.kt deleted file mode 100644 index aeddc7d74a..0000000000 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookDialogNavigation.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2025 - 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.horizon.features.notebook.navigation - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import com.instructure.horizon.features.notebook.NotebookScreen -import com.instructure.horizon.features.notebook.NotebookViewModel -import com.instructure.horizon.features.notebook.addedit.AddEditNoteScreen -import com.instructure.horizon.features.notebook.addedit.add.AddNoteViewModel -import com.instructure.horizon.features.notebook.addedit.edit.EditNoteViewModel -import com.instructure.horizon.horizonui.animation.enterTransition -import com.instructure.horizon.horizonui.animation.exitTransition -import com.instructure.horizon.horizonui.animation.popEnterTransition -import com.instructure.horizon.horizonui.animation.popExitTransition - -@Composable -fun NotebookDialogNavigation( - courseId: Long, - objectFilter: Pair, - onDismiss: () -> Unit, - onShowSnackbar: (String?, () -> Unit) -> Unit, -) { - val notebookDialogNavController = rememberNavController() - NavHost( - navController = notebookDialogNavController, - startDestination = NotebookRoute.Notebook.route, - enterTransition = { enterTransition }, - exitTransition = { exitTransition }, - popEnterTransition = { popEnterTransition }, - popExitTransition = { popExitTransition }, - ) { - composable(NotebookRoute.Notebook.route) { - val viewModel = hiltViewModel() - val state by viewModel.uiState.collectAsState() - - LaunchedEffect(courseId, objectFilter) { - state.updateContent(courseId, objectFilter) - } - NotebookScreen( - mainNavController = notebookDialogNavController, - state = state, - onDismiss = { onDismiss() }, - onNoteSelected = { note -> - notebookDialogNavController.navigate( - NotebookRoute.EditNotebook( - noteId = note.id, - highlightedTextStartOffset = note.highlightedText.range.startOffset, - highlightedTextEndOffset = note.highlightedText.range.endOffset, - highlightedTextStartContainer = note.highlightedText.range.startContainer, - highlightedTextEndContainer = note.highlightedText.range.endContainer, - textSelectionStart = note.highlightedText.textPosition.start, - textSelectionEnd = note.highlightedText.textPosition.end, - highlightedText = note.highlightedText.selectedText, - noteType = note.type.name, - userComment = note.userText - ) - ) - } - ) - } - composable { - val viewModel = hiltViewModel() - val uiState by viewModel.uiState.collectAsState() - AddEditNoteScreen(notebookDialogNavController, uiState, onShowSnackbar) - } - composable { - val viewModel = hiltViewModel() - val uiState by viewModel.uiState.collectAsState() - AddEditNoteScreen(notebookDialogNavController, uiState, onShowSnackbar) - } - } -} \ No newline at end of file diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookNavigation.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookNavigation.kt index 651b98b12e..b4d9cc9717 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookNavigation.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookNavigation.kt @@ -21,8 +21,10 @@ import androidx.compose.runtime.getValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController +import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.compose.navigation +import androidx.navigation.navArgument import com.instructure.horizon.features.notebook.NotebookScreen import com.instructure.horizon.features.notebook.NotebookViewModel import com.instructure.horizon.features.notebook.addedit.AddEditNoteScreen @@ -46,7 +48,35 @@ fun NavGraphBuilder.notebookNavigation( popEnterTransition = { popEnterTransition }, popExitTransition = { popExitTransition }, ) { - composable(NotebookRoute.Notebook.route) { + composable( + route = NotebookRoute.Notebook.route, + arguments = listOf( + navArgument(NotebookRoute.Notebook.COURSE_ID) { + type = NavType.StringType + nullable = true + }, + navArgument(NotebookRoute.Notebook.OBJECT_TYPE) { + type = NavType.StringType + nullable = true + }, + navArgument(NotebookRoute.Notebook.OBJECT_ID) { + type = NavType.StringType + nullable = true + }, + navArgument(NotebookRoute.Notebook.SHOW_TOP_BAR) { + type = NavType.BoolType + defaultValue = true + }, + navArgument(NotebookRoute.Notebook.SHOW_FILTERS) { + type = NavType.BoolType + defaultValue = true + }, + navArgument(NotebookRoute.Notebook.NAVIGATE_TO_EDIT) { + type = NavType.BoolType + defaultValue = false + } + ) + ) { val viewModel = hiltViewModel() val uiState by viewModel.uiState.collectAsState() NotebookScreen(navController, uiState) diff --git a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookRoute.kt b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookRoute.kt index c81eafc50e..9a672b9092 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookRoute.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/features/notebook/navigation/NotebookRoute.kt @@ -19,8 +19,44 @@ package com.instructure.horizon.features.notebook.navigation import kotlinx.serialization.Serializable @Serializable -sealed class NotebookRoute(val route: String) { - data object Notebook : NotebookRoute("notebook_list") +sealed class NotebookRoute(open val route: String) { + data class Notebook( + val courseId: String? = null, + val objectType: String? = null, + val objectId: String? = null, + val showTopBar: Boolean? = null, + val navigateToEdit: Boolean? = null + ) : NotebookRoute(route) { + companion object { + private const val ROUTE = "notebook_list" + const val COURSE_ID = "courseId" + const val OBJECT_TYPE = "objectType" + const val OBJECT_ID = "objectId" + const val SHOW_TOP_BAR = "showTopBar" + const val SHOW_FILTERS = "showFilters" + const val NAVIGATE_TO_EDIT = "navigateToEdit" + const val route = "$ROUTE?$COURSE_ID={$COURSE_ID}&$OBJECT_TYPE={$OBJECT_TYPE}&$OBJECT_ID={$OBJECT_ID}&$SHOW_TOP_BAR={$SHOW_TOP_BAR}&$SHOW_FILTERS={$SHOW_FILTERS}&$NAVIGATE_TO_EDIT={$NAVIGATE_TO_EDIT}" + + fun route( + courseId: String? = null, + objectType: String? = null, + objectId: String? = null, + showTopBar: Boolean? = null, + showFilters: Boolean? = null, + navigateToEdit: Boolean? = null + ): String { + val params = buildList { + courseId?.let { add("$COURSE_ID=$it") } + objectType?.let { add("$OBJECT_TYPE=$it") } + objectId?.let { add("$OBJECT_ID=$it") } + showTopBar?.let { add("$SHOW_TOP_BAR=$it") } + showFilters?.let { add("$SHOW_FILTERS=$it") } + navigateToEdit?.let { add("$NAVIGATE_TO_EDIT=$it") } + } + return if (params.isNotEmpty()) "$ROUTE?${params.joinToString("&")}" else ROUTE + } + } + } @Serializable data class AddNotebook( diff --git a/libs/horizon/src/main/java/com/instructure/horizon/horizonui/Extensions.kt b/libs/horizon/src/main/java/com/instructure/horizon/horizonui/Extensions.kt index fbf7c96c04..2c3c47e158 100644 --- a/libs/horizon/src/main/java/com/instructure/horizon/horizonui/Extensions.kt +++ b/libs/horizon/src/main/java/com/instructure/horizon/horizonui/Extensions.kt @@ -45,4 +45,4 @@ fun SemanticsPropertyReceiver.selectable(context: Context, selected: Boolean) { } val BoxWithConstraintsScope.isWideLayout - get() = this.maxWidth >= 400.dp \ No newline at end of file + get() = this.maxWidth >= 500.dp \ No newline at end of file diff --git a/libs/horizon/src/main/res/values/strings.xml b/libs/horizon/src/main/res/values/strings.xml index 3e1308d2e4..d947e10fd1 100644 --- a/libs/horizon/src/main/res/values/strings.xml +++ b/libs/horizon/src/main/res/values/strings.xml @@ -275,8 +275,8 @@ No messages yet. Copy Add note - Important - Confusing + Mark important + Mark confusing Score: %1$s/%2$s Attempts This assignment allows multiple attempts. Once you\'ve made a submission, you can view it here. diff --git a/libs/horizon/src/test/java/com/instructure/horizon/features/notebook/NotebookViewModelTest.kt b/libs/horizon/src/test/java/com/instructure/horizon/features/notebook/NotebookViewModelTest.kt index 2a2ab3ced0..d58ae47e90 100644 --- a/libs/horizon/src/test/java/com/instructure/horizon/features/notebook/NotebookViewModelTest.kt +++ b/libs/horizon/src/test/java/com/instructure/horizon/features/notebook/NotebookViewModelTest.kt @@ -16,6 +16,7 @@ */ package com.instructure.horizon.features.notebook +import androidx.lifecycle.SavedStateHandle import com.instructure.horizon.features.notebook.common.model.NotebookType import com.instructure.redwood.QueryNotesQuery import io.mockk.coEvery @@ -39,6 +40,7 @@ import java.util.Date @OptIn(ExperimentalCoroutinesApi::class) class NotebookViewModelTest { private val repository: NotebookRepository = mockk(relaxed = true) + private val savedStateHandle = SavedStateHandle() private val testDispatcher = UnconfinedTestDispatcher() private val testNotes = QueryNotesQuery.Notes( @@ -159,44 +161,14 @@ class NotebookViewModelTest { fun `Test update course id reloads data`() = runTest { val viewModel = getViewModel() - viewModel.updateCourseId(123L) - viewModel.updateCourseId(1234L) - viewModel.updateCourseId(123L) + viewModel.updateFilters(123L) + viewModel.updateFilters(1234L) + viewModel.updateFilters(123L) coVerify(exactly = 2) { repository.getNotes(any(), any(), any(), any(), 123L, any(), any()) } } - @Test - fun `Test update content with course id hides top bar`() = runTest { - val viewModel = getViewModel() - - viewModel.uiState.value.updateContent(123L, null) - - assertFalse(viewModel.uiState.value.showTopBar) - assertTrue(viewModel.uiState.value.showFilters) - } - - @Test - fun `Test update content with object type hides filters`() = runTest { - val viewModel = getViewModel() - - viewModel.uiState.value.updateContent(123L, Pair("Assignment", "456")) - - assertFalse(viewModel.uiState.value.showTopBar) - assertFalse(viewModel.uiState.value.showFilters) - } - - @Test - fun `Test update content without course id shows top bar and filters`() = runTest { - val viewModel = getViewModel() - - viewModel.uiState.value.updateContent(null, null) - - assertTrue(viewModel.uiState.value.showTopBar) - assertTrue(viewModel.uiState.value.showFilters) - } - private fun getViewModel(): NotebookViewModel { - return NotebookViewModel(repository) + return NotebookViewModel(repository, savedStateHandle) } } From 0e92d24772e397a79be0e3cc35ac29639a70e5c7 Mon Sep 17 00:00:00 2001 From: Akos Hermann <72087159+hermannakos@users.noreply.github.com> Date: Tue, 11 Nov 2025 11:16:20 +0100 Subject: [PATCH 14/58] [MBL-19452][Student] Dashboard Redesign Infrastructure (#3372) Test plan: - Unit tests pass (DashboardViewModelTest - 5 tests) - Instrumentation tests pass (DashboardScreenTest - 4 tests) - Feature flag toggles between old and new dashboard - Pull-to-refresh works on new dashboard - Drawer opens from toolbar hamburger menu - Dark/light mode works correctly - Landscape/tablet layout works correctly - Accessibility checks pass refs: MBL-19452 affects: Student release note: Dashboard redesign infrastructure with Jetpack Compose foundation ## Checklist - [ ] Tested in dark mode - [ ] Tested in light mode - [ ] Test in landscape mode and/or tablet - [ ] A11y checked - [ ] Approve from product --- apps/CLAUDE.md | 7 +- .../ui/rendertests/DashboardScreenTest.kt | 90 ++++++++++++++ .../student/activity/NavigationActivity.kt | 4 +- .../dashboard/compose/DashboardFragment.kt | 64 ++++++++++ .../dashboard/compose/DashboardScreen.kt | 117 ++++++++++++++++++ .../dashboard/compose/DashboardUiState.kt | 25 ++++ .../dashboard/compose/DashboardViewModel.kt | 78 ++++++++++++ .../features/dashboard/di/DashboardModule.kt | 25 ++++ ...ardFragment.kt => OldDashboardFragment.kt} | 8 +- .../navigation/DefaultNavigationBehavior.kt | 32 ++++- .../student/navigation/NavigationBehavior.kt | 2 +- .../student/router/RouteMatcher.kt | 6 +- .../student/router/RouteResolver.kt | 6 +- .../compose/DashboardViewModelTest.kt | 113 +++++++++++++++++ .../canvasapi2/utils/RemoteConfigUtils.kt | 3 +- .../domain/usecase/BaseFlowUseCase.kt | 55 ++++++++ .../pandautils/domain/usecase/BaseUseCase.kt | 43 +++++++ .../domain/usecase/UseCaseResult.kt | 48 +++++++ 18 files changed, 704 insertions(+), 22 deletions(-) create mode 100644 apps/student/src/androidTest/java/com/instructure/student/ui/rendertests/DashboardScreenTest.kt create mode 100644 apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardFragment.kt create mode 100644 apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardScreen.kt create mode 100644 apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardUiState.kt create mode 100644 apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardViewModel.kt create mode 100644 apps/student/src/main/java/com/instructure/student/features/dashboard/di/DashboardModule.kt rename apps/student/src/main/java/com/instructure/student/fragment/{DashboardFragment.kt => OldDashboardFragment.kt} (98%) create mode 100644 apps/student/src/test/java/com/instructure/student/features/dashboard/compose/DashboardViewModelTest.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/BaseFlowUseCase.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/BaseUseCase.kt create mode 100644 libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/UseCaseResult.kt diff --git a/apps/CLAUDE.md b/apps/CLAUDE.md index f7db45f50d..070ca31112 100644 --- a/apps/CLAUDE.md +++ b/apps/CLAUDE.md @@ -235,9 +235,10 @@ Apps support multiple languages. Translation tags are scanned at build time via When creating a pull request, use the template located at `/PULL_REQUEST_TEMPLATE` in the repository root. The template includes: - Test plan description - Issue references (refs:) -- Impact scope (affects:) +- Impact scope (affects: - only Student, Teacher, or Parent; can be multiple if affecting multiple apps) - Release note -- Screenshots table (Before/After) -- Checklist (E2E tests, dark/light mode, landscape/tablet, accessibility, product approval) +- Checklist (dark/light mode, landscape/tablet, accessibility, product approval) + +Note: Do not include E2E tests or screenshots sections in the PR description unless specifically needed. Use `gh pr create` with the template to create PRs from the command line. \ No newline at end of file diff --git a/apps/student/src/androidTest/java/com/instructure/student/ui/rendertests/DashboardScreenTest.kt b/apps/student/src/androidTest/java/com/instructure/student/ui/rendertests/DashboardScreenTest.kt new file mode 100644 index 0000000000..b1f87222a3 --- /dev/null +++ b/apps/student/src/androidTest/java/com/instructure/student/ui/rendertests/DashboardScreenTest.kt @@ -0,0 +1,90 @@ +package com.instructure.student.ui.rendertests + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.instructure.student.features.dashboard.compose.DashboardScreenContent +import com.instructure.student.features.dashboard.compose.DashboardUiState +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class DashboardScreenTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun testDashboardScreenShowsLoadingState() { + val mockUiState = DashboardUiState( + loading = true, + error = null, + refreshing = false, + onRefresh = {}, + onRetry = {} + ) + + composeTestRule.setContent { + DashboardScreenContent(uiState = mockUiState) + } + + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag("loading").assertIsDisplayed() + } + + @Test + fun testDashboardScreenShowsErrorState() { + val mockUiState = DashboardUiState( + loading = false, + error = "An error occurred", + refreshing = false, + onRefresh = {}, + onRetry = {} + ) + + composeTestRule.setContent { + DashboardScreenContent(uiState = mockUiState) + } + + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag("errorContent").assertIsDisplayed() + } + + @Test + fun testDashboardScreenShowsEmptyState() { + val mockUiState = DashboardUiState( + loading = false, + error = null, + refreshing = false, + onRefresh = {}, + onRetry = {} + ) + + composeTestRule.setContent { + DashboardScreenContent(uiState = mockUiState) + } + + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag("emptyContent").assertIsDisplayed() + } + + @Test + fun testDashboardScreenShowsRefreshIndicator() { + val mockUiState = DashboardUiState( + loading = false, + error = null, + refreshing = true, + onRefresh = {}, + onRetry = {} + ) + + composeTestRule.setContent { + DashboardScreenContent(uiState = mockUiState) + } + + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag("dashboardPullRefreshIndicator").assertIsDisplayed() + } +} \ No newline at end of file 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 f7ad3fa378..81069f00f0 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 @@ -139,7 +139,7 @@ import com.instructure.student.features.files.list.FileListFragment import com.instructure.student.features.modules.progression.CourseModuleProgressionFragment import com.instructure.student.features.navigation.NavigationRepository import com.instructure.student.fragment.BookmarksFragment -import com.instructure.student.fragment.DashboardFragment +import com.instructure.student.fragment.OldDashboardFragment import com.instructure.student.fragment.NotificationListFragment import com.instructure.student.fragment.OldToDoListFragment import com.instructure.student.mobius.assignmentDetails.submission.picker.PickerSubmissionUploadEffectHandler @@ -1013,7 +1013,7 @@ class NavigationActivity : BaseRouterActivity(), Navigation, MasqueradingDialog. private fun selectBottomNavFragment(fragmentClass: Class) { val selectedFragment = supportFragmentManager.findFragmentByTag(fragmentClass.name) - (topFragment as? DashboardFragment)?.cancelCardDrag() + (topFragment as? OldDashboardFragment)?.cancelCardDrag() if (selectedFragment == null) { val fragment = createBottomNavFragment(fragmentClass.name) diff --git a/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardFragment.kt b/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardFragment.kt new file mode 100644 index 0000000000..0aef31261c --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardFragment.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 - 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.dashboard.compose + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import com.instructure.canvasapi2.models.CanvasContext +import com.instructure.interactions.router.Route +import com.instructure.pandautils.compose.CanvasTheme +import com.instructure.student.fragment.ParentFragment +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class DashboardFragment : ParentFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + applyTheme() + return ComposeView(requireContext()).apply { + setContent { + CanvasTheme { + DashboardScreen() + } + } + } + } + + override fun title(): String = "" + + override fun applyTheme() { + navigation?.attachNavigationDrawer(this, null) + } + + companion object { + fun makeRoute(canvasContext: CanvasContext?) = + Route(DashboardFragment::class.java, canvasContext) + + fun newInstance(route: Route): DashboardFragment { + val fragment = DashboardFragment() + fragment.arguments = route.arguments + return fragment + } + } +} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardScreen.kt b/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardScreen.kt new file mode 100644 index 0000000000..07083a791b --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardScreen.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 - 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.dashboard.compose + +import androidx.activity.compose.LocalActivity +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.instructure.pandautils.compose.composables.CanvasThemedAppBar +import com.instructure.pandautils.compose.composables.EmptyContent +import com.instructure.pandautils.compose.composables.ErrorContent +import com.instructure.pandautils.compose.composables.Loading +import com.instructure.student.R +import com.instructure.student.activity.NavigationActivity + +@Composable +fun DashboardScreen() { + val viewModel: DashboardViewModel = hiltViewModel() + val uiState by viewModel.uiState.collectAsState() + + DashboardScreenContent(uiState = uiState) +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun DashboardScreenContent(uiState: DashboardUiState) { + val activity = LocalActivity.current + val pullRefreshState = rememberPullRefreshState( + refreshing = uiState.refreshing, + onRefresh = uiState.onRefresh + ) + + Scaffold( + modifier = Modifier.background(colorResource(R.color.backgroundLightest)), + topBar = { + CanvasThemedAppBar( + title = stringResource(id = R.string.dashboard), + navIconRes = R.drawable.ic_hamburger, + navIconContentDescription = stringResource(id = R.string.navigation_drawer_open), + navigationActionClick = { (activity as? NavigationActivity)?.openNavigationDrawer() } + ) + } + ) { paddingValues -> + Box( + modifier = Modifier + .background(colorResource(R.color.backgroundLightest)) + .padding(paddingValues) + .pullRefresh(pullRefreshState) + .fillMaxSize() + ) { + when { + uiState.error != null -> { + ErrorContent( + errorMessage = uiState.error, + retryClick = uiState.onRetry, + modifier = Modifier + .fillMaxSize() + .testTag("errorContent") + ) + } + + uiState.loading -> { + Loading(modifier = Modifier + .fillMaxSize() + .testTag("loading")) + } + + else -> { + EmptyContent( + emptyMessage = stringResource(id = R.string.noCoursesSubtext), + imageRes = R.drawable.ic_panda_nocourses, + modifier = Modifier + .fillMaxSize() + .testTag("emptyContent") + ) + } + } + + PullRefreshIndicator( + refreshing = uiState.refreshing, + state = pullRefreshState, + modifier = Modifier + .align(Alignment.TopCenter) + .testTag("dashboardPullRefreshIndicator") + ) + } + } +} \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardUiState.kt b/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardUiState.kt new file mode 100644 index 0000000000..b18f6c4929 --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardUiState.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 - 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.dashboard.compose + +data class DashboardUiState( + val loading: Boolean = true, + val error: String? = null, + val refreshing: Boolean = false, + val onRefresh: () -> Unit = {}, + val onRetry: () -> Unit = {} +) \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardViewModel.kt b/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardViewModel.kt new file mode 100644 index 0000000000..9aee850464 --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardViewModel.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 - 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.dashboard.compose + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.instructure.pandautils.utils.NetworkStateProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class DashboardViewModel @Inject constructor( + private val networkStateProvider: NetworkStateProvider +) : ViewModel() { + + private val _uiState = MutableStateFlow( + DashboardUiState( + onRefresh = ::onRefresh, + onRetry = ::onRetry + ) + ) + val uiState: StateFlow = _uiState.asStateFlow() + + private val _refreshSignal = MutableSharedFlow() + val refreshSignal = _refreshSignal.asSharedFlow() + + init { + loadDashboard() + } + + private fun loadDashboard() { + viewModelScope.launch { + _uiState.update { it.copy(loading = true, error = null) } + try { + _uiState.update { it.copy(loading = false, error = null) } + } catch (e: Exception) { + _uiState.update { it.copy(loading = false, error = e.message) } + } + } + } + + private fun onRefresh() { + viewModelScope.launch { + _uiState.update { it.copy(refreshing = true, error = null) } + try { + _refreshSignal.emit(Unit) + _uiState.update { it.copy(refreshing = false, error = null) } + } catch (e: Exception) { + _uiState.update { it.copy(refreshing = false, error = e.message) } + } + } + } + + private fun onRetry() { + loadDashboard() + } +} diff --git a/apps/student/src/main/java/com/instructure/student/features/dashboard/di/DashboardModule.kt b/apps/student/src/main/java/com/instructure/student/features/dashboard/di/DashboardModule.kt new file mode 100644 index 0000000000..ccd9aec59f --- /dev/null +++ b/apps/student/src/main/java/com/instructure/student/features/dashboard/di/DashboardModule.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 - 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.dashboard.di + +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent + +@Module +@InstallIn(ViewModelComponent::class) +class DashboardModule \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt b/apps/student/src/main/java/com/instructure/student/fragment/OldDashboardFragment.kt similarity index 98% rename from apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt rename to apps/student/src/main/java/com/instructure/student/fragment/OldDashboardFragment.kt index 392aed5002..2cd28ac3b2 100644 --- a/apps/student/src/main/java/com/instructure/student/fragment/DashboardFragment.kt +++ b/apps/student/src/main/java/com/instructure/student/fragment/OldDashboardFragment.kt @@ -104,7 +104,7 @@ private const val LIST_SPAN_COUNT = 1 @ScreenView(SCREEN_VIEW_DASHBOARD) @PageView @AndroidEntryPoint -class DashboardFragment : ParentFragment() { +class OldDashboardFragment : ParentFragment() { @Inject lateinit var repository: DashboardRepository @@ -261,7 +261,7 @@ class DashboardFragment : ParentFragment() { with (binding) { toolbar.title = title() // Styling done in attachNavigationDrawer - navigation?.attachNavigationDrawer(this@DashboardFragment, toolbar) + navigation?.attachNavigationDrawer(this@OldDashboardFragment, toolbar) recyclerAdapter?.notifyDataSetChanged() } @@ -517,10 +517,10 @@ class DashboardFragment : ParentFragment() { companion object { fun newInstance(route: Route) = - DashboardFragment().apply { + OldDashboardFragment().apply { arguments = route.canvasContext?.makeBundle(route.arguments) ?: route.arguments } - fun makeRoute(canvasContext: CanvasContext?) = Route(DashboardFragment::class.java, canvasContext) + fun makeRoute(canvasContext: CanvasContext?) = Route(OldDashboardFragment::class.java, canvasContext) } } diff --git a/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt index 6a84f493a2..d67a64675a 100644 --- a/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt +++ b/apps/student/src/main/java/com/instructure/student/navigation/DefaultNavigationBehavior.kt @@ -19,25 +19,37 @@ package com.instructure.student.navigation import androidx.fragment.app.Fragment import com.instructure.canvasapi2.models.CanvasContext import com.instructure.canvasapi2.utils.ApiPrefs +import com.instructure.canvasapi2.utils.RemoteConfigParam +import com.instructure.canvasapi2.utils.RemoteConfigUtils import com.instructure.interactions.router.Route import com.instructure.pandautils.features.calendar.CalendarFragment import com.instructure.pandautils.utils.CanvasFont import com.instructure.student.R -import com.instructure.student.fragment.DashboardFragment +import com.instructure.student.features.dashboard.compose.DashboardFragment +import com.instructure.student.fragment.OldDashboardFragment import com.instructure.student.fragment.NotificationListFragment import com.instructure.student.fragment.ParentFragment -class DefaultNavigationBehavior(private val apiPrefs: ApiPrefs) : NavigationBehavior { +class DefaultNavigationBehavior(apiPrefs: ApiPrefs) : NavigationBehavior { + + private val dashboardFragmentClass: Class + get() { + return if (RemoteConfigUtils.getBoolean(RemoteConfigParam.DASHBOARD_REDESIGN)) { + DashboardFragment::class.java + } else { + OldDashboardFragment::class.java + } + } override val bottomNavBarFragments: List> = listOf( - DashboardFragment::class.java, + dashboardFragmentClass, CalendarFragment::class.java, todoFragmentClass, NotificationListFragment::class.java, getInboxBottomBarFragment(apiPrefs) ) - override val homeFragmentClass: Class = DashboardFragment::class.java + override val homeFragmentClass: Class = dashboardFragmentClass override val visibleNavigationMenuItems: Set = setOf(NavigationMenuItem.FILES, NavigationMenuItem.BOOKMARKS, NavigationMenuItem.SETTINGS) @@ -51,10 +63,18 @@ class DefaultNavigationBehavior(private val apiPrefs: ApiPrefs) : NavigationBeha override val bottomBarMenu: Int = R.menu.bottom_bar_menu override fun createHomeFragmentRoute(canvasContext: CanvasContext?): Route { - return DashboardFragment.makeRoute(ApiPrefs.user) + return if (RemoteConfigUtils.getBoolean(RemoteConfigParam.DASHBOARD_REDESIGN)) { + DashboardFragment.makeRoute(ApiPrefs.user) + } else { + OldDashboardFragment.makeRoute(ApiPrefs.user) + } } override fun createHomeFragment(route: Route): ParentFragment { - return DashboardFragment.newInstance(route) + return if (RemoteConfigUtils.getBoolean(RemoteConfigParam.DASHBOARD_REDESIGN)) { + DashboardFragment.newInstance(route) + } else { + OldDashboardFragment.newInstance(route) + } } } \ No newline at end of file diff --git a/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt b/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt index 1f5fa1b168..533c791c8d 100644 --- a/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt +++ b/apps/student/src/main/java/com/instructure/student/navigation/NavigationBehavior.kt @@ -35,7 +35,7 @@ interface NavigationBehavior { /** 'Root' fragments that should include the bottom nav bar */ val bottomNavBarFragments: List> - val homeFragmentClass: Class + val homeFragmentClass: Class val visibleNavigationMenuItems: Set 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 be5807afc7..f60016465b 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 @@ -86,7 +86,7 @@ import com.instructure.pandautils.features.todolist.ToDoListFragment import com.instructure.student.fragment.AnnouncementListFragment import com.instructure.student.fragment.BasicQuizViewFragment import com.instructure.student.fragment.CourseSettingsFragment -import com.instructure.student.fragment.DashboardFragment +import com.instructure.student.fragment.OldDashboardFragment import com.instructure.student.fragment.InternalWebviewFragment import com.instructure.student.fragment.NotificationListFragment import com.instructure.student.fragment.OldToDoListFragment @@ -122,7 +122,7 @@ object RouteMatcher : BaseRouteMatcher() { // Be sensitive to the order of items. It really, really matters. @androidx.annotation.OptIn(com.google.android.material.badge.ExperimentalBadgeUtils::class) private fun initRoutes() { - routes.add(Route("/", DashboardFragment::class.java)) + routes.add(Route("/", OldDashboardFragment::class.java)) // region Conversations routes.add(Route("/conversations", InboxFragment::class.java)) routes.add(Route("/conversations/:${InboxDetailsFragment.CONVERSATION_ID}", InboxDetailsFragment::class.java)) @@ -132,7 +132,7 @@ object RouteMatcher : BaseRouteMatcher() { ////////////////////////// // Courses ////////////////////////// - routes.add(Route(courseOrGroup("/"), DashboardFragment::class.java)) + routes.add(Route(courseOrGroup("/"), OldDashboardFragment::class.java)) routes.add( Route( courseOrGroup("/:${RouterParams.COURSE_ID}"), diff --git a/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt b/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt index f1ffe01a5a..903087be46 100644 --- a/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt +++ b/apps/student/src/main/java/com/instructure/student/router/RouteResolver.kt @@ -30,6 +30,7 @@ import com.instructure.pandautils.utils.Const import com.instructure.student.AnnotationComments.AnnotationCommentListFragment import com.instructure.student.activity.NothingToSeeHereFragment import com.instructure.student.features.coursebrowser.CourseBrowserFragment +import com.instructure.student.features.dashboard.compose.DashboardFragment import com.instructure.student.features.discussion.details.DiscussionDetailsFragment import com.instructure.student.features.discussion.list.DiscussionListFragment import com.instructure.student.features.elementary.course.ElementaryCourseFragment @@ -51,14 +52,14 @@ import com.instructure.student.fragment.AnnouncementListFragment import com.instructure.student.fragment.AssignmentBasicFragment import com.instructure.student.fragment.BasicQuizViewFragment import com.instructure.student.fragment.CourseSettingsFragment -import com.instructure.student.fragment.DashboardFragment +import com.instructure.student.fragment.OldDashboardFragment import com.instructure.student.fragment.EditPageDetailsFragment import com.instructure.student.fragment.FeatureFlagsFragment import com.instructure.student.fragment.InternalWebviewFragment import com.instructure.student.fragment.NotificationListFragment +import com.instructure.student.fragment.OldToDoListFragment import com.instructure.student.fragment.ProfileSettingsFragment import com.instructure.student.fragment.StudioWebViewFragment -import com.instructure.student.fragment.OldToDoListFragment import com.instructure.student.fragment.UnknownItemFragment import com.instructure.student.fragment.UnsupportedFeatureFragment import com.instructure.student.fragment.UnsupportedTabFragment @@ -113,6 +114,7 @@ object RouteResolver { // Divided up into two camps, those who need a valid CanvasContext and those who do not return when { + cls.isA() -> OldDashboardFragment.newInstance(route) cls.isA() -> DashboardFragment.newInstance(route) cls.isA() -> ElementaryDashboardFragment.newInstance(route) cls.isA() -> OldToDoListFragment.newInstance(route) diff --git a/apps/student/src/test/java/com/instructure/student/features/dashboard/compose/DashboardViewModelTest.kt b/apps/student/src/test/java/com/instructure/student/features/dashboard/compose/DashboardViewModelTest.kt new file mode 100644 index 0000000000..0f23ebe61f --- /dev/null +++ b/apps/student/src/test/java/com/instructure/student/features/dashboard/compose/DashboardViewModelTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2024 - 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.dashboard.compose + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import com.instructure.pandautils.utils.NetworkStateProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +@ExperimentalCoroutinesApi +class DashboardViewModelTest { + + @get:Rule + var instantExecutorRule = InstantTaskExecutorRule() + + private val lifecycleOwner: LifecycleOwner = mockk(relaxed = true) + private val lifecycleRegistry = LifecycleRegistry(lifecycleOwner) + + private val testDispatcher = UnconfinedTestDispatcher() + private val networkStateProvider: NetworkStateProvider = mockk(relaxed = true) + + private lateinit var viewModel: DashboardViewModel + + @Before + fun setUp() { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) + Dispatchers.setMain(testDispatcher) + + every { networkStateProvider.isOnline() } returns true + + viewModel = DashboardViewModel(networkStateProvider) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + unmockkAll() + } + + @Test + fun testInitialState() = runTest { + val state = viewModel.uiState.value + + assertFalse(state.loading) + assertEquals(null, state.error) + assertFalse(state.refreshing) + } + + @Test + fun testLoadDashboardSuccess() = runTest { + val state = viewModel.uiState.value + + assertEquals(false, state.loading) + assertEquals(null, state.error) + } + + @Test + fun testRefresh() = runTest { + viewModel.uiState.value.onRefresh() + + val state = viewModel.uiState.value + assertFalse(state.refreshing) + assertEquals(null, state.error) + } + + @Test + fun testRetry() = runTest { + viewModel.uiState.value.onRetry() + + val state = viewModel.uiState.value + assertFalse(state.loading) + assertEquals(null, state.error) + } + + @Test + fun testCallbacksExist() { + val state = viewModel.uiState.value + + assertTrue(state.onRefresh != null) + assertTrue(state.onRetry != null) + } +} \ No newline at end of file diff --git a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/RemoteConfigUtils.kt b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/RemoteConfigUtils.kt index 621281f55f..751e9cc3e9 100644 --- a/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/RemoteConfigUtils.kt +++ b/libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/utils/RemoteConfigUtils.kt @@ -17,7 +17,8 @@ enum class RemoteConfigParam(val rc_name: String, val safeValueAsString: String) TEST_LONG("test_long", "42"), TEST_STRING("test_string", "hey there"), SPEEDGRADER_V2("speedgrader_v2", "true"), - TODO_REDESIGN("todo_redesign", "false") + TODO_REDESIGN("todo_redesign", "false"), + DASHBOARD_REDESIGN("dashboard_redesign", "false") } /** diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/BaseFlowUseCase.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/BaseFlowUseCase.kt new file mode 100644 index 0000000000..00861f4145 --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/BaseFlowUseCase.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 - 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.domain.usecase + +import kotlinx.coroutines.flow.Flow + +/** + * Base class for use cases that return a Flow of results. + * + * Use this for use cases that emit multiple values over time or need reactive updates. + * + * @param Params The type of parameters this use case accepts + * @param Result The type of result this use case emits + * + * Usage: + * ``` + * class ObserveUserUpdatesUseCase @Inject constructor( + * private val userRepository: UserRepository + * ) : BaseFlowUseCase>() { + * override fun execute(params: String): Flow> { + * return userRepository.observeUser(params) + * .map { UseCaseResult.Success(it) } + * .catch { emit(UseCaseResult.Error(it)) } + * } + * } + * + * // Collect results + * observeUserUpdatesUseCase("user123").collect { result -> + * when (result) { + * is UseCaseResult.Success -> // Handle success + * is UseCaseResult.Error -> // Handle error + * is UseCaseResult.Loading -> // Handle loading + * } + * } + * ``` + */ +abstract class BaseFlowUseCase { + abstract fun execute(params: Params): Flow + + operator fun invoke(params: Params): Flow = execute(params) +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/BaseUseCase.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/BaseUseCase.kt new file mode 100644 index 0000000000..9b86b2228e --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/BaseUseCase.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 - 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.domain.usecase + +/** + * Base class for use cases that execute suspending operations and return a single result. + * + * @param Params The type of parameters this use case accepts + * @param Result The type of result this use case returns + * + * Usage: + * ``` + * class GetUserUseCase @Inject constructor( + * private val userRepository: UserRepository + * ) : BaseUseCase() { + * override suspend fun execute(params: String): User { + * return userRepository.getUser(params) + * } + * } + * + * // Call with invoke operator + * val user = getUserUseCase("user123") + * ``` + */ +abstract class BaseUseCase { + abstract suspend fun execute(params: Params): Result + + suspend operator fun invoke(params: Params): Result = execute(params) +} \ No newline at end of file diff --git a/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/UseCaseResult.kt b/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/UseCaseResult.kt new file mode 100644 index 0000000000..b1831eab10 --- /dev/null +++ b/libs/pandautils/src/main/java/com/instructure/pandautils/domain/usecase/UseCaseResult.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 - 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.domain.usecase + +/** + * Represents the result of a use case operation. + * + * Can be in one of three states: + * - Success: Operation completed successfully with data + * - Error: Operation failed with an exception + * - Loading: Operation is in progress + * + * Usage: + * ``` + * when (result) { + * is UseCaseResult.Success -> { + * val data = result.data + * // Handle success + * } + * is UseCaseResult.Error -> { + * val exception = result.exception + * // Handle error + * } + * is UseCaseResult.Loading -> { + * // Handle loading state + * } + * } + * ``` + */ +sealed class UseCaseResult { + data class Success(val data: T) : UseCaseResult() + data class Error(val exception: Throwable) : UseCaseResult() + object Loading : UseCaseResult() +} \ No newline at end of file From dbd6745c215470fa7836773ab611bf5e3e884618 Mon Sep 17 00:00:00 2001 From: Nagy Adam Date: Tue, 11 Nov 2025 11:28:27 +0100 Subject: [PATCH 15/58] [MBL-17351][Student] Extend AssignmentDetails interaction test with more submission types (#3365) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Extend AssignmentDetailsInteractionTest to cover multiple submission types. refs: MBL-17351 affects: Student release note: * Extend AssignmentDetailsInteractionTest to include additional media recording submission scenarios. refs: MBL-17351 affects: Student release note: * Fix flaky audio submission test, also renames several media submission tests for better clarity. refs: MBL-17351 affects: Student release note: * Add test asset files for submission tests Adds test.txt, test_audio.mp3, and test_video.mp4 to androidTest assets directory. These files are required by AssignmentDetailsInteractionTest for testing file upload, audio, and video submission functionality. Without these files in the repository, the tests fail in CI with FileNotFoundException. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix PR refs: MBL-17351 affects: Student release note: * fix assets files refs: MBL-17351 affects: Student release note: * Fix PR findings refs: MBL-17351 affects: Student release note: --------- Co-authored-by: Claude --- apps/student/src/androidTest/assets/test.txt | 1 + .../src/androidTest/assets/test_audio.mp3 | Bin 0 -> 41131 bytes .../src/androidTest/assets/test_video.mp4 | Bin 0 -> 5506918 bytes .../AssignmentDetailsInteractionTest.kt | 269 +++++++++++++++++- .../canvas/espresso/TestMetaData.kt | 2 +- 5 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 apps/student/src/androidTest/assets/test.txt create mode 100644 apps/student/src/androidTest/assets/test_audio.mp3 create mode 100644 apps/student/src/androidTest/assets/test_video.mp4 diff --git a/apps/student/src/androidTest/assets/test.txt b/apps/student/src/androidTest/assets/test.txt new file mode 100644 index 0000000000..9bbedf7fb2 --- /dev/null +++ b/apps/student/src/androidTest/assets/test.txt @@ -0,0 +1 @@ +This is a test file for assignment submission. \ No newline at end of file diff --git a/apps/student/src/androidTest/assets/test_audio.mp3 b/apps/student/src/androidTest/assets/test_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..57add90168f1156885af82b29c9268c7c061037e GIT binary patch literal 41131 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 Zz!?08Px-I-l*QZwak#%`T?UAk{|hfQXsiGL literal 0 HcmV?d00001 diff --git a/apps/student/src/androidTest/assets/test_video.mp4 b/apps/student/src/androidTest/assets/test_video.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..91352b4b07cbe2d6fe83c6b39ad4d7c7d71253dc GIT binary patch literal 5506918 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

o;g9r4CWeydN?#S$4wjm#q!|g7&p~X;r2hh{*X( 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

K8w>5mn%^*3{{e ztD7OS+LptfS0}mL&xzOVg!^EYSw!}FlHtHa%>JYYyXRh@%emV zPat`0UDV$k3#3E?B~OS=fhZ8_y-r*N{rW7`j}Z*b85j~I%f~px9z;zsMKZ%?0h`wU zu*t_IlD#LKdrON?R>J?$KHDqUwm8dp>2;F<`X`cQ4Ybn zly;78mk}6G0RYjz3BOX_s#ck7Mt;eZwwKGp)4x{r(2&FsyhlD`^6-RD#)6dR6Mr?; znZ9$E$YFDvEF5Ovv_YsMuyNd3;Qo8xGnyl(LPa?%o9yi2xbAOin&Mk~Wb@gn$H^7t$2v%Bv$N)2}&39dWV{De}V{FHsu z*s#aPn+ij}5cQ!9YIA8D?+=?pGb--c+)qGxV8m zW!C712>k>oSPZz_J&t;=YW^>C!?GBVV7@IK*Y%?(j6~HV<12@)*UOF;& z9yl8XLAQE%=+3i@KGRZ6+&j~`OW!{A8zFYbi9?BwQsmL!dyX`)uW+b)YETJnuHZW> zH54v*ck(%5u=v7CB~d;7<&6O>JI|#1=d*Fheb0n(5!N56)zOzu3zn*6Td+gUXfKcx zKtPqV2+h(eYhs*yv;FXYaVErJ=`dad1z1JFE0flSAon1@R$)>ivSRE=?+4NIVhWJZKz1 z3*y~2bzUiW<%x$~okY~a!XtYHCn$fXrEMZNsiJ8}0dD%@Pl$8{OuygOn<_no zm^ONL@{~a3&vwBEM_Su6Ev}g@Xy>Z0Jv>V#!9LkzUN4SykeJ*;D?1KCcQEBc{$8&zcIV{H_$Z+wH zs`GHaK~XOWO@1kC9z=QYnBM!=JjJ(S5Ifws%!%)23kTj}iU@~`r@X=D@P4qMl|r

E8%VS4kH=CRb<7Yj2OgzP4G0u?}-HHJqzgYPcxC{ADAO6&J?ueh0op6f!_ph zhQJj56yZ9_kdv1nhPKO3eWBh!gpE2jfXZTq|Kb?&ahckJu&Rr(^(~MW(721zNg-6J ztGp!t=FHPvZ_IqXeV)X>Ap}SD?W#Z9U-pmKfw7bp&<}R|)b8+5e&rZ4WDmz`xj8#0 zTdJg7KSkj1uj`J~I|r6tXBdEB1!3Y{#`KQ~C`v~|!l`l$w~E6_JG84m4WnZB{YHv~ zM)Fge_mIjki~?siK@o?qMdt)Rfg@Smz8;E`jA&pWLAPVnvp+0RETzI+7YWZ+h@G4{ zO}1W#&O-V)?+krcG8ZA*7{DM!|)?@(z=%#6_8scy2pppK9RJ=aWGp zNP$!iGaCf!O8mDLoM9V4SgFWx4%d$TFfnas&FKqDOIJ=p>7~=3714> zD#~_;pQ@q|l!M=CO#ifpducFh>Be9h3tTx7_R-tF^WDZ9k3*2QW>;VVldmlOY2lgu zaB-POA+Vx-0)``7%ZbI_1+;+~p=Wr1^5pDed|0He?+d32utGLKop<6}INSeaed#4_ zBP?anj8&73ftSoB!qysApyokOifPOE*F#ZP9_qFm2T#d`P}&3o4kVu5(4cIfL!=44S;YEfyG7ZP##g^Hr9A3Vb{xbH|65WT(TdG zfyB3Om>qF2&f3G1(^)%<#RHD$Cx*H^3&NfD--Im$4LZ@OBOK>+wpP7TzVm-Y?OvGw zVKR2N)4St7?u}oD%g-K?K=5jesBmQrG!h&`2G$H}QO!afn6N&pi2Br%wgf(P)peG& z7PYjkDA$C49IVH8K0(-ij4g8~NF*Lvkq-FJ4h=-xFzQ&jvEp2aZVvUv*rI47hvk;I z;Pg-hEl;GuIIXOqdj#<&UJa833gpL}pn?8gSwQWJtktXF+_0|Y`{+B(hm$))rZqIN zlBNa^#HxQ8fX-o$#EGXXf<}lUj!e!Kj)CzjXfmaX%R9dE%B1E`WjG%ngPs}1A3B!F z{QeVeQT(fGE^0+!PrqO2-}2G3p8c?t8Z@_*q^rl58X)sn`E|;$q(EKTLzIi5o*l@Y zbB9UTU*U5`Vc?IFr)lm#C)epfw<%c@fUA`rCd1th}< zF4cTyAjsx`|A_6J7SkK{Ult&eym(8^zliW~@i4^mT;*Wwt8 zdI)&Xr-nfjr|uiDo8D2L*!AU`!Yv&G@T<|*+7gNwQl+Dj@RX~ z)mcAUaQ{$vlh^?PZSt^_jjyO^O-Z{d$Sslo?1fyd9w*jAApp>A6Qrx*9ZQZy6~LHE zGx2VE%d-gv=vv%C;nnyBfdU%dsQ*7id{f6TVrxcOA)D+h9z{()0UT z^$mp0%zbDQB%wG%N($tVsr{`nczMHF{J3hlA%)KF#~4<&?0#szyx2?ozo2;A&k>{X zEGObchXkZS@0fn?`nK#g_<-dvJi^&ydxW$V)G}=~a5IJifN`H zNkaW;AE_E18%tMZwCtv!#CsX!$%zD0NZW%;`N-tk-gYz_obw?=tNie|>355E9D606 zdN5%Pm&q*F1-tJ=Mw4u|@Cxl`RJhAksYiqS|C$z%RA7M2B$8ZdRdAH{PQFtNk*k{N zJ)lzZUT+VoQCr%j*N}9E4p@nyBek40ehSST^q}$ObR3kI9FFS)?_SPgijpvBPXN(KoY?)Ud%-?Lk zA2e>Y4z!6N(vMcs28v|*`xFR=RIgw1&@Azv0E}D*_VSPKe2si?_FmD%8wv2KXP#fUo#dHgIh5S9=Rx-9w=2|!i zO7`l4;3FiF3g)At6{-Znmaph=qrgG=Jr1mD7zFFr2c+i~sIYgi1Zv?#+8!-J>Dw8l zEVpjG7l&(1ps1_Fga5!n^4N*p=;aihQolY3Ug|A&G0#Q;PwogZ>4?MiV5YX?Ba|&q zragx?1&|`xbr#_MP@MEZzjRXk#|x^|&lPRe>aA!7$pU9CMolNH4knhmcYZ1>{(CX| zd;;?jK~AjID6Ipme6e|w&NFC3EHCDyo;i{V*4D|Uhy#;iP<#8pP1L#~w$8=yPGOa$ zhQ-;-J(jo!eOYrao1g%X*{NMyohNMGNX?K%ElOiL1k;-ydymFc+S1dImzJwsAs6-5 zgDujKq%ZP*wkQbJk$QM8kO06<1m5Wt+4qaz@r(CC#N3~<`oT56$<+I%qS897N&q;F z$MuWI3-El(FEy*bXG7|d%>MhQX5&+$xqwpL+l+4K@+O-JH4PwP_2g_DFAwz1oY~}3 zDx%ZmKwrQb49v!TjI|2sll82gqn2UJ4h(jlaQ6tHKYAuIvRJ6MZnaGy~ z8j{n(5#?dP|L%9-xJ^q1UCyTpzC_VdDH+vZ)ih23RxQ5p{G)Eq@&#yN z(AVeG#6vDNyLw4o-ftHa@Nm?{gr{uc7NyDm$6xCMkWO&*<@l%VS_-n8?o%syu>9#1 zjB%Eez@d<*(-7yj?Gdo2AAr>;ymiS|_$FPzPMwbHpUF!{M1no6kGb;QUG4=Unt!>6 zk8=urYvt1vXX2^842!1?F5~ZZYtj$J8{PT_d*L>9*=BH`N z(M(fHBxqFihaL-rZv&;TLMf?-qNVQGLNQW!<4RtN8j(%F)g@u9L9CuoC%xn$A>P zIH%D78a?lqS!TzE8Zlb27NbnZ8r1$eEVz_Xr|+Q3L#ymsUguvh=!3f2_EuEE@-e|3 zdmaEW$_Zn33?ItHkJq;Y==jUqK4GBFFRUOMbxi?^jO<6D#Wm>z%*5+#cU2)QSv1Ve zuxAV$Gn$E@u&IBszE4M=1ziV!y$o-Go@gX-&CfY!FOe4Jh#a#UKA# zTNVw7lzPNnK#?p?dP#7t#rGPGxTX4_AL8%_1)^@9v5qx)p!Ki`rjg6sL?{fI`B7jl zHTxvUF*}Bw5x#xg3ll^OKl`Bdx{yr_c#<$R0$?FRFsKasB4aR;Gz^V)5lEePym>$A zQhWSVdulE%AMA~jG9u|}98}MxsW!iJWAbkvj5IvU^99G_Ya3o*D@tEU6xcMIX`1>e zn(iK&zGVC78%Uk0wY4*ks&{m_#Ylan3((71b@Q|iQo1a0FDE(xYYZ6blGJbStrpDl zu;k~Zt(Td%r2IPv9Zryg>*LI<;ml?fIvcPK2#;~71HIs}mQv)P9DeKWwwXzQlX_N11+tjy{> zTI4Q$L#YE(>6R1(gsJ2SV$lZ92S~8c8+Ky zOu-3DoFa(JjZD3U%}@KysB!|8RC9Aw3irddRyJ|CXEjI2e+n^viw&n7tm?^t&T{4W zA%HEDE>dy5KWA!C(%hJv=O9=q^d2e1ant?4xZY}4Tn}JS&oOhq?=CdSiW}hSQ9e!I zkAUJ*z37XCkLq?(WYMy;p8E1GS5QpV-dO{wRKuz6HG_5iCtkHgqqm86u7zix_Uf%_ z0mhjln6uK9mD7+q-h4B4#6(WVyszx5tKOeAC8A{9$(y9Ti1e-sh(E1d=~8vLcHu~E zG86p+lLCk`Smli)OLD~Iyo|x=IxUUgQa|(rl^xAoGuRKCuVhH&&Di)TbhcLc=?EbM zC_?*LkUgtII5dpSMar_U#O%WyQcEsK+ zuE{FfRX}3XGg!4Rv(i|SP#oCSoPS<|%rrV+>mcSTfXh>^23UFmbJvV|)*6sNvy&T2 zplR~mb;n==*rO}4ZOt87oH%_Ocj3*ROmP*#wwR5popz$~A3NeX((QUEc=* zqD((A**H73-kU)j>|NVCKy`p>>UG7i_ByOzI&(n4`1y&H&J!~9H<9OpGBEYIAAQ8= zs6-gu3_>1zNg;CdMnEUy>ZtztAJY&Ybh0+`RiV>dUhbYpNl<*TGO*5LxXM(KNPehHeM<_R<*&N)W z3;9nU4`lI=9Q3N_=vQR};+mX6>QJX3AzF^1yLBNoqaCz3TW{N$1q zIaC}HLHMKUwlPhnQim)sZ|bt#U3#Qrx)FvY-LLj~oNV047bcN@I$m*x1YqI@Xov{&wu`Z|I>^512_5 zx)5N6h{w^wkH7dqqlf>M1vQBY{trb{hh%#s5AADzG*J0Mn~W|VgH-c1s=9%l(d9nf`3c}#e9`DM~XC?T>0JOlhO_(((IlNov5 zDhj=DnyZix4Up1FOmDAt;`pg_)1V{Vfgz z1$^}6lz*0~{HB_vsnefF&9m_=(N>B9%{2p=Ig_o% z0g)Tf|Nix6WURPub!xmqDdMUi4ElfnAjqaGsP49~u?QO=kMT3$mKP(-O=;~1D&?}% z#Tz8>DO|yFT8Of$F%+Jw1Kv%W5YT&E*FW+^>>t}PV{Kvkv%Vy`e(=WwUp3=)?S_(x z;kUPv-z}n8YDGp=>8P9NsEbw>0;|O))We9xeld>52m+-uISRx+=Ts2IJ%4v60)nzs zI?4$;wBI?tKg{NYK;PR*iUr|Npl;U^k>^pZjlm1ypYnV3QBn!SQc zI|cw@F02NjPUAwxWa&=(-ZQvFWDawELs)1b0gxnjR_@+hm#WwtoDbIx2RH)zHe!Je zCpM)@FPB+}NW*EfD((P3B+viNh@S6SI?ar&>nNi+HQk)4iCj!Ry3zC=9vzOw8{j-FwfC)(KO`F$_fBEvHO|rV4i43$5 z7<*1T%~(=FEbU~fzg){*;oDf%$HfZ6i>E5E_EN}Ky1*rL6o?Cs6M&0SN3YFC8&p8g z5cmeum&Y*dnxdq=*c^6$zv-3DP}d~vzksa_^xUwcf%RvK7?z6!o^cXqTt6(iyjon! z5+S(jo7m*$vVIp0y8!2%o*=S-`09>NFU5U1M%NRk$yr(+-fms4P9=)0Fw?4%Q#hgkYNJi(D%_S-g0LWfcK(uDFG)0$gYnUxK*RO#J%x73Mq^`P!9)odT^%o^XbEb|A9s4MGNP2=vA zNqD|+*Bo4pUxx2G{U zdn|H52*i@9S-Fh#M1;`xTKt@*!(%EYom<8sAw@DO&5tNGY)l;#E{vzg>f=?W|c zN%2dB|H3;hnn3K)sAnGM2PNXuqQVw`ncycMXGKGT4hbQaRST&B(3AFNq_6bXPS;>c zOW=9%SKeIV^67Y4+d(~+=rykKJ2S_bdW?l5HzW`1U){ws?f)4Fx8O=Fd=N!R(E5fr zk9tO)6WY+AI|lJ%#pG`!o??ZBW-8#y4mpASo&e<9VxAT1&m@QLHAo60xUe5sM79Z* z>{opKq%0HZDyd5w6#=5jC+-ASzyZT7|znjuO}cK}>?$AAb%E z_w`Cv(Rw40yQd(^9aWUMjK&PY%g|gv9RQF{D2bIYGl^v*@u~V8pCCK6jE;>TlCXT? zj)Y8G&96WwOqk*bZ$vHTbBp}Lc<3H_MCEc7abGv5n{#M%E8-lhLUt08i z#Lkr$QEQ%7Y|BRt6d{P%Mn1;gUN?orN#aF0e$eFvd_eqasa%}2KcZ_gplTG1Bo`_rfd!xtCDSoCl?5vpx z;m@&lc$IuvNG2&(DmDRpswhi)Dj{Lb41Ry8_XMaYNLC(NU}+R7^6#z5n>hs^NE4ZdZOJ= z#WaM3vpuKJ&nVV~05swm$$*ce8t^WfQ<7#>V6@$7+e;p2f3%+rfwlvb-nEZ$?-`A&+OU^k8T`fcnKVPF ztebH=Jr>j62o(mwmA(ladb0dz!D6z0e$j$@MJ;SOnjC9h>_ZR@#f&@AU(|>x9IYDP z`@DCGM7Sl9ANTMWJq^8oF;R?^-4mY^nAJZ*h+2L z-j=YwoTxhP?lqxakuii@_4(wq-BkW_#Oh4v=r9B7 zpb0y2;5oNVamyjrHz?&Lz^DF9@f=~geZq|&ID3|R1t|)}H}u|l-gBUFWHXT#bId4S z^}it2vP_g^h0lB(gEUnP0N{7Xi!JGYl<;FokiGJ*7jF`tWwSjmfyO>-E;V!wDi$u8 zZ3D&Xjw$>ojfH2W<{XTYSO0z+;^x5v^SMk99yAOKznUPB^X3JXYdG{)J}FSJ2u%^L zZ(l9XMelk7{%ajl%Q3E&i$yRxkhMxVUpqq}g;`qIq*v$%>*sG{e}6|^%Q#wP`QY(pn_dsZnf@xGgy zBlZ@2W+?d%s|%Vv06MdXGxNCSKT%0kq(3%G70s(_NW zkHSP*!5kPO*SqemT*r~?@^p?Xk3d$Qgz=_SEYPlzvTJusX4!j@i=_2m4{KSXisTYF zv#6CrH8YBu@eLB;5+?cBMEd~1jv(sWh23F$huS^)-+|o)hM$WYoWw%>*qL)K)dZho z-VGb=q@IdJ@B)k@3BR*E6_nr-Bo{5duZwTx)o&h%f&%1azBuJxA zLNq9(9IUT6HSVv68rOfZVwf^u$*Snhm*?@qgv}B@!j#wks5Ul%YP`3iQ-QZ(RKvb9 zCDnO9$RCc4nW_W)@CTZ)aNe#lLLTQXVTx-X8+}<@%=^j7=VxXg6qq58qj}6+Pq4W< z7E#is)fY4yw4iG!gjF_?q-;YsZtqV^`420)Ux&~E;_badT0);6U(|1HRbgj76UC}l zBFm$7fv(odex$FUg%2d;=GC;gO+EclsMk z`DnV;5>%bb=rpGe$jYX(qt$k`7qwjN&U~;>AJ`6>1!oyhkL}C)`!cy9KW zEn*lZ<+Vk7b~rOLxC|@euBKu`JqNB`_M*!y0FeteeR}+YD_mZ+h5O5ZKy=E{ccB8B zQDn19j}LY)RWDYC_69bLRQByz5aItMoxWeYb4L+nZ;%QcU=>Y0N!|>R9vpasDr@3yPr1mkce%)lr@`Od?cHcec z8}tg3f)Lv}RL0@Z2{a+yu8jwYp*U~&R;7(0sppuJS~iC-AlP!9ckPHT682Kf3lR{B zyOf*a>)N%%sD-K=LGiAXYwOI&(&2djIZr4VWPo5sgAQ)<=*ybQN)_Z1otnj2S8A)P zBRy_tfai+yOaUmtUYQK`I`*rX?_skC## zEIYN;%`y2We6v6>&qYhu^uZ{i)UhRdFn+!w~SzW@yzvpdBoBbR)vM<2` z|L#9I;x=gDmqxQ1H9>T7G>sCr%)+Uf^Vzz*T`tuecByLAWk-WWi=3Ofb6h95SdaQHM*loq7I><7{ANPfr{8B89a z#&%Nub3J^^ahLl0NRorhzt9UhyW2N1-u?S;G z(j_(1wkC)PExpLX;!L&`AJ*pv+Y@fE9>koM!?6ruRt1{}gFW-DBQ6xy2q=R1RS=A^NGTiBw=WieZpgZ;JP(ZqS(}!meI9`A3c7dk9Uc8<&8^ z!`*mb7SXzp0qTwdE1-(V>0xRLMAy0Zn9h;jlV`Y+0f;b54om(h>AGS~Vmc=~C2yU% ztCVw}JJwH;c;36W+{Nsh$87vK!EPWYw-{xtK7A^AXp2x&B$yk-8P_}#wOsQIstwuV zOjT6T7$6-#s;aS2L2Elb00(D3hN0IlD^Hx33#nTFJ1EL-SJC7%!F*|K(2wF0iB(3N zu{lp%pUsf9-S{cT#DVwP>W5e+ibIvg2P)L|7qHtlD7H@@n@J(2g8~sk!1(aHnC<4g z@%ZTTCqQM_gxzt1VBS1n9uV;u)uC8yL$vOQ@426atn^*(K*y5u7)^P&9+}&)98D2l zfFCTRe``&3g4jWp?>h2~&ZNv^R}nN|s@Nuy>=a0#%Lt%j&5TsJA3nidG)@3{?!Z*N z2@yovxAN$DOOB{a*@!HCAVeHP*4uWrp@PK&T#K=5IoCkmXdbbAAl|~M6LmxYHHs1AFQ#% zBAfl1U~|)g=4y-0oqr}b$e8h~l;THkinI`>1~uUuZJAYgI?Y+ua2>NYspbvL<$lUY z64jrXv8i@bFm%vK8?iuh7@UTwG6QGPwFJM(toTIW`D@1Z7x_&Fx@#?%0~(kM*V}DR z<2k+QjhxY=-b(9d)2JLpMu@*I^;Dy+yz?L~NoE{rD`-}r}LdJ&mjh$AU(PSwjBQdsX%BYfd$AYY!!;?ydH9c*(;HvmL2 zXGSZ!W`vQ;=Z$hWVzAQ_jcD_5FXloXi2roIN*S3Q722+MRzJnepf>Q=D?F&S<$sgE z{~aZ_i1T%$^`o9z8kv{NgT0Ppt-wvq5Pf_)iN!(2tiX_0=2LVynumSpeg&|D^Sw?O zvwdN4fB{W^2$sdMIWBd4DC@L1(i%~3A}rZi$N>g z+bzu%bp5$B=9co($Y*Bq)~0X23jeekc!i!JBpbxg+xgEjbU~buOE`WpyD?M8oLnI<%JH zE1zctFzk7o@ko=}s2e4;QSix(zO90D0#OIDJv!Qn^Ykb2+^isz0=NuNXRk0P6_jEk z81Z#(MTAS{RDC}tPb` zPGxfkQdYCtRnT=8R^|uV!Jc(2RgMR_WY55XyLcrn!!&&v=PMz=f+$2}b|@4Lpy(UL zhX%MRRQv)Dp5CeX)Ph5p+hgx>|H-NQp538$#L2FsQt$)J+)?#ODVo=;YND+vYq@Z) zxY8S1%%9d37j>UoH~Ho6WA$|v#9MMm50?$E5KcpUu+qt( zuiZ9R6Z8T#gAFRL%4>XYIT)a}k&|qYOL)g5&H9A)6#fqqHMhZo>kVy;5*2U-9y#LK zy$=7m?w(QAiE^bpu5T}wc%?-D;PC$~I|(^-3Z>$oUUQmx|l zQeXU!9AgV=YYVBpq^^Z3B5Le_RS;MVH8%R^0wlgZbdu<-&=2`!XJ@Qc#$gqZ>!6wwQCr?3bmA5@iv zNvTKxWnzvUmL3GuT_H8por&5x<9|$9e;5=}O@2542wu|}8^}H|F$rjuwTTB5+L@X} zTfz7+3kKAu+mc`2uTLi9d+LNc0(i)KGePjuw}B~&{T8_VAfcHUdJ(#uP>a2^Ec@eK&1Q(+mQC9k7OcSgE{lZpCiMSlt?#1L|_xf z5!dYAG}c3hK=hT~ovN(1TYRP}!dj?*2*;*jX}kFK3N9kQ4rDPIl5P5xL3cLiuHT%S zXp3;+#g!*R2H0~u?7z%DDz&*1LF@^)vcxq_f9*$`N008zV24JXb;dm`prF_vA~irS z(UQFTaVu4zYZ4w>tCo>KC9Tv@@^K^AA4y>fDp-++u0uxh`1aywAD{F~A|aWL^sCf# zWWj<&oJObqq(fG)$IXXtHB`xf_5~NX!G!f=NqniwGdcP14!3w<1G5Ke3l6 z^Q3RVH8?kjg!Jx73BuX*dxI{P*<7waGeX&-Um`1v^hVOt zf43D3nBudS&;6d+vhmn;<1ufqJAR9y|BTL9LbIW%IDhnWAfzPsIiA-N(&@=y+H88+ zdxY9`>2LRY!jUAIMCM2e`0d?_;}F5q6(T#M5Z@o96(HVVus>1HrO+7b2#$PTc^<@Z{g1 z%&Dn3VBQOMsI2aQ&j7X-T=uow*ZNZgLF8_Racta?j6AC(%9sqLr9J2 zh?`AqX2dcN!JDO1{kvtXiUvx%+*ar>7Ba6F*4>vHcoHA4#w4ZqiZjK3W#qL~YX<4Y zjq68?0rpIdS#6^!`!X0>eFl6_n3p>338PS;LI$4uz26_Eap$DU{blTE>VWjYK88?< zo*~&E3<_2ZyWb7%S#Ln^ZBI2I6h!zy9fIFqYJ z7N8%@zql-^Ac&PJ-;$9X1l{3aE9ce2_^FHa3+NJgzHz+we!r;4bNvh8YOO%FYCsS8sC3c3RZ%66d(AEWTNKv;wk?^~2N*rEG56b{9Kzx%b}rz`s~;E(4$@rL~C=(222-v?DQE^?2bNO}MF%{|mXI~FLBho?2v zkf8vjY4ok8dvqZ91ji*wnwPL3?$dc7ccT$qqfDQ1Zd0l|gGj^tMG0zc6;ik@co%6p)peb4)99C0Eud9}_YsIn+E z&E7-W{D2w7L_l#HA)KCH>{H_Dq}%;!ZtQL!-VTeUK+gwn6ss0`jWkA6EMi};g(2%` z6i7mfr9e`^cdC%iP}ZM5{nHmb8Cu$BIz6VwiJYtfvC#<9k0Z9gFv?7 z7#%9b#Z^LXu;kIOcg~o5d|9ro`QO3YJ#E%ZqLju?7@Vv;C}TazCG}E8{L&pes}A-P zblb)N2wmcZh~g$Fdm|OHB6=WqB_XppHO0>x(s4-{mptT(r-U>6%EJxdVR^vn4ERb_ zGTVEROw;zeLCLjn>eB2_0p*%Szsm_b)wkf&ierIw`rk!2f>Bh!Kz*D8ySzoN&(LMe zt}eRc#7?2FGq!$uM`e!V1JlVtpU-R$nNF048aqVNf5oqWiG{0;20C26nd0Wm_oU0q zv0R|!U!5~pN{7D5*^@uZ!?)Iay-dX4dI~hXbyqd<<2?uv`dS!Y96wtCuzPTgjnNax zJVrsTS(b6nzvZUM5PuCjlK&*9(?YPuh3J&<58(yQG3Ao?OebOB!n%7$z@mBD{Ke}vK;Rjbq>ZGY#zHk#9` zBQz&9-ccrx9}-de=Rlz6WH@?IYPA`aMp&7VZZgRa1yOY1gXHnq#Vu=vQ(HW7T(s8Z zqTDvMU;}NaWStHK75bT|3{Ss2Ji1ohbwL2Q?02`f5_`L;5Fu4}NAI|Iy*DTMJ{xDr z&TmrEn{=K^?1H9kKki+*ATMNe^Tl8_vz7eNfAA483*`_-Sz6Ipi0PxfCLl71A5Dka z0_Fq$+f;dLO{pzw!U-OD`(rcdp`>t6v!9^}y1?Geu1V?sA^r0>46qW`xyItP+ME&k zwOgC_CmO$4?xH>sZDb@8s}G-fl?hdDR1;2;*5uh(1itj)-w=D6hj^#7|82I#H9=W> zcZlLTJma(W>_@EPYYrH(O5xd;nj#PVW9QI3v+Ga|g5dfQ#_TIrYxf_qOhlf#m7BU>w{AKP>}96z+Ge0FDhtP^DI+G zvjKs8o6z^^#4Me-c^Zx?Ab};kfR{u6dc&bdl%a?B&%cS&L$RNY?7A8nePi(>VJXsggtDObq6!K$m`KTWm?joi(gS)zA;}wJy;m%-V}TW>HSQX5wo=u4V#-GWZf=-h3t&J|x%_Tc&Wk}Z zajhsjiP7XPxe){e-sm2Q$5)2ZyiP`|t4-0Fi5`cx?RjGwyzvWW^^LAgoewSH>sOw! z0IWL31{L5CHhVUBay>)m}zNFLTU=gh3Pxihg=#M1~fVd zvWU}72ALIMzYPDG3vS=oH`W@zR)$AT^)b03e|1leThv)Nki18;2UsUIsW$z%RD$LL zX>G3(a@W)V0f{^K^>=@0lDRezJV2}5tWgl6zHEsq+0r-0&y;spKH3on8Zd4@GI{rP zV0OL5*GI_n%F?7ChSKm9tYtkebZOhh2KNkcVWSN^q^8Dy>0B4T;*&j&WMP%Tb|2Xz z3pp1FX7_K~|BN{B`e6dZM~T`a=S19IxEDgs%fz)MuC{QfLkh7YHd-+q34<`X-|R*1HCc@&46^ zto!QdM$E9IAb|@<$S;bG6mYe#qCfjY?Q87ga~cf5wyIT=l6eqWy{%mo=};}JepoM< zqI|#arf2>D5qi;*$2jW#>*_@`oe&=RD(JIL(YwC{l$bY67rek#KBgrE!zSJ7+5eA`@QNrVJQ zKedIK&TNa^Hc%mA99Sf*!bHLQ0CzBx+%nM}T;K&6&{`s@VHYpk|E zhDjy4#n4uUc5QHrPk-|TFY^UOB$^4CH^=VpJZ)bHTQ}4W4`yYh>(Wwv=~At)f+cbW z--5#k3uj*qk-qVKfC`%1rE+;h*}OFKA|^WID5)D8zBYL~z(50{qDX(0FZr$$RZ#LA z*Bm|!9U|Yscq4Zf7%9NC9l(oEHOtcJb^O0JsgvN~e?f=&|VBXfHRm*RH!SjwY8;F#ZgiLUPJw za}O2LwcwZL%=bnm%)j7gqJ8JZONT7|#CNul#Ed@n#X~uL0rIwD>4u5ok)CD>K-x!V z{yGR}D=0k7BS}+2@2MyZOc=Y$m|lQ#H8y(_2XrbdHC7gziA*k+Fd=u$Rb$5(vz_5; zHht;57=HbE73hCn7sG8>1rKZaqH|lD>tkq(ApkiKj4>Vot9Qtfw`O?qcxmGGz_bT~ zKe^&ekmc3Tz}E%@0w+apWy;;q%pJjCmE!TIwYHv0A(@6<(01KW+%er+DA0%cZ%ZB# zf@cRZ6lBcxogy@-`|{Ma9+H;t*s#0gTg6gey;@Wl440a)oV> zinfu;K7AA3-1ke=OZ%W_IT(6T8a=3e^r{J4&;dYgsazKJC^wGB) zqH+9o+f4qO`&h~-^dnEm2-Njq8Nd!zWK{MeA_P7JxJ+H8(J!!End``Bp`u*(Cr&Ec zYjnl!K=l~4MKdedtFITw)KNjf-wnICN|YW+)#?LY2B4ldt;%IWbg!)#Cu6Zp=yXpr zS67SS8AP{pT{By+eDp#I#H{1vDe4sXeB1UUFY@Vh2tPmqyC=k4B2|`&iBEC32zz(D zX>C3~g2iQn{|2f9q@EALhjR~6FKIfFdTCnnC}Z2xhWi1^z;{e$PQ&mwerf`y(I-f? zqHfF$A4}@^{lc)Fm(i?qswdmPdMd6mk?d_PE&Wux;MZ{b*mKp@q#glz(aG0CfrlH~g-MmE)kYqWk z*p?RS0_!uQRHQH@Bs5qh=#E07I!Zj zE(v+vJML~~|EMa_xj%8S*lcCDsQMP_!4r1T6GT(F9zqYRDao#XDb5D1ExBD0nh5^i z0P)NJ1&ApL{{x6o@`}#)MX2m>Ko)4CBvV3cdQ7(6FkjsWSYy^)5Quxu8O0Bx4Kc$O zd1|R75wGq2*Q`d8HV2aP2agNeldF@9hJdo}X0Sl3_#7~)(6#(%)wo0)I=VgYkL9lC zR$7$6a_yQc49;MI7)lGnp`2mNAhU{c77xR7zhJ6;+)4YmNzp?F^|6x> z1=1=9iNhpquPg$LuW~B7e=^xz2BMi7uR=@gt_A%71Nm3wqBpiL(%2g(JmZi z3O$OF3>XADG@sd)?6sYpXIyO#8iO!I0XTS!?N<<=nG~Vk8QTGSA{-+0aF>qMWZNye z`vSH1k!=vQn}!ZEO1)Ns-hZpLLti%POe)-VLk}OQSbUZ0zKx`e9^;eB*0G3XWQBo* zq{~Sem03CIo@C+B6fIq_yeLEA2CS@oL6V#pI0cJu4zJ{|XO{9oxw}B2xO`pc;+B(t zFN|BnzEz%NI8xTB@9BwzMD|{bUmE{!is}0%C^FZ~prNEbh~_M+hq! zh8hInOIB(24`k`K4UFq3HrG@a>~X!k1;}b_en%Xi?jhpmYrP><%gmXN(a!st2I<9{ z2F)J~0v&%ml@+j#alK~v3+MnF7HWl+QB`XY({YG?HKMUd5;)&K=kd;rSN^nGNZof%=-AOZiEF+#K2QOy5g)$sUHQzE=$(wht(KfA;NtE8$LUs; zFqSU;h3&N;+d`=wDke-Au2^tIiohFR1ZVgCwND?Jg}9$Dt{$7H^^bwq&YIW%lb9z@ z0YMzi&6498QDbYBO>o-&^4=P3zz@Ygj4bvz0{u!&(cV^4wZ^v#674*@3ldt68I0Oe z05G67HkP_2a<-~Q@u^9@44z+*H>5M#zQ*EtBVJ{xVaMHtUDi63x{7j2t#z+&ukVev zWwl~PIJ5=-iwRNuf}fE#Sr)L!w5L-oIrZ-tF?zm#AaCH4a%yqpM?bauQ(jAJ6G_4S z)bORbD4uHFA8x(hlv@K8y*Ma&dcD6D4z)?m(OcC{wHbGTd``7m_tltW9haV50f1SR<$9bGs1d1gDU)aL9MAgV&94lOxAR>0?~+k{*Up13>fQ zb_aL7uqwgOIgdq@|7Z3sD#0NsPOKzXF4XHp$u{8RjYs|hT7lb_Cr56A#UOxz>6)~? zR@00=onlthia8LED^&Kn^nk!blaWT#sOGl!!diegpQz*a#@+JQ0Zc-o{7t4V@yl%a zXA#EF@SKZb28t||mGgxLal5{AczFkA zVT5JN`jgrGA8)FHv3EK5GoCz^S-D2>Q>9W`9`hrp7gz~ysD*mh!uSl)L-l z(a|`CsRn)f2l&+t024$gPU#&pEP3o2-Qo%SnufA8C(wQ`We7S%-K9(#hLt%kbpvpR zyM*I1UtPnqLQmur9dUUPc|TNLZTilH6buY02S`J%?}4pKG}f<^$Cm%oY8zK@WReX5 zCC5B|)3?v9n6}=N4Jt`0PIz*X)2GK!1MeZlDa?1{|H=OKKGsP5jqAvQ)S5C}QX9k{ zG=ufqJhGEl1rE$&ZKU^@0eWI0mfYR^uR@@o#T>_o-RAj-TU;DOr(W*Eof5+iu+o+Y>dBXkGyJhc!$MC!%)cojZVpVfCT2Ul~^mfll zscY@&N+ki`%|Y37Rn&pOW=p+Xo6iTg;eG)_<|$i75Sf%wp+bOHphs0i)~InZJi~H< zVOYR8E8!+6xviegKVZ&f99WuwsdzBn>A@X}cYDd$8o1DyKkJEVk}i#f(*@?bGoL#U zc@eHAK%`l5yq__^CWBqHZV`RVEDvuZ{-n%BHR4t7Jv7ZabwQ7UL*-bhZxPZCbi&~{ zwj%XT8a$N7f(qU*qz*p3U3Al)0{|X)4dz(Pu0rdun~B2}YMO`Zwpw~BPWow_TGY*aVs%IFXJlJq_Wa}K zdvf%B9CfIFFWKd&O6ty@p783fnz*~yiLMERfv8kyxDd@6M*{|hH0#1cd}k%v9K3l` zV-N}&HbtTAGx=mG^MDN}ybOV3bxeR2^mW(?n|CS71 zScHvfv@}({R(0?Cvtw_%db?MCPhu)v58ohi2e_h+Pzq3Tt+E)Ys1=r;QNtD-K){nw za_Ku{?dg-Rc;4r|_dv;IRv2o(GJ836-%sx>s z1RB55=#0**c?r0UJ26{P`8mfuMWW5!vrPI=Lghekm%6Rzi52Ck+H4aNb3M@(7k6_l z8&o~k(7(_RLH_~;s>JZ-g)YkyXP?|pDOnO>(%8vkTkY*1+Lvs9LBo|Ku0>dS`ukX~ z=$IbHMw{Q1~Kp;;z3sTlS`(q%*`|AK2j%8JK zz8qJdB-(&rZ~in5c~9$;d)16X#oVCei4Y8efI6NnmUh()H zzTTP7D|kZh9!u#wc7^A@D#QyDhWpb{`1XdpcQ0eU^Z@M_D?bWwYEU0PYvqM4_C7SPu4HYavQm;Qoo8 z{OLVga|;G<1JWP?YU*q%&S93vhP5MMzZkKMM#^P`iBSDv&I4a$jL7b_knAzn>#^bM z8MMk9me|tatFrEb!*i(3u1-b@@T8sXaeH4nL~&vjum6VtavX^#-RY(HDQvjrQ5*K! z_qXW1niuGnww%2SY2vQi zg!u93qS!$iaXY*&vRU~5sx*q|l) zr-ZbhE+lS`p(l@oC;x#aI;ZOZKw)Mwj5*!&K#vL0^m;6n);ePxnS_KkxW=WMlr+ID z*%t2U%eG^4W-vb5Xhq6yr{P>5rOnrhdrGFXd`|HqGLVU#Y>7#C;%oD+3_!C|FlUsh zNs)V$feS{_3I2S_V+f@rRF(R7sHm;WZgdPa&8Uv{A~UzLd3ij)r4qQH*mN0C)Dv+N1nNx2TU8LVVWdQ zE1}e!%~aS8NqDw(#2tQFB>Nhro}$R{ly}L!1dZ>Hjym zG6*-0(@$M2YS^GwXmmmnShYr(r>1evSx|voZB?s(dV}Z+1ZT z8B-K1L{pSb7W3iYf@3`@l+5^Sh~l|IO*sMk17t9Qy@X(&L(an0c=RjTZJ{+keYF>I zAv8e$0&GsVnL#?xs-)QP_MNCamk~K16hJATt93vY~alo_M_& z#YE3^5@`+Y;v|Tdn5bsR&4(;UJ7@Bz9fka(_WfFDDglvD3>Cqr; zR1C_bQX8ktUrv?7O3~IHc)3TMKxr%T9gaU!7=>!)XYF;$japeQVoil zCf#8rA%Nk6)J>RGEO2~)Qh=O6-%RJ~KHhb#3g`K`Hd6LtY@z!bz+Lx9bL|(oiRAJn zH#eXL&x*2u5f|>o&YhLFugBLylI67WEs2RT6S__+0ku5FMo<>K@hwkr0+_T9rdjNq z^KuVo2=sH8hke~T5TrKMUl>NaoV2H2#+U|DlQD|LNyp`YvZFA4tFHsxV_Xk(GgTTaSder8jMvRKbCq6;^2I=pJmrX3BflLtpivUr9ZzOUjaY=os)v=5uhm#g3Y$F((Los9kH zmO6QCxKvtz=#J&LR*8Pnf3ONR9B_y`%&&A z&kHtL&eJVd*H)N&vrnU@#8|5Z`;f$1)`|D5Q!#LdFk&h0I#?L0JZ*}X!oKedA(Hoo zacR!^L;Hu>C=YOJKVg&8wB^?cn13d9w2!^@+6}rI3>=vek_?Pqsy?X7?;Y4hjD&C! z@4kZ_XT38<*Wc1b@%KZVZEZo*Cu#AcIR$#&%z!GdgQuiHPJ^nA72k#@AW27yPs)d7 zwlKU| zwya)KB7K^0Yh<|bl}d2qWS=?9l)wK9-oC|^I#pf--9F7Ms&C$ut5WL)lq`~N{)}JS z)TMC4f>R{lo1UL23C;#M>5(9*#1g5KhAZ=)j!D4xFGz-X=Z_gxD*SZuvdj&|TgZP; zgA|AO`;YK(>@;!WTxXM`zjEpcFdr3Lw(9w-0LYquYs^D+fo=N`0ka!6SDO8XFS_&N z;cnCe7l%QfkM$;supM6I_*({t;q^8(@m37+3zidtnHDkvkP@zz4tLkN(`oo20YD5s z)*T(eFy-R|Lj`*hjND|6OZuM*QodLU4GIR;;gSqTk;C5cHL(QYnhV4_WE(2!@SY(& zV+KnyEHbeBI)Nz{FI>=HKX1JXqXmVQsMvZBa7upT%n#PitnU#aSNKnqi9tsgd--$>`n*;y zdQF<*T9h0&_6;|4kPb@QK;2Hxs@Sd20UP^vM=E6DQmr!v94Htv@kce@>O~?B5D^Se z5w!zl$zRz&wyQcc%)az==rWbVh4R8-YKFX+5)ARD7EVF~gpaBPkRJ{H?(CU83LnNk z;K{~hn&T)$f+Qxs$A!dv=kItni_c3NoPGpx?9sDz%Rx`J%T}~8s6pT1Ifn)N1ea6o zBU~bv+oXyw4M%JYT07o-y`cCN7%Bc6HhcavsRHNgHBxqV3T}6U?`S4%x~2LTpQJ(8 zpVsGO=Z#-bXX?EPTMiV|ejB+I8&Bq+U1joUJ#&U ze2y)EM7bI44La54o?0!J<%OhuUOhHS$K_XVz8Z7C01;42Xn1ML2ZeF7RC z4Q}Jp%u51P{vvOp7xf2%uUZ1Bv>@4u7V~v+-*4MOaZI_C>7Woq)cn-!LNsxNPlS+>g}6oR zt0~1gBg-gzfg+BEPjO`7Tf25Pcqtoj4ow~!{<}G~=w;6sok!k@hRHWv+Lu&It2llE z_(&r-Mw!Akm3b-%#?cOrz<1w^CTbT;Kk(C4lf%%Ib@r*=>aZ+Ok+t(E2lRXV;m2KL z!cu>1WjLcxo%H%f;Fw;`!3TC%k2#8q<$?F`-cI@ef2krqruTu^rRe321I%$a{CLByO$nOtxUVSUR7G(&I(5WG0sSrZfgucFjGOrin|cMWV1nx2hP{l%!_mN z3whNIw;Y_?cx7hv)_*PCT}sm)ktZQTZZaUl0XS|po|((*cxwc|XqY&1EoN;qP_88Y z$IIRkUyPure}=1j5?PhOeQ>W!LKq}V7f?EdFGWd;#eu^hw2>_$1vhW`-xE=WvZ%Pi zI$tJU4qHf`1fKnW@y!@vi2FXa|G<|RVPI{|2s_f(b+bjh5A)j*1a@-F8cC+O7BmW^ zS75r}wQjGK=2W>jfHf?8M8l^0SA(}evur1EYP;$og8X!bd_)E-fkd4LIutZP)%3rp$MOV6g%exe*?8Z! zX3p4pUx}pND2|CwDV;=CF_+=Mn_1qGEndjyK<$j4@-2x-NOw-`d(>e%ofuCT#xvEb ztQ+YUCn-B`OJnuSbvKK5SzkRQpcH}gOTwk_d+i-N4QSp-KmZWl+b+nx)C`8GpjW{t z7Pg!D>A=YH0wdk1g`JpS8m9k<9{1CR-=PgvPXLmS_QP;iTZXe(v!mCkwjOHQ8grNv*ysdZv1kL4$rPPiPK2!vk1*JG63Q z=V7?>gD`oB{>uIVQ|jyN%2X%u+$?adGeWB8aPP1?Yi)KTxHrNuERwg5+R^07 z%pud1OI+X={6kYLGdBK8U+xg^W2MS$BMDX#6gbkgpzkE8&Kh=mmrw$5<>`ZV4C2 ztHKRR_?R2P*RkH~Ia(I)qp5cSx5qJ%0mI+mp`temZgs-!W+42(=Cw#m-koAKzLBqAoYE^K^5L8==ewsJH^-Us7NYQ^9;pm27 zl~t>r3L)JHG@*ElwQq#{?H8YcH|I}cY?kWNL8f3rsxO(qQ*Msdd`>i|YV6E%uxsPF z>|<_wId!ktaKrhS;gVC4tK9_u`Re3C*=rPP|0u}aRA|_JhT~l*OL&l}1*t#W92e*#)2# zs-F1MK(@t0y0%3EBOq^-`enj!OApME#hi zocLmY-RKzZ?6bnvzD}{p1vuqv@f;-GEY@M%PFM5HXSK!NBJ+7y*DES;S{v*M zZU6h*bQGdXqsNSaG;e-;0Da3|R`g+Bh+>J=3w+d0rGbN!v^AKL(Wi*3x37VpB@ykn z)aNDMlh#}J@eq?dLx1|;GE8rF*YKHrx0iW`>&)2)W}W$z8Kw_-cHgsEf=|x_K_H1V z&vCR}EyJNNPVVW7=jLXll zx>BBAP6K(5OBWNjbkB#pR0KlENSaB^;z_nH@$z(U-f0HhRdtbG7iD^ztp2rC%ipRp z>XW8}#;bgNlRXB+D;4kB%*i6`v?XH4k4;rbtc8s{QfWpA@IZ#6kR$72!IkSZ;s&@o z)kw(*ed?U!X+q-=zWsjKB!XOQtn}R4K$mmvU88`YG2-F&8Vsj-Ge_YVfOvP1i&*X* z{f*|DZ~=Ejr3NAYzvraJ@+UJtCb#qHUZWa!pyKnTMf5p>()@Ifl_zDN3~878woK2UY7C{PVAOa=*2{ zW`tx6aObE&R$tOt{UcV~Adb?4Z_~a{<1I`Nfha;1Wdg+~XT;VcrV0{&egphxJqi;R z7=aS=A9*W?x?{eUm@5dc zs0$d>+FsW~&>XQyez0a(wDb?X6S7#+lYH54BtFg{AT}9|1U)zb9|!eRPQokX=g#99 zOh2hGv5{QV^VC!0+u5D8y&4T~_&)h@&}yCLR9ralwF+}E6(7|Pp)++8p1H5;d5VUQ zV8`#A+d~EHI6POIs`P%1=9LURZ69Ex8bh(|{I?~^#u>cqXgt*eQ5>PE)(6}+wn?4) ziSPSb2*{a7GR#`xog8jY9QJW*X&*DWt{(DKPH!(tU`iqAe$!HPUB)78;}E+H&AWn$ z>@9&yT@D{I0NmMb_nt=)+~&h-b~D*Xn-NXB)_~&p3NU6lQi9ygYB(SRuzb*6EofSFg1!Sy#b&)1{*STMGU0i-_ucqifi zbp-8E^4oyz?R1J}D0GV!mkohRD6Fl)&5K;Nq<1&G6bmS%4^*n%r2{$X$UTnDF*71> z4j<^&Zw;ztoPuA@SvV<@#eb?pNnt64rY&Tnzzl~q$OdCKixsJz`a7h&3`;qGpg21L z8;z6GVy;|CFXJZ-;ovU!OjJz8$qrx+>P*6s>+TCeD%Q;w`ik|;*FrUAR)yx=@_Xu# z{#7G%&i}(F_>*YG#%!y-5}LW`jw18GJO?k&sss?^UTq|&L7iW(_nQ-tf_A!G!=WvJ z9r`nZ!saM2;%q>KFwZPhl>1_>?Og&;l1}F`i>pxU$j_^Ou)U+w|Ha&us^fMdd>-Tr zO$;%Sz4GRB>hXVfu4q@CH$WrMGVYB=N>f)Gb)d0?@MyJ9$NnOqakKkX+Mz$Z&tABk zXmzHz=D&s!b+tRso!v=QyRF48f}ODcPxs1R>&D+$jq`JyPWgVROwSo8nb9qYYN!?( zzNA#GC*2wHb?a77nUNNDLhqkXgqk|jz7yg)6Wv*DfI=5p20M{{b$OQ%s5erS+cAms z<`^2#;*c50=i`0Tmm~brPU;hOmvJ>YjTeuZnG>Z_O-4(z3{-*=4#Xn=SUnoLB{gQD z!rb|9sr&{;VVSG67ge)ZR*lyu^S?;T2+{GMK)bV~UZv81xCXblG5?>jG>a|2y&od| z&$E_fW?r`hQ4u0Bp&2Z~nDpz5w#1Cu+89$#-|~J_y$c{-9Q$!RjVKM}sy&{D+3D*dZs@1uRw-@Z%plsNz zBU@#wdMWag1}`oOt`Osq&y$0?epsa<@NA0TUjR+kx-E$X8a#26Ebc+UWj0i`bxztB zv@v!Tf?9N>Qxbo2!Zb0i?}Wj!P0z;pUA4$%oAaKpMV7KqHT$~*mAc29D%e30)Mx$* zyc(8zxIh*{4GbkG6)Z;l=%uM%3|a9;-;%KS zes)65Qx~C~QXwNT&$$IgXgDJ}5Q=Q8$49Zi*jMUI)>gQdrI|770LEYS^0yIBV2QnE z1SsebJ*Sy(aB4o$x!lHdceXkDOr_vkLgsR5k~8QyuSx>`dX-Znl4Kh)tn!cHnN~DQ z#wRG`x6P;48x=EY(o0b=Z zZyxiHgTPm!7gUMS!E#7m_s5DHhJ-=E`y`M^?`*g6n0lkpRRV7up#I6;eTcpf{pUU4 z&qL=faCxPQ4tRF4t}fxs-&r0cS$wCX;|c1U#nb(WlqnU!gDm3NWf9lY6^J}Xu!}kM zV@fW0p(pJa@Hc}Ct0?4mC-ezGgiZF!FAm*RTMgBfk{PfB;*Y%V@SWfXS@^LaySDf5+NmWv z6XMb(KK0i~WBi+F`Gl4oCyGxS=1D5C|1(;-|G&{nBJTgtN}ScK^L`P^Yi#fV15>&f z#`|*)xe|_VCAg-KF`dCqY=ciT0qZq4h&DWd6q23s}P?Y+vzN@t&Au6?_| z{Y0|0^^ExpiO2y$GW+bOmQ03E!$i#Yl<2){*x3zT<|BSxiq0=s=p$&i-(q+A4-{zxquwV}m+aVwS;Sb7;4QWKEJ ztWDUHg*bM=Vli~OY$_Kp_HQfj0+|DeaJu@JOK`0M#{5%|F@@Pgf6ymU&IV{&N^(Gr z+7Ci+L7HmZPYeqOAsO-o&ED>YKBXN(I#Jm|88WhmNJvPaUT-WTb*IPlf(V`L8|e^2 zn&#tGNQxJpUo~W%O)9t_#iTpVg~q`WC6imTcnCzYVm||Y6B3PS^T_o56j1a|#t5O&LGmE>YQCRNgE zu_pOo&mCviI%GUUa(VZ5AjY=IT9BVCu7PYe9npzGhB|h9xM_%h^R25=eV0=lUJv*2 zopGCe@M$IN zSIh={j#@rz-L`E-QB*LoFR%O@+pQW%!P!Nfetdc}d9_7sGo{Z9p*0jp<}}`g61mk} zo1UefL6SQq>O_PFnwzO}%brJn=L}xCEjs%k=sg|j8x0#_A3?|jX$PHK1?&2KpU}j3 zE;90<`|tuVaGHJ_Pxk^`n|ORCO0A>~lGDz;g;^92Kxra@K8yJEn@S673VK(N0?b$dE$ntd0mD-0M7F1_K0CXCfA?r}!}j;564XDv#G zT~rH^;A?b^!8gZ&-WQ$Rg}@p=mh^xey0r?Vf)G9LJU-wC$_r9SbS?k{qxpO-&As>E zMb{MBDoeLCYN_V9jPKO2K-dVBSG$df4QBQYs;rF5R54aSBK)~%YX@iHB>vZTz$lcJ zF7EZ-J<%*Buz>pD3z;Cxa(?M#6pjb@us!Zh-Tlac?_`)@-tFpOeGg7hC`gxo*4HT}YdRXR-;i`j|!5cvsq@bM>R)ZEo+%V5AtXi^FLx zJP;m&{o#FI6tB33ZY42C^|p54pb_g8pSyC%7Sd3`nxU3K^^ET3y!TLP-;eB+^c2;s zvGQL0THJ44yv!@Y0efCgi|gwfdewW__tjRN*VJcqG8eG@sp8rzq~!J|R-&^8AX5o2 zcyc|CLae^OP4k`kphEOMYnxRY3x~L9!Ccj*fu$&NCq>;4OTMW}rQI?ui%)Im{`&?R zK8NHi#Ns^k0L$Btam5+|?brKxooWLIp5L>F*V%TxWQ!AP=|+nLw&4D-|8EuM!RKZdzmH{k`jZS*`A4`^EUp+z=OVC6z;P;{zyAD5t@A0dDB~Q`lCoIeba5o%B_Nw8;m=8&y zcQEppl(U*us~Kh#_NZW9k;?g>YqFGy(qa0GM^K|^j1}`;c-+c6meYrZWwU2l^;NLw z)sZc3L;FZ{HJ^7C)_v~$E9aWE)WoqvJD;sg^w@|*9?enh^U6wNUISFXm8{OeISVeY z*3$FNU?)Kh+yQzf0gj3kAppNwaXk0OBvpZVv6>ZeMN0+^0+_cnINFrjX)EB@6%no5 z{GAd{F6XUeqJ$5qSnLK+bW(8J)ru@Dw>H?NJ^4-}qpX{(;!F4}!6&ywMx4AR({{+t zo~P!~eH{m$14^Bc@*8TxIJw61VU2<-xoy7v$>mVIcG0>@y!I75QduuhZ|*|I6sq$5 zVjU8iiUTi#hp%@LpNm-`w};LS)f9FNjE{B0aSDzBTbz|VQ#G{4Z^rgb(VTS$Brw5~ zZ*hg!FJ?%8y-{9&u8P3@Gcrs(o{+kxU8XuvnWF1vm@TI(d}rm50wIiX#Ev==ry9^L z9wDV8ha-Q6mqY!VhJiAgQ)oigYel_sgrZwMfQKD97EQ{VB5hxiv@dJ%ac_EYbBU3L z`o!4sC5y@sP-D=yQ`cIeyuhN~Q%ulLlaQLqW{Yd?Y4|0X8rzUcf?vfAf?{GmpeQf3 z{%SNLTvF4mZy9^@<%HY6qi#=K?)R{AUhs;Iqko1?;}-Spp}oU3W0>ky-d!}2JOlF-jEnqzTp(5h)LVM(eY*XkcTxI4P-uhu4EQt9S_K7 z)XIfu{-rks6#mJ@?J}Za6~x9h`(s#O2+Pdbp@5$fMB}(PK; zXz80LKLuJcv`FVR%m?2HipqF(LeFKQ+4i*aW3|gqn$(K^AU*NO&M_K{EIL4VzaHOpx@WMS6XA|;Afpltv$(U&0h%?1 z6+J+r5Ygg~JrG$7l+$f8Nl~HT9U0UeamU41E}adHgUXs|Rv1qxcSn#g20Xt~-)5w! z0a*vva>9orouZ!mWsRZceD92d;ONg7`9i!uB>IE;f81y&V&tKij*EPMIUEX(l4=#x zOzGQtM2t&$0=-w)`U1nvM+GGK;GL;9b#Iv554H1rEU@U)hWXb%{>L|=rysurTgg(MLxf|~XH+>Be*9{=5#!RW-=q!1_E*|)m1V=Fzz5PkzQ9}+?AqIqO@lFmY0 zZqZ4R;*6fAJrv_)Dd%4KSEGCsunocDG<#%9`fB*LY`BSen;y0irP_~9ld}o<(>v91 zasR7HtijlvFAPtgBtu$KLjkxou=_x*uvq4=#5VcW1oXmN7yxuk_FS)in-$T!>IK8U&!>EMh%ThPlP`tP`iFe6BpyG= znWpWv$LG~?z5GeB*wKG3rP#Iwtft4vF*1T54*;3&%-*Fti_#G)+=JP~uY8bLQ`!*f zGonC1oK==l)o{-%OO1~@XA@I%m2@R-4^rEuy58--b{xD@!JNqMgZ|XHy!%sN#8-=<J_R@73@7&0e+JA{ooH8s^p-=NwHb zsRAbQSHu`X@A*V%-Ld+bLDd{_(N^id5_<0Gwjef^$gV+cVGr?0E~B;NZUU<}C@jX^ z3EBL=QCbA|GYNupbJ3VX&HNuQkQSG9PU8{>tf|#kYfp(kNg=r?yiK|kn znT3sfm`im_3*K~7c#E!^*;99`4-apFYUc&$uf||3W2F-rMbSQ=uedebuEk3bI36I@ zlZ{Iwot@=8uYO8uGuW=NU5}Boe(M!0rgG;=Y zt&sgHnIcCVloxn&$q)Xk6I8IcTO+_!-uqx*>Q&+x;71Q%H-qKn4;Oy1 z2S#zW^1t`v(`iUn0sSaB2CLlglKMD9iy3lOD=k|y3>t}K)Lm|)mzj9#=j&wB!DKQyUCG?EDMDZ4g#@*M#% z?Zl%~KS+i%nw6u_rdwiJq-d5tioHH)%auU3GoPqYCkK9id^@fG>Myyk67xrw7H&p7E^0e{XI>8Rw-vjh2g( z1QUIaA|yTBI|4j}MVjfch6GQ@nha3N3pv5ia z2b{0F?;gMW|L);Eto&@+E4RO<;?kbh!f21PW5oW7s4+kZvgx)ucE38wRt&C@$--i^NQlo}1f#XdOp-Ndf9YDy^bWD1k(`~VlthkCb}#YDdD z9!P>P;DjF~-)QvvoU?Q_WBr(4zxx&(cz4h&X&>662BBnMbpXUGD6dq&kxyL%mDFI2jc zl@bv6OhfigE@XDf$5g9G>*R=DX>~s0rj``*GD5>qNpnd6h2-*f>|Xn)Q&UfC^Nv_j zel)WS0MtD zaFLuR@H0iJ?bH{W^u*)~Ku!KlkzwdoTL6&X3jDTN$gka*#TA7ppjvI|jTUb;^#+5} zsi7GoAN1aw;ypQ{(x&a|T<4kDZ$}QVAKhykqYpHr!8HAJ6p(QyUSZ00gqwekx_9R& zr^W@qToNU(z55%Z#&gDO0ZX|elJ2UBk-7Qt59SN&e=d&pK0Mp>YwSEe&2qO5D!F8I zob$4hS0C`SOdbJKzv%Az)mHwhu{{~<-Y$u6grXLsIeni+kd1%2b^ZAjZP)^q#x1e^ z9I-X_3ku+{LG9LtU>7RrCJT3w%#x+L4sPmre&n&6y2Gnt&60B++KWZ&C@6FtbD)PT zZV<|ZBT@7*_FFf^NmPjYpRA&15y9*!f}H;gEuw!?Rk&bqI=>pQW75E9_r3#&Ld!k0 zTlk!SYQ+ZBIl=B91@A$;Miq$~X=VLIHy1b(oMt8vgpex*&NT}yb6Pw6y+5DZDd=i zqE%xGj|<$W9;Uz0pbZ2u6HdR(?}tu~bl4fVJnaWeK;?p$$hDw=ZJIus6 zh~7##qkcbT(z1!`h)@bM04`fh#rMdK0v)Ckj3*7T5lOEY`k_s)Qxe8!d$@R>@cx_lIYzs1{UZ!e9>ZG{z<@?_=1r;k!idJV;)&(O;6 zP<_@iGVO4+bsv9aB%fx7-l^G|=16iy1Oi92A;4 z?=%_!zj@3Y<&TfV`~dPs9WJvVtC$E=_Yp!I?=&>*XxOJqF<8qP>MAdu0jDy%8U)_Q zzj2!Ln-X69TVTyeRrWr~ADlSF7i66Oh%UirmI!U66zcg@nIfQpB%A~h7OZMAeW9;z zj=s!%hMU1EE=Xr!aW>{Zi}u#J6W*{e^Qxw|iz*S-GlGoLcSGRX*rUHVnH9-bz!%1M zS*|1wIWw5U;@l173+8wlv|2nJG+YA6$r>Lo-Bvtn^sfjUb14oHQl+?X1xhsm(A=2h zm;e~6r}Ml>5v?Th%g#ow^hd}buM*eQEG}8qM`}|>a+>P63g9Q01UM+B2)P8Ic!yo9 z%(bwi47oI*HC*|`wx}mIXDrFSA0f=j2i|;x`yix_ofBgvqP+kd>63s*@<t28Jrk5MAZAOyen8$|Z zHr{qBjrqIX*XiZhFPh~WlzczZ3&ha+SW z6Kc01og2|=!zE5t8KBbc<_5wCPo9l;NeN41AyaN%4GCF}1y&dzntNjt0Ttt04gd90 zAq7+drj8nqdSzr;-`{#m+g!kJ?lE!LfGZo5rCw{$J;{BmhV=>$C87KD@pzwxwnY#n zj;c$b@n0_9gNEl2^-Vaz|A4`KE#WGL^g~%6g4Xv+q$1Qr&Haz;w*zVR@b)-B^5D+x z+7C}W!P5SHEWzF98lWti3rX8%7RW=ezwLS)&aXs)% zRLw*OD~~ZPd!HDXv<5K1e3)nhTDoH(xHPtiOu3J6?L%feUZ3b~NAe2JL1&oOaslF- zAdYyV0tx&%VJmke-1~I<$81-siJQ2oG)GzsJ>H3uLJa_GR2He64EnNqE@xr9-2&n|Z33 z_(mXu>5+KV4AYOA9AD0s%;69!b#&1|TzS%5r&l>9$^l<6`+eZW()#3gZNd0fjKl42 z+4y$krjebKi^Rd=J?CY8CHpLF^E1`Uva!4t^Trf4uvf`1x4#5GJ-p9K#|V%K1%~E{ zSVM<7CW=ZovNnmEEi#|snIU9v0k;CpMv`I3^Nza;nN^L&cI;piEeN?Klaz-FCGu%` zn_pEvJhJC;MGo0T%}||*5R4g~Osp|^Sh>JZ1%JS5GHjXrS4^NfY*1Lfd@qbi8|dsI zmx--4c0I})D&GNpeZD=MPg{r&r<~$9-l&jpXs1xsiDQ2pqWP?3ELbOLj1PcF$j3m2&Uy}YA$wr zfdcoD>9qN8IYP!b$Q5H>dd^Yo!9J~KO#x+QqSjdKRD3yC*(#~n<(af@**EfGDWLJv)<_=Zs4)w7@X!FE++b%ZS%MLKXI zJD^DGhnpSRL&|>O71sb3(M_#QY|?TAlEzVKuaFPW#urdi7@)kC<4C$VN@A3l0TurU zzAW=L!LD{2=!DQ&UQ2xi_+$0HB_LuLjQwl_zA@_$7WkY>X@D%v;N>m&4$OT$#2BUl z4mtjcSsxtZ{DVX_?WuqkgFtHAz{S+F!O-sHC7a67b&J{c&RedsyTBf;gT5X(jL5?Nn*v~o)MkF~ zm+LuyZzoEL)fCwaV59`eL4>DTwp|_i28&h*S!{}uWV_KIIn`iZ@*Ki(dy2Aom`P??v&kJ(udMdgFqPkHAx0pl}+(vM7Hf> zI@OzqsUX~5{`-ulU<1$tnX+UX#vrY>?l!_kwc;lZ?s1F>>L%t|ZzL1-} zh1RL8vX3vU2DK@%&rt&bP67khJa3vWVbh5Io6Uek!CnC%F6qjgij|u=r|0--VTMfb=Dj zM{=-epjHCiOi2#wZ`wn(>;29^w1uP^OZk0QvUQ-Y8=F~gdN{wsD>~k73hnQ3t#|(c znVQmp2oKQD9|P*FyqL?n-{$K$5Ph=xrYIaJcj3$M-A`t&RwmwRkUw zh48}d4n3_^SML2QYA=EBVQ8R#&5DzN`!5d3J`u9dV^h-sYo% z=arMo_&lumMQlnP-yjsP(0*skY>NzI=kDHnW!JOvKr1hF+|UZ-?Wc?+lBl($Y+!Gc zEB&*T+>a^?PC1_*wXht-jG6#H&e-Izf2G5rJt6ShiRT&`P3SLXhBHwD2(UOH*fkfA zBhl}l0?zVOUz>Xc0@aUYgi<$Oi}unq`tUK=-^)YCCy;v_-1wRDr=pEe@7$mp)&y8p zZDAOXy6#^7$Tos2`2Q0+0c2eS0-T58BxwO6j0qovb3d+LeSr`EWTJ&NBK&YG6Kb+8d@amx{|C_a^dq zL#Uc1i8iiQqfB@jSqPuqY!z9(F8aQh8lyL}pIea$>|PbKUEqOd4Az)#R3-JxOOa|N zOkSE8nm^#vQp3fWxA9{YU0cUC?d$3~rRMDXTM*ZMj-~YbWUI$0yEo9{BVo{b@G<65 zD8CH3xt(YHE2kDiC{P7vLrd9Sm&kHKqmM(Ndq;e95_ivb7km?eqjUV5_=@fb8+yhC`VkpMk)3}m%D@kbD0m~5SYs(i&XoLad$!H&;b&swH> z)q>4^TFjwEpy>Gop|Z5Glwa%}6~P8apv810j5CZmj553@LF^Q!dzM`W2+jE?$nJey zK{XYjDh}dx_Dj=ng2-iBu9-|!)_mRuf2E<|PcW)BB||dMPQ&^65H!RoV{xn)ptO0| zOdLxa$&3dHf4GBZ7Vx3~kn%Igdd0mo0yw&hs6$hSD(y;qW~l$^bL)oNNXgb)ZjxjI zi2WSGaJ?0Qw0Hfr+<+LyDKc%TT@`<9#X_%VUUf;BX23&t{fNmNOPh?ajJ;Lz!#Zh< z5Q4m~KZ>gTkz=+)JSY{X_b!L2Uyr`1K-$?>rYWekouZ;Tf?)?<;KTk`WCVvd>GwQ` zg0>-wO2sKOq=>Cph$_&i{;(T-@LA%l9o7dXr;!+GpprgC;fD^^~T^ibmL;_-{eE%8X`bDJpG(!s#8Hk z?=T= zKj-h^6k#}*N}rWi*nYeplLl%LpO7q0<9J*?D^>fA3#o`(ejc^@6(I}gF?IxcC;Xbo z)(DHN1)G{sJQTlvR}I2&!f4bbccpanDI8Ud`Q8692pi4lMH!dr#fa%%;Y-vUy$S~I zf{00Rb{-_Rw7D%1v{Ic7U>1EeTpG1NSI(D(`r)gPD_U+1DG z6&1OeMvwB#i5VD(m_|lh2ec2yAL95Z7xZ#z75}vOQt$CNv9^eAAlDB~z(Nuc#*X zx8?(~N{f%KJSenF0~P8`OZT;z%C^#5`|ft1R^a5E->JGm3+Z6zKlrR4)S{0g!W&9x zpH4-Dg{E1IO=Y{ox}q0hypV=&om*ll3KeEH2=N``ukO-KzVJfhS(;={_qQ1SKNW0^JHkvP=US%#O$ za#^UI<^6IimA3rDpqUg_4#|A@YfIPrnqXiNVO2piGxg|Mng3*OloP#fPjJZXd~LoK z9gww|ve+z5_N6n+s|E$(48h!7ba{>S+ennK4-s@2dSy5_)(TTZYs_^OS0(1B1er}68Wgc=jinQKj4})67|=W;QF7=daBQ}q*HPq=P969QDqX6*8(nB2 zh05=9GNc`34eqLx_IhPK5VIUokVvQh1~G5LK&*%G>5&`kiQE>C6IC%oeF}uN!ms67 zcVRD#`7GtqX6o*CWQ+;G*CKv@v@H(4cUFzOLXFc3UK=U`V@1li#o&d)d%zCDHePvfHb>Efd_C{i9s z%^%{G$eZTO1O2Su{fI}x&jODRkw@$o`;rJ0tSO^5GKi4;t)GU|Y1QEcW-dYOwli94 z8bLm^N%7vgwY1M&brnj>U`gpHr$j z3A1SKRMSUISrE`7gj0xoMBH_Xvuv$|m@KpOiZ?%%%}s7oCFfgDa8w}dtD=nF=$S=! zT7IScRsgb|aZ(%1Ju(k@auWJ#`2vCz?J%P=6R+}CK1boFRKClPe;LD~NlZ%9VwIZn zT3v_qTSnk|mVt3NRmi-(pHi(%4<3K;Mhz#^V+giiikU6HEVnl#bZ?X5Ft#Rcpgc~H zu>m*>JhtOs$p-Lxd&A(YC%e2+3F3 zM~IHyiDs8E^f||?to@)yA2hem10ipjn>CN?KBKGrLS=lZ;##R&?$oofCU>ouxE(L= zs=UZ+eUh|lLU(pv?S{6o!MFujwLD>>fe8Z*bp(0ftlOk7if-%XR(=?bIkUN?#p-2T z0*Q>z+~k_JBzCn&wN1i;4L3o-h${@M!s7?8@Po8~g_0unXT}&|v+WZ4-?o+MbZNNQ zm_r{b-m1OyQn((&hVOLf#-{c=Efzr;wr1X8%9Qtxk%xD7`_FMUvFqjM^ORV zSrUQjTWEJ0zD8$WBgqstya#28JP}SVn7e#Cu?G2G`sK{7Iawizn1*Rtn~dBF)>N9djT($uF)`L{8WBgfxQxi}C0FjOd{^uC&6S zQISDv!WDZ_8KC`+>jrwrKq{DF;20wPMY0;QwDejfY0c&YH2_T|Vs1UM|AUvX|3NNxel zP-S1)MFz!VGsI&;g$|V*U}LARv;hqvA*=+T33qeau3Npzzw}vx$$`%d_9uBAmW|?( z`(-*X)6mpm-|mPpGKf$(`34BhDXiUB1s+@n%yPGmmb{&~kQ0l=K0FKg)OPy27U{M^ z|3N+ldjVFjsC5tlzUOO*3Ug`8aNPcg-3_M4c8UWMT2RpnrorP-4~A<5K+XU}a}d|h zW-IVv!+Zb`WwW9Lnc2dOW~M@R2#9L_WlNsT0MB)8mj00iA)Q7C9+0_AcR zBWM~WqCE5D^1;eJC{&~VOH7oC4pc(GhpDaRv=>k_-O!l(YGB|qKPYG<{P?V(df9o$gRyIQ<)m+esEb3@0`;-G-@vdGl z;BHEqOx>!X*yU4fp6A0e8H_!*8I#EC-8+fYUnJ~x^dA*>GE8g8<0u802b<3sAP%U> zITV$Riy>8n1d_LjBGn(mHgXc!Jib7>$y&5UIZg(7m3!^!c-;X8y!y2!XO z%%4VgR04l)4kaY+K+p@|H(8J7!1&OTfFIz1?8W3IE$?-YJ%5iF%pK1QCyNjTG$I?o zXd%uSqLVyKW~`aOh@QJYQ{63+(iNWg6PwONgL>f6;?s%PMu>rL;9woE_bRASMEPRb z*ZN6~z+f|akB#Oo`4YiB7wh^K4^v+LQck>yF3Xo=^c;QdHSxZQgASHbeTsyUdTi?w8R;E(HzsuInKpu~oBM#qLnF-{ z*lS*pQ_tF#?gW)Erp`w3P&}!oW&q>7>s}T(xLAT$Q+#|{cV9}|Z;q%+z)2s?2xS~4 z8i0>B6vf^U!(=VM{&hBd$%La(;paSd-v=?tUGeU1X9Md(~nW?)WS-<*$ z;^m3=PtX8BN>Kp>0gP3JzJ=Kus@)kIkte0BY~2#-)J}BX>GcaAbdGl{D$cj zINd?R2uBpYwQYqQqw8d!Rs`KdZvtfGJvmWIGAa7Bi**2r9tlz|OfR>yp+STiL{@xL zh^@S|Oll~to`C7yudhmhbRH(o;u&eoPl%$+-{^T& zpFu!$gaW7Gfn0_>RxP~V5{U=SwJB4l%z(@}XX#E^^!c9pkH(#HbVWBaKhob=)k9ev z(V^EwsITiX#}wy;+R7y1sAptB?K+jHc$pp%MK$u!RBk$2m>HgQ`!W*J;9w!_N?QY# zJMVn7g9-K9HW4VJgr*@|n}$WN-y3|WqbOHehzm2|8|iw*X;?uYv+DTXUA?Y0H{|DjqBhHCwuBmxG|S z{-m^DL)FU4`_P^9a5V@=68A3em9;lYuBKuq~oFnoT9LYBwnR0 z*RrdC3F;{5@F{YRlIPszvd#LuYC*GyLs9<`1mS<(WdnR8c>7m!&zH={%n9_jEw?@_M?T zXqxs-ytnPRzZ2tv>i3?W^O_h?qK*4db>VaMtUvXEb1D$>2RCWOpI*B193!?u_8cC1 ziFG^l?40Xq!%mMJa6I5?4(=r97V7W{*S(x-05jy4ep}H*wbMnOKIqUz50x6)Um~ie zAAe7jCCuVb-6W_=-wreAj&%gJb=QuFpJ9^xP!5V=U$Eg|5bjdIhqsEOK>tPF%WW3r z=rg?K(Z#|ATX^BR@KYe~xUiy!8KWUJ=CbB(&!;A}QA}g_+{-9*d92 z&4JlVCl0;!5uf0_C02wK{CYM@o#>Kj=M+GwQj3|uuEq%iU_vtta|CMtLD{a#ytNrN z-+>&*>#TZ(R4AU}%h!AQbzPY}hV5TbAtLY8HXpuXdHcuw^8WLrb2U~CV%6Gj2&I!_ zT%ztANC$ouXgeuhRV0bZ0@h_<70z%*G)n2!2G!f;PDKi!XKDsqdle41; zmg}$B^jq*n{Mq5NdWToMq3fOZBrhduj)!^UsxWbU&{^3u&mw;~?q`T*0`&+J2A%%s z9vebAXlOTEzo)3udl~q`$>b4#C!Dw(!kXf;pyV2|A-<=~m&@)oVA%U%zc9t{J>#ud zO`L{y(chxfuf)<-lzQJda&^k#rwIRL&SLaN!AIyL5?GbH z7Wl|QD3Z+gZ)VXB8=Q4Cn0(YvdtDa}jh^yZGHq?sCCVImxCKE2@m3=J##>3s;1Cwj z3s%v5=u1?=%2uEo=;mp>T=02g^D7>N(&1kE-n;v)#69K7P~-{U zGlJ(7_Pq6%GN`Xlgc&c(^(1l0<_7c1N{q}(zx9ch=m*`5sTXvv*ne8%y@ACpQobcv zmWx^4&5^`o4&0Q+)9dYt8PL<6>MdlR)QCv9q=a~!Vcx8vF9tx^SOx;td3(l5dveN^ zNGM&6Q{ja3eJD)p^5mm-8>(+#z`Bre$!h}4`R_3I?}WN({!xvK&1TkEe&IZ7xAVjl z0rZ`?pyF!&WsJWbwG_$qqRjqo0g-#c5SUnDDD8Ul^KX7? z?SEizk-FswN)PauoDWT*!3Bep;r?$d5Qwkyx9=aW4EDDNW*i{vj-nu|p~>&=_Sm9T z3DwWB*uYA`5|Ks0s%n!?1ZSVp-C-^6mBv+mOpo2sor+dT!~y@Ci zf`)hApz%AOW>(o{8&vtqB~@VjA|}ODa1$kgVu>*;*>QiCkQvpw;E6GHcXLnXcA%Fo zu|4%bg#beA-kfZR-N*e!Dqedct0_sb1Tdj0D3Ru`@RykAr#!9VFiIiia1|@LzRx8rHC`NFF16!82PFFfQp^|YFNyij)yc+?+k=5&skizPeFw6cO zO-;px9VDOA*i7S-?{9Qi+?9)SjffvllHW!aV)JIK=cBaATpqbuq|CH8*v~fENf&`CI7v0u5 z11%7G1nnghSB3sF@}ijCVSLYf3!Csxc?M(H_7?~8uEGiK9KDs=>e1g1%nHS|Hu-m) z#dYgcySz&MfVF}JT(AcomS7u!N_p|g>?5H6F7)n}{LD2OccRk2-8mRyCLGJYd12%$ zB;;nB_x>R1F;G+=!)}(pt$1F!oKd2-i?u6Tw6>3pQxtJnKY}* z&x1p*ylh@lI=5XEJvAfmfaM7v1o8ilp+8<)*+D#eVv?qLEbZkfG9R}ui)}jlbH+1N zq9{+_hPdjN7rTnn_bkOScp*?&bc#&b32`TZEy_ZCAC`ax&z<|eyp-|k-%+~xas=HF zZoV%7ltT3N(C*|Ei}jZ=eyOLx#STX(n)6F0dv%=w96#19bOz1F?jA5!cIF? z(~jzZ?ziotmm>@U^Bc}T%tA=SXXGd`TW!c*n(H@*0dF--Z%uWiI57$l+)umx28Q;jWKB7#*0Bwt*p!zR=}t11oAx%oow%#r9$ zYhbA5Cm!3bbC2MzI&ar-hiL^2>8yuVmH{k5o9fL7tU30?N2;recS=9N@ zfIMZ)a9>Kk$t5Ue{!Uj%$|-rNfn2={wK_@cl2(4oeZ7-v^5mHcF)u&kU#p$xq*Cv$ zs$*j=zqo3_mH@kRFt60oLnzD^mf6*AK*wihXy@L8hz ze>uXFQ`5%G`5)p+E2@8YKuQh|VBV5}02LMwzm!!{(`gH2KKOO)bB;{iQ%6d^{ERI} z2Bd-Ug}Ez)UPh(?H=Z5`;44^ZN#o|}{|!+;7QmcNXuyiyaF-98{Hqz7M(9m=U%y9#7gCXd z?Do);(P^-iWr4Yl=95%Q456|4r&6Qb?htM!o66sFrl+WBPAg~84>udlh~CLjT=9XT zZzNW7WCRV5x*mHQ<-i|k-~bt za!%8<-o7xn92O$K)xx6|P^b-_uVE#3`j;y(NH?`Kpsz)qINUGr_$b#DnIzVAY?M4p z6>0*(oc!Z|F>NEKJsVTrIEEq>#C$6d5$vU`Y`x_$l$m0n5{uZrm1}~wLGtM^cX$3K zcI$oDrJqRk#=!g(EKU}%hN%&`pGEr8+bo&zrQcdUq#>8?mTU@Kvzk2_j=S-MT*hvG zky=QqesEA)fylpo8Qh<()f5k34g)zhF;>QDymM357(#OkW`(VZ6i`>yNw5M-d?fd{^QW2dtMZQEfmF^0|umx=KAj(v83_1$Ypg{Deg|fR85`{_}OY zT-w@1b*YgS1tWU103z84`_9;@rwjwGc)wtUjYhoZnbb^5Jw}^4!93n(>2Q+ZJ7{e9 zm|V1JpvEc^e3*y#>ew1&s_V(L#VU%~io>(11H*ehcn&mkH;HYr>t<-xO4Ow(f*Fjw zL!)Xk_XXb%Lv>WDw92q6Q(Dp_PUFvfOhCj|ar%+vtkmBbRLgc`eP<^8%}%S1cQ@B2 zmt06=SapgPSL4WSsNl6&)Xbq(DPYv8!x0W(k!eU8H_1m8)|R-<0Q+=ur9aB4O^??1 zVJirkl3He>vB&JzCYZVDKe%o+Ju8Ml8-WcrV+!W65kqUVSh4}3rNCQD^$Hf%4tAri*Nw<2E~W&}iAodD*4~Hx zaNi(09CtRCeY$74naTVA8U;j8QjN?X#k9?#oyixOiD}PU(JjlW)vfLlOla?tsxdnN zqOhN0iuQlYbJ2b!{yAOkoaqGOJXKOaQjrhuL54j^pNLU<)Q~FW@sM-#tHzXqq z16Y9Xv`(zU?qn!suNrV#;9_?pWo=%fGoN`lE}TV1`Hx|3Oxt1J7gYtb-r{wb(-$9# zboU*@iQPW6H26I5mqr(f;G+HZ0Fn*MyHhQ7KFIZE!{PXhsIQ=2&N(4j1}wq-U+<4T zc@TdSl_zP2rG}sg&n5V!JD8@*S?8RBmQxasdK$mDM zqV+O<;4$TF-i#Wecz$m;hYBj=SB+>?1!8oj&&ld8ByCu8qpP)W^P@d9Y>nTBH80rA zIP`=mO{E%BPX*Y|94Ot%)T+p?|75&FlxOzD7L^ibeOw(3mEU`=|lcsc`Th&L?prTSfbUTHWM!iR3RI-nU<|k6@iIoXhE2O(nv^3Q#yO#S#K%a$ z1yH4(XV^Fp^Ui>dZ+!~1^Rq2u@B(?CuzbWBXo)F7;vLoXZV+w;mJ_4+mw)I3H$zIY z*b;`)9sQ5!i3^MV=vTdB3h?*915Q>2YZ0j3P(ratGvy&`Kn_L2JY|z5k0VPYFq0jM zr+>DZ8M6rWsk>o#oQXx@@B3~f^DdU8w9PL$OF57;9-t=%G!rT1ohxk-lqJTl+(zp} zVxX)@6^H7~MPKU<1AVoDrOrC>7rwJU_5bkw_e2A)A`ZLg;R@CkR4(S*`;6NowCKHK z+P6}uLwH1s$)q)=w&pLAz+hl*g##0QIO@k-pfp&d|9?B@Amvpv?+wC z(<=NgJytttQXF^V-S%)!N`L))`7UG6@%u;{9-V7yX; zTHGRUtRUT6z)-JwmPQSW*9AX~Wo5`76xA*W?3IA;bt>h$r6_|rJ7@WA9}V^RU?}gn?^KN=)C!do)=(WR>f=KAVedDP* z?H#aPm~ga_HPfDC5Yk=~xx8-&ko(r~i&oHH<)s_}w9$L{zE5nB_Sgw?^s$t@lLw;= zw=2N62atWOJG@h^_{Si9#mhB8Mm6!X{_WJQzBLi@sBi$Z7UO6MhA!j6f$mqyK54nlkWn9UI(;iD>Z|a_%>#VhvdRz2t)X)yc$)=scV>sR zLIl9I5`fF_mtc7NzgH5M--76o-8r9ZB6}fm# zAGf&Uw3BY&c=Dl@qb5EODt3cZx|>IlN8*GT4_@&s#FX%_pxq~(4$&Y`Q-V}u>YOS@ zT`|*=nQ_yUW`E7}Zv0RRnD{?+Pa9U-nQD}s+xebm*YNxHL;nKxP3r|UUVri7nX&Rw3U9|xO#nvOg6Vx>G{*4e4gqw9O=no>G&2hx67YguQYZmx=%lh?46zXTsm-B9 zDsOa#q)j$!%Af%0pb1DI$$B7&&wLxwnnVwwLz^o;xn63i_C-fP1Q~7(RcZ5U0}eI7 zWKXWk4%48n+%_^P{nl2zL%o9^sqs|5MV*fQj4*dDaVpe?ZS|&ZE0kb}2Dar$t_mz1x_YbYcC| z|NHOc)vD``Wl^TjpmX1#*hgQq98B-(pG7{SaI&Ijnf>q;D=%Abzb^GWRBI6=VwmgJ zZ4)yEdPX7$w$9ikOE7sUhxB;BWx9Ro>^tssnnVBsgT1L_`LfQ&>?aIWX#{m6FBW@v z|0Acpy}gKhZuq>1^~+&{Iz*t!2HW*h3{3|QHSM}v$^j#V zPgQv@IM!eAz6v2r-u$6kO{#H_TP2Qx)T{p^I~b3!qMyIAZEC2+u)$nQ%}DGj=%+y3 z>z$IaV)xo(*}vhHRi0GiiB}cID@W0$(X~**O+tZ~r#Up_1|$H@m+O4z!UeI~?N;af zLP9Awshrmn9X>pROrDVFn?5mfqlq$xbbs%a*A;2q<(;>+=8J4&XH}Y*#2qlz*zd@P zi5^`HIdnUvuytgTW>N64jOnkoZ8o0)wAcEf1ax#`dMG^-3Ah5zq~SG;qv6L9z8Eld z!9us^t&-C$HTR13IsCFKFv2Lc=%^iP6A6OGjohysjhls8h;9T0%M0i{9IxqaEhmud zSn)GtoWl$_j)m9ng=UYfMnLh?$lU&tw%^iFjXriDL!krX!PLdVgiMYU@*&bhs#YZ5 z&8XV352lwF2C|SBKV$V=1ey_|>L&JqvKLl$yJ17&c@%+x#oSRYg-BSc#)TjF`ohGp z=>$LExQR$T!vD?itqGQmZrTo=&sfQ?CUvKL>1uJ;bIiJF3Aq4ptLP-?ePAmd5Vwj^499L8JH^D^J5D zJZ=mii4!)t+jWc#qc~@QW8ySetp$r6_FAMmM5^lTcT2R|!v+Yz-0b-J!%1l$+NuCG z-pMPoerrb19JnJyEJ}>pxBNUx+AQ?tn;K^v0;w;)HGJ8BxDmj$<{dh@X(dPp$2%cQcH7s&Iv91wZxjE&fKl1;?;c487+5s2;c`BVqL z<&-HRH-g&9uZ;V(iLqo(oD@$1T+wNmDnjIQ4fZ9|DTB{D7#OumW8wopPo8#$E%nAH zT0s-&{gbx+HRDFdaZ;*T%MIx(@#A+guzNPx99QzLpr>3OLJ*V{;XuE0Mg3}g) z-9`WAvxJsM&Lc1w39^D%dl!9K#sR*^_V%UI$I+!17I;mHPZ&)P%PkbLUBI2A(_~Kz z82XV!{3*-fJJt3K#q=_;PBT*c4fNx13WDcIbi!9OC|mlm5!{aHdQ0NbU=s!U+}Q|{ z4Pd69ru}BIX3kDgIK0v1WnTPm7h-WKg&+Je$cnWA_JiD%8|!k)z8SO?mB}6a2wRdr z1U!cMA47D1Hx+lng)}EOO>Vlxzw%i^KB+@lxL$?pQc6_J5)SjL&+r5Q}ttPm^ z!fp!zjyM(6UIR8>#LMU`P0s-k{0R*iZj1&cUlz&1bx8An*;ahNXgjk*c`rPFxlzRi zpcx(^UHp=F2FJoPq5cNt>(V)31~}+&8|{abI5!ujM7d@avRF_EM9z1keA=!-xhC)j z8A!}*@DF;8S$@;yp#E-hTfWnuu6_;ll)NovT`kgHqKcewa$#=cB`tNFZ{kOCcNF+8 zub;O}wsjq|@_W#Iz+K?vNy}N9m*}eoa6|ZLPRRVI<;awDr*m!cM}N~5@Q{vcx+1eU zE$aeCltZp%vEJ%!HKd5P?mp*q1J@bqm!R%5tH&IK1~uN@bD2N2iox0m3G!DE|J~{F z3i>6*j|4B&EYO;0UDcz7TA9;KF`x!wby)jl4z(-1L>YnI)J|X|KE`n>LS@$);2t1z z^_fbhGwgDCv;I=Wue}7nj20?5Q>PK9e$yeP&}os{dmnc3$c^(s3J$e`~e*&OU5zC@wgmo6?cNh_~Ak)Z@ zquL{LEt`wBnCvu3GQv3%(Z0mYznt<`XItE#0Q7$k+fj3Jb{6du)n&c_f4|P%gv|^7WN3d%^plN|tXIgUP`gQ!3EIb?? z4JhesSKylXnI`Lf;Dm!B>@T~katu>l{l|r)9iLsTxbvf~XuOq2^UgdrGMDt#lznAW z7RT9sCu&3OkC(xZ6|4Q*di*UK$ZiNG5GkASDyWBN*Osngc#^a5ZZA3y2NYzI@=_W< zTxKHl$Tm*j85dVb2pTb>F}=qFrr9HzD^7+htxbMkmKk2yTK);OFzrW^83dNw+QDWw zwO`^XZqPFK*`sG$XAqw6h3IisO2Z#|Q2^jXNRyn_Y&E;*l?8ing&M?bzxj+Wm0SKe)S)AFB;%~lM>S>qol zV(T~0d^kaj86+;)-yO!n{&W0ol#1g#Z@d&9ecq4vU`SG=fRaL zx|aby6g~6!Ax#%Al~T^_4wi#dZmYk~6>P+^ZE%*lf zCvUPTQ^V(xO-2-;y#F?9h9meiYlp=!?hUK`tK#qsf{=HIr|3WJ-un5!M`x`E!Ek{S zdFRCGs1Mf1a(szW!#VJ9bS)7a!$1w(n<8s$CMQ5SbWAZZrvmSjJx-S?Z8!DfMoamf zoS7}CWC+ybY+F#2t#mkuw7UsVzP2=@B$H4_bnbUX1$hK7D2U65E-dFi`-h%jMChM9 z{^KF^T_fAl^-t1HVRC*DdG}@>eR1tx9G!J9{)}yrX(ee^8ozEDeq2>Mt<>;t3t6aP z14~a;uvJv}NdB-jT8GsFMNOb`hK`t2DtYXhC$P$4{@#{O3vbEN7sGdYc;niHq&;G_ zkXyZ9cE8_`AN&vG?`%1wMkyx-^9~?qoekL-5OH`*$ zH;71!VzNu`n*_${9@Orw?9BrsPTliR=x!&24)E}5&1!mel7w5c$tv@vnz%fpj~mhy zG+=jNT6s`BcP{Vqjm)+%B18-u$V!A!UsbO@Ku0T<=FshUuIHf~Rb*ft zuS}{Dv4a$M1gf2?r|7kYZ$&?w@YR5k>Ua@}XA2=HzJd}K^CA<69XNkzwfO1~qd(+~ zweujzD{B5Tx;XIAs+8S##jx)gpAT5{B)M@2rM?9e8u7cbj{nOMfN}2<;$x&3wxmrP z)#go(+2i~mGkwLMaFA4Xghq3qk&dt-;#pcu715F@I=3OY-HoVH7fKH^_pXabkWXp^ z=Qhh)T>3i%Z%7q|eylup^dQ^t7c8;D9Z=H}P&((TlunE&8)68KU{Jd)6~>f9{q=YR zKSXLe%=3sZestTdPvbR_Qt{m%DSEoB0imoTRO@YGlvG#f4jn4)u8G>8BI@=b(f{SqvxJ|NII{9cwbUxH6)WDk+pgbXT-CXY!ktC-d(-~ z`9|^|bgCM#{w7`>SJ0viL)l}GZE;%(%tS3aixnp`{|a0B*B_mUCAB1bC5J*x)8*)7 z{s^;++mVlqXv69ycbf=n7PSHF$_~IFnvV75@>S4;@+VKX7p-5r+<~KtM-bq$bUpx* zts!w9GZ!Z&=22pn18sfuZxu-R6DX{7%W37|)ut>Xd=VT|EDR3Bdq(!3BOh#85&rm* zYDpr=J(A3472^)&dm_3}8KUHiU?Zw?z-t+u)$zmElswYSaHr~o)-D(GilvBed-xB# zSjX%&eO+~ky8SQkunbkhgUSypQm1GbR?CR?7yckt8gPWwU**azGmB!Q94Hbj7KeNq1;Og(olsUO$XTcPT)e+Jr3}3XJPbzBXDm>S*t}B-Gl)!^KHRZ(C ziA_R=jR%=U>=qM!$~H|RoEZRYChToU6=GN7YMg0f4wYHHM(0UNkC>&@Z@op@BygbO zr2HwfO{(lFP18koiDNQ&Pl^A@fOSY~W2!AcH*dq+W<`j`saNI-cO9N3)Li*)qQCS# z_}#I~RE5D=q|h@E(KI3Pt`obI!JmsH3_Mj$t+8@1>D>A6>adE+B7V`5nV69?I9D#% zmT6RthBi$DA6^gt^~s(GCj5Yn{e!XB8RGa(n8cRc*y>rtAT~qceceLXhGaTlmL3`5 zLYx^~w0vIRk9ctoCQQgiFa**!%Uc2nT<#wr{#pc3IVy?@dLE|kZbt4xk?f^%`9~M_SV5B&TCO=FzcY__0Ar zhAoy*7T{Pxdw_tX`Ryhs37>LYpx}dEuAvg$qp$ZQ%{9ujrR5W3eZPI|~Cc_`1`iJ@08!@NG+*KtUsKDXT;j$Q2d2;yqTWmnKoohxnK1r!N+sss4GA*4kj~-ZxTf@RNUmst zxJ{nQ96O=_n=-5msmvBsMmHF1uLjLPr=QMD9F_Qreo6bA)yZVQK4&3^6Sz}k;e;Upsb^c-ZHc{hF6Bq$r$@6;-!hpOGu6l)FMu- z9_y_Hxp(_tf3of?3EPm>Zg8DAvVAREK2vGouAwq{xpdi{9E|_4Ozzq`%9brp&)qU& zw|JI7@q~)>%seJU%Odh5xCu^3@Vm_%A(dMQ(1~d{eS;8yk zqaaCMCm18y#qQbd!prUvc4cGvB@0z6uh^5Jj%_Dz1fh8a8-*1sws?&&K@&m|Q|O4_ zoMkv&LnO0(-6%$tx8X$iRw74=;57K-$jjsP5B;roK1e&+Oq-ExrD5~J?;V#pwDq%O zZUU&6GYMc^`?)4&U@RsTE|Fn;D)S9(Hu-xtz8(;dRT7U&A9JumQZTi$;FewJ{JE-F0-~fUI5P8Wq6u`U7XHL@nQ}#*Mk@|h0e+y80}c( zS}BKCB9BpVeob8~g8x~Nv3g7%Sk-a+-(7iwulTUa04w8G6t(e4N()Crb_MHB%K zn+gi>-}5BYvh0^G;C>|eoqnbnJ~O$6CosNSe84Q})t`&gy0G^*LbldGYjI6W7We6h z^cSK|{gBGgOa2b7GEPIAM$II!VA5U_MtO`QXyaEyeOlCABK}=yVo1<{*{B zm49{Nmk6di0ZkQ2;-{R$%dp(njK#I`uqxl@mHIN!$KZ!S|1)+#@;Q`Ml?`>aBrgjO z(uqe_^lAT{Q>;x+zX3LG_sp`<;7V_GB266Ud7Jn_H>83$m?%r$;yLb z#cAjWv5D1%miH5c02*jVQXOk9anLRi@)sKMe2}J{1<8b-i^z#?@KvC#?muIBLH%oqD)Q+1giLCmk^1WXwK66rMJI-`14@z#hsFW1vepge-9Idzff^0K3ar(KMtD7bW~ z?jt@MY0W;DAv@!a*Y_Sc{|81jBaxwGJfgav_*qeaC=MP z*~Za_JYDDm8E;57*GXrF<&QC;EBR>X!aF7LKXnDio6ykLO0GQ>f1Rf3QLej27&#;D ztb8gWY|WPf*RGf|Lp`vNG__rkMB*)SDf5A#^IzTB;Xdci`1ASo;EL_n5lJKK5-6x@TQU{K*Bl&=M@(#Bwpz@H z<2GAmA8E|;#gMm0M8!5n9Y6BoPACXhxN-ieN~ZjR4T*9$kl<8f-)B;7j%RkAyc5?< z;xZ!Ve;_;DWd^oJ+Ej}~0pi;d+UOpa(UAJ6ec9l8xWl1hSbgvH*PZi+ft4d4Y}8+I z7%dyoZ7bUoncfV&dZpYPwt7*?fRaF`6ae6LooDMUyfN~dd7a0$h#L8vT{b{8x<5t5 zoawtS$GhjDBo?4Q!mSPIj`2MD^CWFp{@kTxiku^K|AWxA$H!aGui$NviQ*v``6h={ zy#_y9Z~M{FNo!sTLr)miz8Fb?a06cNHH#_|VEN^q@g0m6nyK0c(ohX+Q~>+wD`%lg2vMw z?XQ5AALK8WE(U~b?ck&#)#XNl=405b34c!g*v?puFkC-kz9?Tf;`AqGcnuIijSq$I z4u&*mo1r3|m0tc)Pgp$w!=XMF^_;tHx|Rm?=vEa9D%@~Yg`+Om7tj$Y$SQUtt0+tD zSFZNs4LMv;MIs-huQ!&=W@+&J?e=cbEW)a`*bAWij8{9{D$7pPj`3nL3tH#hM+fRK zB!1^qQ|>q{L{KcA1Yqi4^PtHnCXXf~N9!|<(oMB?r&ynrdNnX7Pf%GaQ~=EA#5Yrz zn70|l7)Y)f?_0t z?190BF`qL4yw4#fKX6l$cRI~eSG?R?j*sno8;ZqFl`gnp)!JBB>Uxv6;|PGNXGqbdQS zCma9@Q#rH4yWPZ%7GWau43*ibKFmLuXq%0N?4Iu(BMp!m{<$=W#VTgN$pCt-HSR0- z4MTJN*7#`OXDXV#+;NM$43mMlK;xX(eRrsO<=q8Zm<0e_V-ufWKdv6kPfSkWELFm> z`XQt;Jh#Rs(=E2$u{A@fFCNSgsbIe?^X=viQ*j)k;y+ZtU3c!=8amnp==XdP5WsD!~&uz&&O6z}CF%8wH@B^;0`ut4a8di)k-{xP9iyoEu8i{4s^cm4~q0$Be4 zkv5NDsRV^FgjNJt@#=f^8RSs=$k`sX~zcoL-zNx zBh%8{iGHNq+3Lv-?_yerwY!9kn-w|sn%4&z--dN!;u7U!hh0sk-L0-td%@(J1%cMJ z=jtzV%c)>Aqg|;T`kT0Y{yD}iX7?KVy;-TvcT!rId<3I5PEtLAsQp^DL za_2}^NB#1vT5_ApuX<<$(20?W74IpV>0(kK(njajKQA9){;5y9Bf{0;-#?{Xk9mRD zikMb8?IjwcNb&h)Y!S&`5zK=}Tr-gy6_B1oiU5(LQ~cySv3`L!yTqtcn;DS?GB(e{ zz-UTEvD#TaGwx+)3;TZrtyHpSLdIkQ+nx8#XyR2c#gZCuklZ0i|#>63Lvdf{(u#aAB zw-lejtIv`lEZw`@nD^p@7%MKR5&Zx!Z2aR`o9CTDk zWRYt+XN@`IGUl*Iz_IF95-#y4MwO9d;q}e#kQ-xLiG#7sOZ68WJ6E1_Dm7I70(gZ+ zr5%I?q5+H{J;3O4?p~gPIO&cbT=gsDXioCqvJNX;U3vTsrAN$cwx|1dUXuM6+O}#! z1G4finvn|E=s-}a7LwHc8Ca`A_;uI@%Myk@DAMIXEz#Cy+|hY2q71RwQ@W=rh|{hw zn(2s^*IYdRzjj6It0_Qnj6p$eZHDTn+MWXV9zP?4 z6MtC&>TY19yUiJSIChA&1*!TRvN5@0?ko4oVmcIH}nJTs>Ps_Ss#zgvqCd2&F z{d(79seC`a@IVn(Wt5nS&j<0~KVZ3^jH$Z#8XEuxj1hGjZDx7rhOfNZMYMoe-Gj1m z2P&$xbC;Q{9BA7+_+hM_H0Uj0q{)~1RPonlIqv)=&!CScYkow;Jnnt*?gE~W9$tjK zIlJYvgIj>r;4PTi@|@q}&^(*;j>HZ!=7F|a&PK>YN)ZMe(xc)#UnUV03i4|C-;67q zdJPNaH8)@;gTR?iT7K{T(aPh!YW2XTBR<+YHE3JilNeNLO-&U$wfxKKan}ZY6@3dtgtsVL)q})JhfW%J z(7b?$xOIeFyBB8O9p5qZASuO8-rk1IL@r-G zVkEIfwh^=uUF95>;F=uQjaCK-)6yAPs#YeqeD|gc?{sJGT7O@I5Yj=0Rc#y;Ecd@^YGo^>Q#)8 z_q@Y@|5Qh=MUVtkMF-$$l8revCVJYv~+wI^ZQN99K&>X6s3QZ{LL) z*CB8UHO_%h72AfTFMTPE7Leld;hiNF)8$dSicO3J`?1y}NV#U~Ehm8B=cM^tGa#q6 zX&2QqB{zg%l_0tA<{pn`-UVwW_7f-B((X%bBOt0t8)}vWjuq5#!+$ElE^j5#4x+75 zq2Z@*S#{l@m_ki|+0S}D19MqF9=fN~U4vR?$Zun8P9CiS#A}p+Z*F5UlKJEbyzTH`V+GGOFyE6^T!a?|<6X{>( z?5~?QCu1Qy`Qp%EtpONDa^xfvIfOOE2d6RJaYJA#ac<)=(|WQ5cI$6Y;Fn$9B3_Q_ zuio8zVHq|#)TnuS#ow}5pO+A7&WVFDVf<4q80J|SUB|~#SH80BlEscGSqeP~AKIm< zn)$lB1+a@;9VGnNVD>^P@>HKfVq&I+5d_=W3?F!Z1Qd)@0+03<8iC~}$}Zw0 z2pDX*o8g~p8px}qGO>Z)JU+vaAw#2RW(m%S=#7fEG-#e?&TPpdX&)vjO0EY@=qgq$(F}RENJufsX(5b!26GbZ-6sn2?Wz3 zbpZ5a8^F}~)2qJGSG_Q@`Qs)!97+UNr0pn1&o2G#ItJf?W{V}`*05`r>{e0T zjI6C0=Viyh4HL@jhLwIOp{;$#o9`}{LJm3Uedd5;YmTcYpv~6d+Ctx}a2t6L2QbpZ z|6Yb@=63g@D8(LwMHx^2;5a3(2PM*19u(ealqJc!y@%9u@iq(4_Z4e$AK`=t9F@r- zUFbevReP{U6ww#}Mur-Kqkh-iOj1z8eAtzbwR}1R5t@Gd=LB;GfV*oae;v4U`v!7L zQ0{c?>!1u$U-DLj*x$V;_}*)$!u9;ijm|v%IX%wy3``1#O#Aizo4k)eR@%x7qi_BD zh)668Z(K@#B%<+GIBen*9jai>Kk4>w-kW&^3msAPruJmAHG4 zl0c}vu;xJe4tQPB&{|1VE(bWhXSh%;Vzdkx=pM)|BU-!i8DB3faXX?kN(M?mqqNZX zG!bKUt01?m;BZsv*}cSR^?bh$AT&-5u{W1G&zfBb?P?O~MfBVKXmefPmX+ijL<4`? zRhc0k;{Fr~Kc8sQSf{Ygeq>EPQ2J}hVbNpMIre*$(lsa_Vei&Iv&!(_m=^~Xtqc~K z{9z)k&&@Yh1hq+DrMew=7`MU(qeaC2KPT9PT*eP zN0ajdox&P7AXn+QNFMux8i%~Iea5n{7eCkmswu?YaVouH-Wnx#$_2ka##HYGS|vCR#bJ*V} z_G~>%`t9^Y9xGJjxvKrg$*jme~$z-=)v3Uc)Kjst!Orplc z_0bxR2zrF*xf`}}FQAOfOK&6_tgP-#jIEm>B20u!Yjvb^dXuN+-*1-7H<#R>Ld8lW zzWFME#oRAJAb8^P8@z@76t58eEC=fkZF@kG}0{J>?Ywe$Oy}eUQfA0@jS(@q89a`V-wW zL%&nP!bahcLxUvLa~J+pkK+_SId7U{YWM0@?Nti$q9{5(7T+xty&X0=B6D4!3HV5b z5!{$Z89^oh6`Ef-1a9nG<00|sG2babKhZED?(uJPq)aE?o;W4jK3g=Z;9BZN?6&5< zM-@)mM89XO|G2I%Fu?|^+;GW2wchS)n37c6C9_}m5~lIMEfFD>Ajp>{b6J_7hw zghZmilE>-&W@EW%GXzufTol5qtJF93YT~3;##o+|t?w4rTh8HKV|VbcXsQWS8-EV* zn^emEIP0d8yM)$LT++1@_ycVK3D*wVF??eT1q8BpxYqtlb*qHru}K*|uQ6s9db4*z zX8C+gt)l}8#H$9$7g-w4DEsG^CXKmDPLkv%D3Mc#atn1(eKBhoA&zdMYCmlqwL(%| zb`k9g$SjKULd^bg+!Rs@k=C(=ALAW1%mro^dKyHpAgc;Az-uTOyD&pbKF~p$b9!&( z{eXBlXDMV=_7TO+*wT2%(doQhUj(%^ZwUwR(R;L!k~=R8=; z?D1OkO!^~VWD0;RRV(S%=m=I^g*}|8k*CW2kL+}eQ}w&H)^veL(^NZAQGW)^BP}XN zk7rx`(L%%uq;ZG4CwipF8X34xSSYUb%rVrY)~EVQB8UYLmhwN!nsjyePbO9~iSaPv{-%wEYWmPasy z^si~?m(XJ6r;wdlFalk)B}2&MDZ^GSgkMf8|XJ?X9`_KEY&T>}iRBPNh?Q?YVwT z_x@AW-H2PeT%DOJfv@0aw~8O-^%oR=^~}f3%3%kh??p+8qwNLjp9@tTKB9q0X{;iE zKmB?AG;~gmnrg%Z+z;`@y+H#Ay3`>C#9u1))$^P=x!{M&e@9mNGS$+=&o_sFQFH|* zVie)A$K^}tAC-(wzOTh}>F82F4bCwqSOqY2C&v$fyBlk2)O1C^cg-vlvRlw^HOYGi z$>K^XVVe?~9Ntyf{%`j*FpOR_Z5vsVXgXwVlQ3l7*AE#BexO##^YW|Vpl)|2F->It z=K0xHoxdN;ZBAF@tFntFs8`x8Eu;eFq?OiV99OL)qNdOxgl0FoYTo!G&KsdBfmw$4 z$&UQ<>S9C|wF7bK3&~{ParH4Hn@SUzOE@&_BoNrBrTGQKlsPn$PWwv?nku>@v#ML* zn0vR0O0LFITT}t}@*eqe#^Vme(YmEOHU9i)fFn5sv;U8kY4>OBsHTtQ~^t&xRid(mDIiB0psLJW)G$D7&05 z+1}H9@Hw_)aoU0NOG0K-Y!#JH!d`Jn&HawR@eQGIf0hT%cFG`DcJ+h}Cf8Zx5B+3M zYPBST|JDcR(zo4E(Vj1+h?wjexQowtiQC^>(|q+Fe&s=0`F~}#F4bGO8A_(!=0pG3 z9UX#-TW{m0Zo%6=Zz7?%frc2C^G4>KWIU|ADCHYCY)iiV(KBa}pw`y|sW$Ig&|WcU z$50_q+fsaxsPSA7<5F;8aak!Ja3eEmLeQW$6fgK1kG|VB`l=o0AS}-YgSf_K5UNA7 zW9Dov5t2r=e#Z``&2X7jC!Nh6^-CWWNxKyx{Hk_nj!XXtHth$}aWdaf6krR5yPY3+ zyPXtPLzBZC&KCD^31#yBp6%@B7nYj_5?-VB~l(Pv%VthJs8)OOnzI<#6r9twC2K_=rrR?2(fC3e|7huCMZJgiHaxqz+8u&45J z(*qxPQE$mtVEFmX)W^|ZAf7rn{Xb&`-Fl>23r6(aJZr4#t{U%`*|vVIV;R7u_ezCC*enK%^IPV=u1C=LTkKS&3A*gP15C$^V& zPtBwBPX(TH1d>5Yk}tS(^QG{m&Z)s;w0@scp;L-In14ANTK$~G|FS%cs0}xKEE7~9 z9|USAuVOWzP64NRE=oU+B=l4C1RCk)y7~0@yY4!bq}*&B6$4zW43kV=po*^{2;--F{2grBHQF@xI|U zA5h5+sWdvMADxHCP8A;+dc#6jnieQrD1YR}1?@Fmt8tum94=dv&e1Tp_AWP4|kv zl?empGfpwjEfYEy1}H6LAnHqn97D&1QkU@4VLde-+jEl|2p4jbsL{~Ol0J0gjRQg` zMp*MD9)(z;M_hG!UK(ERX&pI}&b9;2QLz_aJBnVl+sENwavtl)mf*ODYrcs zCyQ+9eF*KD(=o;0I3j1b=TV(1_@Mk5@p`qbh+7p$dPw12RXor%I#N0$Lrtd-{F(O< zVZaXLwOocCK4njxeV}HczLoJ7GC!v6$y-v&_+h(Zjkry*CtOp8)Q?f8>gcD$_u#m4 z3cH^`P6_ndX}cmpH8vW|z9Q~5ZG#p3u^*>}He})|0Wjd`^M?=dg5f26 zOJ(p3{#&i%L0~yB3v)+zT?)$aT&rW4H;YCnXT-lreCWB?YKwMt73ei;MbTm?Z90h$ zqVc8b!avERCn@~@8hg|@8cp)M72lET+bxk}ucpZdF^c(RKic(RD*|=^kz^Y?BS2IR zd)`W+Zk1I~Q^qhRf63xmEH*ERjfGBQq<(BEL%?DS{=Wuleq)@9FEc`9P8qGyD>x`i zpG5MsA0gxZf2;M}$XCDaUS{K-G*x-o zaw!JJgt=ir;f^*RdIbi`O3cK{sXhxiH3~DcIb<0tiB>1pEr$^gZlEmJc4EQ%4C5ho zh%Ynxxw_V9Po+dGH&E*3c-)j4onM$YZA=aNrF%231u(4$g5i1?cjdZ8uN*#^wBWMS zvRDGFNmFo+Ol=}E+~63T0!Cyl0{1lg0w4k7XAc2ikCG<>vsnoYY?&MdGcudWCJ=FW z47IKr4&qV9AXK*F4MQ1vXQGz`fb7wmAoG1!OU}@WE~J;A%GvyIEThZnfDL5f_pPRP zZ0syzPh^RHDaiPyIxSTPiM9*0*uS085@Djz!3vBCRz%6V7Mo~K744ee%y4AIty)N* zih#KJLS;M(7t6zRZ&D9-Mhtgs`BwwK&|HNsC{vjiO2`vK;j6r_H2IaqBk(B6J-T6S zOv%A!dkA1ODm%lz_4cyYPsFP`KvJ_Dmz~|*a&T3#rLaD0yX4v0ig|*fIByq3ZY>xY zcfvgL^iyKz%?+Mf?lU7C`L|7I4L}c?0A`-m*;5EpVf9cno4uPjVtyQQ!5W`{M znWd@2`oY~EV$vQP7evkpHEjl40>=C3i`Lh5z8bg4N9sZTdysW&Cn6}(3%E8;ep34u z)UeCXl{!iri!fM7Df#^F5PG14l|zEZEv(GqEtvtt`i4PKtubx)-LL>kUDTTBi=a6( zHsAfbuJDq5Yf!lyi$!)ZWC04Z^XtE~;s^b}O@{fdfTIR;tB4i?;W0CYfo8=cd&{0g zA8angb3UgwEry5%$m}HERgra{FQVK#Ff2XVukO`)My8!svu?)BRQ8AYb3Z51Z2zPa5 zsDO5QsK2WBQ0rVrQwp6p&#d}BS3CslcTK_f0qE@eXF1{V*$Wf@IVD#;9%dF$qK~oP zuS$}3w@+zyTx5=fP>_GmU(W|pSs%(}4e|_fy^x43+Na;ZhVNH}!#f=$2OIPzO}>Q> z^C4eqR2lkg+oWO6k)#f{vdL=iygU6a6s32z8$leD(8VN4U-7fAouR!pgXzb^!J5-U>DEZoW5B0HoWpu_$5I_ISrdi9*O3bZnpLKD(5u_J*%r33Gy zSoo9r7P4Y!fh6mOrE{fOt4A*5N;k)}ddX1XAr}XG^pK~eTiJ-w1l2eyA4==>P*FGHiMocRzh-+{=-F9T>cZVL_-{Dt_l?Y^-ZE^u}g-&4% zO!Dc^;7!YbnOy?&pC?xTD9WN)I08n*tThXC=L(~z{;NahUOW%7P& z^p8ky@x-|$n@{3DIP&v*wWPPwr^z}>t8}djubGKGJ>>69jRUBq@v27{R)z@ng3fH5 zi0lZ_0XJ_%0st0m&AON;vVg+}C-3Jw!|!h%*XN*bZ817T-_-&OKj1=~vF2)4Y-T6+ ztHDcjj}0<`>wQ1&_dMbHUeRfzXlf9GQd6Z4^yG9;Xq2h z^SzG>&bm@0C9A=dPq`t4iwDGDi@W^lW1Ok`As|fE*q0iOMx0tka9znw36f;SFHlT1eCtIAi-RAX8DqinXARL6pW8!B9}v& zci6g>2lpa{`KByRazrhF-Pv9YcvJP1=xm)n58CD&Aj2+Hnv@DJ?6?0wnZxi<*vaeO zb0nJM77r(K_N$he!fl7;vA{2;Xvl|GJeF1j=4Xo3pYR=O+tY2G;^#EEzKqlhXh8>K zkT&bHS?={%hqCD@SbQr4Ktx@!ajLdLCf#SD`=V#M5s`0Kt478!s{Rrm{X)tD0I?0O z;9Q1!IFIPz2=-Ft)|wYt$^gP~h}fnJd$srR&>Cf@T9y^s_V)P&(KX5#X)DC2W@<}7 z6)q=_u}~*Yc4=i>CYr@(TdYh9i-RJ0;&m2_1&xU!m%~~M?Eap)-g80Drq=@POK?h!!gOo2o1VD@a@Z{ zY4o}R@W`VWAk+S0OxUjN{d2mhy|+y|*`9{uzEc1_L-eHEGNoXYO&J0McB`ng1kF%g z5t_2HW}%v6m(iK=#5HZ2{pK1OSa?Mt@%Z@iS4=D_X1AoxlaSWkNj2)11dX8DbyiVn%{_!;wigAwBdP((iUeLM130I9=@m%E4_;XubGP>OWLx<5_d= zbs>i%MY~N1?q%VhkAz`&s z{Cs~GG5@}Z@`l;${e(~ci zEt|*Plk~3n2ip|hw~0$4*Q$v+B_=v)sD!65!>={p*N(52X zvx^Ko&MwtC9X{r*Hcw=6Exo==CZxv&JqhlE0ptk%iw)*yIlTs-f9)Kgo$fc>xHAy} zxB!v6J3aF%NGW{wr=53f_@c4?EX=*k7i;L~H65Be$Z}F$5RVhqxM7fQg9`tGbO2_d zGrUpdxgr~^awZ+cKP!1&^f-fg1Emz{e^&fZOL7P8_?EBg@Y=Bh`9|5S#$ zLtg-{;z`!~R8KV%`jW5*jRR)mU>g^Y8-R1sqdS={agI1z z-fL8Fl<xPj}PwkL$KlY{P2sU*jbh{je4d*mOdJaHD?@ z{!V7of$GH`Tc;OALP&W|02e~%GZYzF2&wc&&0Kt46Q}Dr=x|Y_k|xF*M_p73uY5bc{=O!m1V7P)}eG=c{(GNZvrg@dY z;dqS)ZqW79)DmpFW2zVn!eva2F-C%MUzj)X5g8H8EE@wdu6#wS+<){LteBRoEd4l0 z(hUcJLG*X|A-Dm(6C`wizym&ohQRgZ%rD*|9p}pfuRU%F0)-oB9nf+<*af+@-@_yv z$nMly6`zZuZLQ2QzKNP7qO|esP4QhU@;{i89|dISOVHQ%XiKQMvH!yKq|p!LWJ7b_Pff1 z&9R^4yK`;mWP>sMBLyp$S%XosF7=Y#*XG+s8w96$SaOLFqToik`LtLvGv78ekDeCdJ+z3)Tc52N zdkVEa4(Hy2r3y#Qa)}amutz`cTs>@t8j@xS@9|pQk{V=UizYg~dtlkH``#X!#yRim z0o4UQL4YrobqOFQA$&w zx^XwGCZy)Pg(u(3FkwK)ZP2;;O_;HzQm_O|wL)WkrxQNn(tusRTT6OLcYY2HYcPP> zfl6^4A<5q~N2CeTeC`|?-9i*TC7UhGe}2in{(|?MR*U*D;dO0MmgG$;#3J9V3O*E2 z+lN&2&9iZxm50Ardxes9f=%O`bDFq1B~!{>2H&69rcTk|>~118M2D!dx|47VBTI!| zT!AAKMquBOGOB^=e|e$a6bU_cuU87t@P(JRfgbHZ^Mq~Ft9+|C)Aem3AO@p}(ASF- z&7$aEQnL(pNeyPFdLSDC)GipbZT#sng>GaFfj}!!oz@~f7QzMZf-qkT#(PaDrVAIC z%&vVQ)a!z}aSk5VS;4$eu{0Z`L|@+{q45lhNRgT5gC?dJ45m51uPf0xxd{ABcIbkN zWaYyF+0WV(!LczMLh%qf^Gg3=%vVB|pRRn$fETW~^sv0jGNr|NNGyq?*&%vmR}llmHYq2T^dHi#Maso zFrd@Ngz?LDsM}XnN{WFT(DLM^YQRi*1v#6yW~?r{XMsczkXCLzt$r3HOUUXbEu3ya z%G1(beICk0)!(Mirpsi`?GaH#`8zJQP-+Uh+{M~a)rup`R!uGJ^dR|zoEqgnNW1a+ zm{+xHYN?%G3p~u9&L{i}WDshLWcSfFOhJgvK6KpN$Utc#qiQ(f)MN-8`WcfMir!T= zux~eQQI_-e39B_UtO!Gq$6^gBw)zZZDLmFY^p4zp$<+L2X0QvE% zcbsfHQz&NPIz1~QUlK<`X2oq+Gf%Ta3%R49WJevpvUV(@007vY=}6Tgc=bpT^%PRTYa79^i^qc6Hu@7cood-vUSVmsj192C0rb z-M3SDhge3R#Fr^TqvXKtdjFp5*0uka8OS*$#KVjZMCCzTq@qn{bVG)`?#rY9>IG0RXB|Te}lleRXVXI6iP0;$79{LHeO=HQ-TFP$M&cU z^3T``+UwWT*BS3zOC{!`n+Z}AovTZyxFiDZ!MW?d!LpGRYS~$fH`DC(3ISvzmq$9J zYCCXgMg1NQhSCziUz{gmUdcz$8P9!eCkT(7>=}KMN(9a! z9O(vvhT~L26zU#8ix#@8ZCqi$6E0a;@HW@~wuZxyK$eMcb24)v4IrTFQYiW;oKpYc zfHwPwB||>!6Xs&F1VmcZ=WD=76bAo1;1Y4KMKq}V?Y~#aE5ay>-QDrq;RHePGc@L) z^!l6+|7EL4HMVN})Tkabk6AdyidE`P0H@o=VSnRdr3)vO0FPp85U~xsV7OHdn7UZ^ zvdXc)WMnR$NlK1U=#>hutC*P>&$XA&1ufdHUY@d$-=7}UU`#!U#uM@-{)}^=V4N|n zMXXDF?4s>mE=zu0*>xvr^hKbWmqxYil58>2tjCRk$j*BKrf9K^48P+DhtaKwli;pT z^47K^Ed&puIjKVLfGO&5?7*UxVfE>ktl=gIZdWirD|6&v<@?|6@&zMKecMTR#qNeaLF(XIyd z9)?_pfKyt(o%`=|0QKC1YA0<8ZF0XZKO8k$-U0z_-%NXyguiJ1kw*X#|D|{#);luS zwoKP}OEHD*q^bVewZ^znZj>i;S|t~7FblJ_YJL+Q4DI2`P0|l!1|$d4+?B2G2(9nv zxPa~|giiBz;8;=J0C{WaLK zz)DW3cXeh;BBnl+1qC{}JS-pu#_HtD<2LrN;Y-z1ztiY2GMBb-k@Ze*c(ILo`BzfK7)A}6?NVCaW-Z(;#a#k&yX zKcc^;a(aU|i8ns$xsrZeutEoApdL5S1YCvYf{81_`M9VSQJClSHaJG%Cmm1c_|b63 zy4iZH#qR#~iA;%1E}TNf)qeO-Fw=ARBIJaIKL4w{_JRXh%0OE;(DnWAPn02Un0=6a zQyigNe~SJ)?vD9As4LqVL?qmo_FZMEVw7d2fv5G*zJGAM8kJj4^8|)yz70C>V=+4| zAD2VJ)$U`eax=37TWKc(;(be+eN#1EH<6*KU+OZ*oZn-mp^S_05HW`d3ZH^wI#%{ew#K$mYJDE)rKXr8HkglH>7p)D!-`a`b+y6^;2X)x$yg@lM%bf!CM$axM3^#>C=Ur@ z7g9ooBoyW9W=|>?uNxmbqv_Hw(6CKxM-Pi2lO`UKNr)g9@NPU9VpAvL@q8h8_V&B@ z7sp|hyPG|>`zOoz#;T&rbGcGsYR=iE?RKJ5^FjK5yQ|z>qX)5311k9}d+Www@MWX6#y4KzWzHtvYN+cVl7}kz@KXw%g=vp)*^#f8Q8umDjtXpIX$yZS zuu(S1kFcuh)F8rK`Kk`K@@QWDEKza8` zbPNtS9)yDf=}rqz?~C{-;)=0KC9IFeBq@Q$VlQ}|8sA&AJ#LEINc=K}7e-rR$*o~; zq)=a{cPhV#Bp;(`WXQZ8b4&9kPw;f&dN;E@DEMK=E!Cw|u!5shG1}WZai|iGmxA=} zUeNJT`G5`e?a?Lwa^6Qr1S-#d5nS7fV|vq1Uoqe;bYsvU>wBxw}%Wb7sy;{U|PTqXI#8+T7sDlJmvX{yUXfOUZgI z4?*<>fe3ow3PU_?rLlswGk%HaX#pP=HrC>-WYZk=%-Dah%+~@@V zPBtZ1cRZiY4tHXpc@-!MShz)A^^WwCCjS;4g`MIA&J!NNv3>MbAksFG3|LjY|Y+5jA-W@9;#;CRSwlAzR| z?fe9%nS_HK@2_#{3Go)&ZqLLl>kx@foXdP==-c$l1JsUhSP%GB3d{>GT?Y1?UthF8cT(<4|y%#d6k3QzBo8qyYvMCF4=+$rEOns+TUpF z)9Lxr_USs)7Pdoe#jJTjKYh_ZHn9T0rj6_DMBfy$!P_>{Dd}YT48W)qZp|a%kL!uJ zrfF8MB10urLcYYH;P3qK4oWL~VR!Ocl(v`9jqj>xA;80e>M5I~aizP;G%7aCT}ea_ zrZGZ>X5OwX*0nJL^MZ_uMQB}ccG=WMr|e+_4TR&Mj#&7>;-^ntDYEsD#1fz|FmNh_ zyE9f%U&QDt7~QjI3;an*Q`u2b!TYd&U()#$n-^uF!dmCXx;xP}Bs|Febk(QS63Ef= zi{xUhcQo~P2dtwBnGnhj=sGir0|{^#E{|JW)gdD`?fDHas@9k?kIJarcGEwL)@}9} zsUz0+1l#sc%E4hCj+L#&(nzc+1+G$#(!ov{m53(Fz%tQjYbx7(pGUxhPXVz3QGeL5 z?8EuA=^#$d0^Qz;f>b$Jv%QruX*J+XGaainaM86tSD7PqhqA5^M~4GJ9H0}veB3Oo z;JhtVt@fq)1rATQ|8-M>W)-o>e+$P88<}lA!C1~}D{cd0kCLG2^r@S^)G_*t(co~3 zAl{n`J50+9`+SHraV~%&17hX@pXrk#q;&4FO}?$TYVBXR&7X22s^1U-)JzW~F#K(f zkjl5&3+f1W)@03W;^g^Vhx_tyhN`572*;1A$_Px^N^l2>{m0PDg+L;av?SEYY}hO~ z6BG4oB~mL}F*Gs|bt9(a8!8hd>z`i=W9e)wkmxt}kfpFqkU(ia=1W3jHb;^kA)`Z6 zPLA3vx|hYt=Y1yg$p?$Mn{Ow8y)VMPk!)s1nC!Z-_MN`+9MSM^=6tb33Yq}58TF{I zKc3Ejm#dVSk{-=C^~av!Pj%y-6T^LQ^DupGtt4jW083~nsuCh5*n_T9EC&4Ke8HV@ zX2cBMS5H8DiR1*nl(~KpK#(=9P!>!&0h94c4`SLt{3Cgw5Do-`xFGr&76eNX>|7Vc zX)$~7=csRG={eaDOs4CuP3-GCO~(7on!zjX#aBUT=uB>O9F5A`@)SU!>B_n}0yWZj zS|M*coL_JooST^UMK0?w0@!03(WDh}Dxab^;?{!f6z#dIA2#V!0>Em5vcc00W}R;P zI^wGS%e;t$7s7!xZ|+D^tnadM&OqUBq;_;k3~1O$)Ow$mx|&^L+83{utk+wJ+8yq^YHL3odN~wL1h&o0WDcNYIwgoIrpvia}Si=+YrG= zBslO=7nwrvV#z(7slve^r+cHdA03_bPaJ$Z!m3>?Y>}@4 zM?Mm40_XoiZowhS)2}B!!uHKBY=y#=WY-nxW^%&nkiz~jd~A_sSqc7mlQpc6-7`?d zI&^e;Vax&)MLAwY?5^MgG)36mVU=^Fx# z!+EgYgbX%-)@XBY4z-pxMgmeaZ(uuu2@YuM4~tM^1i$~zXD5VxZ-7M*N!9=DpDeAMd?4FWD5>34(=!XjD9<@m?ce*nW|$7nt}BcH zDuB0rcr`yjJ|54ZJ$qAEnWWKnq}3tyw#~a#Zm~FDtqy4u{md^pucMD_M#5k}x8&0* zT?ozK8d+3S>47Nix&!5Ke;uKgKXUHwzO|9>f&QZg4!O*ZAzVcRca-!wT`bg7%!=Lh zHJ>rpUz<@<29+2*(_~*fd4`aL;p=Zo4m~d+{X31fsQQ3_Nc`v8)~0SQ7yDKyS;8-#$s7WaKYiCx>4qQVjcqfG zM{YmS{e%#`fVJ3AERmi3#6GXbaX}6iyNaj*LzGCoh=~kYVa=t0z>0YTX`?p=zEmkM zik|5h6yj!(VeH_;|Gq)s)Vjf8%+TC6+g{|}khvB`K)G*G;Rl3B`ee;eqUwGwME8X| z8obk?zqQuNE8>oJ02&ik&> zd5D9CY;$DD$_!ioZKhjIQ=}^j1b&&(w(aGwkW?@YW=5KpiqGdmskpeUEK@E(Gbc+2 zAZWdiH8)0#zI?n4P>JX9JP&s;AIr};YI2w}Ep5LUq*f(hrUB74VGAN>f<|qzql#qq zEw`2YPRN_#Xq5`#;h^bR_=VOuVwA-%l+~fmeTUBL-I+%FB4#MJG;(?UH1hTM4^74fv!oYii;@^v*EuEjm91X#D2P#VczcS^I3TFS{* z50CoF9Ga8^yja0PbXeF=vXSRIa@2a16l9B=uPsOC|BIB`Cib>p3--~+ks(TFR z;dj&P)CD;R?Oor)(w$LalP&wekT3Ihj#MDp5?0DxZgL)dr%YgT!wZ#UWOK;!7i6}O zx^W~|cNp#==tsy3E&9qcXLEb~T$HCWzuWDgb|{3Qt1@-9R}Udugu&SI0nL+{`=iul zx1q|j$>@LSCB^BFtWr|mM$NTZD0YfS6uz|N3BYN(v;;RP??1^*EFtBVrF(i_uqi5( zNSqek@!Y}JLN?w#L>;|v4@_y4`#QCQ2bSuTD@Wphi?Yu?tAvMHzA=AyW}d~Poihe2 zy>5#X$Y#)j;jQ}Ld|!fhY|t?p;jo%@{4Cy857VtEA>a)o5}Y75w&3P5O*av#bsVW4 zUZ*f4)ee8~pxzKOPp1cRUR|>d4yJdI7E(>2%{6#@`np`L>j^-IBLiFQ4QbQ;mJ_i5 zy!-*JP9#BV6i}deFdV1ho~!@mI{wlrO~tTxe-|6Wl&1Y!yrNOQP`hS+N`64)kE2BX zY4y%Z8=P^@kumjq2Eb34Q(FW@v%v00&%=?v6;g`1k&E>!oqzB$s&a73pW8~M(Fl)A ziTAj#ORq0-7}keP@`N09bdHF5^#h#d^A^X3KYT+vbCY3hypad@>5x{-Tdyb(@K70Y zjP^at_tpqD9Fi}An^6D0wWAuRP3n<}L#lGAcq6aOa3lI9q&A`Ki~={++n1o!i*XtY7NxUfOA0 zf|Jc~_jA)9G*0a^g`@sKWU_~xe3huzn@3EEHpepq2zNzZJft>`*pUHmz7_CHSp4AP zG28m~lMpSkMY{ucX!Brzpi^26iw3B1l9)iPfxudHw0g4#>lUJzzDyprC;tsWk!n#> zcB`4pm&R1QFQC3Q|BwxXEthx`qoTp!>TOE&LI%wSb8Y_@$>SF^951k|r@3>X#m4?j z`MA*Ycld%oJ$f(?{c9O$xM+Zf9+$tkWIYoSRUi{r+DtR9rj^;Ix5vLh)uiA`zYF1C zAi?W^SoHi!IIRCl&}Gyfn~dE@l#Cl}&3!h!Te9428%f=?A`B1qs~}Sv)(jXYlONMj z-T{kBZrhstVA?`nE`!Ru#g%!~sWS;2$1L5OgQo~{2cQi^6w!i+f1hh$c$!cprJRGP zpZjW_O+sREE&Q|brFsgvBkv8PF=k}E?L#+mhq5pe+?fB=R)P-v@JS{vp$B19@zou3 zIeOFbGLcFil~rzBGJvD}syn#kAvc=1GnDk5#;~SXPM<@gJn&=f^xF)ekAG@5thctj z{Io)5y0@m6{W1W$k$lkr+_=?=D^|pAt6s%Yp4&7&O_YcB;3=@yn_T7lY0BYOK#!hw z=Xf{H6(popg^sYnK(wn+0D{1LpM@i)35Vlp3+U&>m+Mc7SFk4>f=cWND(C3xVinXx z>UN`Y>j@-goAynroWTy_VmsPja~#+uJg2~5*JtJLfeSf>zTeq{@Leu|uzl4x|0ciP z&QhEZ0o}?9*5fJp(A)(0@&WI3CK+EY1R?Hsi_fLwU@{xr55cKQdHWaBiw6NtVf95V zvMkiGZ8q?+Jnv4A$b~t$8?`I{k$5Yx$7wI0KS^ZE-1?&74#U9|6TZ__k<$)YS0BJ{ zCO6ah`OY?4f(C$LnmvN$&}jpaiUq&wO-zW@lfBal<693ZWxH55Eb3sBx^CZ;ZiGbG z>cOm=si3Fx$TMBdmQX1+2XO2Qwu-pDfS_8K= z5a3(+hS;HVQ;6wDaPacCmux>PVk&-vCKfX;FVq_DZ7%1!170eJlADA6cnUUzdmer# zx&_RW(cZ;I#F>c>cNsJ55&7%A2OD1KVZXdi#e&Op%P!Q*7Vc&p!(-6(5?`_w|6_V} z8@D2N;~|~^H$ce0fH*?`>5Y|yvDN%-fW}N-IGNCEnGM!94f<5Qi^rEzQ7Owom)a1+ z=`O=c4tYjm8l~3UWmQ?5qj{=!Uw7S)Rex)WBgV@!wor*-V7y{JSXd&hZ&R7)MfEFwoj13N}#*_>MgL*k4y9FMrD|XG^kW0R$Gx5ERX1q?+;Z{`$;Afm^xmD#w!lfBQZmW z1LOPQXq!jTK{bNu=+SLf{5o}dclZV$oZ<81RO`TgSu1fmCu387G6T?g-&izhDZgx| z-2+Q1R3evSN^vfJIErV~V5}3ZBZ&U?c@@<%N(s~Ec|rAeK4D3%a}+4(G@(zNxHz|O zGqbqfU`s6my$WZj?Jl1hS-S>;g`7@0$B!eHOr_2&);WEzw8+%Rcu$;@qG-3h{p~jg z$oy`6J7lc|0spdTBLNk34eCh!IZA~N8m~_h2oS=gmeZG_i+mbt_C#C}ZeAMbU`DJs zqQ{*2loFji3t5$0#>h^vczbj9hvnbTU6cf9d`w(E6ugZ^K@;x}sn)Dw%^p1D0dy7bf1*vIq+F%3T@uph}=A3hPm z<|Eq=x4)IoTKX)ny=u4L!^t}a&|(bQb8pz86rq0T-lc^J^^I^60vh}-uJ0pr~ll&IQ$1eeT|k`M2DyYEFBpjp8Al> zt$rqPUhZ~9<`+hqbC`*OzI(n_+wF(CEq2e>2Z5ZcpClI9C=%#+ONxkEX{CpqOq)mubR$6HP$E6g%QJzSn*9s($&u+)-$d)ww9k`MarACA%(u&` z)ak3__<2P{7Ur_RegmdlzFhcn-A{f&o>eMtE(FW8x*sB`A4HyErlY}#uGHMH?_^hv z*W~cnj3{CEoC~kPcJr=@oG2g&RC8d{2evEr)M4NQx~mYl-n3|QnUe=e1Ed&B7>lTN zCz6au^z)yCBA1+36mZ^^Y@R?l3gjtd)aw5h3Nl&Pr4NFbMkcePb_THi4V)`c|3(EnV?e(;9yLmM+Aqi8J#;c-)0Pq5?xhepWCUk75ADcCIoz|AGa z{sVNFy;cJ>C4sBmPihvPEf2P!{4QfH&kJolkfDJM^~ef^AJJPMsiw!}E5MsOc*Vpi zuiE1Ic8<+-N5~_PNJ)IPN}&<;N~$}Z92c**jiJG!87{|Y&F0xz$v#dSBM*zUg!@|T zK&ysnm8gxiGt`a>A#tv~WLz&Z0~3Ue>~NiGjM8VA__6YMB2;sCv3jr^$J^d%=(~Oh zhu8_X6@ltvaS3g_QNqQsQ!uo584qk|;hN2H`X{YM)V4b@23PgkU%wTyLdIRUA)%b> zbO5iUqhOo)cYw=f8H@S8v)yVn;7j-5#+<{K0w1=v<qR8DT49}dDMhM+v2PZ1<(X3MG>uqdW{f*Ia^UfH zOn8zZNgg0(dM6N%*}QGQ7hdVF{;!NH$hRuCUd@qbNM)?n zevCT1WZbJTpp1Hf>VS_Iu+9zHU*Y?z-AM51k}|g^E#Ukb?!TTsK%e2!(U}i$yS7Zy zt^mrk9Ukd-HYjCA${y?h5&*d#PiK;_T_0E+$l1%490l8|64x}X=xl%HcEaB%g=@Z@c5)$F>RA-Waq0fLniWK0g?x1x@NSjR^P|$2GLG?S~EMam}<5 zHC}OF?p0e>$^^p#m){(*_wp4Zo%nbO-|_t>-oQrK2o^z>?F~#&k1+QM#{ErvNMOWh z5lUgcdOJxppldO&@c%K14PF++4UQn4i6I6Ozok-}eJ0nTx?ztX6$Drk7xZ z3BC*e5Oi>Sl9E6GJco(1rD*Z=cbwMRt+E6c0cq0sdg%eYRWcIY%cf>=wl36GFV4V; z+KUig?3y%aD;%li*@$<-nV4)eQMm}vQcY7i&sduwOTK!YTHgLY4by)^y3SwG1o1*x zhnsw@p$mva-Eg4C;$Yg1Ef_^^;kR)WPP1Y)SLPd|C@<$UB|x4Nh%04H zGW~!4U|*BLgxYY(XeOY^D|Z_;x-R%omxcQZ$@+I1+w(l?p2zPT=4M8g*peTvy{ka{ zP}Ws3QL%m#y(<8wJEwwsKZaqBZOip2vlaeqml~|E7(#-TjpC~TPF1e_K8(3oDjI&J zL8n^Sdk1zv=dgoe=r4>)vX|YCcUiwFc1Du@;ijv7az#51GwcWLL7MHoWXF3cjrI>; zAZl=tACXhn&!la-AE10O2b3)_uR$I1d;CqTP^??D|A~a?TIGdEqTdS5TgGEwc+%D= zd&b|Ewsw7iG#52T1r$U#4D3$(^9V_?A+Xf$$ofcFfdwE*qI0K><<6px@Rki*SBPih zMUV}BWo(Wm347+YKn+Q&662ZslE_&B-eg#9S9n8x;WpV;^GtpJ^{tyFe zA+QWjPVoVqzZZqD_jEgV6Jh?6N-%i1w~NFX%6kFf^P9+J*F&l^$M0At1_DL?%h{d`;TxPWtetYkGhYM>9_ur#zN4 zd_k^_v=G6I;?5F~Fb)b1b1%j%zsuFzl{x$lR;qtj3O5#KT!_KdHpt?x}NK5FM)#g%l{mNsfzUSObYIv_v!K-~6CB* zd~()#o=!;+NDOVw+%Yi7oe27RzjaN~+EO?CIYh0>HZc=WYoj(1QEDGjR5eV6%~^VKibi26IMJ~8Im4|A-r-gklnjvO+qc>vMZl98@epzF#1bNbml3XMG^ZW zr8fho5?z<=ACi-LZoX@SV8BCpTmua12j7aT2om9ECvE+zY#&HSoWh&2?PID9=Q{k%fMznA-;95Q2B1+ljzS zWT&D0=-dhA&V#{R{PrMD1 zTm;~O!c4@K9VpI_1|EgbCPr_NLYv8|DQJ4{KoJ#H)`gQ*clr6&~YsZ-%8Q04q+^al1e)ROpDQ+vG z12NoNe`y2wV0ToKKw_61e`wV&@oaL{Eqit|}{d8i;D=lFt+5c@POgpNkfX5Hws?tFK_q60?ed+_}XY zC}QBuFCUvY!wEDkT-BC^^T(m4XZ{Sp0|bquG3LRKYuG3$Tj-iQTO~(**D-m)J44Wv zERKS5fUch2aIl9eeY>!*jBAP*J)|Dm#XArUSx9@7>v^CLa@ib&A_1=eII5Nzj0a#= ztJ)n~(}qt#0W%~CdaUcz!SObH4XdJO@Q1EEekva#%@#$aZrA$!O!k$~(=~TutAbeg z*I1?)zttqBAc=yKHm<^%F+G}HkB)ghx}Txag}zK6CCBaf9NzHRbCJVR%o!!*H;=tT z`EU_3gbVJ*X=sh_JCL7|)Xo!pvRDX#9P1NnY|q`CFVv_N41yDrk$-ed4*gA}O!9W% z4q~B3u^y-?Fd=sMEC0P4MUsY&=>D7fbr%SlS+al`3gejiK=wV?+cTTc!&$2&7D@6$ z6)Oy|g7g<+SDcpcyTd}EA!l{4_9{6f^dy*duKbTp716Q_0Oy45_xZ2+{u%deKnnFE zk$kSWt10Rkb#k0nC8AI1#s=pvG_s3noOZK!S~ONQqpR1ScB#TO3+KjN!6B=btXW7U zR|H63eT5w&75%(C@#h}%9UxPKUGD-fO2a&!!TaWQ%$47>(mmZVx&AjlXzxM%>vDsk zgF=sE{ku80h54>$1)Gl1SEhNnF%KaMD5yQ?{fu(w5{3Ow2WBG!$>ItLk5!Jrh}BF8 z^5BItM0sH0NH0vS7anJBC(S1r;UYOWi&Xoq%t}YG`=cJ*bzDZ*CL?5WFo?Rh@Jkjn*t@WiywZwFc8d<0@2wgn2RNs7EKKLtXDStz$IDZ@e#L(_!s1K!` zE7u`wlb8cJW^#hBXJ=KgicJF9d_lr`?4L-7#|m9m+qUAK>DlS*C#Taxm+u_~?{|!k zQbdbzHF~*(a$0$O{oxD=&+g2F()`G>M!oS^;1OZxGg-bn1(Vyb?AvLcn)%~FvD(n6 zzB9%rJ5w)o_Jbpj^!I1WX-zg0J&}#UC})@ zm{!rQb{MN?Xq)XZJHf)Mrq0(r8m9D~_Ky47x$Qmby2Z$)qZ46s-rIUZ>6@O|Fq?Xn z=-$v7{1?gir-7WJ>l5r6EF_#G-)gBNh|G+o__0}^W{31#7I$r``Bvd`q%g+Aid^kX z(A~uU;L38VqZx#dTqa-BY11QPB+C_dT2lAW!-9pqgP7fq?RL%C);kjLERo~gYF$+u zd@_rflmw)I2}_7SjM)j z{B!uk59rr5FGIO#czB-RvjDw^{j*7I+0-55%&Xz*G{Jj=1neLxM<_61dJQxV4cVod zX!=UMn62rXMzx*a?yOHG5*CD=)`P#MG z!FwVD)E_<^f3Yf*UU=;HRE9(yX^JDw-Vw8o$l6GI>)>nP)m z7a+XmD~Uv(LTR{?_IpjjC`ohO7eEnBc?$cXqZVAv??pw6(GpUt<2A}bP8zP(HMadP z8gsbSix#pQ2&shngMpzpDGKh;M<$&EXl;L=b9opvl8;~@&H<*>{w$p6ja(ls79(wM zr`$pi8q-E&P`bL7v`JDtV!;{kJ&XXE@=sK3jCBfL9lVEe02$;wW%AzImrpr53BQcf zU9ls@wVprZCp$(5-1K^bv!kCqX^)h;zQ>er%OUOA>s3ln>Gcl~2^F(xf{KqE+s_Zx zF&q?*HD0`aA`ew-TP|84f$&_l?-u?y@qeDcYo#+iwnJt2ePWUz5+Iu^PrXk?YexnP zX2CgNRK{w-Ke;M9JO{Lwn->9P$t?4G~6*%!&LO^ExaoRYWXpOhq zA)52YG~yJ@Py%o_+9|T70NyNsSeGr$Isb#@({xpCix4#lfAXz98wjiW1*CyFbJ6Pk z+MV!=R*U)ha}PYf`8jn?%GvB6GrFx(TkUo?&UfXC;eR&<}>9p776kw%)S_9stn z;_`&W7m8fo6+JA6) zPj-mC9#E1dHABT3Wv_6y)3{qwE1N+-7KG7o4nV-wi%klonmKa|bDspPx@|&@@o#^{zRTzwIjkUOT9vShqSk_QgwhAF?f%d9U6JS5hzD zYI8x1J_FpovZQfXkiGgKrBtvDYy~acsV6$=sDiA)nV#aaM5@TIyjg%jLGl=!bf`zA zlmPt`E;3N@JntOw6E@D*UY40f`wT6jpp1!VxqvDurWE}Pj4qgeTxJx45nilCZKt(6 z@v!$ZwN$6*f|@SVr3!#8_AEW>(<^4)2kZ?|SBOA&>{5rgIU}wtPFx=}(JH6HbvjP3 zaSew_M=%dOJzbyg=r;q+CO$b;U*=>a(?>0F3qam;rS+=^CLYm_fJIxvc0{-4{N7Vk z{Ytl#xk|;eu>Lt|3_slzZuM^+;MXe0)nmS1FVG_O_ZOrmk~2_eNDoGhHsDvq?nUn5 zCU@tYehKz6rGSI@;8van>jn%*n;u3&n8Kr2QIU#C@~0ZS<05*dD;hwF8&91Am3=&c^myd%A*AI*Q%lcfblNLZYwp;igI|TyJj=_Y3`~`KU)9t`CEQev?hN6_dK2` zzhG8J@w9SELOTgk-*$!8HZY-1B<%WQX!z1$RC}VI)Z;H%Y*dHq4Epu|2Vq`#&sPSh3mM7$bsu1;;7phJygkiL z=G-zlZ@q}yAobeMX8BiPmPt9_@-~;I9TMvcj)yWCE5LrA9q@e7^A*T1%cysQH|B2ikc5gRHy4a@AmJG8k6ad6`Z;pgAR zBcqBR0-U~v@)wbp46WlH&4dYvb0bn=dd@eiiTiH@3l&d(-7ys+VtS7=L z;1xT^>=YavZ<#?B4hb$ho3Tv!A+GNrZkc8{pvP5O3-dfwZVNQ{QJf?HeQfWiKQxmF zcAKWOb!q2N)CgpX*~Ra7)&S@Hv|FkKkk}=7P5+`-kKn6>y)<@w#sffoUajttxNY`3 z0-kN}^=4t_sOa?NwW1k_S9}R(IkiJ&$%Q;ifn-NBh-y&7D);_^+gFQrzTIa@$OibJ z%;_A-d|yP%SzuZ4;if6s@ITue3|jTLEOZM@0tiY)hlB=LGZs?jOw{;c#=E{c}XsE<7; z@ZRK-N!-M)KRChv{*)H!Jm_Z+onoz3=3%8K!Dt*91aidRyo)hw0j9af;=350S){{N z5#&$uDAlguy?3|2;ikm}49u}l&w8{2p$!71k?Nt50+WV-8E*p<)PUiuGXKWdwV(>xDO#uBwynRvZAX$%=(lUc@Bb}1!>_gL z-98Z$Ug=ZNwc3*upus(TZdaCEJOMM{InL?O-U+DERUKj*M8!vNIvwj+%m@e_a6fqx zi-28L$Bqb~RurU+dpWju^N-q|D~TK-cJkp5oL~sYm2U69a8gO({ZZcLrmcaJ3l zG(A|ggxdgmv2>r!OndOB+OBn0-n!Fuu)C{riGTmq1HO&Tl9||8NvHW$Y}lzU2l{)M zSP!ICFV^uPpk5vqA415A8r9I>-EXCdui}`BxYn1Qz868p57}FREO+>CyVb|?fe5WS zjA1KjoH@fb$wmhPTcXP7z5wCNU#>Lu)m~uS6K5vGjA4mvn_ADKUq%USi}zG``M8+t zf>+iXw?4-QCzi(KOYK~iB%F}4ODSx%-f@iWN)-$g`ClJ5x@!|rHZB^tzwO+eK;zQZ zKWf4kYq(r8pkj;b(3$1io1AH*A+&a}aZl?3YsugB@ensbe|=PxFDKeBSUrwsaxxs^ zZ`ZI@G|nGZ$(I1I6xrI!H*$o!OT=$rS*xYt;8at`9rNTtDAWr5e7SukkL=9s&pOV) z$xAcM?J-4h{lFmDY+nos_Kyr3^SmgQ;Nj4DH0#zpo!f8y9^sbfLLk|@rZvYW!fval z(W`DcjQ*@9c9uxa1ajuol_%oYJa9NT(GSH=kh`L@1DeUU=X0B?dQ}&Q4|;?#?t%NT z+e(=D*z><(XQfC}T0%-PUXi$7nY{Y2tB**>8~3;#XG^Rz$XVvh)kz2-aWn;6y-KF7dMz+@FsQ{d`Av8RZ~ zge10L(M9%{o6~N!-u9R=VPShxOl9>8^#Ma&KAHen79=%KkoWExL6Xa6q}|2tS9C8f zp|zVl;Q~lWPB`~^<^qLB47W~jN-^y$*m`ob%=9E(2mABwGO;AJl!@*bay=l1jSK6} z>PH$8lJ$uiYk!4A47Yw9d3u&_n@PXx;QjxPo4s;g!K4Cj42J{2jcDF@xK#Xut}IhS zbu7&s8QU&)bq*JBtEn<@Ef^7^H~s{k-yEV388fhzk6y$IU~0PkFwU>mBP#^w)}M(m z;ydI8Y(S!#gCL@&>|Fx+O%*|AriB#lvNOV{xrLRyo{r=}qKh4y{DY_l$)k`NVhd{3j}!G!fPBkRy5 z#=CWPdk5T&&RwOrwh^JHDF3^mO^#pV<4RyscKjJD3f@ce&k$9h{6FK@lcjuGwN`a* zBB%@*JzAZ`a(JG>+5&T0_RDQYdtl*QJFi@S$z)gbP&1qhb#0%GRLVNT5ous*k7SG{ zBv?mkMF&F$sX9&mSm8%@#fO!jmBV_2`61S@!_x}fp4+OownxR=F`m+;_5q;I6n+(5 z1?NqCSh5ZPmB3CWI|y79A|b2f|uU8WWuQ(xC;>@sDUOtEI5 z6X`$0*j_h-G^QaH%8*TN$M8==&A-FU5q=RUc8GY4*&ek-Q0$Q)slubE6`^ncloLaf?K(Jfp)fsPo1v}EC)emKmoo_F!>UT8ylQeH9Mpbg}*rCxc0w@CDDwWl4Kk2T`53G(Y5XPJ%2O;HSUh!E-NrS+F zTJdLn6(@)*IXhTkF$Mn~78|DUncwdMt&A-(aHuRXT!S-QYI@$W)#Vqn zu@#1jIkSPlfV)7*8}^g4%?C2wg}iPgd>&P@psuTAQndT&`j=H>_UO|))rpe3Sor$v z@lcDcVF4oMo9CTN_!KUA$y41G;GkfNW#g7r(tcA%Sm3(bNb>U(K@GgJEU*EYB zMDJ*YV+E<0%U`moTMM&ptSx+gDTyMLbnZkV=93734e9PFR?)|+BRJJjWBPje#^@_0|bK``1Q5eBq)b z%Ai^FT$ih!@KVAOM;b;n)cGJz9}}Czmy-3qiQqRd(ZUA+N-0D}K-l*DzckoH%KEz}6HP81TPuY^AG|584}_vO zrW5U#L?T_h5e{v@c?b;j){qgOoyF#M@_do7lgGWghSE6o2cJ#W{obg-ULvukRcR^* zoPfEJ^)@ST%T1H|nG9{+qidU82mk{|^&Q{P=*u-OOnq%qIJ>1yAe zs(~cygGeJ}09v2l;Sq(W*&-y&w55k-vZTpMp|Q>#}zL5u&LIG1*EqHeO zK~6rXm0Yb?M*)V>!(pRSr6Bd{b~3aVwfq$3%?!omV7(KzmqI6C~t0>_Gg^-oNniIXe%j~C%M>+^OlU2}<{tlZL57XoV{-1()68F6t}_|~likLPKUwM)X}QYT?L9i( zBrxDHTBb6U2vSad!SB@Zh}uI}qRKo7Tw(;t7{A5x3x5Jp<8k@Aa!AM$?t|HH^qFiD z$5O!1I*4okcqwU$tKT%qo% z9kW>DTK-5*CjGIq%2?)R$o>2c*>hUw%GvR69;ZZ`psDJSV%~z9DW=v$URSx8&ZW$R zrzzoxXNgoTUw`owI2ctAaK?b2{UKyF1GoSq>C?fo)3XU2a0_@77-7p_nZP8@6qq>t z54BZ%M8U3aMeX(!huEX00tR>=Vq&=NAUK%l0VG|zy!eT(Avhu1cUTO3Ntny<^8BEq z%c|mjoj$i=ggilgfLQ%>ikLLq}d6qwj<`iqXz_nOI60b6Sf2z#0CovTCNpq;N6zij3LbEcbsE%9A zF9(Xo<#6>|$v&1m<*vmf-$>9*8H(I~lzU5;Z+_Qlhf93oDfVb7yg^l8kLap_rT%^b zbF?&F@wiVR0Fy{%{p}p_{4l@Yu?$kch7q1*Nr1O1VON5I?lh19<`#%6N*c7>e5eJNJ)Bmt30^`2m;O#m3K zhM-nJLO7Y(YoA0r#BoJ$>{J(Jvgw>Wc0L$(L{y^K9rMu>y&8!=GaH7H6XoL8&0Sq> z`F(bVXq9q6jdW+s>POXESAeY6DH&lI_tIrXXFb-9gu#K`{U#Isd*%8*yE);T zXnn-}Ub#P*T9cftsw-P!>x-lweZBa&?%R}7fm)pn^`shjIBf+kHZKzbgS7QcG!QE> z&i>3o+3DCHf5IUepeXiU9@F9OI;UKfVU`IuBdjd!ftY!vX@2SUPYIU4XHVE7d1Ugm zc$u^8L6!*lyhnO0%aUJ@GjCQ}sQb2R7T|Z&v_|5H4lF7c!}gr5Sq^%PSO_aO@;aef zgy6u&#ZU-T5===nDUz`}Xs#Iu=@Xh%<68B>r@W^HZ{>vZa;mRD)AK5x0)S8TlPkdb zSD4g*hhj&NhE7Q++O=+*9KLGtAciu0`XU-(fvytXVo2NFR0g@JkOi0l+ugX{60lf; z`z(aPeWz9cj@5IKLZ&yM)zgFYV#P*N2R#(Xzj>5iCFh^pnlkPE_fwiJaU;mA8-K&f zk3LK_C+|AjmznZg9tw@%Qvcr?s$E~tKP6J@iK5IONGC2LNP(V1Op0!WoKZ+s*0~aR zqT}Nhn|lB&k?HiqPNB!tb^9oSRuIQpFnRsZot0UtLB=seXr0dY3sN&uSBrslN zIy(~VVEn}4Heuc)Q%5^C-WG}aGQQA3Qyo@;r%35vjXO^?wzvO{jzmQ8q zU`U^L;2`=M4bQp9;$;v8e z{X6%714;4cQ4SdX_|sQ{d2&@yep?BDPH9Inc&4CK$oiPV-nB!`aXzQSh<{de}mT*hHO`xYSo1vmK_w~K1Cj*79 zJ?TIQ(yWi75nBa0^-t|6#?2qTf87E+JMIuH=QU#%$Do@uKk#`*G9yO)hrHX;zy3A5 zF65uz*#48CosualR6eb;?qTqlRa5QxL~Kt@S;-P#R7OD{L>S?9zDCphX13fR6L_Ne zDO2ORm}1!``pjOlifzB|FAjNV7NL%xYv*Tc1G(SLMTTdWp0_>5hbwE_1AEO=YsD(; zA*J_P>F{wiP_Vxc{?H$FFC$C_ko6I~bx8tASnwAxQ$K9O{$d#%-}rb?+~d-~S>&VI z@3mk>M;@QoM;Kd>4Z?c2acc&baY<3|K8K&zrj+jDY`~-QOs9*D>vW z*-Y`S#Zy@~ACxXV^?~)5kd~^PwbbEJKm_}q$IjxiOcwU?44Pyf2oQ;As;Co=f|l#F zKBiH=_$ty73pnf4?Y$}EQ4K{B*tpw3iT5Cx^tL`XvT>Hi)z=4?iD~a}?>&xCu0mOv z%4`zd%OF2nM~pYIuN9$>M_dP!P5&j%9cKtG6bJT7yky>=0GtO|PjBslPARaG>9okH z(3OYu!A~)s(JO6JSp**v-_-?g&*&TvubkhF#Gg8e7A~7513FTcg$L}1rerTa2v^bd zvdpQEtXM2;bnT)ifnE5BJKxtf#J~tT{wA=k;@M}45kEWz7xXDwJD~_#DIeMC2D@9=}5RMv;-ej)SvXM{_e^Eo^Y2W$ZP_&xfdRYY* zUZT(x22M;3v2Kw0gP5XMP`N?G8%Hy2`)s%}wLGxFF13B>Ms@FuUL=nolg@ zT)k-NOTfP;ot1t@Lhh5nN$&Zef8+-#CVcCFHg2tkVOpsV5j_=^HGbA-83N0obV;fx z(+K$EFP0v|GteO!f~wyUGc2s<1PXw@;}(w6eYcc0zv#rpk2l1z2YP}Ca}~<5b1v}4 z_;ulD>%}*Wv|(ctLMH zW9nE$^1WiAGqJ<%?fsdlImXTzzAD*4VPKmAp@Ez7NL; zz5(2wCg&^v9{_3D!cUmOo=NQMC)TLaQ4*J zs!UHE`DaGSMfqMfPPvh*>9@9;`tn>M*&cS3VEP(5l4r4YAn@V~V65h?AcKT4L{>({ z$o!0s4jtFImR4ihvErQb~_>m+KbY-*A7*~X5^g(go)@v{HMu|Pbq z@B%TOuQWh+tFqZ~T<5eoAx|Qj-iURn%O;h&JO_&zyHa6`0IUt;F?;jPBr`;vqR(#X zE%s>cax-7yR+a!pk!2wb&03x0xMboy@nvXEL?5!fU!+Kyn-y4c$s&SPOQGwN#;FyO zW7uY+e~ z&}g6!k|K`+J)NkcU(uK3c>y=p<_pK8^Cw4La2#0ZGX%O*MmT$;g}AXA)}BTDCdymn zs9PXH=zeIHZ;$OgRQJGrS^t>* zdAV7nNNIgWs^iJj4a{gM_8Wvb+-k)xQsR7wMwJb-i!ee&AW zl_MP_54~o~F>D#~Bn91Yo-!T2;9Gh;uhV`MND9CFW5Bdn5E`NMl9iBL_#+%^6Decu zgT!V@(yj%G&>xisus(>}_f>eIPL&~OzT<%U$8*mM#BM=FpgPWbGEN9VX-VqX!>Kj9 zw%`(bl-H$Xy2W`HKA=RJUYq!n5MF?xP^Z24(AnYj3}CX*bYHB5vkyHqdU@lM&E0WpnQB~vafnv9&G--LMtDa{Bm~3yGyJ~!kw+-p(6~EEX2Awz80;d}ZibJKoCRC&3cRh?YXZ#V=WGt^ zW@4rPW}UJu`kP6%Rb}DIEOHqnr^KBk*w~7(%e&6rV7cvHuSEzA-EouM9N&|VkaGLx zUBMH}d(u3lXi1e}8}Ppjb?Qm2UnjD#2i|Z-?5{*|7cYfUHX@#B>MjS;BVf5?dtFnp zsG-nwW5zE2bL6z`34Y_-+7{sB?`>9yH^quf1O2oT`!+ileQVFCo*of?0;vK;|MZB< zqT`~Qg9kK|dcddzcLaCtCgRwqi!D+rb;BxGn8 z=bdeCU=nO+7VLtw`@_fKKDDL2KU*jS$@ny367Vcye56HoB+2LnX(4W9@*=W_+nc1E zE<340*UO5yNqG-h=kubr!7rd&S+t588JbYQ$I_K$FF@tj>O&`Z`!@G0hzPG=x)GJsMkpYFxl{N|s%v8-OCD`YN4UvvdKb$1(nVRN&6x zI)gbC8iaw)`EqAcUgr^BJNkJeapXbsT_lh5v_+%35By$Avp%%Wg4G#S7Dc9FPEYR7 zpedn}J*YfS7br+js}hND3u1vo1fy4x^&=6Ce~4P)CJAH*!i+Oz?xFlvqUug7A=Y@3 zg?Sza&_{E*7NepP9**H>)CQDIj^6_xgivEch(7ltRhzJFE9E1yKlq*!F5QRo_JN#= zVf!H{S7_xrOS^j!IA@DHT^5jnxG-F_*rA9TeyB$;MC_vd>TgkqG5$u#vM@%dMVjq4 zj{wsFKPXxA7u)!D>`ABlcejb3D;arDIIj?8HUvxLZy&n!oQID(6ZiTlDfnu#y`vpV7=S!xA6juYD_jKvna&m}-Q7>6en0?NY z#K2bsrD0>mW8%UWU>)tziO9@!^VAluRyR+3uI{wQoc|nZ<{fG{mILpEZ6Qr`R&Nqp zdMl^8BQ0T=uJAc`Pi9E3pu^4&c(^ISP8dJ2Zro=~;a@c_wm253}*mX=ewKTF8vd$5L1Hw5|p^sxbEIMN3jZK+#9F^#W) zY%MR6rIr;EH>M*2p}6cUN(!kR$;8+w6}`FP(tXfyh@ zg?wanIlB5#$bGU<2mI_X%5s)lDp<%^_!Fz+hkjL_nQjkrOP^Rm$%?i(x@L~3Op%RV zNH*n-?s8<-8Z*c#ucST=d0${@4k@)X`j4q%>n8THmu~Z6mFDRF1z2{nA_Dx~Q{0V% zdu1NplnoocheKr_A+WoVT!L=Wb6J8@4o+Qv%nm-1F$BTPe)m@39FM0Or5INB5C>>(_7W`IySW|dtj<~A8)HmR0uEtSS_FGmBG@q>$5x&6hF;l(&uh}`;am*`22>kbl z$WKWSe;xuAVY}bVorQ~&VN00->YFa$vjNV?r**)6=b0|%Ky&C2O3jMoPBs#0A1ilD zR*#-<LL`0sq5s zA%&6F#D6_REHV`QOHd2E!G<#~!4Dv=Jd*wM;~pxAVl_{@zE@=%Iz%n;rufg><@hIH z((d|BF~JNcOeO?*{_7ED(|SE5(aav=28;v}s8$&J1^S$p`F)+4DZycDA8ET@M@t{; zSX|U`#0Fdzm8d5IZu7xBAk48m05|2GN_0u@_5T<+zrD}Y9gEh%#a3P<>Tj)lYK~!> zEDt@6*mw`&n=(Mk1p7I>C*d;J60Hc`!OygIhK@G4u^oilRy&bKi2)=V@N(xmgPEZA z2xv6jsxpL@?8`bv6k8YesAvV7OoIE~%xm}kd4ge*+f;pSS6Xm|Gb`ajtN)1eTqJIR zk2{x2FA1O%(W&hnpd{fjYQs2ey3EHP!O5h}58^uaxH6W<9jR*R8};@Je?{Ss2N3hu zd=BXt<(3q^QnZ^{UmXAh=$(v>N>3jcn zu^iM2#3JOnX|h{;^}!S{qyJhQqI;u9Kr9lTyd|=6(fTo zGZ?T@mnUl(bK&Ff3i!l*>z|)vbXG|u`a>(%S;tL9PeV%$eU__C=N@X)H7 z3026O>r3ofZ_c}Vsn~O}#P+zYFnoj4L}M}9=u}P41}(`p3Nr5o-|<8m>)mBr(L~n& zaFdIH?8C_td|5b{&^UlH){BirG%gnq-RJ9Gax`=ByYt$a0Xs#KW^NM*aPYjX5nwO z3aPbskVa{UpbCAMYVgBRbbl&mAzaG-`yXU-HyIS~ zV}A4vpBf9R3CxVh?Qfy%;eP6d9~N?cy=JP3;Oz`u5;M*R91hHbmahGQMHY_qEGlR@S1UQL-|CG;2dQl zO-6a3SeX<|Hy&!_>%PpXQ?;$LIu+CqR+DxQ?GL9dJa;K6kAZ9*tVZ@Ugq`XI^rt6l zpnu<_Z%<6-mhXq=oR! z%)wh!|4%yrm~MfVLs{-wdI|bwX)pLT#da7e6pa^NMJ0RvG%Ae@ro`e2>1peVM;bU} zdQYL7E)!^;FLV=@cixN@9fXmi65LJPHzY-tJqajsThPGUNI%fpUnJ>3SOmelYNQmn z7H^ZzcMSrqx*Yv&Vx7kpH77N2Sr*UK1DS+z!vX;FoV5MQ|Gmm=9hP1GERrwves138ET^vBGXDvS8A>w-bN(RO zGN;~lD_;`U9DPpiCZ==0>%wv;}TK$~u{;&IE z)__s)evR14OB_VTzw02{w5V1q4iJDNnS*C~j`}aD}KW(HJseO*zK^I#CjAEe zk3k-h^}T%!^?69!;>4DsyW$KwyYV~b&H@!s6XC$&mvw-yJ9H4r)?5Dr!F5daK^BG@ z6WQhHC)^JR^k~RHyXdzE<%(uZ<> z(#JK_CL2+JUoAWa<1WA@OV7Y(fR0H?ePCmphV^9VYh!j$GK?WJ9@%ue*5U>{XaU2I z6Oo*nvd(Z@Wlc$g5N)(kgTiY{?yj!_CZ>aXQiHIT#tg;crLR+|O?w4Y?dC1n)+=!P zA8CyemHL13Il$@Kj;m_%)Zbykt)F>8~vkp<)tOQ-$xGli_1zbqZ^RwS(PzQH? zr*ga}sFk?>2jKm15oH*-z>~L~svZ(GC*mQGe*CA-&4^&7&Dgt3__|lI_vyC_@bqr> zfR#1@LpD@mEJ#=%XtqEIGAy*qpYxz5g9khJQ5*e}?t*K?7mp{A;l-H<p;|-71WVLD*aa64kO)!{ZPgjqxe9Lmo|Vn${)v zXMnJq8<-!z6zSBF%Uy%6sYK(Fv~5fB%2(!8to+Iy-HjF0a}O-j5pY&htR`ooaR)|u z_H>0_onimhTjSJVV|__~ma(48vUV6eT_)Rm=4bhLxB^L}Nl+s+NOPh(0rS1&q6=A> z>=CRbh04~;LB>CtQAl6f{5dn^eNDArRDA1f&q^$M?pOwlf}|v*k7<(`$v<+cDXDNG zG>r>jrZNooSPk4S9=`4ENVObnj8l|wQ}E=hZ{QPYRXryjV1V%Xt)h;^y|Mzs5KqW4 z3EM4N>TxZsVEKtU%Yw0k{gbeib#!p4a<`7Tng4z7nD$M0a-6d&n=^PJ>OsmmhfZnHYHV=i~~sw_apfUJ`h8DBl^qtEOXd{sOwTu zuOx&)i19jLXS$`8D?}m~w52&WRK`#0WRn|`q7R@>>4->CAdr`qli(mYy=hhGu(Fd1 z2h)>GNL_wVjr`7KFh_|UExn8BkgqDf0j)ms?AZMIKbpozY1LnOg@@bHUhxU7xgOU) z!H8}HGmdIvVk?cJ*YUO=5T|jJf7+daLQjCL8GO-|`mYBsG7dd60h`iKRD(I~KQ%^) zOVF|$&k1tt%D&wS?NehfbP8q!6?K&yR*L~{2`qk0{Cka4lrKn4U6O{{k5PXz z!{Tb4v)HQm9Ap0Y>>stTUXE%US*+PtoF5-q?b*KM5Q3Q7kllZR=kU!4mlE(6>AmPR zi8)mB*L$_nRLPT=@XS)h&?31w*;@qSR4N|E5~ip82+B$GA#_xe>I0_-O%O$@ z0M#_#8BwC!JitZn=`>!nxDB&OHm*!mvH=JwAqW9=a*R5m*VYRlp8w4Jk5B%1+fcUL13Tl9dr z25ofVnOj+NB-MWwW$np@(vISc>Rb+#RSdQh9X-4$GpIfL?UfdB_Zgtwd*irVYb&iQ zakONUkxnTyBz(K+vEw&z5cUZ>^BWE#yi^NY1KV9Ez0Ywr1P)^OiH>=J#rUR|WiXz<~JL z$YN+jdQVtj>64K-%VES$3SImw;w|xeJikeVc1I_Oo~uXu301z%kK|eKP9twTk)820 zUQQt;Sp`GJd(El*A_$+q&`6z?$35p-cu%}k$WK75caY<{0EOlA?zCXX(*4PBU4K}L zij%{%t2+Eaq~pkw_qhCuu~46D(65by$ipzdX|XoOG)SbpbfjFz?P6Jdt18f->4LJA z{MLR!c1n5jd`$~ruTp@LGh2|c#bJSG=^7`7=H75*hbz~EGx~r7=<4!H9`tRSZQY}U z9h}@C_Cd>yp2&3Y!;%yo6L&8KQh2xN9fXsg^Qde-4eYupSb zmJ1kwG$gU~kXfg)5GUk5NF;Ho^CLZlE@< zM4uCh9F--=`?F8T)ouT&o~}3KOT%CM)=SF}rW+B83vftV_>6p#D26iL+WmN!(^6>#uqy8MYe>pC=#WDN{;hh)28gJx?(lLO03Jtpo5=X{@$K7n! z0s~?wp!$rQd|o5*DagmPZCs$KT8rkJ-_PmvuFp27-83@@pnhZ5(nEo*`_%N$8U>2; z8S>bx>p?f|ak9CJme==1;aH}>>>}Wga)SPxP2*k`mc@A@>bu8L1&SdyCh3d+fh?%K zR<`y5u{IwkrK&L$PM3)GK#7`3MP{1^hVU-H=h80 zE{IsZ3+|(m3qtlCxLj-!lZLSI%iWhWifMz%d4&xK!=NV_$F~=%7V(@o-pzcpyoNyA zNh`0~<|Q>@7b1r1dGBP{Jns*<^C`?Eww|-uq zkgu)~O(6eqvd+-b8Zf8qfD#WOJ)sp0PX#*&3X?|oIlCY3hGX8~+80Nn`o$UbfeF6uHR zjg{5!EoJ*@-jg-o$Q)M=)Tg_)f$&r%^V4$54y*p>XSm7(aw0G0&SQkH@zEoQ6W8QU zoWPZpBRW~J%*IO;NoRPlB42+C96Jpf0gtjKbsQ@ZY?9>C^AzvLs#?DKsb6I&A4xch zv9;9`8A&42V3JHEZ)V(BVfkDl&_X{k%M_d4blE7-!uLL+(;k6xygE0S`r>W)hI-G_ zXMPi@@8fziw^*wOBd@bz^6m(8Pd5saUnPPw%vHQ+kGU9_MCZ)=WoMMCX@IWVR9{og zMl+i7C00L4HeB8d%)0W%+xl65bXt@U0#bB_>TJG_A^a=5i!&^-H_sX&5~Zg1Xz3MdFi4vw<8js>0F{fA$zp!M4W><`SaxhX%KlZ7ESfGG zCMjy4&++XmoxeZ`{e*4yXzAh)RIh;j4zVi~TdZ?Ms+b-f%2X;)-)hM7q4D=uIUM-A zEhxAIRr2hgZq*Mf;&t`aNjwd60YGCG$)Hfqw>~<9ylbT9f&odf%$5Q6=L@^lS*hHq zm`;|KFM}(YJe2g)$<2pQC_wKFs0X03(r%7$lqzOCQWtQ5uazq&sHcWw<8{4oAn_}5 zR4K6^J+rrfudi&XqnyZpxW_WS-Dfj(CZpXw3i<1$_V?wk=+SDrgOwzRTkDIRZdq&dK%`SB|BKh|7L=;t;+H9`0y)amgFR zwWWd~LNRT5U;_iTRL23>o?x;F3bT*Ayww%Gh@C*Ph1x>_d5Pm80fxj?lTy%=Ot@FN zPWnPNUWLH-=O^8LcU}c01GO^WiI-A-f^=h136_0nNvL;*Ej9F4Sp}1xK}P0)qP)dgJ5yPGJ=$*s4>}1 zL44bBK1;Xtz!JXlqkdKUx33?0s$+R(#!s3*gElyq&xjl{-%2}rL7J0BRc&!v7p2F| zhca317g2suEa}h2bWHdR=M%e1G(iho^P6T!-C0w{)*7c6U!)|~Vp|ppIaW{ha=%_UD!(q$)q1Gy}HNyUau~R}iO|M!~LXQNWS7cYlWmVSai3 zpC;sU4(b7ux!-ZM{;9(0M!!pcBl|50RP`4k5_^#3J{-d$4gMnE>mP?jpr9|JYDl2I zFfLR1if5FA2wFwP0>B?BMm~}JB7NlkBK4cygE|qnflfhJscj}&mw-all|RJ@LZVwc zGBLSiFt+v%#9lhDq?0-mj^>;`9k;Pto;t?zmp>py)fQKc-qmBu?`uNb*DrVgrkt}y zS%Q-at_mPi3OEG*5QRfbLUuvxPtN*0P7T}aF~#7^UL7s$Rq)@htNjlI^p(!QO6f!t zHBn8Xo^0Ag!;rGvU$7{op|BZ8Hvm&=LHYpQ4M}rG*KIxea zHrZ?=UFLK5n=mpBiY*yzinANAS`h69dl*=4Zqw@4ks%M}&Xll-NT-DI>`*JQNh+#7 zONc@|3Nb{6+boG&-r;HRr?I4PqxsK+DVs3`zhO=1dE095JPiBZF$gH~5R|VTq~*_> z#*vH{$E!ksrrIXW7%+oN3juR272=cw^>LKXxwBjP?;*St_4-$r8W42J0VNeGW09fU zE0kt^G|zRO&0k(EsB5(0wmfL}sd80PEs~qjr_q-e0K^pN#rqdum$c}rPeFkT zXqRnbQAcYa#&g23A3Muw&~q~HN^S**93u_cJLks(L^P!J0`??Uj^C=8`dlLq^*Z+) zZOup;kzyNOpB%w)&+^x&=7}1(-jA8B`Z*z_cUI}qXxyEDKr9-oe$caa{_q(`!E(a%8 zz=odB-!@&2wzDn1VcxjmDgzJMZeYyrcK-=twY( zJ+!~L25pa?5lMm-_9bCsddWkO725$g84vOq*-p?^&IZId&We?A?sWM9N-qgO`Lf|I zVd?v;$^z24*b;zF*!UB{RMM1{j-Zvj{Mc9kCQV@yc?C*RGx%SQo_IC-W>N?8w!}DN z$+PJ0zu;>jNI?*^8m$E#?7hj&RK8c*r4j~xF8xU*#;$F3e|=i{fSXXcNU38p?eoYy zvXVNB5w55bxmO60v#wF9KC7U17$RX6IA=-V@W`^FwE-Gvl2gtyv>5t&lbI^K@)tPD zazNPX!qo<|FSr3&eoeCf!u)ULM_c3tJ2@?*d*M@w&Aw5*w6AA2i z!RuBy_)u?Ymvzv1-EInhox$=%gn>czr#RSF=B}oG)3E`w@I#k7!zUaw;Ip}eQ6C7T z^$|aAIGDsdiG)j{v-MTT4+jwBB0hj3vNgV$T9#q&4?grvP+rkNk6rth#-a%_Y7E34 z#uUzfI)+Eq4fbgEdo$9CabvLt++{=FTa*`wpgiQ}7}Yz&9R`#z9w;kfuKlSZ)f-B3 z2IBSu6pD~oKGoz5Xx^4vK^yy~gjI1=GFs`y+S*MMJA1W5OW2^`sVdmzw$85QiPxkX zL(77LWyz|OS*7b%R}tl#u^+$S3lUO@2|h;o82Z{GK@AbX12E03oYQowuW)<7ds1Ef z30C*V8ZKtj|7^K^t+A}n4FQ0_0+IE7$-HKv-imAXaG%l2TnAk`HrMjep;R_Q$f*p7BGkRLh3*!RsLp{5-nn&CHqcv;_?jIq^7K=8-P8mdl(yFB{JQ<5HiCz)cdP2ee$Qkuz1nO?T_X~0apLkk|$o1wV_s464Vop0J42} zC6F&)xV}9sVBCy4^zTS}8z*t^feSYM0{Z)%oL_f5_v^pLzOnHPp1~(_1@0mQ9FPno z@J4R zGA8u?P7j{}sqKYsoc&m~-{Xv#6ZxYQD~wVms1h!0}tr45>q zWVMjYban#s$^!cxj|6(1hQy0@$~Qc?h~$2m3W_o6m8hvUm8Q@2*1=XOvB6+ZN#no` z5BPf$)C*($CqE-P>+`9!EAcWvW*W*i^QqWm@07HA$cjgoHVWRe0r|#HJ?8L=O~E>x zhMMj%fo_lf<6q79rsxfs#0b!&gGN{E`{bNQPLbsaoQyKlPn%1Kn%GZCr@(QZPR=Xg zbg``Van2Tn8Av%~!++{qzBhUmi{t{Z`d=f+9P=ytLUp$Aopz38JM0kFTw1#j!`J7$(cLqjJ)H*#KFhua5 z+qO7*;QqFn<5I*lvzFXK<{BS#fP)V^_6=qHAOOw|$Fli?@#Al_{7`|*b;HE&!N?Hh zy4wHqJ_tto*r(D4$@TrI2Rt^rv0!!p1>=#pjG^P?5Z=?8N+yoRCfVUT-TFR9Q|OSk zwGR@aPh<+x&lcT#Ky+2*;ASmDiF)TcBO6+^%)TNjZ(_>@`}UGGk=2@ggzx@u7V%PT zihFRMEJ$Rv_Elikv|)L75|^PN=V;Rk>pt3tv5)MSWCkKJ>(FVQ3c5wl=hpz@4;93- zW&C;IPRWpvz54e|RLE_u?b9rPf5E%hynFI6PBfoZFw0qg0jr;G|LLd6mlT8 zedg6zq>GR(lo*l57zKwV zMz-McfAg6Ce5IpMNN^1yCO%tn%asRPTqAQn%_e`kqBWDiNJ#VH04)i$0wjIky*aa)7x+9vpCzU>CN^8_HRqSz~=iDnEF~n980rh{H(r=3(u;EW20m z^2}gLBE)8i-Prn#sl5;vu#~j$uTqk;KaD4QqBpnq$evr|A)BL({7@k52jK&VYGzkK ztq%d(EdY^$!m(QFwDGG@a85m?r;29wLvya#B1~ia!hFw6sIQ(gH}atzky}zWSx-LX zOaNWB<;!PQsPUeIuFzrq!1e4)?ioospXwddtTO4q^=$tg`a?%r)iq~GH46(kB!+Fa zN^4s=5(N979nM*w$`re|Cbw57hkYGBbb%SSdJe<9YNFz_!Y;`Ty|r1rC?%ueESYRw zynBz3qn4pRRcIo{mEE$mn8AK@JR=&ckar4~J`E>HQuGBLB--#$o}l2$GcDd9d_CcTRCQV07@ z)LB>n^m+UlAGb7P<>oz?Hh?S$uz+l2KQQ*y9UqHZx~-Q)~kEcg~iwL;{GDcnJTFvaEv4@@`1Cy zS2nAUslsFOIur%g9J*jIi=T5#9*yjA14NTD`I<qN46~ct%c3D(?f@+2rtY=B^E!ILTa{~+4j~r zyGKK^&db+&Qa)^}@>KHF4iV_o#t^NkEXvGorBh%lJtqK_QI??XFxPR*jy9W3hYC~& zaWw{Qx4S$7|IPurX6f~Z1L~M*?|C=0d^mI2B3O1XtyPuk#`Wp%fS0?Cx6tUK&LMtH zd~!1Q2)+iD99h&z{kR$PzDa&x4>cKFm@0`4zqdtKKT+VQ!ZG?Z zS{*#*G*D;+WyydgE_WRrC~uctw}YXVE2aoA>k;zM&4`4Ry;uGJ>?!+gB4%QVa7P$X zbl?@DQ7WC?fY{cJ@pLca1vf>z(tyc6*ebVhA zZEDPT5N+bQk7`H=^g_762WMFOPqm5QU3t`-^=Gy}2uB-00RVBBNaW1L$fFo&sj!5@ zPgW)QoOl(|tE7+*U&YyCW3ZF4PLWCAEoDos zIGA{E8aC#^%+;#uh}pW$`~bK|<>U8FM)-8b!8^$iL0a&Ob2Id=!cX(Otw&}}{2Ov3 z`z~u{j<>lf(t&hLbmi)9^wmVKtKx_0Ke3+TaR;<)#y=gaK~(avso+Y=W_k;-EN@mwSUXhp zKjKo_MUgYA=fqqv=)LOo?J<$pMO*CPHxRt~=ea0I;jlWyAO3 zA(YBwZxwtA%&50i3`Vj}gdjf_6|Su3ja7;ptB~;J?N_ijhAT)DNCW*GV}SW*8E1zzynj-gG(wvGLL~+@-F-R-v6&e&Qm)%P z+2{FFGKypUgqY2aGpxzuGHW|OzdbaRt`EtffvTqqeuuCt0h;4t@+{GfU`ARKV67tu zgDjfd8`~cgVsxV}!preD6?-lG>%DOkm%{D&#<6WZHz`*2_eMg=f_^<#OocDf8wYw^ zb?!EI z`ETL*8qHsLZJ4Xl9wm2@Z*b>5$S&b^3d~j)Z#%28i&W%-cc=}nXOB)cmUIg25+H42 z-6ETFbRLSiAVL61DY`DKkRiI%DkRSaWKwfO6tb_6p`l@nt^U>lRLc4d=;RIq8?;`m z0mWXBZ?f7!+bT@2{!4+M<%Up>cB^~ED&uP9JfJ_?2s9SvLHhW*qKA5G>JCA+L1ER|zM zs*->s{G=If}8K_o9T(A5;Q-WfXnG2-9|7g!Q{lY zQo-N)K!+gObiu|7@n6TDc=25f+`}GULG{t_h%Us>v#COdSUXWAK~t=1cqj7hhIa#v z%(V)c)1n*4gfp{L=J0fm>2ZZpvE+JVn62g)a{*yb9J2u(z@bUlC}%WDB)MMG(s*j$ z$X=y^y{7zlPKWsx%T9J#i(Uy~Qsu|!;JrhAJp-M8iv%*|jh7Y}MJH)Bn{ZEwh~kq; zxf|#4WaqIt-;SekniqqU`MDJCjag?bJ}mZB4)HnBde_2zT9{Q$DHr(ao-{%5Y1##O zC`p1N@a%&Fh#J3y+1_>vY?WutxZ0^h6s-&2lmRFPo^v_6z_CdX7HvVG-73!7V%x^| zy3bDkIty9eC8?P*@v4ufn@OsNkU{lwU(`ZTac`%w2?pYdr*pZA6D%|T$$i-cI=$bH z>YvMmbmiIqYSalcj(#)&2!?_mizi=2FwsS~cDAND+>ecv=jCMmE9eCsu zSxrA}1vCmd+S{aw%mZh0ufp8}eI3uFgwjq>4 z2*qcdR*8{c_+|7bM(Rjg0#x`~cy;;gPQ|W#-WbOF>5LnbT3c&VOjZiVbB38wgcr3I zmzS1!+e|qHP(buU;sBGZJ#x;JP1he+QMi6j@QMgdH(#$@Q()HRUer`GfJ z3VeD~zmu56vjtkU9_S~2G`_{4g{hPzUUA7az1)$Zc8sF2Y%)cPm*A+?+Y z0Bqxyw~kkVO~zABFjSZ~ha8Z5dl7GMs1bO?^kAF6v zowgr5NhFvm!-Kw;9Z1ig_SSDj+OCNtzbpFs4FC|}UpuKU?wC!{(S#HJ3PS{kGS1%` z(}jTA;=zVRp-EBEfp1(2H1<2LW(j_!Mzc|oiH;sIf#3gu?HLMy71^0upH6Yr60ZPiFj@@qy-c#?4j_$lo> zrcMWx8G_O2FuBC10%VoFT3BJ`1Ae)U)i}LYt6g6F!N;9k8zbKwlv1+O>c9_)UxPD? zm>l(7fvm?FF@u8AsCnUblxww>WI4NcDkcQw$_7mzWIic+d?<20dMO+ zjYOWuOK|((ub8*h^IZKxDGccZvB+aEuKg1e+uNm{q3$$vgh!nps$2$CnDIjHI;ZN3 zy4v12C6aAN{#3t5ZKFcgzmop!bdI98-u_y#do3;Zm_^5L?w#tLtGOaJi%kR+huF-w ziK(6{S%M{yW)b+^&pkxAuB-bTI#omVwI3>$J+#4KaaZhjNCB^t<4DG)P$8o$1LQlwnqyhzK|>&W$~Z9}Hg#sF(Nc$xHx ztywaXb35b8nt&t%Vc<7eh0q3Q@&fA1Ro0vVi*q;vl64+mwEqueyZV>W1he-l87089 z4#EM73*3)a3FNm4jH=4~#m+=~zy2mB7W`LDDbIm{nz76@Wm77kQ3YAN@6MNK7NS3^ z%HPK~+`Eo}bJD3gtBH@5RJ#j%MsH;!Phy9LiKgSz{(Ia+$$3g5^^P(Ml8zHiC`Ib{ z?hkFl)~X4i?w=-hELQ;_;%*S09Wa(b_Hjzf-qg?GEDgACd}jI@x3$Bqh2XtZ_y1k|9x&tlniW+Lk@EtoZnBmwNlnix!rl8Huz(!(BV67}D`1LE zqBNmn<;6Z?xD<;^?R6i^r}t}lA=$JlEHh)8Mb9fBuXnzTsg#loRqvTj#|? zhu$_qDrb^V29(;4oF7BuY|Xz)dQfZE*uUHbHR=LH2HhDe{Wc|x%LT(;ABoHtXY4N- z^xduotU8_6x9>jSgqbSYi?l=E&pUs+nicT1t%a{i9j3H%Z}JG5pU`}t!eY5I%Q`4O zZ>S3aF5WfA_n$_lw3jd)@bir6x7Nft%Trs+I9b|}xXgEbYq)tQKL}3Zcn(#~Uy&jU zAr;bNY{+-^0JI+u@2Ux774=u1aSMc^!YhJhOgfRKN~|aN#Fk3?StCE*BP#Kv zo)Ve8pP&#i&>MVI0_YX@H`Do-qByU=Lq`*MH2f;Ja56Nv`UKH zM(wcqzh0fYCip&6_z^{Wm&BnS2`}~!yQ+UFNq!X+Y$m8X?wqwZ(Ros#uR35qsbp|^ z)j_8-ik9gU)y6-AIQ0WB+!t(|jd9Cz@2^*C(U08o)M_)PAH33p8ntJmg>GjC8>gpy z*{q@lhTn>i#OXnwco~Jdbv_))c)R7U8!3KQnJWPL#JM3`#cfz*zFy?r#P{34@(#3s zzQT{jiO5k|LYjQ-BN7Mu*p-EEo0c0^NZz?%mB0&}YGVQeFRkDyOJL^Okp$G!aGDsF zo;z+10|R%XACr!zUgCv%u&rF!=pUmzgkH2wu$)UHYRzOHqLym>)Z5vtqHpWLwlrId|!KdNjrz3lCSuKmEo6*?+*D0JDtd7?u1fP zdvOa)dyw=ZpW0xDOjA%Fct!*irGi4OB+b5essyTZ!(o!x07>mXu#B&!sDPAm&wzb2 z8HM!j%~J$KZjg&vq_*CQy;^`x1%a6071?7t=HL|~Hh_^YjM*RPDNb~0=jWp@$tQbdo<3}QsK6UrStLN*?Df}A!=tKv0tmQVXI22n%{*Mj- zMaNT9deWd}bQHr-E4b%jB*puW+abX}xp(fHWz7q)7i>Wwk%!JLE~dU?hCGSnlN$eY zp+>CY!ehFRxoSwpIPAHxWG4W)#yHcumw>Wl*4ob-DvfQhMC*c{oE>_4T+H zFnDuxf{>#9iD?EJ51Sk!Ax3j(?}~v!?1i6ct{by#`m$qdDSK>2zCjk@^&;Iw7lmnn z)o4=-5MsdGEiWLNGPtFmxivQl+)rR$9cWX8V(cDCg@O))|1}sS3g$S925bJtyaI(5 z6ngIbuY_WXcW;Ks-We%G1mqe5;k}8pyW!y%H4Do0+F_fyIT=WMz z5;#_;;Q8q_TSj*Yt;OJjMA*WdprtG33NIn7ERgwZ`xI)+B9Hs{7Zf>nIqz@pl&^m+VYluh!IwXHU{I0xp|q8J*#tt z3ZWD=v$|i6CgtzL4BlT#iaVC7E14p2M$_63xuCk?Ao;+mlfGl8xPrEjNM?+G8w~U9GT`xgGw)ZXTRxr5*tzdmv!VT%025J;C zYZiw9px%0Vd$w9C9n(Q;+%#5vGBHBm7=Y0o zroX%_LSMtWqA1B2VC6e0Deci{zYNJtH>L0r{)b0RE^-t#4s;t0QT&Lpk1DAXnezV9 zO;!i)`N9cT({%u?NI_kQuX6TMp81-7H`kLJe6itmd5S6N%nF%Ny>t3SY9rq|D39(& zn7nM~HYI^fJJ6z%5;uwWwH_!ea3N7rS3x>LtSv#>%W`Exh`Xo{2EE%u#4|>*>LBW8 z0pJJ)36RRA4nQain6Hzh*%E8dMxL`n!ZYH-Dppm9RlxD3+)rgqJQmoxJ#|n$cjgU7 zX13C_CMq_L$SnU-K+*!*vYY1Sipw_BZR0HVs+ljgM&NaP!!~aBK@Poi)G5v71A*=& zN~zu0uI=K%QS@tL*QJU&_TUWp?2?M% zO!+zzwfSRYTZ2X}0b;RJWc6|cqfIIEOOk8HDpd{ew<2cC&y^+Ryj<)Ia0ZmEv>7Ygj%uRHY{(UvZu3 zoC&0Fu=?U>KQy;2ndjKf%{%hrGtW`dSpoE=nr+9@z24HzHQPFLrl9WVjCHcrtiGkm zTAC#YB5_z8vbU&A+%p~F?wO32n$&lEm@0eRHgxP>aGiZtHmal=1W}>(HdSYO<>WgP z(y~R4wsVzn;nPi?xx3#NSM{LK8XBS)zizzU`dbd9)D;H}hI7~qdll19sfj+3s@ZaB zi_m=nB{qy>;@-g<5cz!Q3JNiTW1t_IT2-!DV9KJ61)+T8;qSS|gfP^(mynUsA)J87 ziZ5i>zeaCGtvGGI^PGuJofKuakaSpDl0)hbHAf*l zt(eB!uWQKNnTtUG;Gj8g)IR))QYYF>oy2Oy>zs1)vX>Hg%!DX@bn<_{#w~<>0}7X4 z3$Ju2cxFap&{<=qmn&^*PZYT;X4^ zF2DR%^8f5CY1(hi5~fJ-ag16phA6R!Bz!}EFE20O-CtP z>tIWOE)=qS4tJV=QokCSlZv2N-~-Un2F;!tBi@ugdihK{j&w1*gD1qXlFCp)Qg$Az zxLv6^@UQX=RqGu8aA#fXT$%j=~h=};YkP>)Ltr*5GHU086&DcF54HsnY*`hu|Gh4a#+?N4Jgu1_0 zeYI|gR{5B=A{P#0PZFU#RQ;H1;_ku1fL#B#F_wpjY~9{3+4vXVCz;&aT=H~AoT1!- z|HGl;Zsrqr zzG29y@kHE`F4M!GB(Bv>QnPCbjPFPfN#X}XO6I7~#ChofvujdIo&u4=f3b)yX>LLv zX;)e)&Rt5#uALSQ5h&nK&5i6fSl`CV4&HFMR6P%qO94{cHt|f<5p1LScGg?xU)$*xzj)SX9u&f}ioFZ=@V-wgCa+sD7#1>xa1FSo zt%0l2(R}B%TN~8GegUQT3AclA=3;ZlgnlXbjJ!O3HcU}^$bK&$bVd4X_r~@9r`*B5 zC@wF$<1;tzwD5DL^X_d2R>)wIxF!g!*obw=_sWs|;4w#lp*Q{)=c*{g1BE?uMfq*g zvpQ}wG;TW`qeN_27Cq-1WM&*-f8z0AldO;i5fi#J$rr>{%dcuv8=gH}5mg?L&o5ha zb5kW)Eox$5VCR+}W{<$RrAz|tqSkN2^d|eZgjuteKmlqDh0|+Oyn$e+8TycyF}bG_ zzu%*-_Xe+Jm)GG7bk~i~;bs9#JI>%T#f#O*vLr5nvX&U(eU&N(W3A%CZ)fMvS@Lb4 zhCsSz6zGQBMFK&1ckI-Crc3RJ=Uz6_eBeBf+2ot#cm!g`i>>b`T@i`BJ1 z{Iytw{@{8$YaSc~h%5?%rkYrdKZ3M67y)|FW6IzIYQUqsmSo@+lFFtnKZv^N+1z-Q zH=e|DeIJ$Imm*`>E9SA#JnSFtkJ1>4m86;!FOongj^xlPC=_tF0`gU;ZFnAYNjgCv$E(3@^v1G?&Q%pksG{Hw+oQ|H{A|x4 zX&UR3fO1L`yq-IHC=JPi6~2&kF@4vW<^h9TDun&4nlOdZ*LEYT`id3}J($dIjEFLCDUa2wCv_BR>nq5|8E4g9Gm?f_6Z~+WAFY_j|~R2{!$N z!*xlZFiD90Uu*+7+C)gV&j7#*Dxa-wqtS@1Rz3)e+1mp}QO(?ex*eb#wqLwS2P@0g~hRuxair5B^eiM*+H7nz9wz2Oe7J98{-a*lc9LxL_lqE&HmsbuTYNkF#0jz#pJ+`1Ln1Yasu z-|Ig!(b|3lc7mcGJso*0&lnJY6_f!XDoL^7v-%TMPvZix|^ST z3iI{%b`QH&d1y~V2*hQ+z%;PBXss`6ttxnu113TDYTPKgd`m584%cDkBmHi8P( zZI2gd3NkPC-e(xHKu~WuqmFAW$~T;(>BcMooi!%yN?KIi95l6&f0r022f^&}DrB>! zwy4FcGX@Y2ZVODW0xx?i&zSSf%ZA~6?gI|sBdA~@K}~1Jm2WupqK>)zdf~H6L5ne< zfS3P|9ZZr|RY^JamDm$3;;-Obl`ADb@e|*kSLqDDR4GAp!wbu<7SplMM64nH#uy37 z@uAAs_ocoFk3I}6B<_4C$aWl&4t#VT00mL$Z6BnY{5LCb=Vb&3e`+4KY%NU#_3fVq z#E>t5N>@g2+(Kxxi2DRdb9ssoJCsh(LG1_v*JW~i?>A+3ErH#e{+#hY2IQnM9 z%%C#PjObc_>x9|`bHW}vfocn7lD~)!meX+BE=q)Th4@7m`Zt?)j{{Gl`Gi~slgg$J zT$>``uy(O{fnks_KAUe^7m6h_E+WvTjl#uJRdXobb< z3P$OaB<7NjHT&0jdxp)yxLrPhTkXMDSAIJCk0Xjmdj0`kI0k=+e>Js-D`L3ZvEbDW zH#EjK#?WH)8*{GL{01k=qGVfpMWJg0>OXIQ*wfPzE1@&1EGdMt!E$ zZ4_%9oszIwGWvSp-A6NX2Kc<7c6>4Pch0O|{D*Olpl6UG^-LUW*~qiof_1Ze)w1V= z%vd68fQkI4iBs9so{>2J9b7EG{v3QHpk+tm9@EKl8NANF{cvqrl%uX45mI4~IVdOC z{gpvxVNhZlJzk<1SG}jl|F7+dmbEKE+Mhd{NuzgiHugZ^(c&_)*S zKz34X=qty09TFsz}P9%(P#;kytKoTl_!!T)+ZtLv3lfcN9opHdns-maDPxB z&+Ixdz>A>Kg3!e&=coUWo;!t z29n0)wVExE58(z1bZE+38~d z_byF!(yUV?dm(YsFBmJ4dO_{3P-hw36s_(8Mc8c2I_IAx#M8V<72`=QuUgvzDGtRq zH)TkMA1j=TGG>;dOzk!0CV2`@y_c=W_|wzrYPaRxTKqber5^6Y158=k-x)?VrAnH2 zrw}uqWr#kBcKcEO$a~m{g^yxyPORK_-NXJ~x_6p>k~#;z?MS<=4xqGZsv%|!BWd|Y zRV`6@&hZ8l;p6!RL4J$jRIKzNj;Jnyc=J_0eeqYv1XQ|)*ZZ{;qXjIYjKYg~CwHY% zZT>1mRqcx%@bU!Pd^EV66K1w(Bq0kfs2~|y>aYi1?w#i--P^QTt=8JhV+PqtcPGp_ z4XwW=0o3a0VUCZ>Kvm+U>2T zNN|jb4^g#wOTz;<)y>^7FNAHEVuBfB1lOHt42Uvy-t$;C+fm-e;#;oo-7r{eI;ywM zC`8yL5Th_e;!b<#%FcLiBONx#@!y8?0{6UrW6S#BzU5Daph4D_o|D9`%KMksWlIkLX zKRN=mdvbTB6xF>%nK#I2gxV3|THyHqdEF9-6p8XY?xCxqEtG$`(k*Gr63MJVj-^~G z4`?mEc%6*BFmb!)lN6(^8NtApl?$Vx7_xcsm3A$78fwb~LP9*_rM>L0jbs>eU}cSe z-Vzy`G%`*nUm_d!XoWUp#;Kb1Il)>X>MhEN?4|s)I%cGs(o@-)<@8*>hEuyv%mNZ&~C~6 ztIHu*XFfau+(YeMuoDRII#5D@r$W=s^<8OBx0gWzCedW-eh(2mB^0l9t-NYlc;*^? z!>Z!S{(0A}lDP~3~-5Y~jwqT4iE|qC0BRmrS$ednYV0 zfzp)x)lc+CcQ;*;=Y6s@A@nuAR*nNdmLV)YakGeYr#u15xg8sGnZVruOVNJ=%sreE zKiQI4?D$5}6TJ}V!X7+f3!|DoW@-w`Wb!^w?ereqXWGMC1A+8Gdm^~AKSEyer$+n3 z#3KY92nVhWo`(BKVlrL7{0AKLbO3w5bH!4wh%iKgRFn0JzF0E_M(;txjF<9}j4J?9 zuI>-<&JN5AP|wj`GFMc$?)juS+{wu;4kg6UVI^?_*>9*PWhYah@tfOXfiN+~CE&8C z|;DyviDyArLePpM_Z~8${3QG zp2<;S%!i5gpvJa#;^DA3@EZJ^9*}Jhm*=UeJwOgZ15$9oE_3)eA~!V%m6$aKK2}%t z%~1Q)XY$Viv5k;8u(M_)a#=2)I8bOkB}zs*gjDNajz6xeaNCv)M+6&>ZTDrDkOmJ3ii(h{WYQ& z2w<9VWj@jTuon0F+gq`9G6P^zfLC%rF=iwX(;4p9wpGTS)-={Ci2E{qM3yJ^K1ap**7a?v7A8+X}wS7Sa4WzLN?Fuk`vOoP*-Ao|uNh z+*U3TziFeS=rUemBs}r{f$sQgg4I(!C4W!SyuX*r?g8fIbK;OMc1Je#vJKgO9Lg&C zt!2~Eg^GcDmc(*H_?%OpOyNFpo>&|BTRy`lJxuB9zT!4JdnVRQ%y}xf^0zM`=n0jjbK)ZL8CjwEC8%X~BKjWL0 zLUa7rKktdTVd(~+47jy0CJK0LDrd{&Wf} zTXmi)k+!wuQ$EPe`sII`ZEPbZ<_atfp$odSxlE?(Z3HqgjEQ9nT2Lv{3P3_+!ZGB0 zz@Pgk_mr{h*u$`n8K7Hp9f!7fBjXQG{8^&V$tlkMy~0Fu(OGZrA!%z7ieE?9X8rwG z2FfkR(@a;;lV0j{q<$`m5R14kVVIa?Db>7O8!)T(s7D2sGKeRZda^ldMDJhql)x|z z=V?-aPDLYD4G{rk#4ubqq_8}k>MTmTdn>o`Zu8MbpIe%cyKXLRone=K;G z?8(VA6;<50Z$4J?8aUp?IBdhk?(Zb%k3scvW&B(&7NlT`D5U+qZVjdKB4^40U_h;Z zjG6OCp%6jm4DOu+S>_Vu2RR7}TX$9a`|hBWVUvjHADV&P(EMH=M#&+?{8&d6#@x{L zVo7r5Jd2pXS=bm~c6<|m0}_&)N|e6{?P2M5<+qR=#(#oO6sjsTv_kuSt@`cnT9Cf{ zYh_(X(#o>RGa+JO@J+D5BNvvdDu~>{jalgP010x@o)GRU$()XC)Ul~~dxwzqY1F1( zf|N>-Q-d_CGo>UN36O2XHxV>b&baA{$wC1vGg;zBcL(DK79CT1$TW4`15r!FXS zg`sa2HV+&(-N*VFzX1tKQzRTixE5-}({Flap(_d(ZpGC=<_Pb4#gJB1bcWxH`%vCL zuM9UrxA1qTa~{a#BV)QWhamFK=+GpM zj$$Ue0~T5G3&YB2t4P5z&M=~d9`eujJoc;M#u?sTe7XF%Uvwe6Lgyz@Wcsv7O#~oR z_(kzp;-Igx1i=37^JN#Ep<$>^#9r9}Q{LE*L7wpspX1Az%dC}Z6S-ABw{1r?0XbA6 zaX$^)F3-<5)~I1U{EnWP@v_ff=RVhyB}vp?%mW-p8bF$Sa|Bdvq_i{nnSTX_oh)zoHd8C0mONIO!4V)Mgb(Bb-X<>74X zS7X%PPAuF3+&V~pc7p?86lOTr#u(mn#_`Ha)wIK4IN0JM@@^(FkoBAv{u8@tkar>B zVgQBv@xmctD#!p{i*=)nZ^&N;sW}s5gv`Gqqa`5(L>q3)4Va<`r?%X!>-=fl(R8Ns z^SVLSl*_WBEDZItto75ZK`t73l)>)S0Th%a&4@X5G44ekLVc6?eeP2<;kVYZNDK#| zr<%+7aRc~7G9$M3p6JhhyY`Flymv?gi}2} zQ6w>{1Chq}{re^-XFWU`LZuGz=Bb(~RkRGuC-Aao)J-X(vU7YeGL9OTECR8z`nv*H zM*wRxEQiv0s@=9j4dZ!W=qOo6Dj|^#9hlZn-oekK^_o(+tu&F!-QBNF^hwiYehAku z_uB8C{-QhUB!g&gBtUej2lG*3VFhA>p7lWqX6an9&A8qJ=7y~k*9X2TTabe1Ab`*FEFun1yB;*DI!V3LSbvdz0E zrh{}s73Hd_lBsvLU^eGJVhVa#4ojOb)T@5Z^;+K`vN0F3lL{L$K|nGUGtHHXJ>5C7 zbNwuJ`_Ku829gC)C@r?ZE|&h1W;<5t#sngr=@GX{iyk74yO%*!pyLs8Am@|To+tMd z^E!gf+yr=^w?`5;{}TtGvk1;&QtElVPlXNmH70=c94a!K^ojIR-SL^nv4RW%G0>dz zP$TUXSRxHzPZG2DAF;~UIDprlxX&MXvHBd&>x6bgMmVTYF{cLemAYo7dGep)A4{C8 zK;SKo27u)Y?RpVDU|vTGOP^lQP*c=7NhuUnMwwn2u+}!f>%rlAQp-il#rdoREeAxj z%3yglwsq41 z*-r8t{o3H6KC5A!L!gmQ#73f93ujs596VdzVOC)zXiA$mbBN&-apTH~+mZP5!Qy0q z@jiZ>7(h^?xO!jX8D>4ColU%rBpVE?WXfENcE8Twgzu-3GMYq-lPOo3 z*d2P6ULrHPIc$6Uq{+k3e4P*PLad2%~@04OWMG zM|s9wY4QV(bY7&4%av8JQAE-&YS_R0+?zZu4m2whgcgf;2_gBikl#;%SgX_s_#pC|>3^OmtFWavSZRt=zajLN#$E}86d!|>~>OB={ z4vA!41g-r>hp5dSNm^5LMnyI@us_%Dw5(?J96synwpLsEM*NRUB4_j>3!;rSDf#Gq zEr1D1q>DN{qULK2`%WY25%yC4di-^=|J5@X_2n?^By?^fw>l396tIDl?_v;L4`XAN~5f*+uolnsU_bGbGf zMBhAxnT6v1rE|hxs{C6{B6r75%oXAqJxJ$unf8^W4XZL+PP}c$i~NAh$~rX_A{0+5 zSEL;?q!rFu&_^*a)b2U`9;cI~;};bm^(6pSr`D+aYm`VdzF zYRlh^?eCqj-g);lWUnX#l4Y*S&!YP&))GE#i(MNf-ncWUKS-!RrQ=B3j5)P~5NQxW z?C?B9itsRJ&DLUL`4L(C@*|p1)Tyih3#gx6E1d~bK=!^ovE(`KW+>B8Ti17DD#iZ? z9FDKC!TDAeK1YS%aQA3W(Cnk16C9R;c$kt~?j3!YU~$(cf8z)&4Ao_NMpYelmPT@t zY$!@b_hN0|xKQH~HA36wN|51TxfuP&<;Oh-+do87O?+mJv2g?iBqDDoiyE2tAxWg%6{Dfs^(Ig32rz-7@Zi zW7O`bQ5sh~Ku(Mn2BZ~eyICrohAezmm}mx0=aP!{Sgi?#PbP!SH@>NEkX^U$KDCA9jcO~~{%&F&zYuf)DaC`4q5OF@3y9#r7$Y)DfdEWq*o zWPU40#a`TBgLRS7`wE3?4TyN`QSH{rnVkz8dTADc?T;8>%{i}68d*BOYCr5*=#;RT zLwAiz%sv*s>lqb!u2Cj_Y1i+1>3R__yD$r>45jr{JxO1g3v)-uAN^eVkh!nQ+ z+7xaBRvB9)Kb9Qnm7`(~wGi74vG$OKx?0Wu`SxU_S0#D<#Ed73tb|JW#6+}WO+lmZ zHy-W;-lRd)W37{;bNMnH&T9`IqH61s^{Yzb2U?%edfI#}I)-fPSFM}MlHt1*(b zp%872;r8`|i|Hf%6ljQA3a%!@uF(ci^%(=BAf;|$PYpKWkWQD5_@}#Fz3G{iDypmB zNfVj)u)`(RdGJjkrK4wx$YhMq9hHgBI(v(@szOUw%7Ic)DJXQHM0lo^x~FWUiwCXC zp4;oPt+VU1p0a+5h z?BsL*7B4(foiR2#9a0k-h0X}O>c4B)BmdL+i@m(KUFB+Efv829x$^b6ah;r z>#ySDZh3{_ROcGTM;${D|6;tSB1a9%&m6RR@F6<%#-(3k6e0Fno(;*Qo4WwtZ13+h zv8CKy+m;XGu!X~QyY*AAa=~!CQ#J>!QxC02i7z~7K`}H!PPT2;}|G@G>kG%s041eyW80jThlM@?E$8DA6_nHL|YfV zxB)oLFesGjc0ywH_^v8Rsv6<1q`)rQ5NgWJS-f)0M0S@t{$o??(GqOWZjk-veW`A_ zg`hOqr5%PdI0d7zsNfFh?K#TwD6r!-(?qMNM|=hfd_kr{`k$~^7rusK3wk0nQzt^j zEA19jHA|y|2xW&#-Tgb1szW5ZJRtjq#v5n3w1K^~);(rn`id*pYaD+A02 zl3@8l1xHN>BLKjX3SCOBH$N8tQ-k@@<#zgbCbWps7atPF1fkcd!1%z(W6tD~xScj4 z@k3JHj_~qMGDPR;SGSJ6I9+72C{VM+ALMj;08DINGPH|Rn*?5xV(&bK#0K_TGsat$ z&Q^}HeAXjAK0(R|7v(-2JrJ|JWusMqrm4ClAiV8k>I+94`Z(UQ7V{oUkhb3+jq#H+ zVzGRYB|y3|>ipt6OCgcEn<%8eNA;_Zj?9G)J%47RsoZqO`MFKVUkCNqyO`_nWx2jT z|2H-ZEYU6o`IaEQ@mejm>9|SP{L;NVjnQ^aTKgtqeL93nBptcLQO z58L1bME2wIxz^3?O`h&*89HOGAcvA)vU2DFZHM4x7#8le)bt3X$k*p4uDIb_%m{s@ z>u4&7afCuz?^|f+*ZJ!F9Cbl2+N9nL1-SnA?4=!>!x0*yd}HH~ zMed^JWj8P(hx34QcwSwu1Xy~(j5^xdEVTpIXU%5g)J7234I@llzHrNZ*~}ftV>=;> zajjX6i0#XKE4y5B>75Hp%pf&a4RZDajOr3A0Q8I6kp2nP(n(XNdNidg;{g1UUr((a zSMj{BILUFngeTV^Y_4>NWI0ydKl3;^jw%tzY6ud$EnWwsewiwQ#7!k}hZ=lwhSOV% zZG^nq8^=*NX!{$u#!qVxF5Zw9@ioK=-W9uo^qG$FVH6JINCZo0n@HV5yw&qF03HcU zXQ@S;5{vu}<1RvvOD(hfG_jb*RJMLQ^E^nv5_k?1QPOi;HE1ET85+MG9{~CE-7CTk zQ0YH!KhKEqQ{iPY=AR%9{0^o8L4DC5*fKE*;*qq@hx8{XXG7T*iWhA3#kT>#~Mw}%-maPY}sBT z(H|Zp_0P`BEv8dptC?1+I06;jf8p;2Aq9!y)?Zuf;FLBXk8)F4(Mt~VzW~%GB3m>s zanNaB##D4|vbWxAkcN><-OiCJLg7gNib91jRsgb{TGioW1 z<4DYxYba&Kk%nGyH@s%Ft@v~{3XL~U0KkP&6dyS>q zXbO?)d^5sPHNp=G;}&+bzf^Im^~SLOFl4kz!Uy;I#hGT`izQG}A%7DTU!hB?pU+o3 zF_`Ddp(Cx<--_4z_P3-q=f>dvCXd!anwC9S7r4IzUcJv{{pktmO`tJ7qp{}JC;iTD zA+wOyl#mtS;a=McK!4|8WE5YRCkWyq6geWz1d5Wa=qI`~U=v~4r^(d6ZigOm>O}JV z&uS!+w$iqp!l@$~wwuN{Et>x3x5r#SZOgZXF?tKg&2ySecRb#tWtMpQMVjcHLZx< zt*ei?V@2MAX3tXi2F3yqyKVN1-o^r`WEvLSAENdIwTU(U(SPRCL5F{^oFJ8Rbl?-P z;J(olxFFI_E!Q)%RzKzIU&f{&TVo;y5E<`-G!|IvaKSL^qyWU~PVtf>6RQ8dv?~)u z73Zadu4wyH_i&P1^q+QdAEJsY@Z1>}KQuoBHCCC4{&2vTubJiT}EpeNc-M9sOy%c;Oui3eGa2l>=LIvCxBRs*(Gtj_LY;vUb&lBb0I@7Q`B_(n&>%r?Iv}XYM?f?t0hRlYeQ|kwkU62ud zU0S@c;rjz-K#gcY&D#YX=Ta`;*p^fB92_T6&X5}*iY3B52|!G6h8XFz9|dLGcebAn z(KlMVse_n~9u@N+Ke=E8)BS|6dn5|RMydW_OwnxMGL%SkUkQ^Hp4S7ypOivm6IY1U z;mX+qFtSa~aA1ZRqgK)iMEuV03;EvBM*MEnwYt!C3iEmJ{7+C&eFJ`9&qM8=vv8Xi zCMiB4l5u!`?W=_ddk+DZzyzPl{pa8;gcpRUa&%!{CVe!_d zEsCN*XD_UEhyt_Hy>3!8$Zgr*5uvgPZ&}LG)#i3tK{;bGd3Oy9(_n-IHd{P+s(oxd z*{I|3SNse4h6 zzVe+mCqNo$fB*mhGEM;nC`VRG++0jUKa}sq5j@dDr~m*1000931Vey`3RF%~{{-Lw z00RI30{{R6004jh0AGs&0pKW-tLXs{w#Z<~X#ph#xb-LGhPhfYxMNH8bGe4j+95jF zK2Ej_CIk4J)GD1&j3HB~#+%B`*8Wgp6EPn6<6n;pV?@9tqiMeak<)KX&r8}NVN76} zGhyRwextcmOd~kL_HoV}eO>fJKesI=j32YP5nTp)_5=xUUxR7ie(Ja987P;aZ6=+d z2zTepQ#@=lF)VxNTRby)1<{bZ0|*&q<;j)HXnq4m*P=eSLk;E1>`la;5*=6TZYK1l zTTAGX&ni=>Bdc|Fs%J>-lPM|kdyRwsjY1uu_Mz6nu7SzEua-O!h~j8Y)WLOnihlaq zm_7*p8E%MS8gPWiv#)hz!cbH#DPuwFNM)$iJE~5hU$_Pt_t5Z!H_}3++J5$MPj_zf zK-}ZxJQQ za4K$2EXd8S5_F>`j^JMY-$6rPcMzu#NCbPoTsF_=f6NY(r*YdL{|D~S%hVwz+_W%F z=w-fzB)3g(tz3XDbzyZ$b>NNNc8C=E9&mq*Ay2sl2m&iUsH6x(^ZK}AzMGnQ zzmCM1GKy<~gR}sBc@BONtVz*l)g?u23A>#b+dsTWX9Q6$L(DoVW2$chcgT4satf_y zPdzhB86i{Oy&E)&D>fu2zVznE6NGUg_qNJsKJvY{o1y1>L?Ds15|?eY#8Bt$K5kO7 zV(VN|r^RhK5hi+2768o4$gecybH5?N>yngK(m)*0_2aak&OId$r_^U zBB8_D@wb$+fp-esDPV1C(d7-vZ-z%EAt}2AC9Ti%r)pHDX#p{wC2UxB>Q-7#8Bq|6 zMkYDeC7tRM&+1Wc2H7IzMYhprX<1V@Pw>uT98q;&Pow#|>7b^qDoHs8NZDM*(+rHi zfAvVVJm5_cWP{#pp6}g*#dG@quFqFNxr>gXm_k^q2zsRT-%cFY z>*zfaCK7W^?n5is^0!w~r$D=da+k2sw><1D{+A(k<3AXo0&1t5{Wqv8bR&(>!-h$K zs!KqK)*BwMECo;OCUvj3f_?$17O}T^&A8)5i}C2uDZ*CB%M-7mhBX)R9j8!u@Z9)q z&ob`mla*k!-z(U9+f#B)V*c3?diH2D%LL)*pf{0&2@Zb z2#{b7T8@CLAfn67YBFl=H{O5JAdj`v!%(1RP}NRXXl0%w>x#tL+ELM8W>rLibXWcX zCs8=v8^z4JB{sN2rqk%y_G5!gjpO19GJ?0~P;L*oAZo*IATDc+LtbHCZM=2qMl1B~ zPV{W@%2`Y1y>l$^((GOAMz~swX)JNZpI!;i-|%5qh~Ca~nvKL;*ue zF3pcjE*X9X&UHfOQ+ucTslZY%%A%=tkal?+Ag#&WG%TWbL!)~XAd9GLJczhCrD>W3 zAm4nsAljWJr{|{Op2+@I5fLt)HQO@z5NkY{@zs|SMn^7gJGi(Zhekq#I;lb~JtnAt z?*{DvT}z894?R`GmK2!Z7$=g8>LwG%=8O=JnuL6RN-WXn$lJhVoVS9n#jBdQfh~K~ zTk9c_oi?sH@|Bz{^!FdKUGGGqyf^xobLv*KdcyZuxfy5%Xd=v$Wt|c+!gb?3k_j_AgP$h7J#SYJM3u;u4xM42W?OX z7%MeTtn6a4xe={d`VBj~91+Q}0mLOUs#3dT2izfdy$R{EM)=6*coC_weE(Jz@ABES zwQi2#`6&DstETNMv04QbZlawA9QP_aRNm^h@dL0FJ2dm`_o029tyqIS^YUCVvt?`x zCF)4}moRmPMhmSD2yW|mtJs_a`>*zj&*3T|WT8<#>infULcYT zex?Q?roI?VuUab(PeMb!z<{DiP}B!B0?8*Kv1No73ab2dqBjIS3MVcuuG0SPe#vff zZ4)<{o~1*S?l8xw3cZSF2pw}gXNRa6XO7)nwOMn-Fa2y3*3BL<#z{7}IFVNO9e5Ys zXO0w3bXNQ|tuu*6o^8ez=J|whMFtc$6pUy-CgWivW*V%5T*-&oK8)C5p}fBL>gj_aB;RJAo;f@};cE??g7kUQf>FZ}C_+BV~a_995V z;V>*O_Hj@&FqxJB-Jr}`Bl*x3U$p=ykz}+kH9I*ZrBQVkEVg7Yz8z6Ef(sdY$H)M zb!$fd6e0^&95XhAP-A!~Wkdw^d*!&_lKD7i7cMg{_1;pjsTJ%e5@@y(Xq3#{Ujt_B zHd1#5UonhCeKWPp@QBu8P1U=uK zp={8AtBqj{a(*~)69cFsBjmH`(NE$13Rjv~@+EA+vL{Cv%x~TnV#n0+E@kL06W;P_ zl0b?uiPX?~6O-bJGc)yvbV@RR0K^@uWf~D+VBunA_j_b;<`*^6>i-RN?4vmRz!g^` zw^2igQT$G^cR6Kyh=$t4k*GS4|=$G8{`h zT1p1GqEg$s!Zo2L=e3YMTTg%YU-g2>;rz=HNoo0*M3+{g8EYYeD?Z{$AfK;dw&%5Qg+Qg=61W1&^Mo_fn4$DVX2` zw*b5Ra1%!I-+}LjZ!?PD4d`}V?mmI|e2_}gwxkRCTjXHQhW!ox36>LQCPz$yxY358 zA9-BD1e5EO_2l>LKPWmP4WwUbs|rR&Z3aB{YBJ=wY^?b-lqE|W!XKLD@3M)wX{TQ& zUfo{udKN~Vs=oHuH3rxF8OQ5@E^}dMgAJ>AkjVXLfOTlo?irh56RoOm-Vov}JEB8x$bgPkB7_b2uY;>3U?l!8=jUQZ({;|MizX z=fg)zkds*Dr?-MsjD1{Q4+s3`%bmy3{%@C#)TS7SeU$ezbe`RzF=s@aM!OqBgllGP zt+b`o@xNoS3hQXMU8Cj~EwMN`05M&sX2~pyt)Ujy#Z08`nGu;bEF)u7lkGuY>+^24 zHxVq(==~=W*spEY8CJgssMlT+FfK;=`2*MSi0>ZkE0~qqWS-E1?ofueh4C9rZC+L= zabiu-8~E_r3;agLtUj)?TbfYku1fB1#HFY;abh_v=Y`LeqM8N(0iu%ZZ(D=q6=XS+ zj)|~(OS&3w5*A`7yHgF@{koNGx98coTn83a!TKCklNOm%Nx!NzfU=fp!{G@G2PT{# zrVa!?mm55Lj0IBhz2@knYy7(BcdjfGNqvD7S~IPTbW=FrS=<2Aj)8H#1nJW|epPRj zByi5onK~4@Cr&uGWrSbj=HA0ZPoYZ9|Ab$?c_zNR`Cc?J%d}>ZFKE|9!1^Cch5|$! zkAw6C(qe{}TV=WEBVex8DN3Nn>~R1U_Q`jnAGD(^fWAiNxRL9!p65x8i6Bi!(dhLz z|2eu9vCT}r+WR&NDTXn^q?xhEoH`_dt*WbfrAH`CgUuCYkAluPJXMo8b#LiF*HDoF zXP+Qo`UrFn0PeBr%^`|6y{m!JN}HLXhmJG)v7dwV*X9~FZQ@U>sYf$;4&Cy7?yI`J zQibT292@Zrgy1)SZjY9}H`~ByeNbkeMc$}z+uzPFl0x-~=4xMrUI83#r%ALYw$zjk zn9U60ns5j^yvB}>7P%gxnYjSoHVod%;K2tHr@tQBZObx;P zz$wCapG(USYsh8Tcbx+5FT_Kvkd<|2O8%0hZH3_pyUY5^ zRjG{iw18L$FJsiwEMZSZ;LVF05qGAQ;Fa9;EzX1|AwR~#)O z4@RvliB?TXmJXYCo};?nUzJ6}-Nnb<+?Y3L$uPCnm|Hh(JSJMdk$X}(}hb--V+p*>c(Y|k@K;*3dDvd?IuVWbHOf#lJS3%*c>qX zBdG`BYm;ccXAph9VuDf)IE8o81W5M0)-jtZ4CQmlQcQjDezc z?6H)IS&dl+jgG4x*RTZKF}d%G@~~t-Wbw-#!g4NQg!cjSwGTbYXifBh!LVsB&=mnF zg+e%GV>0zUHgb`QIp)hAwF*RhIUg@O7;>}Yg?Ucps~X?wB&TFhi+MgljSF|nsOLw| zH#Jb6HsoYybd>^AwBKLyZ#)86;#Dt&2wGG3Jdx;K*+1nMyCoYP;eOT%ih_5;T?EW> z=1d1+qbUfacUuhotn3Kzj%gbzjB;%#iQr~FVyQ>K7x@McHpw$O844r0oWiZNVt++w zor=X9zatoVi4vlM4ATKfVy2Cmn_hC!Ik!~5Lj4=AYdA((<(!8ULSD;Zc3^`@eMa73fJ*ZR))2aOvY5=yLHwfr>BSASk_&yIv`3T1R4-@PK zX9R*Ahfs?>3es8CTqn?yLl1}VbiRhRKOUv~S?36i@{+xqk{tm`#cKk5|ATAkzP?4G=iq1F`lEWOT62I(8 zLwv%Au3~bpD3JwZnY(;1iB$1Yx~U|AY=c4e$TXDCEeA&0HcGK1C8RmYRh5yiVnbs zy!RTO9hFk-FnM&cML18(i3b;D-5SgZt@kC=@$q%-)6RlETRc3l^ln*5%_)Q`e?2N` z`+h*Nj7P%GJab7q&}P#lxPJi1tPf*O`GD~qe~Dvi{z0(aB3ouIZqPhf-vj+H z=a-mN3NpjOfy9kDn0Ps&YZt@D3PBTO?VVVqTc3|l!lW{6DqdzzUo8$lHkzi76O!Je zAx~Vs^*yy5_7&J2u)}k*oJ*o*ZA^ys`H5$ZiEXv3X8?myFeIj1+AfofY35pMbwElt zs^$_Q>^E-mzo2mlzF*y!+x;b$Mx^kf6tSubedQaSMqikYMEM^NpY_|ND8i;7fO{Dk zU(eau_Znqxcc-@3E`0wYht`aRoDPyjd^}Cv&w3Tl+-AsmF#k2wq@dn5HlmU?JGX>` zCQ(O8BUlcJpR4(6Q^{f-K}RtB zfx6y!Y_kdY1{q_5+%)hf+R~5+=6TUl^J&tSypd|ot#*E0`(v~j@$r8O}E{iQC zg6>c}QYyt>NILvN`6>9vSU`*0u)kPBTWf8E^wagqJ=}J=(ei>p4vzX>?X9TN(8c~{ zrmL_zhr|qc$aOMw6pqTG6^_geGss+-*6WvdHe?G~*)_5d!5|!v`@{5XtSbDOT zZqTb}U(XE^DoIA=MaGP&rP-X!&{$pbU)O+=z@oyqJL0~)myIj`DlWgl-i|hy01gx} zGCX30I_ej}YdmlmCI6rT?$6P&++8I1OSgNAWNa8Y#T7orEv!dpInVZ|vKm#*qhZNz zI|l&~gzFAGx(NOhUz=hyT;t@PzsMugxfaAae6zeQR|Yl4o3Ng*K5i<|{pZb#@1^#?w3ei!rCTs@)j~?5PPjhlegj@4e@rS}n zg#@O(QRNPN~V zh18(zJUaD9&Tkq#TBn%gp%Y}wc@kk60+2}(O}gi+q*K~`_~on9hcC{&e95tnhKhNf zyNa4SjsgwD!9qWazuex@N5f>u8YS=-=1cLgXV6Bb}0kO1V=4|5v0feNf3W)Q41&Eaz&@|0g1to_K$fkx2 ziWZPX62w?K^k}#ewukeE)bQU*V~Ghk;n??GaV=a6jpU!|Hk zCUA^x9JPp~E?#Q@KV=*ags}|9AH<&EHGKGBn1KK9nYDNo3)e3)n6cDeR(f%OX z5%}^~e))bJxdf;`Od1~{RSMy4@+1Qb;Y72iFlXG+4Kp!od3)i(WG#yHRhuMRDKxWu z{()GAfX4a767{Y>8Bc8-Y#;)!smumNHSOCADGJcV)cxOSEy9$HJJ^QNIfgDF3=maO z=I)c|TV>DpJCiVuj(Uqk?Q3#fjkUX;j9n(M5*?R*PrdWv=o?wo^L^wz z1!4M2Ry+_8twRsQJeYJZ69q7lz>=+8x|~HztHGuu=GNwr@4F8_(itwCdvR@e&u+?N zBnO9G$g^i9lrvh!M?+RQ<&TwQGA2_ZnDS{u$*d=36H=;IfAgOpV7>3A07p3Qs$8@A(qz`dbf^EHr{#{OUmCq9E}b_^Va`HO8YC3|NQwaD z5@?3iN}F z__h-CY6boi)R9I^+`#H4)*V0Vucr^*Jz{AE^o90UJOt|XHjoPFJX18k?r{KVSm51Kxt?boJE@t zLHW0`r_+D?_Tsl8*-xA5QqVVn-r-r4zx(uaKL3-gi@&ukavv!dgOdSjt&FEZCSpJ$ zq|Qd|9>xG_1r!W=x-}QIu~Me5PnQk^eX}dmZD&%i=Z(0M3}_UQbmQUQPw|yMY~l`p zpP$-WM9t$$2q)_34OCsO*tDY8Z(xMA7;u9O4>5we7OB!c1G?x)k#=?4z=RjGiC=;0lgNaYZ>RO0k*4#W* z_sy7W-k&wX<};K6{STf{_h(s0;taG@w%?JEO9BNX}KadV*6}5CUyM&q7gt*)eRt?YT&x z?mJYrWS&}VF2)|+-+~rT3$K|nz-HrNri6b2<}eY$NFKJ zJ>~ITMML%>IhBaW21SNK^bBlMe}X`n_Rr5tA)#vC&Df$`b@&AO1X4i6DJ^3^TC>NFSTbZ}_$-WY8Er@r+{v)ps7=TxM;AHxy z%dd|-<$45MGg;QQhRI6}=&IFQ&Sd2PVJk?HcC&k#Ph%2~@?RuS&}va`(dc*^f-gRH zZD`pnO)Jw#zu%jk*mna6FC>{j>)nBqUzqsM=&yail=I9!JT@zS0@F*9zI&c#kTW?o z+>C;a9hBz_{VyH+3|iKoEScHR-XHxhH&Y&Eq9SLcUDT^zbc83~yr406%8-&8`Ro6* zu4;`^?^yA-TXr6qDUE6H&KqendO^i039VBhv|YnMMrQ7*gL=&$+~j@IB1wB*Xv;74 zpz`nO8R@&5`#+oipny_een+M#+dvoiN#1tYmz)R%*Jk@omI3@6)h{mWOy^5pk3LYt zXrbln6#fScS~_(MF^7H8j%n2#X=ga2*6~jYzUzZ3wultc65PzrIazBN7_j3CgpCqL zS=xQ+r*hGOY!&>f;HjImK@1Oq99Z)Vr_VX+HYe8DY0+Crhr3T=9(l0SM@s4saj@Te z@Y&BR(U$l6c_ptGvDc`tY`zt)y_>q;otbY;tg4ykg%D;-{z@Cjs1UT>wx2-wIc z*1`1tvhcpI$1FH7Unz$y!Cci>}BFSV59I)6K3d_f^29{oti!@IvF(wJSiLW+J4x)GCB!~yx;=E_??uFbBe$F zQ~PfkmjJ)VluG7x!P?J9H4%NHU(GK>^y>j!xIfRLZ+wsXdj(@NgL;dkvfyGFQ*dBo zAlXD#T4vkKw`aA$Ol{o;3R?&EpFIL#+?pl*S{Xe6ubASl9;08=a`J1LDds_SlF6gV z2L^*hMMoS}`US9t*C%>(t*FJ)$-X}JPlnqe-ixrcoMl-935957A}|qq<`Y$D#P*-c zZgliDY0^k730SW>qHRZ+j*1vB{!w#=JwIlY3TToNzRVKk7SszWTJOOlD<7yO*>^WK z1Z@`uqd!#iAlFvLD4;)lrqo(ThIZIe*>Pu=2^81~nfLJ4~?VCqq$QTPDu0 z<-9{~m3JN(3i1ifW+4LS#FkfyD~FP9aoG1?&9t*Z)>@lZt`KwxEp2y!xM(EhWCcv! zjUIKNhxbr2C+pPqaM&(5?2*3@p^0*-cm@VI7Ft3T_@qhQBxb%3zvmv65_LX$>4ta# zhKo*!41O2I9LET9)$0trU>=Ck-V7dbwC-1p8k|C%JeJpl%Cjam*UJ*$B2Oqs!VKZ> z*6v*^Wo|KsT`g`tG<+gL`YFth$a4+3!`e1~ak;tWCDqF) zZ{@TnblzYzWzzA1h~HT4Tlq_x`x!g9T!d>`d04(NW-Ju02Zs>Wr`LD~4C-K@LL|U5 zy{|`Rb9O8w)+zZLafWieW^ZqWi-Ww^cF7O zl{Ev;Atv0UU)ieDVPFeF6F-OYp!{E|~?kUtf=6{}_Z64iX#loLOV za*>zkG*87}#nxi5?$vLx2|FU&$d$}gMGFZ7OLIjoeM7c2COdM!vKyGchiRJD=4oHh znCs*e`9`Zh+%_~2>ge$D9^($7g7C6q`7ju-@RqPpCaE`K3U}_{K+lVt{pW8|90l4C zftF1C?fgWqe9x)qJXy`l=Lq&$G}<|jzN8K|z;aL9{9-<;SBpPd_vGp+6`WT^_Tuyo zVG79b6(rSKfdMV}3@X>SW_32V+aVoN4Z#hZXQeeUX57I2Ir%G^*}vfJ%2oQ>(%=&; z2kt9*8-1IwZ>W6Hcw}#H^MZD=%!(_$r#@uU8OEzVjBIzlBqXV|)R`xtn4%X0>Eb!5 zBp*6e12eyJ;;n!E@oz9!l5E>GaIc{GsT9QG%!f2xr}U}!N8*3noL9+={jfvDf^gs6 z5-$@9i&)OM`|G$}9!?p9zfYpDVW-d4JCb2;0gc{W!}Wvz@X0U!DIlKB*EraODP1%mh%t>FI=pzcb+5hk;BEbx*TwWLcK%F@+ zQAoJu3r!i}woW(L%67op|G0tND1r;j2wv?GhDcTdws~q!;HO8S>+G!Dg8Q?T^|fIo z#aq#2_e$Wwu5}wYJPcEk{`c6nR&(SfP(0`J71xtGJ}vSw=ksFP$bdF!$4;(o^pjKP zs=IfTzqEP$saO!20n6eG3u##qZC0n)pqSBD!~)29!yZpAGswfjVI2v~V31&k_wqCX zj&9Exz8i6b*#G*aBN#Cl(0ue*(C9H+Eosz<U02QC4vhcz0*(YrE!eai_7}E|Dfw zc0U96IUMer`4zLH!-Eg+&)nsf-k4C_FA$q{;O{s#}3sN?*Yg= z2C0=hx79b0DZqC>+iZC4+OIp#CA;FD2{6~bM^ip?{~R^mbFtm}p#`iFZ0(Wl`?g>A z=pnpr2{3syY@4hAh{ZM8O6XQD5>yqLYS|v01bW~`j<9EMPSX+haIqcXjJ=9t{WO6> z`O)(5%O^3hvzL~gT}H?IPMRPbKxM!^z0&P>L8{HG@#c1$i^`?1r5^!=pCV9`<7Q{M zgU%$xI%M17m- z?%#IewN&sWy^H0jIJ$G@RPO|<8f0CJ92|&5{#IdC|Ap_Fyk+NC#f0PlxO-bbn$AUh ze!xA%JIs*6v*e1n&WXB)#&Tyy-g*f&+)4j)W@AHkRl?Ufpp1G1=c*}u#hvpXl&B7m z5L^QDZH_VzvqA_8y^?FJLlz%87!!|-5{NVgRqwE1$TM4L+NGLgep3z;oro&XD!vS3 z0kE!J>k~Y4896Em@17jVX7Fm1_7(|PMpQ|-=&SzY#IuD?7Cvnl%W;dEM+SWi%DW}2?X$a6XXP;f3IR%-co-(d~qZR8+1;YpF z<2f(^<#cq#F2us#uC(+N$>csGtos&G6T?22>1!EQ%Ps{YYgcuw_E$$)sW&%!pEuC9 z-Kd88Uv3;Al68%wnq0zNU3V7>=`0SJ&ozo28}KVFY~IqSrIy8TRL9(0a|%8to%qOg z56U(1O8hkZkIni0JD@M~A_e>!hB*+8x)`%!GBmT=&70m^7?IEL@6UG398VfkShG$X=n01X zK}p~2%@ctCjUO*}H4!J7ioAcVf+STZxBWOh3shEMEwCkZ{8SdpNxcdWHmA*$J8#;I z1k9Tw4Hy3iTv zg#Zgr=YmtnI?b&3*`cY_e@ZCM}QCvtj>H_G;tNGc531aD^^D^w~^Lwx>C#OI{@vVzxRwG z3t!bPvRA1LvK}Hwk2h+>!yf9ydTBF}hc=`Dx{rZt?`|1g*m+2C0c?Yc+fT=K=`F8T z@@ulY@Tk7cuNIYSDY#cCgzq)xZ=f$mqbYg`CZf@>n7UQ#j38Jo6ZS8d+{IDZb)7fl zWzvui6Wx58n#J{35UHqS#s&1Mme{@e0EvqltseYz;%DsHlGiQn#T=DmmKn%%0ZBr* zZa)!AaA#k=e^p)x;n%6_kv@uPkfla$ovYDO+jIyeX=z3yis_DmFj0c2AVxUsmxaS?xtJ5odNaF&okN6;9^IxU%uQ}Y1`UK${D z3=buu-u%Yo7bQ}CTf4N@y1uiAO7oDVA(H7ox(MzrKNhxvBUFEoI>(lNK6F3eBRsOe zOd)bE#(Jh3k0tpzjfRRVFg==AEedz1TWGLs?!VuGbab97edK7hmEg95Q}7^I$Y~t} zb%tD7mC=Zt^jZv$iD#diaCt>Ej08%+*{pxtPHhQ^l>|{w>)VJ_h78_=y5BM@Rv-DA z+FUV2m87gTU+I1a=8I6ct40{e3tLtCG5=P`=K970BX4(m!+UKuZ4>N$;3AZM*AHt_bKin61x;*ws>|TZL56_ zS0JP{MU;;gK!(VPDj^tg`e>n%i48Q#bUNr##&ooNZjBS&?{Md>5BqX6@mb3CezEAB%y-b zpjoLewTQhYgodcVA9lz_<^lF8%{_i3e@kENW|PP}u`W=G?R=bPhdo*OOx||ZIcfM< z1XQ2HzSW5R>QykX9Rxm%O2Exgi;>1Nt2a;#>@Kbt@z`m3ELMB(s-HQR+YD)q1wn=- zGa97DDaLr6(T*QFmC2a$!mCP+09$Eg)$I8V9$WclFj;v3zYgO*lqAd0;xt8Jc*b0G zP;*7%V!MYA^xyJwqsdz#pDKF=_xb0!hqf;6gxNDq_f_NKteg+&rQ$IlTg2GU{J4k_aHI$e7EI*+5IOBsxdJVLEkYay}BR$@| zN=n-;v5?@Ky7#IHWkgRg6#KSiTeQ(+NDz%#I@;#RL)pz<{>bwye@ zF=KxCE(p#bn&%v}^On;?v$j_hV6ksXxj8C8Qzt~>roi?xVN#KsHzVb6?bRXnOYTq0 zVF?|*&(vy&K_buLk~9F!1ry5Zt2yrcEjO4k55T z)>G9!#SQKFWeIc6)KT&%lH9mKevMz34j`((eZKzZtousp;NZ)JC~C#N~L$547MyLU;^D#)BjvYePof(;O`0o(r7Az zXZgsBDDhL6 zzpv|*rCdvu>nkZDy5*klk;GCQp$5%isX8L0cMhC3S|~<^%w3b2NL71%Rr^ty{&d=uF7Z zaM%xF!=<;aGPe&sIsiKm31>ZO56z)zUyB%{L!EG{JKv#O`&`v{%4~O?{t7!Q=Ww2r?d;Bp?myz5^BGL zx1^(@myS(*nih=`SqnW-meIC?IA$>@WNLWO0t_gRP3xHfF!xm6@%6P`3}rK}naR7Q z`eS}CMlKVPe6zEkhfiqMa^pWV3=E&4&HHXgp@TR1vmszG0b1QXex1gPm>bH2A#%;V z1lNT{N+TIdP?z(;e(ub>fO_TPn?#|ZJrPlJTLqd-3GE3B8^201jOjD;E_?U9i}mXZ z@QIl4=m1EbP;|O=+jk9QXX62{!GyFVvh^-2uIx>TB^AP&uNt~jnQ?Q)pt;jNQVo37 z$v{n~Ud&SoQNoJ9ymAk5--A#6DbUMDUig%bxR5FL{0cI!BBmeof9;~qY|!`WH9d?F zNkYM9;+h8LmcBgia-npMG{f(m8^1Ryf(ol%AgR0H3M{wd?|C<#Sok6ef_sx)nLDB0 z!$IF+f9^NbytB?+9UGSS!?AAmCU`~zaQrcXSbhWBeL*)$zYr1&DEla@)$L_-2f2ai z4IS#B-az6;Eo^s+MmSvWo|Flf(Pkh+UgDcPF)9(PX|okL9=K*NHo0N7mMHJy z;MMPmdS?g0$;l}@TjveCcMlTIOW4reeM)(;@YD_ z4R!#jmk_uAfnn}+#=FD_RCIN#92Arn~L@9^#9M6h=4!Ru%=ZwrL68a$Y1>rwjgLpI z&E!aJau$s%j-z-fJp!AaR(t@WFBP7y>#xMgYLOvFu=Pe@9YYb~KGW#uj_l(eVy%h& zS8XRpPxOmBTe8{ooY+h4UNJDXGI1L+6d|R?E|+8n<! z@C~Te8<1@@l$pRGtMz~b`(6fpohkuxd83LVo13tYTLzJfTxX+g!C_KIgMc6(Jhn9a z1_s@T*I)5k*<2(+VkWUuVx(YEP?{p$$BX~aM2NVR9a?Fjr+3&fH(v24nxB0t}FiS z%EKs{M=0iGA}?MR*)H$5ue8gODfE5SJ$;SnE$ zFIXyRupSqw7`Hkm6u=}BAxi~mZ&bj2W7@k0!prqE!-eN!O)98f5b8P(O=py4N(k;p zwR%o2#lAFfq*3N@+OP`?JYzEwR)4#pYAB|zRX6IY(8Kc|@3H2&j?%9t8W1;#TMEj` zKL!piJ9y^1;)QpVDziJ5xWo)w_KTSUaqHK$Znm%{$XKMRJpF&Sk4^}t=|cGHM07EO`~Z23_rC>Hcv^Ljk^77KE@$JQ6D=<+#V0Y7-1d6|W|e>TTXKE;+@*YgSIt?hnptfh zn#UZ?Od#RQr-kzo>Kkregqt6T%4XkU=x*TY)1`ZCpzV;zUWZ{j`?)z*?NspngH{w= z93>Jf=)zj|OW{@8mCtFFiBnA>x4)7i%x5yoby|Rr0%*4VME>TM!JE1C51lm@96Tx0 zbgaXtrNOIB=ou1R){38ju~(E7%Uj-H=b!SlqxQ8n5F6jlPC7{HUXmtJ;-CyhCr)1H|QUB3O4Nu^@+78F+cS-WI<*b!L!Ul<^OK*`Tw%W!#*hw6?Kd1B5(Dn zS+X;m*J8aDpF6E#m~UvQBpYnuekTU?u9-8I-lk+v%r=b}Yo>FUCQIKiiNwLbD)si- zhp$173!j1_E@>fkCacAR!+%;Rq=Qrtj5a}W)FGeYj0)sfNxN`VZ;0UrypTc?mLv(W zF!RJo9Sn;`-;~%F=tw8{L!f19DWl~LQ{ZMYmdw5*Zo_qC0`>Z3-5(QzeEk1ZC0fjc zNfrnLc5ari@%4;q^eEB=+a~Wk_NeE5dys8!!5j~g3R+P#2vZ^@R-c(SktGu+-EhD2 zdt$JVH5Ts%!V0LFF_*@U%$q^uQNu2O$CTCh2Y_i3Isw&qe=CFde9r3`D*3thwVWanJj^H`{2so#{nGy{2!P_{{N6t%E}Mu}asN z8{}tWAn?`6HyQyoSNuk5NkH!%Q-LU)xJ~L<6CLJ&vF&-M)&kU-1G>lH0&+AcEEW-SA&@xVv!)U-UM%j-yr}Gief-;mH`idL9HYZ1pns zyLtiU55wb14`T#-7UNn*BSea!Y={jBwV~jjaCRz2GdPJZj1cKbgAc5bZwP6jV=QOr z9nr_u8`!45?Hv4Ku^@xXySx{Bwi(@U0|ci=!oF`{z&j{#0NzoixU-bH%}Z{RZ~1Xk zzGL*BTofS5mqs(qvCIp@yni!d@Juat2!{+iYZuniSE#>-=L0nM`{*WCfc?{lEj9=p z8!RuRBey7zS5ZCLEP!jP{D$Sm831WgwKeMSCnW**?zy9!IBpWugOrK}f{LXucUNOD z9W>7CA&OkD?yPXsn=K+i;}j+SlSk^GRJt=tUJk3b!;vk1|Ef==X8fJ7zSW2Z`rraj zI?8~vMSUQm*7zAro#LCNU_^2HlF1gwqmskC;6z~tn;tA$1b5BiM9`C4* zue>G_M7>`7U9Rwt$>;LD!P5fcefNKEkaQUnEXZwa_-n27;Bm{?L0aV17hjlOq5(TW z=6uMA`s~Ho8?aUNGz_dx}3gJo9(Yk0aVQ zj(_3#;i22PQ!jo88=@9Y2tWw2^#ECxJYKJ=Z>2bHSl1a-3yp5JO&ZkHVvMJ`%`qA{ zty8xfqq9B}67Nke>IKbTBPc3G5#%oOZHV^YN+etnd?4?_izUH0q8?D~Wn&f!3}=@q zFbpnCv-EC9kPVf5${(ek>I+#q5K7HzbI}oct#oQC6`23fIj~cGI7~LJru%#kl2Sfr z35uOiqU!b@+}INyJxZzg4bGS@igKUep? ze^H5!B>^nwO#k1K-H z7&n_L9{r?dDV*K3A~N?L(s7YL`csnnJ3FZ>hDl+7yA-JUh*&RQeivMKnspc{J?Ox=gfx@p-&mvjA#Ge<}ZaA!#i)iWn@o^Z+JLIXZGpHN~hK^UL_iYH0^5HlZ z%_4O>M9B_a%s}r?hXx}k{odqX&zg>Q76}sH+s>|>YaxOEU9ehFLQo_0b#KNxfXwb~ zojZ;!$p29HPEaFZD44^|%*|70WG}YR%xbnMcONz^T3Nl1=#NIBT?fM?Ok;>tmnFfc zC{;R;5YgzOl(XRbpYYHJH`o<$N3k6czf#pfjDOr%F)$rMv*dRW=#j4G!*1qnS3j#f z996dO*HfcOF=T;DWjWA^Jb=FIEHi}$9h_G%)&I2-k5iY-`U>fmffX)itd-yJiS^7N zJ?8L;1SIZxTCy+r7&{Gg$ho=T7v0lT`$8w8JB68*tlMZRYoSwj{|h!W{Lxr zK{~TSJ%8`Gfah_M@JWEXG_CkTSy?rSPd#$K6K8}~1nX-Ikod?)_F_!T7kLg#b%~r87)zLt_WVqDuB`aaH{<5{jpDa@N%ub<53;JdQtw zeVoI@Uucv7Teq1Mj03DGJ^4}JYwnlQLP0ABc`n5yp*GSShw@;6=@~zC)0kA7%Dt9+o`bdSWL z2LrK3sWk@d7Om3~s;df3Ib#d++*DQ)o`*eF<{Yx&$LZ11^Xg76Y~wYn?o*0+2N)7*I&)$4xutENQNH5$EKDW<}xP;?>EoPG8lp8(}r=8W@_N zpIwwU#0|^5b=%QPm2Qm8G(CQzpM|wDJaU!Ood4b%SGRi)GNi zkKh8&I%gVX6WjRoj*${mhR+MB@l!JG} zZeW$e`l%V~@H($qCOOn4lgZ8yS(*h<7Jg$pV0wHSa#b{CODinEi5l?|!9}TP3$pUV zW??yxg>p~YC`3KvQ>(jFzL@7Ki!Gu}nzrtsa=|Q*+fJ%z@=b8JHQ_)S^Ap?2)U+ttXUqwKC`pb|Jx~C6EQnyTJo=t8#iFc?} z$!6u@1>{$4vzEGgo}{Q>+qh}$k`)svfr!4}JsG6bBUCEidFDYz$N4{E?jFTsx;rQB zgB_S(^?t8}%f$g;APad$q2TjnCr|s=&GvDgIp z8P#ibd^w#fcsTWl#9J1w%umS+VC@7N{!W8tdy4$10D@UGG!Y*B<%Vx0LFcybk1Gwf z65=>ch80A!keDE_atFxXFiC-~^Iu2@?%K5S3_n(sFRoRNh>&f$a7sLwJEZ`hoARwx z3z|uLL!VIn#cA&w!1FXzR%RI%DH<*UYQzxi{aY0KVbmHp^UUd_xo=?cqES|OCC#uU zkC@kEt2-;V@%1DKCXjWguxnA5Y1ouq28zII4 z@XU>n*Nx9G7^aYeA%w6ip?aRxVxA?#cihpnw0Qjnpp8^1=P|XehX)#F%+`x=ZJ&*q zU1gVi@)dO6>9nP8P`)vjiAE-aO9QdL0LM%uIjtW@nd*S%FElfK$l;Yuqst5VCN z*8$QFmV-6U4lu*6cff|h&$kZ2#`n!1LVQ~SqDdj8+xdNSVNuKN-aL8v8{|tOEzUR~ zsJyY-grb7(x~TyfSiZ7R@;mE%f$!fGz0G`kyU*^UpdI=mUjR17&*m=R&ARx>Q&bEc zu5L`#(I8ozb2^mNZV;1;N)->Z`^251riSvaDc?r zQ2B}c?s9mBPX;&x{j2RSzy>6%6ZQe`)pyOeZ8q5bl9q20jZ|r54}xkgVv;&V?N{#L zic5rOx6TP6c5!6Grg>!}f?mCqf(w&z~+T_A@?g2DQ4iux(?e zq&`2G2gN=UiBvlX&i83_;eoh3CpqBo9LV?-`8*A#y@cj*6y5p91-aC;Q)bd2-{+3F6uiNH?M*!D!aBo9SNA;B8#0fk(9l zZ{-E4OAu^91u^Vw%odCN`pQk~UUc@~PL(lLSa+zcd39SE1WOF);5e6Jq@r#Ux#W08 zS%446w9Gth6!^T$fZ+ZDpMH%-xhMJ&+=TAa==@MLItjMLHTQm1nxlQwTn4U{5*}D= zJ{vXiz(gM9xScSn>rN^4ce%N#5SX6blp;4W&WjB$IWF3_#UDeO)MsV=!WPvyYmW=T z>O8SFN>u6OF%eyJ0+;A~jvFHYcPXdVrh@*TJ9!AJQ6Of!b0g;Zq7~~IsO!pyxWxSr z(;Cg|oKx#){e4rZ2f}x@Mt~FSo@eNQpaen>n+SaX3E=DCbLN;pMk>m$@XA4b&2vb^ z(PrO`lW-N%IiEkX6*s))efQ~bTt6r(txS72@Js1pB$^}!?qg}LJexN< z^$yGscU2kQQFxVn{pQ|}ga_9mKCfKr&ql-UVo zc!w(paQRk8?i_ZtPfh$t(tXH*)?))}&@!x=T4*Xg^F`M|#EXTp3<2$5Ptcl1Z_G-i z&0F{@(}nZRTYFD$euUHd)vHC*nld$T*^x=5JOuN5GlcSFa7(CuCDjpE)?yWO{9P0e zZEz)rRS?Y(8{?kpV2}1i>?IBKHBZBG8Y&ZE<;a=tqbna(_M5=Ir)8-U?LsivoPw)+Z`rR_(}vl#X^cM`5CLLSI9o@-}12kW=Nph^ToX!e#9E0@oW* zG#B2~UA1nowhSDp$_pCS!nC7DQtM_`{_yESPt_{u-%aq|fnO@FRRCv{H(Z5L|M!Jd zBBm$SCXKYt*VBmt?4tmZ)>DqBFY`CnlzleK6uRElD3gSw`O)qw@b~z!Obm@k@SG?T zYDYPdfv!$G`W*{jHm1=cW|KtO&vZKH_*x_#j%l95mv#}Zl=@W==I#o7tQcL2Q)#Td zS^f*tg;h`IL+|k(%NN`-T61CX9Anu%Z0-G1iQpl79 zV)hq~RW@kEUo}x=&+w0^{QZ;@t~a(26y=t?6scc=wXc%jr5wy%%Wqzv9qr)bdK>is zJY-Gpq>J@5PS8NGy`Y@~trh{4(&>@T>ZCLhn=6yR&{6GF01Bn1iD5~pLz4Qfu5{|q z${TPOQGknK=xNLCYqytAk_i`Cpuc621<9@DK8lQ%yB@&*8=X2t536g!75tqh!3ljC z?}Byu`Gud@MIZlqoRAZH%SEqcOni~4FjA{u@2iPQx#sz+QI$$ObQeX!vQprrA}Fg{ zZt~ZY9kLnzgr#gBse3_5xCp5PNWZzmApw4cqPDK5`K+nfW0eEF5!F{2tys;w$AVBI zkWw-XE1O~oHxfZ8g*+kf6v%n_Vv`QnzZiqRAQ@O^Nun|M*>@^f#d11=l<;k$EkrDM$~s_r79f2kI-{ ziyOB&g-fsfmQpC5mXD42v{Y19Au!U)51(p*FG`XK8>|BP3+7FwFb@+`1}-~Us~w~f zaH65WlHo5_4L-ah#>c>Pja^EYXcQ)GZJ8bwtX|Fb3;Ne~qC;fORd*exmdcnoxCej3 z|K0<+gXH1|*J|mwfUP99TMU-4Y8iP43eP8)qr6w>$tlXyxL-~CV}moBorvSv_kjzV z;xihRO7&B@nr;%&PN3w2x_}l6qX?wWSzrQ+{TOMR$A6~TrNHMlHnl6lq$&x`y z+07e1S52$Yu3fB_dT$7GvI`C8$DiMy@Ib>ai@#nwgwfW2FEpAFyY=^O0tRFd_PMR= z)+ixq-S9>nW~47Q(B<&p-g zh$fPV6n(Q5QGTS$dN8j0TH>F~^@nvs^)5M#B{PHZnIr0iL8BVi5QySdLQt}@Lox8% z#_FR#Z&FK~e9o8gH6pbwm(0U|N-&(3TG;K|H$lB2C{6u))DQ!>5IK>KT5gW?nwDC?VZ=hOYfUk$VJGC$8LWjz6|6{=sQmzR|(XKw=OUetBcy^t8s+$Tj^A~ zCtE~-yC)#>KV|Y}C4?tcxxq2;AI$NxD5kO$&$pn!aPIz8ch_a2YHc(j96*}w>y;u|%LLXb4eLG2MaNVd9H`^Db zaRfimM^z;;Q+BU7hfqQqBuzNG1!2$7AiQs&!7j!>VEwhafE{Cl_|^6ik8vaSMv!;4 z5*~%1Fe|8dBEpr2b8A?p;P7AYFC=+~*JaDVls^y&`+nOf@ZG;A{&h8ro!_I@1a!Gd z^8I%=-MWuBoC<*o{KcEX$=YdxDl~f{<{srnDMW}`erJ{^sF_#Ug&U>Ab8Z_`Z_^zB z)({`HZZtn$N3bn<1)N9S>hO#c0BUB+eO-ZEHYJ_h9=^@>wS7g#pwi?FE8+`1Gl+y8 zojw&s$EI=_H*a!Xo?;2UozH9EYeLqxDl7@EA-U;4$_w>v0&8q_HjM}}Ecf%%Zds;a zzPyD$w|%^EdfeG9-PKjmg}b_+;Arb?cLy=?ru3H!|E5dkV1*5Ki-X7`q!R3pELUyC zntk5anRF$&l@A14@h0$*dPHe9|8&|YVfh2Xc7!i|RMnzEYbmNQDuvhBB52#NecL~m zY

#5f=ds@pLUZ%<(&E$L4+MPoZJ!=sqH4Q10|Z0aCWqAzGKyyqC670TCeohA64B zJo1ZD)l_C%guF3sc8l4r6N8C;6LDDqiJvxbZlmT9XX|!dkxGVXXl4fQ+RC~^3=N`J z4Lp~W-IrXglKzO#YC;l;1`tkDlA#$o1jO z6(BcC@R{5^JjFwth5G&SU$)ffi6wjxs)+j?v^N&bXy?R;7>3k?A1u}apKtr?f*cw{ zs#ZF72`MR9%H@V=V0#Quzc3$d7f%QePac{IK(3#1as}%v34;}`h(#11;y1+1OU8g9 zMAh>zq=GHixSOi+=-rYrEy_J+q1mWeBK~*xKWib6NcWakWz?g)-sof4G{vf(+o6TMjZFyPGkt zF4uRFML%Ix<;?%M;bY1I@AH^>=r{~cs;3N*hWr7Prv%Nz+kV+^!a^ssYpG7plOU+B zUj3)6;WzU{_j^5iew_O^9xq)4>356gT;S_HjV9XA%BS;!l=acpijQ4);U~ zn<%7YtRW7gYK7n0|L-p`)dp>>cFM?x*qAYI6-%s&<}pL`qV;$VgnImmd3S&%^fmg| zdbcr22NXu(P|t8axNIt+Aj*eS#-{M{?%QC{?^wX*F_X-)1fpm`;Rs`m!@ zGdO#1LIDr`IjxS`jJwb|tbPB#B<;n}RqNOB)*>DxWRK@#=(IET68_=BoEm}lP$?xk zil(3u-90{XT_hLS2Cw0OJ@l*xD|vF~HgAcc1Iw zzPTVi-4zq%Hq8w7;(V^=7$G~Jlw-$!Y6Xnj@ZU570O_Co3OhphU*M#;^t&ZNKLV@Hl-hLJa=!2U!)-ND2VeF<`DV8Ads)g` zx}QW)ve<2O(NiDK+$|STY60ea5XT?KvvmFLK0`%$H;Mg8a6RG@AxQT>OuQhzHk3On zrdRMJ@Iz6A#Vx`(;pgbg8vL#|!Su=1V{InYxrFisGKu~irw1Dnw(XITEi z+}#u7-ow8e==Y>2d(UenMLQ8w%TcAfGGL*umAwgYLy7z&-Puze5LlBsfd3q#L2M^E z7X?lAEjGnnYdtN^kBvC-f$f>uC#2^J_AG-^rkaUoZMjm@GO@@|`aQM>h|}cpD+`8~ z*EE{qb#NupF1@;9Vl4aRkKNXT)|3Z3{Oz6jL=a5C@d$K8^brG-*s51Ct{_1GlkSEq zG9sEWOo6D1fV@+tC?EMZ!I{hqG^t&$k_!(!=ZzNPT=~w_c29WJri0Yv`sP?2qGJXY zG%DJ$eKA~|^tuo~j1He^rdD0hBXcvb{Fn;(nEyP{c+`c{*LQ@POY=SVf=tU_>aWGL zUHrf6@S7{fqFS1!NTmeY9M)KZ6wJW_CH4+~bZcg1EzxyI5t!w=uWHxk{3Ly8gre{( zAI#Vc)p0jVp0Z>m55yq&;izt4xD^&RReD3>#b1W?&7zT1`QG7ODo&%U&7m=eP{hE zhqt?Fv&Yl=o1bFXkTr@87~^1_7{3OREdznl`to>=I9&S&I6XiR&kLhYAoqFEe@z6^ zaQy5^k8pw}FCWRlHzzTt&VD1cq^VrY%y|jfsoz&H= zQJ-d$3;#rDj2^Z)6Wo~b4!tzQ;H2*>gDN%LVwS$-kT*V51nx;Z7SsWIgqnuiQPEE~ znqq*C6aT`SqA6VpF{sI7Y`-BGS4U`<|19AklgN0itJLB(L1L{RCBow(oGO4Ui#H~k zb^_QhWX)k07aH=pG zUfrN)Sb5PvE8lc$CcE-WYc1q`tS4#dpm5)OD`J*)9E@T9$r6mLp12QIUCG56CCEh8}d@hhu-Ih^!tFbKldC&OOX9J#Fx`QVBgWeqd z;BUr2a0M!1YI672OY;1WK$WdSyI#BQI`c^_uj7`os+}1DtW<`KyqabvnOO(iuA>_n zJ$SlN2)$XMPk5eO;BweTm+ ze2^lsjhlhcQ*3xViAqRql3>pdHbs9(^bSL9vE%x_U==Vpo%Ml(dewdGG*Vf58F%ow ze-nZHiOhent&vC*M$p(l7Y-PwO3!UyB?k--y28qQcK=c}ZB|)g5t?*<3u4frjXxKj z0K_#lNA#K?UHEKwb%*4ORl5T%aerR~NntdJd;+C8{|SR7P-;|B>704vv|)#W`EgkSgXzC2hTQ2T06$gz~gpjOLa zh2U6h4wuFfn^f1cRjRy83PXkM6z)e|mVZ`YN$|#SRv6dC@T5Qj(3dnv_pMEe-|Db(8_dyY z@d~i?7r3bC59^RkH!=BlVA}yH5j*DvAMc?szf!-#Bq3Okq-gRiE}W%0CUq#X>gU){vfyUTAcR6BRtX+^(V zc%9EWM_DW5u2nV^dOWc4*N|D8mHEVGD=eB5-FmWi6~F@T&-GhJ4(?s7J2Qjr6bPQ4 zUBl&|Fan>gw#-L@{avxVSYq8M{2pxnzcG}{4en8kpF?0i)m3=nH>OC{S&p6Su)xxa z_6OxD32{JImVd=c)euRir`E;bX-P>pVB3=FcpNqNy$HZU2%!oZ)7sK}E=sW}nlcU( z2daXY$(n-W2Og^}XENl?2afXHjkZ$4aPlWC|CrR}Uv0=@qNGmCxfNBBq)LA^p;N!I z!OWujTapH+k$Ix{{{(Z!)!XAmuy`1NtJ1B-mjBo=*sXZe)L`z^(Yx(Z{D6ZRBIU&!G>J~Z%_Y=T>a<4QSjFu{3wGI3y zm{FYKz7)+f;UJnz)eP+8q5VpY}~$pa(T^~IcPt>q%Rvo;%Kg$HDCNIO~24GEIYrVaLk@&g{- z(ycUp?L8)xwhwl>PYK7Pf8$#Pn46w zTnB2BX0ntCdvKS>FJrTG2g<;!WpU9Yn+;ead&bG{Sf%n$&pU4gZ^bLs6T#I#$Ojz^yJFSSSF(wkO);k3?5H>HG~Uig=>)8C!Xi)E@U*qM46x*u3~K#14T z6n#~ALI_4x5z6%VOMDpFXE$`Xlwl; zZZ3AqfC2N5pb#pe0TQ2~zQrW_5N?RBev0FcqGEsO$aqYNNT`4acAtI8xAukhG9X^U z=z{xBLfn(9BY{fjA}QylKK`RHfE>BU?nmg1>y{%lugeUsKP_!D?cxz~^l<5HJQlsB zG89m2!j$1;|CFyKFKWJq*5bzrv~q_mlYOK7+JOVqkptasySdAngY%`d7zkb)iteMN z1uiRJc=)D34Zwl>YpJUQ>Ta{QpQ@(-ACT?KKhvO3;Na~Up(@uc+t$u0dRi`KMCOPA zJikTH9T0{?T(3ptBr$AKd{Z|UNx9I|FIAnRi_!e@z>8g5R1)yQ9(RSE9-yM|y|7pN z5YFI#>>rzf)&Y%uaHXTOWh-WpK72ae^bhP5nTo*3tFEKo1!mw<`(hV=?O=)n$wSQ0 z5wqN^yAT2g(}X7Y8D7|Y_a+Ct>||70`?l`L;fC5GPtTP|4vSOvfg{brjI--6BI5M{ ztY!Tqi4c1i+Q2T*3=xAx86GJ|c0t|!@CD&%?y0-HAb+)NBBYhsh*){tkxdE4S2R;3 zmnEIGj%ADbhzart7DVyp3!I%xh7LKSB@~be8%BPq1XEJLG{+JL z0!0ZAYiO^5_}gdtF+&JxS^$gDxe_OM3mxC3Pdp==3KZ_btaf3G;BMuly@qpd&HP7U5QYHG9;Sm|4n5q$OXF462U{ zKObq93d`K9Wt%I-E&kBZxavq8TZz~3KkT#!v7|h%pev9DcoQB@y|y}p(Qd(UUO&rf zZj-Q>%dY=%q<7_~T9@@JVW2Qv*d(1#Qqr{wrTTP2vqyoV9(0(DcLHL0^MwK?G9$Mb z`n+V}s?TJe_3*@AcrYp5(P)j%@Lb>Gv?Bt>1VW4^^3Juwg+GyGzJ?f*D$&tiIs&PzxdFI@T88IqkR z;^AOrIjc%%hHy0EoYH>3vj9A>gu{{}kVCZ$-=Ecl<38owL@`*l-UWFsFF5*O)sbS( zScVPu&a2M=@%8BG(1G-;BU1zK{^DV!XSllEHe8oMO(377v#M+wGrI3Vp?fs-d0%KZ z3Q(V0bL>WY9 z1}Vd-t^(e`-lnYkZy;_PxXp3b^rJ~6u#+_RuEG-NK9Ni+e@S3s5g>jf_T#y4@L${&~v{>hGA0jG5 zr*-m?Y%8+Ofxp>BNtR7|oOptNrih&2l?ITG7csu=)j zyAVNc5pv?`5tgpU!&>%4gN(X_OG=co&ghD3~dn453Bzvo*|eFdh4fMxVrT$-=A>pHBI~VW=hSOG3BgnxOdn&jDXEH zfN29HbA=8$<|0w-sH5C$%zp)@)mKnRG~#8o(ib(^s*kNEOt_B)xBsBi4lHaTGl-v+ zO(iUGoOS@r3kG5ZO^@9fWFPn_c}U1XGLHBV>&2`uMP=Cz76zYgbWy*(p54u2ot2G( z|3y_xMGAI6dH3AMZ+R%Nh=9K7Yi`F@9>;4z@7Y`hg#j6V)rQV&$DGRNVtmAyW?*}# z9}D)UB62RDC93}}S$(A*XpX1gTHt$gbM;?bQQm{Lrr>3Ni0zQ=)6{pSUSl!PXt`k8 zY1OVL%2g7kI)}N-ZeO0Ip#q!k&qIPQuT1Miy*3Rvk=XzlzefH~hj7v=LvVJw|1kL} zJPHk<(|PglrYzX@CxF7T6)2yAyLopcCErq@;9gzCKvay{`Q)#S=Xvrt>pWCo7X0lq zcfRF%5qV6b8DS6!QgE}qWgjjF z8hDVxaXhc%6SBO=T6BgsXh&2*m3G!dPeWEkfcQ0LUofE5-jO!uGKWxp&8m`nXeF)j z=|tL%{iR?s#6ybF_NY{4cdT$iJSA$Z^_xQHZT3J#dsB`$AZt##H|&-6hi_V)#ZQp; z3j{VmeekSjAcoGp^$W7Fyd217c_NlFdb^Y&Fy&(xicmuv0zDC3kM+xMko$q-h=o<( z>ITv|`Y)j_mf!qRq$C?5O;faxf1Ju#X$U(Upnp2D&oCMH^t#U zPP|N*NC5zs2Q(1w>~MPXf+bNUVVpXxGojNjAv;nKxx5fkFaUoK&gpoes!5WeV8%Fr zD18>va{dT{Oe^+x7vc=G*Y0covbrD^YyUp?G}M0F^3_7eT^QH^bG$w#(5dQ*JJ8Zo zSjK&@7twnFKM$BBj)~ zGlkk#K;o2*6*AI)7~vXTuGmuCSk;AcOqPxp84fI_E5VW9=|XTEnf$rXUC#5y?~(JQ zxCLO#(twn>+pdT*^@A-lh+N`lXHIvmb&F|l8_Rq(%7>Po4E22$Rjkxq0REEUa&%%# z9@lv|cXy#~3C%=~`V38PXB$R_Pik|0qKc?t+4kik2uPRDHxiXzTyFA#_H=OSIy*I2 z*B*;y`<+0Keg3QW)!zwZ?P<|Nx&2yOhbQKJ=j&B(CZR6l;&JGV_uiimTQw?uC(u#5EHrHT{Ek!r&iB-LP3}_r#b%2*cMD!dG2XR z!!uzNM2bni^C;kf-CGEvW08L$Xp*l^c#$Fmg!{156POMvAt*nyA$81fL;VlG&Q~C= z-V+ zeT@co{PCFTjt7xln6sjQcrs#wcL$+=4uX6Y;d+K>TTB z!R(OSlKqh^bKMK^xV_CIS*C$ z?i*_?L89*Oz6gu1Ic4?25W)LD3#VYadNjK?L_ubTK|cPQK~V{1k;>=CS@WX;k0ZV* zCoqO>K4P;nnGorGs;)b|fWv5r39&6mZZS&V_6*|SV(vLPcg*&$(?df&5ZBD}-y-UI zkNvvRls@w%#||mRi`b;MqCNc+foEI_W*67NQ)VBG-S1iTEH^!`M>l4kNj28<#yw3> zcgZGZsT37fF}iY~>n)$OiRGN)SkKd1htsH=Kw~ZCLYnR$HiR9wUai>rCDegdK3R=g zRw$frO+p<|8E#kttoeaJ*sKY6-_G29$OCzWqpW7fV$Ma}l<6(2l35vB)z~2=9ApcZ zb5dsm_U)z}I_aix^i1>*zz%A}|!J45EEA>s+m?#GFN_>^xRKFuCgZcOxI6Hi| z&&ygfqD!1Im^E|S8oFJs6AO|VB%jxQ_Tgryf0KDd`ma`LGofp!W5Y5GblOAL29*wm zko()11eN=aR*T3xNBX)b>vwn_dsDgeX81cp2k}?KgB!GT&XN2CXIFR|{2pQe_av1j z2KUu!#s$;7ffdOR{NuayWv$&zRD(&>Nyx1Rmd#EOAvjnRy?W4wiVUTi;a{VA!NwDX z@d*V3hK&b*%p-QnNVc(W>4PB!yu=DFFo8WIb&+{bxoxMhNVJ%J<$)jpuq%3Rt*7#U ztX8qyH=rUFe#pIGqex}+K}50tL29y?zw0e2a|1pn*sZH!7Dzt``pW0dbH``jyCyJ= zKGNN`Xk=DUGqBEP<0@*=U$z4j22M@Y(3k{{+N7&oCrwP3FE+oVfUrPzi&kE(I<7C2 zAD9@4W`f>{rRp%yRO1q`2$NAxd+*7HfhY4>gfO_-%ky98*>;5)XlQb*OJ28@ z7OPjb4~C>5Dt|*luc@&6H#}4WGm3y)&&r$plF{Orc#q7&Y&Ot-mmkka+2vuXklIwe zwd*<%IlL`+aM)&y3y3e7;^7PYZbvLtzr5u41_tttt>~6J=nuRj^Jw24xk)S?HLQn} zn)yMIXC!Cbsc;6o@5*3wUZ&XT#;GcJ7nVCGWlXchC}WQb=6>3Ef#dlS=46JI5@jmq zzEs)tzfOvKVwZYmy2;JGcl`NOZbgfH6;HX<+vv4&O44Y-=&N9{Vp)(>;X{X>T&32I z;grB30ZSgvKSUh_+PzsFao5Sz67Z*5-?P2D$Ujhy%zQ}o!kGP_W6kFQ;7#T$^-_D-mY{9+TKvlOGfdxW-a_ZmmoS} z;lYO%ddpVLVZ@-)<!+~8;e1)z6R;+U@fv9=aT>CA@euqT$dOP@xO`I7 zkB$`3)mt(1B-<_=)L**;sVe*!y}%F&VP6~!ouq9<>az1oPFelAGbmerKsU*pnhPfK z*8337&8QOrv>R|Uq@6mgg`-U32+vEAOY52p81IlAK9|6C+4Ov&>*AhYqJl+Tp_VA- zle(tx;n2^2ydh5niE_ixcGd8LX*rUyaO+4BHB#&x4s3R?ZFuA&_itMr*|*5R&MT;R9rQHX+P067?_r>rr;W0F_JjT3 zI$iT};b#-vrSa5Nuh7YG{l(LF@Ey4ESaz`5ur_XSn^&1WVlAezT<=ch=ky%m9b!<$ zno$nbZ*j;xIk5Hd(WjzE5P7nmn-0lbVC#EomWwUQ%xCT>%oWdC6?=u;b;YeqzU(Zj z@EPl|u+OVxIVvTzf)hNx>b8Vjt#w{OXi#o;1$y}-d&<2Z z49(BB8|tn@s7!YZY~Ld!Y)K+-GBu4Dy5Wlj=KYUkR^YRXb{V%3!z|V^D6a5okWXZq zcG;BtXJdsbC{aj@izk%6X$gM|?S!UJhR}u76|IS&F814NFA2{Wm#rgrLa)wMaN5*+ z;bC26kT0UoKiXK)8#h0A~6eeS{qnbXF` zdAX|&J7mW0Pjh}rLuXGdg`QogseWlL&!kg2lj90jTWae(7Zm8ym?J`^JKXz-{MrR9N%k^1Xz@@}S zm1BU2TUJ>qe$={dXIT`PP3V+pDxD>KAcZevB|UD&;dT$mM&GWej(|_@M~36P?d`p( z&YTr^d%6sy>68Dc8X~94yGrmdo~Qs1!%fOtAtZTyek8k3jQ8qV#3=txc~lGGkp@?M z?&Qi?Zl zff1~sa=?jMTjIx3S)vYHT5Gllo3R%b2A)(0Q%P)M?xwTiK=jVL9)H?`hhVi|83SoO z8?o~STW5zrgs&j-*Wo=j3^2U?1-M#1mt7q!TW;vfV9mWD3!neQP&j-JA|805_VBp` zJKB@Y{k_V=eGGrH2u4QO@n0~1CKg={)XWQEL7^vs7}vvAMNZ@?Cx_Y}*G^3?g09*6Zgf8YX_JAFQ{S+eZ=IWpsH~u$u=iiU`=U~w_UDE$(7`wf zNkJc6O zc!WXXEZa>)VhrfM5T<|pTMoa*67$=nu17!tXaE_LPo-W8lZr4RIN@^U*6Dfv-g+l5 z8ugV6Wpke^Raj+sySEzOJy|TnOqgin?WUqJ!`~nNIarlGqpVRg+SZJ)vA4cdSPo4= zZ~3wI*4L_Nx7s;zzXj*@Lb1{|s>g8fiNs5;IGbE{T_7#{Z2y!cxz`9;qZdB*-AcOC zkTh6iiJkN`^b$%2S1tp9tm%pM*1AO{77ivB3o;++snX8oMnR?-pk2?=-y;_oY>tad z**U4|TmTu;9{kjPX^dhB+-6ifendFMc43Hy{MOEQ)in-Hv>xcBltgECt9_na6IL^#B#$(K zE3~B&*^yoV$@tUUe`4ILGCd8Z8*~zt7Rx`L&FMr7-Cq|EV$iiz-DKXCSZKM+v~DN} z@fhaEz$UB-G|tDYS@1not2$4=2?gRownu_eBg;H#Yz4v@yxNC1vkcOZD9Xrs+?Q=B zBD<5SaxjxMduvkDiD3x_U2B*0`$xKrD>MAxkUW7-KgCTOKkK!sjFw+@_Z& zyhECAZvTd``D9CwRnV?rRA0MHGNbuGaeDPa5tJ8a@mTR3z0dQ{29CouV_Ye?P=699 ze2p;>1v@?WpG;U{jHWb;u7#KL54n);kxAz2bY}Z0C~J#Nyl;hc2?rQ00k~1u2O`+! ztOHP~b?_F?1i&eQQj*9EMrifqjKAM3QZ5pT43}z5bpN9fZ3ZNO>SaFCO?o@2{cZJSFwIn zNpycvzG}ODPcwv2I}$T6ssKDzRIk0fq*Pl{PFSxVuv=X*6WNOvYkkJpHCrKD+YcXD z*EoZn!>zS)ZS&j%jN7cx1|SEA3R;;`{JNTd0&Pjm=E`hLt{rR$L%~X;O6Wp9;Y-{E zHORBM`6daY7r6aM_^qnf=3dJLNqUUE@Qp~w!5*uDvOQy$1vej|Ala^}F{x+MdLO(0 z?r4x8)2fyVM_-=j+4&7#>H(Zvgk;7^b9v~|G0!yV9qD5(7_2#IbY-YvoF%iQARQyYkD{U9r8aOj#wV@9UT65j5-5I^(Y-%!JZzqYy|H#xWl(b zip+U`h(V^xizbM5wJHD0YcdgcXpJa1%W_Rn56|5~E&B-%?btm}Tnm|NgPt0k9^3oT z>t;(z#3A{W$>vNG=>5fMu2Z!)KT|w0yvXopVnk&-ESDwE=Ss0Sgs9ce9nZRF#f2!9 zu@1{Rq>wjg`%iGz61qux?H+RC+IBxOj@5BFy&$5>!cPoc_nL)*nO zMK}txNowDMCWvAMZ$OCW{G3h1hf=jmyfqEXICI;i51>t{Zk0IB2M#@jeOp$oXal|O z90dFV+b=RTm7@uCRBNGLR=+?dF5reYoocKKDGDR-3HRL-EZ6h@C3=7BF@%ope1e7E0? z;8r*Y3s@7DhOgbm@&k9C5N*5HSPQ3%Cn7-f;OY6-qKYh^IoSzR{3>m}TJkk0xLurt zyU7q}4X&$!-=wxLA(YDaX6SM}e0_J8B|PKF+d$4bK(0d(h^&zp{T$WL!3>itY>F{1vf3y;l$?;y)WNROn!i zP-%y}czwIoJ6K={*pvnbUd{3>^|6_TbI4#Ctwl1xL@KgUf9dwWLd0LZ1h#H0c(TwS ztNe@lzei*(wq(Y=ceFh1)0vq1R^HX9H`3-XPNN}d8XD~Khuzxw*?43c^V zY{hppn|hwFD6{anXZEK{BNAa8$@ZfXQWg7L+QO`}l-)K}3dUSvMnY+9KG6&F4^}!9 zn6MBmrjFz9oJ_F+gp9cM-uRJK_1}*k(%Kqa*Ceav?Siti~F2L3y$l6T__mel82!15wsT*3f(ktF`bHwu&k0`JEx zj9j3cSazvLKf#Ls?FoBNmJYMTN|`_@R}u9OzNMfWN`MhY2T1EFLHwyVI~s2ranE9g zvzX-DJFd`?80HbPAbJ+gy%InxfeCe8Kwt8W@vjVwp5@v+hw#jy;e{dV`#oSzBrAL? zT>qxR!~cNPE$R>c^pMuDarkrtwl=npkNvQ-d71_uqPZCL1?(wQkxPaly4!-?n(u)%d@W z-uOlOmZU1hV;-djB)TtR8C!WUQZkIL;xwY5W*un(>Dv9ozUTHvX!C8dqcWLe7BXuO z0nXme+u>Q~4aiAp4I8-#%_)KNhs8Be#KfqVser-M5r1}`(eI?wby zZYIUW)$YsQ$aq66y_1SJUcpe@`SQNmtqOLCg<^cG%9n8vFhzLd7xyV!xSYy2t-aqS zDnR}TOQQ|)mxeLDd8?-fYxrV$Fv%E-?3oxIxdnfv_nVk#*mz{G9-9S&axMOAm))hmDAUc) z0t5OmNcmN-qJ$z=>f+v8l25Ano0#wM+x{a&^Gvdjrt<+m%F7PC`I0mlX4SNcS7H`a zAIg*zE4&zz&X*nh$fX<9!a=HDSRpyF{n3;sa=Wei1WoBU1Qxc~OLA5)hiAi!c4AgF zV!62vMFR>Jo$_6>X>YeR0u7Lv@O2!y&T;LDuhzH-(~27_FC;23UmRtNGPR94(>2t> zU-Z?hKMi8?_r+GiuDVJFtH-AGU=(BLrnXeeh9#dqr^FP-p-D@@Qw(*)$V>7062RK% zvB>0tc9bc!c`VwJobLjVhyOkoB)^wipQ>wdJr$rtyBxU1H=HR2l3tjD)zCkOVS|xT zfJaPv-?mdd7fktqEcq_!invzzPa9#16u7$>@3Lro{EZzyHMQcL8GA$~YrQzMGdg!q zFGzm)wg;zfc(7|2P@Y~9sMw!c?}<+BgMhRFO~4;*wFn zf`LuG14OgO&%RV96%(U9U!J;w*?fdGF6(%FivUiA_p(?0oNh*g-?0qle)n``RKMQP z9e%OGcW}cFht*B0f@E&Y5N2pSi+mC60WAk3apo1mo zMa;kIgvbsnDYTjb4a+Je`___v=yxU8GLShsXt}Z*4KbQD@X7=0E_OgOvckIG9 zYB<^GMq~S@5kfOpqosH!WIyZ6cx7GnYJ|&UCN^#RSPBLmEc*{M{jgNaQTbU&t<{IIf&NHh ziI8<@7d|En)F-}JWsK8{Vnx$^QyJD61zfq@j^%^6=xJ zLV%}af=of9w}XY4LqQ-QAQpLGa4HFP=`CH^|KRA`VPxKUkuo44(EnXvB7qSws`ARe zV0-_!^S}FnKtNRS|IMDztz@19BV|W3dsnDY{Fm^i-Nn~Cur)*543h~`iq@f$MGubr z+oVl{eTDXQ5Q2Sdi!&X;{kzm(9WPSFALJ!!-FK|E-=5w@l=V$TE%6H7^{M9UhX0qH zg3PYrpmaYs5>#{Hf&aeFQAdW{j+uVzP+ux_G#O1-;$SZUfN`!{JKuls=nFy9b`Si7-o5}Na z4q)nURKLY5r{!>y&b3D%_vQoWPrp^LqDE0V*B- zJ!mz6jz)-LhkW&DX9K`904s)l3h3+QLLLaG6lz3aM@W6S1Vy=lq`EfJss3WOsqqzY z;lrT}izo*(V5k4a)f@6G(LshI(g~e>g?FU&9=F2&>gdSJB!3(RUp%oF8Zvy)H~OU5 z{I}(SJStjKq9+OKYNn{9Jk48yND$Dhwal_eH}e=NHlH`*yQ#ltT~QcMCdX0tWpS0F zydA%rLnC$?KjV-hr5UVz^<$%taN|MqG8f$KL_GXtq)iRQ1;kP08{Q=$_0bhl*7(fQ zjXJ(=AIxWK8fbcd!cB{s@UH0nC9T>z*mCezh8bqa%}&I^=9RY`E-i(}^oc zHi#;J@buB1D4vUi=M*JvV_)9W`vs|r90=vIE3JXm&F&iY6S<|8&->uRDg^fQz0^R% zh@?l3Hk_A;?QZ|;rF z*Y%r7{v<#fuVTxm)@s1tE&$58O(!Ro8E%puR-2ikN!wm_6T9TkG`l8@31k`p1S0q_ zZ%r1Hp=apRcSBG8WcWXrddj{eeZ)0+YMa5xrh44cm4&S{cd7miD=mfAmX!RlT`pMs zv2f_Y8mmgb>ZI&xObY!q_k;$yAd|J%3YLcr;67} z1DFZ|?g@%_DD4_W7iP?UiaGfWZT#|;YSJw2xz$H>vDCETfD*BG~`L7 z5_%lXM-tO*;+R4S4L0{!Mh5{&9V|>BWvXDy*pP|+5%-39p@Qj7)qBL%NTSZl6NLub zIvwK9w1kI2i>Vs;zCJP+V-I=XFQ68m&eOO07E4k-j0?vSZ4hgqgKw=fi`&J|s&Wk$ zZ6na+30l*Dy8gl?&-Y*enl-db_gjddwv`Rv>AuG7{GSy9(q6L&Y%~E4aoljmVpN-MXq1_%o^cV# z=$ECSb~eBdaSE~Z?N+y!FX}KW@^GrlwH<)VFWaz~n#v!@&>7?68Ra{O1K@GmobW%K zor97n3JwLwwr$(CZR3t@+qP}nwr$(Ct$p9H|B$Lwl9TQ#_-ULz>(!Z+7>a65D` zq~tib0SI1|LV!o+aZN1xsSo#AG~%dZbzXT8@>KGZhy}-NxZV)H#vp;HA4#WYbOQbT z^W1gQq3kY+g}FxYBUH?mt5fD$qM&8ZX-)22%BmUrpigs0qb;Hix@`7*LeF-HXSZW! z+!?>)9W)g55Ky|fq5)e?@{$Yo4vM*~eA38TY*aT7jc0`hBQ!9<)uui1Lz{HeV+@69 z8YbE8FW4GbsAK(iV7?NjXLRVH6%t2sh!Z;zkaGKzMNu4Km9H1HdI{)zI}~3gBG7_2 zNPoh(Y)w1GnC)XgfEUmxc6y_m_7)FRBAn&I;W*o~@}zpk;n3Td7BKp#Z^2>RFR)Uv z{#2HF-zQMK|1w;+qH^ofatgqvU=o3&m4yl;!Y`#5&6+`XU^qkS0AeXwmRwEJR zOj)HlAOC{5-N}rmpRoySrZ~!lZ6(~Pxzh<_?k^Ogm4~-x{7#}KHhbG{S z;-6)Y8Jl+v+DO}SO+*IpnfpjJbGrI{gLFd$d%l>)ryZX=7K=7+WG^5dztmOglVDU@>-!OD}^U$~iYk4XRm$kNQ||PEC8~BvGz{UC4qu=G0hUbs*Sg^|?~0 zHMh$BCWQdxtQxL8KMy_Dn4X%}Ja%juDYDY3D%bmes0#fjD1fKRFZS|2Ws}aVs!OYK zKcIv$y?jZO4l&g7+8Ak&4##=I0rl46&DU;JkS)p_3kqhCj>+>8XGpiqE9AAHCJIeF zVH%x^m-3ArM9tSl=Q>OAsm&_FgX}+ZCFJUBQ|qE~XX!F?5u(#no>SNR>{(b%Y2q?c z{Xdt`>o}ry6lAIfDE8C9Asp+I=zC(u=qD~4B%K8+$^lhS++m4JH0NW=O!+`>`{Rjb z^bMPT6OO_<4*3T(@S-Xpk<>yjHyXZgkAd#(SyM@adX;jqpj{euUQ~WR4woh*58x17 z=lEKs@$TWMUe5o-((oIYg->BJG`A?#y+pTllY%g0Nk;FemVog(aoAZHMCy)0QwFuf zI>E}(bglj-Kyid6qFi7(O^v$YKnqqWe{g{Fv8p2gC7d{=iB@Z&}=t+Yj zDu@d!aRMm<0zlK3AB}q-J127}vN3`uEIZx%d2@&X64+?$Qql$RJDA2fMMU|y29Mcj z>ch(4AmVYFc^@I=mZpyiT8W(@!l6ZOoVzXmo9W+>PJeL_3)ftVS%_7`E z5$;GEV&w7XtmDo{Sr86j)G~dYQVaFLXV=D zr#yhT1_Af0TE2{*7zs2~A(e&FqcgELhqFRmU~u5OrWjtg zgn5s7-6C#(8)~}qBKcV@dIV`)jMP`=pw6<@@zJ;pJkkLeZ!r90?5YX^r|ge=(vi6N zye7Sh>wg%*91v><(SKEO6Rfyun-ZOEdY@VyuVGM2iz~SgHND_wHYW! zug;6Dq;kRfS%h;%!~w|)B(EH@ml&*@un5pD`h>Qc>$#O{_QDZ7{1YtZxfNcPjfws< ziEH^)Wtcs&beWxU_e|CbA!oO zS}VBcA<8t`V}QSL8pkVY@fQ-3gSc2)f$q@bHLU`AXh2BDfJp8HA)1oMcl=>9#iQ`` zNI|0=;%#f_iW$k_ zv5#EfeSA`{z?!-U>&fIv`PBs#h5Lt62(?b6>^;^kel zs3k-|PodT3K9=@;cmi&8Y~5A(s4(2m#t?N}mS?Y=w7^35EY#T9Oi;AalcPpqj5Rhs zJ(+`G3=}01*ZjRAtwf133&Hz^dOM}5{V=mMkB9Wid7?H<3V>fFB}@1@3q9NBq`3e^ zw!%%e({@C*kS)9cqCSLUtr~FvY#k^)96vK*qp&S3i+vjkm`UA}z7k&7RaYuhY}nRC z%YK4X8g1AqM81JeoH`Ty4=nP3i?+GtjojD-N35Cu7Y5CFR>jtK7v+r0 zZMd(gMq&vkE^OkIP`ez*&?h|W)^*n#9waNK@C^OWV`;+H-!i41lkG;_P=*H{Zj@wS({C+ei1Mm?W|gH}z+LzQ4*|*`%WS z5GBhwHUlBXf|4?>DB0YY$lw64xLW)w$U#y!$+ueX2EA-5#9iSbk!`=Npi=XU=zAfv z?Sd(Ect-Y@YM{|rvvn`OzEP+xp*ThtioO4{v2c|%bzwzE7|`{N8$;9aW*NjDevXMT zcbq!YAcuV@rQCpK;1N)DI$2obpYO;I{>5?v97YIzz4>ZzB*a{eDzAprZ3xrJX95w+ z)YZ+AJS7=u-?v0Ce}W5H!_0VMT|Y2no)=%rNYDMKHDvCJfe@Le*-rpZ=aaU$yRU?X zpY;@wmR8ByBylZ894!ZmICoHLwH4zA)nZNI%!wcIz}jdG81#Bq3PI^CLXocVXn^mc zmonj~@nOxmOA!WcZ=?>0ckW93Ko&r2>8l>>7WH zpqNTK!XG#!%$#n0(H6|a>D?{YG$h)|w01!U|BY;z$=3UhA%+EjZN;{7?&Xd#`5P(r z0rn2rWMdZ{puF@Zn&YjE?jSS=^X)q%?uAF)IA*2WQq8FDE}H#OHH2JvE|(8Pxg zYAR!m`1ZiJX)|KN=q?X&@ll)zaAcvUXcF!KLq`%Z7eVa~>=~M)P7aWcX2+Kxm4vdv zr^`IU@Bm&UPgtvC4Vr^CjbiF|EfO50Ytt&P@_x8$%ws*1^x<)$g6FhU;##oTOx@EU zqzAYg>NAhqQ7B>`J}20|EMP`XuELT1<($F9r}&6=biuqWT_DF^l>AD{indQ5!4t^T z6>$Wa?B?`BG7#Y8-o|=#OzOY8MXE1kOdKPh9cOVpNFU9E(-B*M1a^7?LI5NA`Q?#NMt>E6z;AaKymdKRO>2MTguJ7-3 z$=EzKb;#`;bpTlK>h!A1kMx%rkI}P~9i=u>b@fRhW2P^4 zt$|qvua-c~lIUu*>2cM}*$Mw;x^`D_hpe^wl+vOrT+5Ai>- znSLjk{fDUm3LY7hlMPK809C?iJn46Zy<PVZWCBZZX4+diMZ)x1G`K zH7%uyQ}Rz?dhzMcOXoU4b}P#Y;AcEoW30%MRk&ditD7shrr+h}b`h9pCj}--BsByu z7?HI_AMf3^9Ykw)EemkjdrF3Tp9d8Ee@9HYO=KYX)^t3~dFX z@F!KBOw|{yZyRYpSAyWI%e7ig&Kk#|NQ0M}EfkP{=`1MB4sReoY zp+)G*hZStkxJW>~6!=<5t7ioD-ku#uu#_o5wbu&*gxogfwE*J$Kh#@v5HJ#8v(rpL z+D=WMKXT;lNMwyj7Y%vSqB$S4168&SUMBXV=`L#ZF!Sxd8Z|*(1SJa+dMx#@{V`PG z^GzQ}8YWf391%?c?h3HL%+ft-WlPXO#>?fG-tbjkT3gv4@XIo~hpy#i32DHcbICgw z#M$Zc;}drG{0wwUr=#)FfTAJP68>GopM-)pH4$qGmBM5>d+yNSvId(q+s!fqa)zDo zJ`6A$hjJs3B+@3RLnrGv9frZOG30vC$P69s4oH^!h!xKlR?kpMgJva^*RMHoB%1Y?q(+TiWPJoAW?e z@fh9zyk2F%k`yH}55tt*S)9iHx*P)hyzC8xUI&>EsO zsD{V0)NjHb!purtr`ik%Ycv}zxKLVFzVI)Xt!`1LL6d+LInTM!ZqR+1lxwPe&i2cr zbE|PPUx~PlFiB`k=K6E31A~ckRp%0Pg}X*nie$8f?~Xv+@IeCGQ`atjm*+WyNd~7? z%lo3R(!B>CEmoO?ckDEbp>~aV2jxd^lW{q0j!&IJGRG3kwr8cSDAU>yumB{%vr!bl z=3$`|SHi8_tXwylp8wKG{m4boYBn%Zn29$IoN3|D3DOVugPs_%v&b^tMJ4xa;5!0a znesl881j{bKIn}P_ucBDsBuFN6rCvG|3ZzM50oN5YlM02%!9oZNo~4H?+PT=y`)NEBNX51J&)y4C*za^~vWx(( z6>%LxFrUaL>pFm;Yi=jxIngvA9apgyKLMKGDsE60R_BS`? zn$yoj`j=ho0&zGp4bvCg&q z=h5E%EoZx6B)>i1>@__Ycl{7Q-KrO>pFGR8juVE(L6o*LEf zKfV>K3dz{e!dk*n(m40-jB~5kngeWxGM`JS%p!!o(qjQC_~K2WzMZsKSSqRdP7E*H zAi!5Y&}t%9&$mgz^G0!RO$+>WE$)loqn_XSqTbfC&8xN_703837`T-zI<^YE{sTAk zu|v>pF}Sx{PXd5$08hDgb1uFBBmHcrD}IFU#8%igOtn8#n_{5=S&RQja1A05q-R5Q z$G4BVTLxyVwge2U7y#rwlhtzWp! zvYD)%`Vh8A=57s3Nsyp6VTth5;@kJ|$)HPpLn4#fTHD}(yxdp?Ct8XaY@np>*w#CN zkYO*XA&9%yLIalsofKoj^u=)i)ho}#JLlQ2UmR=+?%>uS4-Z;VU@CH-Wrk@1)-2O| zOn{B^hk)w?_upP>wy;6AOhMWt5$nR&d|#h6i|CuOslum7bP{>sEE;=Q7-!iETtt;gBrY#2$EUIiR{M31g^Fh`hoq3t zcTgsdh!HXkDb2AOdk_FK;79X{95U8;_j>#&DHjqUrIIop(I0|F^mwFyd%}7Ti*$RV zaX=Y62NW3;9-AuiPNFst$@Ixm(WFFOaG-8;l?wBe04M+jH~CXQM(2k9DvL&t4}=-K zaA+NZ!6J}TwdUO{^`S@*(2}3Tf#dk|SMa41Sz@m@*n$<)PRO7Ey-c!wGsRc~GR`XJqSUODC*< zi`Ir+7%)3C48%gAnqnbQ(qI%jup{_`6eyj#wT*f}>_A2vr0T>x|87!fq@$b6Ovs=I z7UL(=uRnoN3YzVy-$wY+B_Oe9H@qTUoTQ$DH*!#5m>6S&QAREtNf2C+YxULFrjf=i z(GWcq`hb@Sl86`RKKkacM@aR%vvC+%%AA6s2`$_R2tS5&%$^2^?I z<|;z);5)lV1LGm(pJZ=T=VJ!SXco554y@8{9w#2)oNAXQRH)VU>0HRqwN2l;V3HO# z=*?%fl>cU4kv#Lc^M+UJ}|rhi2H0_rHo&v=ZMrpaIkX#~ds-{hprR93BdJ~MM0 zd+z?(X?{}R79zGV`N>WRe{6e-Mw-PGEBt1h6nycr2`8yC-HnQYUE;3y8Th=#eMxY_ zE+An{PP>b%ZGV&ASFgRpq`7m!aYP6fUOraJ#C!RqjP*>09~L=FdBSF)7_KYcdum}EO7l48P}g)^9?w7rg zT=ydewW0kaC@4KE;XROY51|uPMwK+=jBMqoV6X9mq5=CA!!;>KDU7q$H0Y$# z;)?Em`zCU(1ifkW(Rs^TqW}UOOa1fS1jzU5UmM;P@fm3}%n<&IsvWc5GpEXb>l+hs zf&6y)kpz#6v=dl|PGU++iBGI798Nb35uoqpGriS}DXf#2cfx=%oEjia36-!xD%1g8 z$b%-YC4@Fj&zi}Yq_^?l{Jh|;`jFIIhCpZB#_VB+;=ww2GfF2=`?WAZpthKdbHdVcqb$}2P+V678(Mgu%wMw* zJ{%~5BZfoJ?(RfiZH6fqN6+H3 zaE5%CO18|>t%8r)8FH~rUHn+nggGk=u)U{9vZMbsvF=YWN)YRaa>|ox1=ehaNL?N7 zJ&n?#J-oi+4;o48R}dW1Esm68c3!OwlLIs7rO_+l5A4)wmSW(Yr(^EibB&!mM$4Dn z*L2Z8!f8{nBj&_x2#V#p(rNCGQ!&-g#JBTcvXnok zjb%RtwBTBilGt-)yOKoI;C8F6T88&ajZbgJCNljO`~9R()X|H1!2SrAhKk%Cd-$i~C~>O{{S7luvBV5Epq zsf9gA3k+v3FFxuf3neC_t@E0%QxWN?s0C~zMbj}6?kSO=~IN30tq;vt7XWxqVxK6X- zTv#{l;`OmF+4^NVGotdE5Ucy==5xDYGdl>KZDUD0PlCr9sYew%Q~f@)w(nn6hCn_+ zWawH>#A8dF2eEbN%Zvm3fz^fXIojzG_J}J}2QoAIsVkdv5*IwM8D)TwVF1#ADY)$9 z2W7DGLqV0kV_@5KA$4{{W{10?UzkKdu+YXasQD50Hw2vJFnhnHKsH)`_~{j;LNjZy z&~U}}9XjBut(f05Ke8GJ$8eXlWPyGsBDYkivb8xVZ66 zw3K29fi*u2?4Fo~n|j7;Ai+~P1f{`M=pn=@SD!e4JIDQA zD{kH4FxF;X-j2?<%!0WveimLW<9teA$js|WuOna;&XDqq`(R!Ndzp;s>d29mt#@5) zhi6|G1-^UztI3y@9EVd!>FfjnCXl_}fL8NL{;6wY0h=1)$?M}lKcpa+O zlTEVjR1fp3H*hU&?^>3B7Qx_a4sNEL0}cLDzVI-SlkJ*ME(=USQCkoe1w zGMt0+cza7VmG0(`_M(~DZTn?h)=@HwT${*z@iDhu*^_FcE3@X;8PNz|$0_-qu~{J6 zBsnyYCzS+;$TNbp#b0Ffw4EQJkN7%pjV5{bOPXLJL#wcmY56)uwWnX{)em#TKcRD9 zx!L7P^x_hJlC$8moWZU%vpGch>99I%8`;ysJlCvKH93WPUyt0|Or~Rz28W!a>BaBS z&H$>_jbRB9($6Ltx zfh&s!@f&IOgJdvP{l#ti7F*4TtE`XoC9;ZEZ+-iaB|^OK&#s2#Ij&Vk)+Y)Or;C>N z1R7iQT%jQLboe6J*hc-rGFRAYgx!pPTa&Zb;nOc8-r)GWqEDx|GI!>ND% zC-Lo}kME9+*1tHPNR#@o%0-k|Pf@rXA`1`Y_CX1_SE!dL;BwbFU`Nd8w@Z{I;|cnE@BkH7w$5Seek7 zr3H#MBOF^0(%2o7SPo#ovjOb-^8}-T!d>G_{ppvr$7;C772!0gqS>3nDMVK{0msI& z1z=p6FQ48u5qlUQKCTk94hWQHLH?U>T3R&;U|1-#7@#r;R!+vu!7=cqh+NB9v5(C} z66UnS{T&4su+%BC2kqy$#njoQj0vli%=gSYEFnd@FCRXUj?Xmii5z|PCLHs{K~&B% zV#4wxX2E^NWHw(hbf)@jyIe5n<9Gj}P!Ep-Qj8J+<+7bqu&l+|nea#Us|L2$JQ9Bm z1JRI-NE-(4Tli~T%R~usf9(Ov5I-Oje(z!Ju91kc+jM$`gToSOW zKNGC;wtcP|?`rO_0-df>@uVrNuCjskErYWx1mfVuE`S`%u~$dGvHy=GxC4X9I`IKD zadrE94Ey-g7shpk)Eyk)YR3n;)l4BxXviHl`Y>5rUgfcAN%CC}Cn9Ws^EhOLa;iqoZ%xL{ZWqqe+a zr0B7_l8bl9N4oJuADx(9N)G?7dvRT7Mkeb(c88|Eb;%ZJ2;$gtjpv$qQG8UNro&Tu zn@FuCGRw~A#_FnP4Z!$GJQO!_Pb0w9ExZQl3A>_ZA>H>7K{JK!R}#JI!wc%Rj<#cR zC5QSwExA$e-Al-1fFx5%b7eiF-R{(~NK0H*j9N3ez1i8nZ?m(eFLKFXm+0%dxZHcC zD>9_=`CP3n1b1o!7Mi#Njlc)(8x+`8$#0ikosJ6i{R0}lkPc9|Q#I=|E$$VqhZ;eI z*)Ju=f&DkC&g4y&Hnsq%S&wGaLfv(ni}dANLp%Wm*1UsiR8BNSGf` zF@_0)?D8dbiwqZ&xbqf!QtgGbC{9tov;LGs^$lo;&Pe2_kaKmdn{K!BXmxCrb0ap{ zqGh=j3oe=+K2+W)dwa-WER+vCCx1Np4crDN78*5Upm0QEcp=^1I|Z1&2qvahibjg7 z4Y2O5EjB9+8-~K-i>(HHDhyZ?$0q1T={}J#C;x={@?kQ_-#cp# z$sP0WCy7YHw=8zDB>Mxaplli33wM1-aw@eQqairk8bEeKx*w1DAdZn`P`l?%dNX8L zmHK*$y3NaWA*iaBFi5#+yI!Fi>%rdC{lPj`Q7XaHn&w)0Gj|U`xRe0;d2!P&iPNlh zXCODP3a*hN*x#%zJManxFt z`c=A;9F5I)0V-IiWkWm}B)n=deqM}A#QadmRKWq3|dv6(FURg3;tT>Y`| z#?Rn48A4~USjRu_c@HHk`oWt+_0WL)e$C3vO9DQ*A+X7+z|^f&zkKzm12aUk@MY?# zOF%SfgEr03D|4>7A!=@?!RG$mXZEfnZ4a8h5rHp7fJ9Yz%NNI1p#&W4%_Z{6*3(r$ zEY_H!`OUrnT>&jYYpNrWL{!wK3Hk0C@{qSppD@3F>1;5>gVPJ2M%f!ZQg8kvL#Q5! zE5&4=U#M?=FG(zhDmpn9sH2F`|QO4n(=ktbLCjd{Rah8wJ2GZ3yWY{;tsJpA;}ez68-V9|&Agl{l$Dh&e%P&!pC z|E<0nGv7AD_MNdNR^r0-XWwWyKif$2EALwNUf3MzI;Bdl70)%jT#eY+YK=L6%8$(C zm+|A7jq1}+Sy0yg?S5}gjaClCoV#2*zc!*3F%2O_Re$)?y*bk3Q41dsv z+cu9oD4HbiVUm1$I%Fm4{8UcX&Wnags`D)g1laeaK!h#A7w-rYM<%5s1P~7XXX6uW zX}W?U8Pm*D2y4I|HMuAsM2h!S0Z2L0CMyNS@jWWHA+7URB~+gn5b=IqNK7E{f8t(ShT zOuxN>x?`D}{b z2pm0T@{(h(iiIL)s?ZAL@SL0$300Dhq^p(Km)>-k`>z%lnq>aCJ1>t%JQuv=o!5Wz}dqZ*w`2$wq9o=uLmt?Qh1Q5N6a>T9ib4DFHI z!JgHx6lkD|7E~&I3sJkX5K%|K)_l0N7IlHbwSQx^+yD3#g%(8=-KJfKm^vvQn7AaTcFY8~!C72*1Z)&=4!k&HL_I%4E z$_z`QuE{Xa@&%d(Y63(#2&+#rFaB9JzSSuWAe31Cc~$*)vi5Qma~31C{lqBOg28pc z;DJhun3YD9h=73TRq7BNSd}l5GCjQm&xzv*0|x(;V3gt@xFYy%$*h7V>tlmk2u~CK z&tM+$5yl>fJJfIyA>e)(LcxlBEXvZcQ71}F(1K8l!bwc!fXg-?#Zb^lzl>@@?^*g8 zykvTuH-x;o(_SM29BiYnBY{UE+r)`2*+;0Hj`u)C(^Re%d1n3fA0&h~Go^rEyTtI` zGhQ{p9uL=BOw&}7_Tx}H`L4p1V9-Hdl#LfKiZMH6T5F5XUJuAW*}HIP@c?kh*K_!< zC2^k+ka9~4CFD@M@8@@x7cP`DR6nS;1zM^eV)&bQWVev$m=l#&#rnd1h(oqTnnYNg zi@7&F6aBHMfx`e4z&;S<1Bn!+yPBI$y5qFxQvIzgv?o-`Y_b^JC8inFuE5zyK98cN zkrzk|Jt?1_metNz@ikt!{IPqLWN$m6k$7N_$ ztoq_nvW^dxL+;q*Mn8e7h!W0A?p(d|v+T9OG&a92Q=lfduM34>rS7=zCQQuGPy-b8 z+Y;|h7qN%ak7G|34?l%-n^lqdF*QTf=xH}5Jtijre;KHPl8L66pE?Q>();j>;gOfH zsr9mIM)P0Bz0Gyl{7>Ja z6MV8VM0{;}fIhV;qO38{L|}RqeF7r>(OadA!)joVEs@Rhh~+t$4acTC9+W}6ntU9*(9(1J~ zmOokZFsmI)5TKP?N?iYv|7dgq?12s1qi#wrP&O@yHOc=%ydn&B{P_ul4QQ`BB6d=Z z0Q1hx!R<<|`3|pTknK&VxF3RdIWm{v&-3|)Co^8~mK6qb6}g@bIVWt^1G)H(bH$+X zbU*!9jr)wo^{efH5(m|(L5Y!>c%2v`1fbaEw%(w<%YmCFGl2G?2oi_0Gy^u^B8%+{ z^=y)}N-^Bao{eXN$T0vkebZ^VCYpGHjJg%d6+~KqPM4Eh&?+83c<1~B0COH|nNI9a zrD<>Ur`~Q@#X>eV!!>B&nTYHvcTkM6ZP$`uV`0u;a zQu*P^k7*PHnw&W5V(;fR-62Trl)EL&`meB~1C5*>MOYAPAA+nzV*b7oG#dZ_y@rD( z&iL`;=lz^NBD3!4CS`8LiZw=yhFgoGYTcg1gRVJyPU|*UPR%pUZ z>?K1`ojAnC{CB*Fw;Fj__BV6Z4#B(#r4iuyu7V*t4$W3m>%Bh>;YN~TedNtyk zX$#3O%X^*P(Z479i`rEJM?Njl3sBdrYgD*DkJ`{jEv($5!jWjBCD*u?>frlUzQ4;N z!r5A8c8m(_{w6KUr92sf>+o;IQc|3n!muWFqs|k0(tG8qLzLekg&?x8QRrwm2KV?C z+b4}z5Hn$gMCdNA**xN2%c$IQjpXPrCV)lRxc#?9QIO zBpEAq*z4McUlpWF=cVZ7q2;uJdc^UnL6QU}!(F11T&)&r?#`CD+6wu+Yg7{}LqhPd zBc>y8fK0|SmwV|8QTwf2Wo{8CgQk!zC@E@{og`3eEXj6?KV)!e0Hppz6I_oa;|?vx z4Gp(d)Doj9#=s@bR;SHr%gi6Vp=mYDvp-eX*^&e;;rPg-Ygze-Hi4SQ(PjIF&t$N5 z4a@K*uRNr9=s2(QT{3e28W)UWPw@n!-{KoDNy&0&)$vO;9yVakPh=hsO2&~$D|WPK z+O%l+@`_`^bL>V>$I46F++O{Q4;-qjBeg1yi-WsZ8GBCJ7v=+0MRd$O3eru{s9c-B zfW+>Qe={&AR3&nhlz5w>7$NvA_!>PDLIN{tdPn>4Rb&Fa?w6UifasNPp~3ONL48Vp^*;=#-Jn@36zvnn@s zznm?6S$Ks&b&2YgmjLJ7)l_>+fYxcE>3Jw9t5TA>AxI@}TSvT3QJn!NU6j(>{+RE;1C_=hmoVCsXk}& zdwQq2oRa+_XN?c6_e@=y8!VBt|VwF9(Jh9KolAu!Rwo)do@;E|!%z$9)vovRIR z%9)1nSePPyK|&;Ag&X*oh@oDs;rO5RN?emHswRu^93s$g=RZ`mIhf8^YoLgH&5DK> ze~Aw} z1c8ahzPICVr#)uH7#8OT=(A34aq0{S3g0HbXVQK{NP7~bv_o@P2*})fA9~&0qRNBY zOYhgYn!d5({FypGn5&6to700&~Nt_Ui9!bYbM`!2<_^E{_Ezxg*{FOEmDRyz@CID=N^_Xtc_hzS(Usq-fN+ z14JsZ@2=B&X>LjF`?_AiHV}z)_ji=ook8R3vZHnB6|`lC{OM&@CnneC6Y%t?Um^*z zYa>5KS!lB#Np7I=u_8ao+6pTulrs^B^$`MlNYqT(8v@RJ1+zQ!Hy< z<=m9gW5t!pTj|__zuQ9g^pZLN4YQ#?w!yX8Xt9jNsoHEZJ!C%LhTs5&ZE$*&H3@Ra zQ|!N}i}CfrO`5jxSIiZe=zpy$2>#i3H~SO^#T%^`R6qGPsPUMh8~%t=cm1Ka&nzQy zT)}DTmL(*Q{C*q*R)c*)iM5Hc?wwEWO*qV=$|qwON7F7Y!dN2#T@5SL9ZQY6k{Ser z3X)9Y=luuVrtVhzx9k()vo{TnLg$kvt<&lj`H#AzTv+ z-s!28p%J7C%EhXqsq~lv^qV-k4iv<&zHkb4WmGWrmPgHu$yM~8*ks^^@bh3xHS-C5 zT|UXl!rRwK$g%v!awjH~D!87%At13xwEaA3@#qTWwTuZ#&lq;pvBzdL#mV{Z1M|XlwO6uJo6|%u2E=sKXwumHvjX_swfzj zHCc|CS587nSPrDe{a2L!+8&_5E^yrj4*X+C((5-6<_4`%SDhRP6QjQ35$DJ7VZO<3 z-El177}HxS6oG0`9XVrCljmhu%MYytJu$U}_3$MKVBA>d%0qNjtc?22;wMCt9&WZxo*y~ZAbh!%9S-sfZ>x7AQ*#>JZck|lAojOen!B~$ zeD?F7l~o|d7im3tgd`1_h?h3jdWOq4;8O(Wv@wc3r_i-shNxxDJCQ`X3k4!8qF z?}H;}WREv~fSUzg_3#0o@!38xY{tR8v+3)ega!p`!JnA>A2u5ASr9)qDSC|U&xrq= z8;x!9i#$qh)(A`-d8gM zwOZEj>^3#4!78vTCnIDh`PZOAkCx)&@qsdp95H7oW91$7Bt((qY>iRIjh)_##~ zeO4!#(8zi)S2U_J7Di&;!+(#rNpsB!xhYfQmKQAdE+B>3p9*>Z!Mpd=S4Ud8tmeZ&YxSAA2ynGBMx_>|j({6BRda3%F<)CSr!^Gf zzEPcYufv4tqK?{X>{l(N&S5q6ZE2%;-8Je4)dHP`wrRC3%^AB9J#(5*lnrNU`W)qM z<**nPPK4Kz(ItdEh*$X_0ynA($a@ems3Fm}IngKlpoLNaGc}T}ON3c;L{G#{HAU1ue=1Sx8&K;#3f#Cfegp<;4z)@|zpmCy#7FL>N&Sb>RjMz$ zqLe@&Y^nHhOg9=y%I@ zVWAmJ9>B5KbsRzr(i)r0=^F%&nI3B9=l=p501`h}T!FefACq6G8qKQSi>|z7t%Xg= zViZ1WP)YX9v;CvkAXS}>J^X2e3~6Pt}+fV{-5TUZRLwoBw6v=-2A}!fIO2 z)!x(GRf{iRL@_K9liPvXCnB6E>J1~LJy^L4cE0^>+?|GJYeC;#rIl#JImDb(E4f?LgJCG*Y#)o#Exrg{O!0}# zy*J6iIcgdl>`9>G+iXE);gj=ZoKTsW+A3qzp(E&Ce#-#DYf+vv$MWOdBmmN{v`-Pm zNY%tjEM~_DCHI0&0A;Mk+>HFH?-ahFI^{0eq=LY8ghx%3i4JhXe>7psD*OEb0hmF~ zVj2dkV4FMGQORfFGl~A0><(e^jVd-x&+IjN2giRiU5c_H0(t)CV-MyIZz;A0d=uLh z=?GwY${#-RFE$l}mT<&wwb6T+d8t5+_;JTMWE&Ds_vAb~*aDk+I)(@%X0Tdl+(N@_ zmx?8t&T^AW_c<2r_8-iKKt7BIExnEi&Y(}Eit`zRTbS7l9GPXg*I6_eC6tNj$I1d2 z%1hsFXuX|+ajxTsUQ3i6o=KXHKV|6i%|g%*gNST17@vhJN@onc2t5LI*=KW~NWeHAsR4A1)Hh+f z0DiSkB`K(3yGgQ1@u(6iAzEP>?uX`bCRZlKL0j{&9@W)+ChWaL-Qg?GwLG!IcW)gZ z$NvCJK(xQ68=CWqKUbm}B@s5M2@+*{Ue^mMHJLzXURsd+O58)uB-P@r6%#EyQr20UR{fEy>|zTWx() z8HC5R$1lm>l-G^74PBi)}%IBf*RJ%@fgpcXtg^6Ul)qeA4AK_3?DFla=8!Pt znH_F_Up_d_g|^ntPq=r5d-GRus?jmn3guJU4#3ef6qvaMJ+M2{m}Wa*H>cv%=ITzEVc@ffmW0zV7b3R2g|)fCoFN3O-KUGIRq}8w=uud*%pAs0k5Jy5yjx|t&)*s|P2oc3N9hk)4?^KSW&ly`&nl8r z^3fo!Wj9(Y>aIaJs1zygOEZ}ijXY9#f1H^QXiDn zs7`W0=cc%`@wDb%)pOTrB%eKGIX(<7QT8ea@zD z$^i`SAl#xA8bEUY0W|^s(9CVvovJVmS5q;rIw)R5O>i)@F)t>G zrA(1EilR*Yu>>3dKm-$)t9QGg)oOq9?87g!DeN`{styQNwz6tG$Vr8Pdomdl-8z0b zu+UacMnX3oR%rp%xw19I3#0U9AY~dDiDkgei7Jh_S^usOfN01Fm_E{GOWikBP%%tW zJ_@CMxDICPPk&rD@r6tA-4Oc&{*z;%`JFwn4@WOB!7=s$NZ4XHy9f(Vl`T@%;Kc?x z^8bQ~EAt=XP%`@o&Cavi1R!;dFaOKZT%ywz`A;lBS)knUFr}F)tvh{xjZ21!TWZJf$KNo2(UH4SSvAAXelJ( zkJ_2K2$-x@(>{q>@smXBavzG?g7Wj1OJ3r9p0JJ6(qbUw55HFbj#-BVXdm|*$Ixyf zd}tXXAiL>&BkJ?4K?Orr(ypG(wJgMh`=M~_h$EbnR zsowi-VcGZzsI+qJ0QF^ho%k$309(Q)iu^8qmi_@MVKyewY%p9J4H+RF<#)_xE)=(@@AarJ$ zxm!tQ5-)bFGD*Mt+Y!TGaW|#z*6%(+vM}XGz8Qu)*ft{;W}$sn%@UhEv+isqWwe_` zBW6Ct(ONN~pW9NLv--Nw4B@c^eR2P|La3OKx;~TO8pi>A`vNC^Uhe>fPfA(1&1I#n z%DNwAyP?-5KPEzC=}LgP3K~zvYzNyRZ%4U_%GnNr%m!?;MtV2c+fy5*3w#-j=}NDLE@ zrP9D@&-~W&xp`l4#_anl;<9@h{@ok6D$;~mdC1k)v^&{?Ob{6Gd2fTsBcU)*1#bQ2 zNFt)GN@FykJW3Qnz^M{8+4KNcUf|fx@0DCX`G7W7*ZJbmJUUsYPiB2RmkfJ=6+76z zA{)8(i%)ld&xU&nZM@;K#N=bEWDH^KATK%vkx}g@!}P@cbm`2lm2Izts;ri&R^lsZ z45d$Qz#wdLK37_t@^$%bGz!%U0_Y$`^=3nhk8{6_c%lKbtR9USLh^ei0`Bfx8(l(# zqFrTrQ$DoD;y44L5T+hAe$}9`m(&wJBR5MxnXQ;)d-4Z@1PCzHWbKInLx`Hi-CxGT zcR#lqOG|N684D$-LAb02h~3VteGbnHIAV1l9>@Ka?n$&39;A$UOgi*Zm98{vcS)m-Q8 z=fA7^)F*yIK^){ihM58V(CIIgBb*qH{3Vi|VT<%CzN=T(%8=bd^y)WVjY&e?`JWV_ zb9j7B1nOhU<|XHdJq8bgs_JwJ&@JYMq;-_IACblaB%K*WuR-b1$%JWRD!1`UG1Hw; zAoIlOV-DD>kRbr=NCQ>T@*AmH9@xZ8V^9;~?m9E+?cWza{8_Jw1sF{VE8>mQ^W+LU zB8y@;#jQi++)8(b>CF|abf|Vk{yNaTspc?4;vWjDT0zOTsBKD>jTwxm>|%1kem^a| zS0>`2XTXEGxyVvj_XFz~=Y7&H@-0(c=si#{SiYX~T0`TTfOO4ZnXc(KL4326uX227m4&-7 z=dbu(LMz0eA3XRWb0@|Yxi)Psbr__exP{3QH=*l!b5_7E5xHdprKBMje`U#m-5ZEz_EGe6kit=h_^#S z8Bt0o7k@QY!H%%5;=7HNWjk#v5KNTxEuDHTy)LJV4} z^&NZL$dpKR4b3Rda@;^i*`x??V4#qyrjnVJ8v`aCj=#0}U}Y)xkt`%by|9Y;CrA+& zF?_n_RcF|l-gf<6mdg7S1}a3*Q;dSAAKSsz?vRX=%Z&0puhPyE=1e`v$5q_Lihe8RP_c zF7RaIK&cr60{;hyaPd-8g6LLqe)gj!?gD!$!5iR+O!Ut4{moE=v~Fhgt`j69jI4GHN_`(b49h`dM86>sBqrIW6+cd`i$USPyz z5E-UYBL1XPX5#~-HI_V+GWdEF&z1teE!g;NA4V`fH3!q(u;#RFG)Ao z^eiiU7;T4T{+lKQ{Zpr|m)xRVV^{Ax^M(le;_PjZS_dNp;6P{MpK2U?QWA$FKr zB@W0WX4n??+;SR1j`e1`2XBiBn?vr^Tq6WaV|_26fUC{y+J=Bi5}1WEH=wTaDfV|+ zq&eCV{cw@O6-T`r=$DcqFdMaiZwL{n*(Cx0Hodu(uk`YsuZ>su-y`J-78Z;xZCBCOAAla*|MncCn;0^7)8 zJBjMLI$i=fY8h$?i34@=X!olibx9a#bKg(stVd24M)~JW$rezAGrLi~ zvGys*i2B^caphxgisp9u_dI@jW#%h3^*>ZR=5?68JKE?%HVHI=;P(Ef)>K_Sz-T3P z){D1vNsW)$I2U0pbrnHmrh79wveVW4PD}msK_Zotwmde!3C|va-t+%^0~At+hku-2 z#I-EA-Lx$t9fn<6Q|RuM-pp^I!e%cP^REqrrjYItCvFs+0_M2GttVW< zJd0}AO{6JKIeKnV9iCw2HVC4&Rv!9TNRE5fQ7q6GFB1ETWE9Q*uNZ*>Sb1$h? z3S)%&^kyYD*=CLbX^0Rfx#k1lI-Z!|CW>FuX^t9vyA`|S4MyoH+i;XRf;C}An8%8o zn94el`H%~}^Rt=u$Kg_7-v4~n2fVbn0F@4ACQ5o))Iue>Ss@`!5G!{qc*}F^A#IL* z+O$yiBu**8-7yB~+giV}U0b~{_{8)UodO02d5ARE3Abb24?4TBriIJ%H(_!Qp~fq~ z@a(_q&X>E&SYG2CS#Y<7ocX(#_cAiprnEB1t_$X55H7_Ay*CMqQ+s`P|I(88`tvXh zkEH0bW9ctncx0_&o-xDUH;1w>!4*5WS;7oqT@gvjJCGs8F!gKQ?PmXg*7t0bhAriV?24^(3d4iVL6mDPn zC!$&~0h%@nYkGlW46%kB4(E)%PHrHhBBQsKTI+ryLwCuEB;vXxn$nkCKYSWK4kQ~R z-a8FWV5kd+;dD1{hWtWF$G$;XY;bKX_h2HkTG~23#Y6OKdx|3KUuKCS>;{m7F$7fp zcH77W?b?4n=qZD$tY=ZJ?Jq#hf83snibVkpqU6C?E0v1lvQE6_Mhed*x)kLt=)6OA8%Txo<2mb+-L6!9Gnj zZ#p}j4GiY=^#|7vx2~z&1(`mOh6VSDr-xk=raD&YYmWMhurBL2MUzp7*^Nb#zG z;3Y$yI?ayRanwpGg1pg_e(jgVqZ>g})=~{$VhWEcQWK_9W%wN==?V_%&b$&sT=U`O ztP3%gd!DV6>nyqLst!gkZ^c}Wsz%w!B#M3*=5y!j>xgm$shJ>s1->n-k#TA$& zVfKj!yl1c0v&Y&LpH7_QviJNNNStH8=n3HOjjp&J#8pv(uC&axzk*yqBb^qB@WYZ4 zX_ww&Zi4mSonH*Iu%|$esatikfMuoW*NgqKe>{x_)MT*2wG&81%wu-tA)QklBl^dW z4S9+R00(t>0${3r7t<4Rfbq7opF@o)qlZGG>M*H{yc%+Fie8d5g~&3 zrxLvQBFYRn zf4Fjw!T|lWn9qai9Tn{AMHJC{sJ3BlB!?LoxrMjzTxq5Pu3(}_nFQ*$2hNXjX(@Qq zlj9mzFHnj@IA(KO__<`6nIs!HfZ#uNZe?f9FHkdnIC^D``f|4DnWp(So~t_mz#Y%D z)ShUaX>(!dx=~SBOUd&_P+#nPJqAKaM~#pdv9Eh<7%DloAB#}G|GyETgE&JBZ(yqn z=uZyRHyd_hJwQV+S*bTVVl0#E-9nKh+#^deZQkpA3ziIO2`k=KY92#oM?fXpXEw_X zgj}M4071)k){_h?`_7VM0!|V$+rG)T>n{B7noL00YppKmp_CsQBKxcP^?mOS0cVg`P}3-1?r+HVE(2(Nki(^I!xUz5LF}e z7yre38X1M&mgq)+9R^G;fV_T9&AFqGL;v8_Fz8vs(dMBx?cfGdCy(OwLQ-`$A z$VI-J%1Emk%T`OA{IsgpClYP((x}v2ZOLdBDRvrPp z0&3t37z7;KIui}>C!YOEw-sET$T923_+ZaxMhjBSi4ts5yOY2v@;GkEl!}80tNAsJ zY;G;jw5)9J#we1^o42J&_Go6sHw1}2)o?kq^S_+GX4Uey6bBv@u+B)^-2P)IJ9FT1_vOS}0Pm4iK zjly|nCk7gNV#-|11a^(+kYdmOHi-02!uyV$9r;BO6eo>BMp2jnL2W6GpP31VY@^BH zPd0YB88oHFf{*rx)ADD=a+5{FN_`GtyUaAoNjh3GHabp5hv3D9NlKe1Y<(7%pfXE~ zXhpN=E!9@w_M`xxxy`~86H8VxAb!@;(Qb!ZIJGTsoEP(^+^$Rq_V$GNl_ zAPK4ZUJ|@?#pYQ*V&+|cK{ahYRiwb11fa5r-4L~oPTu`;5fq>K!WBGaI&a|8AP|5+ zdaD!9ZTDd+u5G;*1ndq_mmmz1jFC*&uair{TTqpgr`k_kJU&`Fh;~I}9D?~2xbgS2 z$Nw_d(+t25!S^1A!szF|wc>eW5%==%VW4^T&N$seSG;$5&S-dU*|4pgJ>zPf(F(Wo zW-gz~*UK16nvl&SM5o+VrZ8@sjvr(r;o;866$mLKw(RrPQ(Z8boyxVH4bAcz8ke=Y z$(I(P+P!^8TW-(j)ZBzfbJ*hFp-zW@Ix| z!*iq#!^nOM*A}HRb1l=iar7R56uT1!q;H5v>ql!-d;3c)Z%H65Mv#ZjPb_}I$(qq%3ZnkDVb~MFQu*9=1&R&pl^mlB?4tX z=qWiGD<@hERbJ5y_UTaan`wise=GuHc5hj_Y_9e!XT)G~Wxdcez z0q<`ny0okm^dWtdrXYY2yiaVs1T)5JO{;Sj_bD8z`TRQV=|BCFyy44T^KAv z@MLO*51yg0fm`1rwXru$R}+U?O))fOb&vmIYKZ(Y(1AFmYbZ{d+ss!3P}dS1%{TZB z+~#EtS^jKcosUCdSB-u|TkDG2cpg3l0ofZ9UcWcw@!khM6e2TxN;NX91M8ktWPl^z zyqopFnWNlvtsWM5qBEvR$cz=`#aJLCG9PJw6vWUVFq5oPFsh?akYz(45@*=-R|zi2 zf^QHXbRBS!k)RUCG-xgR>HhDjPE$aUb`}_uy()j~r%lohuwuHlOdhx{D81hbKrkY0 z0$8KC4`#VmR)=pB)N;Ahv{km-3RF9x4Ii2%qxBXKgn)Y?SKQ6J+p?R{QX4(7Dc~Wv zzh#B;Lf2omW80y`UQZbFJ3Ry4Yypfzdvb ziJ7O4pUgXYQzx~&;V0pkBlmbqy`;G~MO$u;nz`f?h)sv~T2r77a9-IGMrq$`BRQ+F z)Y;qU+YR8+pqopi>qoa_Bw2D_y64|QcLv?tR*SqB+3e$;5|T5yj}%Z4Z$Gh`R z#@L`EvF@$>uCyE4T7(a%N8xKaBvR&w248o02OCk4YxPZ3(&%@^bFh;N@MX6_vp4kPtuO_JCLSekM0J!|Md< z07&LHoY8t#MF=CUyQN=2Arb?Q4nvk}WOxkt$an#qRe;au3<2sCCy-z=NBB8-RB^FI zSdtHF1b0bqTEO+WV|xh93S(9sxObI3*uSTnQ_Q%9hseJfAg=mOL&EhfGun zT<(ou2W8jE>f!-1ul}$i?2zzfO}R< zW+97t9Rv%LaCw1>7`kXBuysZb2!`sQ)tc7K8)nmM`I2xC&xdn^4AVa#qSo2+Z+u{* zOO!5*=Puq7T_77ms|s!~+`uv6+(-Kt`^#)YMfE%YJlbXi>Dd-#b7+!<%s-J>3@DVbCyk@~|uAEn6enc!R)9g?6!^ zX~~|ISppD5m6=kAQ-!bRH8%#%eo%} zVSnPM4LM{mhbAX;+S{%raMrR3Jrb}fYAM-CEkS<`79C$L{rpG%Yw-uQ7ZwTWj)N|^ z9WE;o-+Rk@huX7y(L?nD%c0wGxOniUAauY<jnP(%DySa(0ijJvwLT&j-31gTDaxvJwwf0a}O=fibT}fZ67nmq{ zrcE{T@$#m28JJmxYc{p~oIN!Gi*M#aT*ip3@to0)*tC8ju0C>P(<3c!zk};~d!|-rs^k)hjV> zp`Ua}3KkS1lhG?0E4^j}jRJVRj*&S%BC4exX_1M&2|gjI6(IU=*%1DE(3sT)!7A2I zaF>@d0S`g5;F^+3PG6H(QfjlHHNAQkks{h44`97HL_$fkc%m!AHSyE(hcbx?vC#4LJI#sDSl-n_@Y?JEmHM8{qL%6*%4bN9T?D7F_cEOu?E-tWV zdYup7*;seg)U(q(5Z}iU#?opO=()mw)KYQbB?Sz+@DGuhcXC55zwbVGhn18E$f6C4 zJ?Pv2fM1--d^2v1h|sDl!3E6eRbM(E{}fRA-OrPlv}=@;Qy63+gFHK}RDRQmMS!!q zn-f}U6Zhx!Zn1G6RU1veW5eWm*J}3hr!5Mo&J|k#LjYEP4^O$L8xxGB|%7M&=zFTj6l|Hgj)s2L&3F`SL5GhkyTk>D^bbqNxP8_jpa|e^R*ynyEUBP&Rva{%n zVcre;r9Rbpli9(XAZtl&Gh*E}3e#WUkW(lbt<;$Ckf2euW+-+xX8jOx55D+)>w~~B zmTt;sRI#C4U}8*Mwzb%;atKVUj54=S6?8F}C7M~jhjIR%*8VK=GB-prJc>63#s znLUZm1ggU|C-_|NTeXQ@$mz7&?9Uv6bjd`)Zphi%GI$YfP$}K zQWO;KDo!?!$g{mu6eDO$=JBG4D@2lyu@5u5Z|+gk^m>OokSvjJ6lgYvlWu~nL1A|d z3b7+%+J!}aWAI(g8lqQX>S*?<+ztbIfpI-8^``CYyPsSt$ksa|u*5u+%ZgjD^Sp=B z!D4trPH1EJ@Tg9s^&F#KMU)$}nk4rzb$|s?WS^kZMKv2x;2F{}|8D}5HfuQl(jpLKvk#*RyM z{k>_<&afZaN$Q8Yl@?GFZvD{c1DCd7!y0V_W4@7 zXu3xO2TorT%!po%!BH7)PRXhWNtVr+4l1Vef{Ribuq)YM2bWVIGsO@Gy)u0nq_XV< z$1lnjBcO}*uJQb7An{PEih7zt--T7RelYhZRy%IYnZ2yQsZ>I0*+yM#A`ZW^%k%Yo zupb7gIK(kIzZ;CG0IMLWd{c2s@;|7A6AV>3vTc-aqFG1gUSkq%-C}$5wt(y>E`zy$ z)PNc45DQjNOMt5v=j^aK7;eXaMP|nNUA~odo5z*^$OEx`SDyF6O7*+ss<`TA4AD&x z&`WMk$4z5QSl0-wm}>{X|6>`!>Ps7X_2t=A-{zNYW&#{JbJY!@O$cOijAb)QBus)8 z%ZNVpu{hJO56wAlt}=ExJ%N3IPSMak8Lpcj1OXwf`)4<5tQA|}i48Ic9j-ADnDfy5 z@NFu`)WS`J!L17YKUG?O$(y>)xq4DbH8YSNOV&?hRzau!0b-1b`W;nbZ1zBh8Ql9O?KSDC$FsE($(uXUQ7@;q| zaaZ-)tTBKnaBc!}wuyjMOG<_}&t_*kHXOfW2tYjuJXJm4xB`%UjpUKE2CSI&EpD=D z`SocG^~f zGX8qO+0Tee@#r`7v_cHLCgSKfR#5{N50>ySKK*&+B}@{_Mb)v}@rPg|r&;!fN&##= z8$B#@{(nYW1Phxd+NEzSKZT85Go1mwLSMA|&!7orl|&UDr}Zv$jl7KNWqH9tS+CxM zA@qWF5fl_-Xei|yAZW{SD&BPvbzuZzc z>xPsX9_P$u-`ZEg)PXtP6zO_qT1tZu=J5r-@+b;en5ponM3L6{{!&>q0a`8;nN{vk zz9<@?^fvp38y--%=&!?)Fe;EGF9#}v8wg%d48RO1N;b)tlI=`Uc$q#4+x4ab10Cd( zeSQznZ;}zOTVLsUE72_o+p4Nz#B+A))q~`0+O|*3uz>m=B;y=I+m6YsaxR@2mR2iRQbfGQR^%ro_BH zG*)Z$Bdp|A*wjAuH~>(wG)fH~fcWZ-5b(&!N%#Y}-Z-^;$QX?}Cp?QK`k%08#O#FD zg>h8$Wc2CkV27w8b~To^a}$Oj;%eO^nZ#(azZF~rkU#YS-u z%{CwYd=r^r#Q`|3(&!+QXHw(tkih9O0QFbxiilQHpq6G5+$*r@oJ0U{UdWMkG-C^V zb6Jrpg6A@TBCSdVMUS;OKgv=TioXsH_^e)9ZDHw6?r&7R(@s*AJV#im1hD2fb=Rn6 z7Jptd^D&p1BGRD+M*%hDhBi8C?RfK}8wx2o)w28+@pIBOX-(jR^F&DYB=Dosrrx_82e? z<%C1Q3(NPy#nbrzS8|XlCK-3Lkb=|X%eZZl4ZDVL@i$4PD=)aH!XeoSL4d3jjfUW8 zwj)qJ*ZvUfdTW`U{QKXE>ry%Gc2nLO0cy2tUphn;3M^&4Au&X6(_*^XNUL~*Cr@~B;9f;5lH^Cqt$2MmNl=RG}Y1Wotq zB9UJg-Z;_OlBzK3i3q^-^)oevdO)wTXr47V+}aB7N+UGzKp6kI0l;Y?`Y%8e zCTDXdQS)Pq+irML)XAb0K)A1WmQ>$QG%xWhH6ct#!g#T@89!u`dm;B61h6QVfQbwv ztJ*hW2uZ1B=^24Qiq~*N$eXpN5lt@(p=J9`CwZ|1!~eav5GVyz(jMOLhnFdHx~wT*|odfmFTehdjIu>C^4_ktmG zy%6#G3^uSIw?BH70MMM&^=zub1-TQwSizJ%7pgjyu=z>~vhp!+WpIbA zm@-v1iEi5XqD@G#YDKCawSz(N?}-6JcL*<<*d-8+^P96HDmE)ba3c5d zKQ_k&nt(1^1TW(q9Cjw}i@CWZf=nsZwdNw=`&MHXya^twbPVB?G}K+rudX;3uXs{Z ztQ$yv@QJoo;6{-qX!EyvFO#9ZY6d|>F@G$>1`4G!F6@*!AWaolB#3c%8WmA8&xuJ75QH*$-+!A`@Aj7F>v>kMC#m2 z820}*x`1IpR~mW8Cpe3lY{OUpY0k??8-WXr%p;JGSwV2 zwOsb$iD|ETo8FIlR=JRqENwqu<2|6MT^D?rsmuFGU9(e&hE>~DigH~e9Z`l}A7w$K z<5JRd3W1FU*`A;bG+)zTJSk( zxcOqj;9c%?Kdbsj$(5iI<}p{3bV8Q34R7qXFg4%g^%p@}~ z4=W|Ra9TgutnwkdU|4Jpe^%C6f?J^<9RB-L{ZjTTli7Z?vQ}29mkk<}t|^T)pFEX! zMKL=JCbSBEt#|$BVp8~WHo(WXreFeJRs6j-3+LKUnx>ZEvex#m$eq-C{6Nagpwkp; z;83#5fZE@?Krn%b5#l>at)9FTbwIUzFXC6W@c{d|>MU5^n*Sjhk0UQSG^e1aiI!kc zen(1QS+d~6oSdpUzQwg8`_`3=Eux#7-d(vU2#0UR;P+GnhcUk)q(`yDl2^;~c0NORHx}ljA+S~Z_5}0X8l|wnKuz~^|DqJ%PU2VvdMg?3TWMnk38uq^AC{jR>Fe0 z9CJNv?d09U4BlB!@}qXO!l|BC#oWsISs9NIYNO*d2G4{~fwk4(0~sx#=DFK-@fe0^ zZM|^s<@6E0$$>_2cs>w?U)5^JfG{eovgZz7dpCSws#{Sbp}X&Xk}jYfu`)NT%6~ ztT9wdRe39bmx{S09UKa=caba7r=>(;vc)tdC&BC`-Z^?mjr2>pE$?I?131-Xpalk# zxzY?2W_P`MT>}g_N<$Lj+)K;cnmrff5umsl{*B~TN4V*-*dj8#kmoK#3Dv zB>jKsfud2X0$Y(TC@y&r#I$0k&=L)HwRDm`?UqwMGE<^$0Ll<51my{90ap*hY|0!S zABhZ+%*MO6Z80xO&qJrKA(>=7c8@?-n6^JI4XdoTJ{pvEj9vTe4?WuP&kT5c;DR^- zi-Po~=JJe?^mD)PU3f{{PC_6S?ZYh3z`!DKlGEfM;87~Rf-|HwjqIlxCBEZR4N!*O zDT&$T&31TrPPd)i$U%9n{urMf7D{pZ1M}PYn3+-sY~kP#tLV#Y7fER|S~?Z$z7+(w z?$xf?r3i@1&nk?%T-YAOM%??ZmV9iV^k+CdA(|W8-|7(gnj2Cvks~!cJ0Mz+GJ{hR zF&_fl<8snnEG`Nz9Nku<-_B=UXZ2Rl@m&T};nRTlsbmz3U%V6?*ptbfXBOslESlCg z3qvXT6;}VhX~IY)d(YTLmMB1%aG`e6GqlxX4Q`DEL=P8H4$p;qhsG#JXXCRPUx)+$ zby39v_w|rlDElYJ|YG7p-O|tQJ;Iz-j(3Bc>6~x)(_`zUKh?wJre*zYCpE-}! zx%tdDlPDH)=#AMBzhBIKyk>Yc@enBozB>0(4oMw(z&G*mV+cZ_+(=Tq*0)TlYwZUp za*T!>w@j-dpp{A_I#?RyltC9gF_7Y)IgCR?ZXxXDpb}F%+S$y-2wmJ^B6D<9xhgb{-Gwj`2)*FiiO(-F)(E&=KMm=XA=6$1-30)9dc z!VIC@wC2MA#jwq^I5E95XybGkZkhiztCgglQ_|Cm|LOm6WPC4cV1bsnA5Oj&Lh29N z%Q{?@Ss=4t*3+w*OjliIB}9Tyj)`#`IlUmL9r$nQq-`f_eEw@VW2+W-MhrI5WkG#F zAwp$T7i60#v)pF3l;H1#Q+}gfoDb>H&e9p5!NJcn3$%0pWNl4)2vk)vrQ!^)S(T)N zKR%K;b49jC1!4Ngp%adDVd^2iw86T||A#BABv6LdzBI_0iC}{IY@@1^pbXcc<33sf zWFP;^GE_bYz+~iTvgCY`5ZY)yNO>Zbn;qCbVn9ka58=-WCBW$e zMSayvXC5ld#GneS0Xo#XNW^dlh=Ktj=A*LeR}ju#(i>)69W^L?7qclXPb_?fPC46MB-lUPwd`muBdjU}j*odP$tw;$) zJq{TCv%4KMouA9vo3!PjkOK+!R+bZ`P~#QxuK@JYx!u@{Ip)pC?D%V%SQQ`SihUQx zX5~i#VIpL&?nd=fCjo2w_}qY5UF%02Tb(o<$#%FbxmIECw?26LMpR}PVKH+avb7@Z z9n;*u#HO!&R)n)Y8w*}Whh7hgvW+%`aq)0ul1$(e9zYw3wZxlZ^g+%fY-2svma9g? zWDS?PZ){374E^tyBPe2AfC---W5|Urs#!}p#vcVd!Sr7x3`|Z*dG};ErF%I`G|UP{ z(ZS4;?>5sQ2B0Mj$jO2)K&J{sGmwlH7+(tHO|DedBPGw^T+=|App=7ouamTFA=_y^ z^Am^S+20qi07=y>O?Fa+1>4YbcC-H1n1Y&|eWye=e?Qk0Z9R1WQf77m)qlP7f_+@! zJQMY8-s6QXJJ?j?TJF#NQBCNsq4w9B+jN}SxD^kWzO*baerCyRFMmj-TlYi2!xU{Wsu}Z1HWwX zrpJP1<0w6NW)J4@1hxOmfm&SgIn@>tIJdD~2z2H_lj4X$Mj57YgK5fSv0%52c2G_nVP!BFojw{;W| zpJz_%-~vlKDWeEXgQ11&$u1!F!l)YBcmm8*N}U(>y$syc~A zXo(A_!&%?p7nPcS*WIke(3%@Mj|sG%)ftJ4CGBS4E(vFdX>-6P8>mm8#-ff$ZaVw{ zfKCCS5&uA}6<{dBY3ap9K3ij>UV|)S#^RQd`mZdWqoyr=p67ZJ(s4Kdu&^v^L}~p7 zUbg5EWl7CMBlUD{JS8;_M8zG+60%!2!o-n_?$o-~ zNN8Jt2t{YQx6sYTmjwU)&_%8N-)LiOMQ_Ia{PvRyO@AO09q%bVxtw|oo|RL-^r;7r zH{itV|CwR#Q|T6}zE`p}XCZb+oD5thQtYs(wP)}8(Cvy7({epuH!hn7^h=|WWDBE! z9?SbB5eRfE&`i=fhUHZ}U#TRr7`n}3R0XJrB(p%PV)^0<{A5gGZuN>>e;E-u6H|mJ zSSDj>v9X#WYKyKI_w%TKw1kutBPV{*P~JN{gH6;SlS!-2->h~a_Y{$z@vAuu z2!e8DJwpa$;n@>h|6QnIXq|T|2PmKRukILAg7>JRxPv4BV<_InWAmZNX%gG^7 zt3UeRYpSf+4bJps2M@5Q9G!(4?s<$xo zwr{Cr)Il{{`k@fI`Ph1k$6 zw)rhjG|iVQy7r%}G`Z!bKHU02Q>g~!haP%{UF962N zS0B~4d}(Jn_*Nc0B$Dzr=MPcyGLU~z?u!Mi|CO;x!+^#q<8|Z^$S3M+`;5utVOAeb&*+!mv^nLGZ%Gn|v@MH3oBO zUD}H;gRRWRJ=Pf*JE3^F;qqu9&wZ4!sYraCu6`igyA$GYXzD+asA$745q}fptK8Ga zA-f}ikL^Sx`dA83L2M|vL!vsSXXosW))*YFS)UP`C1KvdDE~l~ac|h-!(Yy=bNLuW zrz;MKK$vf`Jot}Dk6E4n(MJKmJ(i@S$v;tB1y4>s9nt=OOq8GdS+#>tn|E1es;E+P z3b}#?_?Lvr-NNaOR&3P$uJ1&^25BK|@T3^VC+d|o%f~PpD4B+-P+Mqp1}6E`IVY!g zzWB*+Zz-mL)n~^+PZ-kXu^`a*bWhlCH@h$`q{ zUps+Fy130~KV_~?Gf}Ks>iemg|GDL@EhuDpKE<#9_iwj3M}u?I0#H6sZ5FUG4{1ZB zNm*Ix7SJTx70vnb(XY&iYStVrov2ac`ydwJeJ)asjDv<_70+vBxdN`wFRX_kAHZ5` zy$d~22QH{R&x#ho_j)qdr?XQAZF}ezRFpZw>+5P0yQridN%7jpE?ac+%-o|7Bt$^& zyAwVHjS+{lN=<~mb&fln+b0|Ymfo|=#%19*KJVlo03qi7F8bh1&BKOYVJFrVmt=j) z;m+HgYP*7z25guCG&6|Jh+k=zAf5pwK?%)yp5|4GnrvMWoliUm-$SxE7pA<49P?t>>@P+P1{d%n*7V8AY=uRt{~=R!Ww)JvmU`} z$g-pE8_bRZC*nJ@q9o3Bw^IBar=Q&I??R0KZ8Cq+DaBZ_#<5yg$fCr?`6%f++5s$P zOpriX*@rVVO5I+DqvU(Y^;z35>660iWJUT##Q~4>i5!88S;;+#oByuB43r$?leG)N z+_S8|7wX}i*-tB&Db^T5WPT6}HzLzP({LT6Sl8cH^h#t78^$bHZVHfhN%CZ_BP`p{ z#)|!eN@^Zhu`b?CrKdJ2C*Gfd$>!pby6V<)zZJNOUq@pV*NdQO3Qbw9GNYst(c%0& z(GkN~%WVe#BUse zSI*clcC;=F8lojBNPmw5*YfWF56OILi@*~DKnjciLCY+m=$ScL?ebu2w@`q{Y&7vYQg7qz2Yfg$h{b0~&%|06>8VbGlJ# z&1kxb!hobMLCO7@yHuD!E zPpIP=E2cp+^*hy+&P7WjvpX-cd&iBC#hK= zr+#(nB@ia^uDJt`BarPp@`*c|T;?;vc5ok>RVVW92WjVs;$6P&Z_6kD^(y`Bo%9!b zsY;C}lwU@hTT7H6-2ox0Y# zORCu3K08OX%FYBQ2(-<+$g^kGrda+$BjG09*T}?Fs#M$ZK9!Hq6ICtg)nxl!gJ&6n zRP_qA-60A*j`RjS{BuOu6ZC>IAWdm#8iq^y;J6>7IXS+*FHk8?%jb#PbW+Is^Dq$0 z9K2?vu4b_5oLHJ-0E8^`85`Zl8Jnpo%eCjE)Ajh4VV$>-n`-Fo5RA_^9I$&bGryQL zLlt?9=lwjG@(-C^?aey zjA1~obpSLH3ZGpy5L`-W>axJ_$nW%vL622#(VY!~hAG{5|Mk?YuI4kwjhWud&4{4I zI@>LZZ1^ne+1KpKFqNI6$~01cMM^V3Ar2`SxF{O zwk%t+k(kPDT1DfHMcBG3L-N0$C+UsGmy)1hvR#}ijlsN~Jt}I9y@EIhO*n43QRj88 z9*z+~+_1ebXI{_*Y37Ws%6KKL09p{7jt)8g)LJ@y>1Fwft2#xb%mpx*B=yup7^(OB z>h0sgaK>HMo`eBp##Mmq)A83QPi!mMKVSZ?XrMqKA%T)L3rFhw^D`jAMl#{bdX2tw zc4L4PiAAJq_lQwnhot?Om!o&Pa;S&7@BA_ZMaOG3_tL*qLsyURaB3hs4Ti@Vmro}s z7Nl~Ef*-H`xw+3X$7aP1DKT;E_8S z=Ldul+gYJXz?>P2q{CmLmL{{~H){SACSg9v$o{ple&f3}zJZPhI#dY(tF(&yo z(qr_O=!*h?hz*bO=f8MaXSDl?GaKY^f{FJ3v%KwEizdVm;A#oM7_GSw5MN}A?wIK-1I(A#d zUs4Y=VN33|Z~DUx9)Xu4V)!8d;T50R^VS@O3u08E77Rv_v1KO`@;_-!g8vmWyeVa! zK2|q>Osekbe~1yngZhtmX)}<0XVGwL079iJNb_Zcbm#$Yvfk}ZYARmm2@o>9*I|{o z82c#(CLrH$nHs;FkY`Dcm;y1;3A4TX9=Kz}`9=*at;RA~9SiMH>OmoN0$j79MoFLS z2%3*#XJ|D#GU@KsUfeHC)cbQCYF2qIMGy<@&5#q(9m?~o@UMv6)YqkD)Me@M5N5!c z+4b7qaa*V8t}`HFh2Wh~Tb!+WLdz~l9<9_!o&Kw~IWq5V1UsmJ$e)|#1gHrk=PhX# zn-p>-EMAbwJS`(m=@M#vUM23Q`EP;!Wf>RnD?#$P?vX5=eA|R|Ya>EMs1$S10z`tz z27lE1&wuqCSK+2*>>Mw1Bs>8Bmz*%rX)rv6XS45wthlfXZ)!(wX0uZo&E)|&hvkL4 zI1qjuk)v1_)j;xA_HYP*kg0xlvwnTYIxB@oJw;O%8_m~<2jJ=sc3-VZI6Ya;`pAs7 zpVuYsGg!6eS+k6ln9+;TiVPiAGm@tK(vAQcBtu-D^&An=@l>%hA3>ba z_6wnpZn;%WZ^xJ!Wya^LW3^hplC;zYh6t)b`k0%GnG4qv2N=dNAHJu`$^gLrq(%sq z&5w!*e;%XrHps*(0wEbV#+&)(n5Yo#gf`9Xvv3ch(c9QHLpy*yra*^M!~_w%mb`(` zUHkr&-nAZ5;_b>A{*u|d9>LXscdfN_4|r|LfPFT>G)9>UCTd&=Yxb~ElE0f}=fs{~ ztOq6<8++=P){RfR)Jdz{-4@iag3&9aZiH8docG%O4e7w5vOv>xhwuYK@HZXEtju(9 z2Bp@Ve$gHDe57*UL0H0R?v*VeSh?1K_l0P2{}r&VY1e`1$>5*2s|<}ygD5!Q6p`0k z3U|&Q!weQtyYDn@Ams07R&0oE)TUqeuD2_8V^Xn3=bV~0@^&s$MEpQpLNTG<@ZsbjA%S= z$uer}QwYx_i&1GDP<2tvyA7!7pa#*~sf?y=JYhw&hsJZ)W~=Qgpx@(g|4)cDw4F}$ z6!aHG^YqHm(1a77{z}P2Sjc&dW?vH~Hb(W+=#*KCKfekh=M%RcSFVy4vDkM`JoJG2 zRySu`7LfGn;r8v2Nq3qJ!R5g0q?)q!-J?_MlZs(D>wUx3qHl7jWcwe|b}4>Mmyrrz zMGAbmB-yq1I%w{wdl3LLnO9h*82ES_NBAR=if`1!4 z&7JyZL%exZ%#3*jTs1mfS8)%83(f~}WrmJZ%IZ^i!;OxdAq2ttt~Xs)j`4-@bs0yJ}u4_lq!2O zlsDTQ!K-CBBf?LiYd36{u4e3=KL3QUe1@@&JFOn4$-5j-bZ&e_`=W1DXzlB@5G#U` z9Z1qtaY(L2R+UZUy=m;#`{Pj#5)Hx2nve zDmjw;Px)@GSZw8L2QyP7%YTe&#tf4A5ao4?HLt?mlE6IxQyV+(ViJ~6^#C9bfwr4r z`rn|BtSlDb;sL52Ax|X1bwnhJhwcll#-^R@MGhK$7jbREJ1CKEI!gGSOEpQge8t(# z(UmL|t-JpMN%;7%*xa){0)q$0_R?DwWunjAQD?h{>Fc!qR`utF_NUaXeNI5}Ku0?o zSbK|#BcWOAL-9(gH5e|ps-GpM7Vr^vnL_nBCr5^K8f?BWhLJ-q1q3Gz6YLYR2<}!q z{_{IO!Vi^Q5dxd(y9X$93)fLRxpa;8Rmw|AsZGw)uj${BUQ5eJhR4_<^k(hDrBzbiuMPspDOv61_ z!xSw|UC>Z-QJyaZ0lNW?9Hx{{E}Wfo@4NM^T`dnFM2JTmLqR&WDKQCyn#2F1P+gi~ z1~`eCgM0g2{%_(j=|YP&+IWq=;pS%NVHLRUT=m~_0+g>yyh+^lBm#a5q2YLcoZbcn z%+jfC&_!|0NI^YFmf)*@3IMx@=S0N)ga_Gyx_IG#?vzP^JG9g{d|ukoaZ~XZ)Vs5$ zbUYVQ-}vjXR+Qjoo=;=uynt7hTr%%*Wtr%jR$w5-*0;NNE`*!(>UCknU3zJ-g0>x% zc*R6*jGt`ZG7#O3s$5X^wBJwejg29A6RH00fJegUw9OX&ef&w_3t|M)F4F4a`|NLL zGZ=JY#bO<3x+qs>r4uM*l0`O_@SAWv;>!@vXWXZ4UNmDr50c!jv*B;o&ev-?x15-s zm*&C#e1y2D#lZ=K@-PIH{gvh1*;IN7GFbAryWg@{16r(`{dpdPSj@%KWHtp2ZwCk^ zU>~^nC_@7EFrnzg=0y9^rx_EQ^YF14x3-DHKr5{)!a=0U5w!ThUAKqtwcwa|k*aKX z24YK+bl7iXmmPuf2^ojFWMIZ|`T_;-t=b0Cfat{2Z zIFl&vYpl7taGf$|Q)ggnj3``g>{O(4HP96-1L-rm_fo3(vmybQYoy}EQZlRVlHZFr z6z;#2*8Zk4sB+^`L&dJFv{S)_nPX>T(9}6#t8S@nA#Dg7QY)#j_7WWLQbZle!vRb# zn;h&;~@t>p7 z{nCcGRzb|~&YYmk!npd=pB{c^t1+60K@xn`!3*INiWU;YvEvaPB7`GYQo98k>mC_l&AtUv{MC9QZC}r3M-(_ zA5#4^bXtIVYqoOKgws*a{leR5+0to&cOX}czcgxuRMs8p%UH}Tj#4faPWQ={SF>3+>^6`4J+;m&c)vU8y=~t zG;X<8_l!-g0$wDO7jTV5ro^9ADghy8+hyf}yZ7taveMdQ-PNMKT@J&{7QFWPSkKu^ zc;ojuiv}$lN>Q%JlslR$E$)HU_QT~z|6Uzgq0FkgPbm=8PuTdMZ6OK4vfU@oyI91Tzza(OT|>(gr)t$Mxlu$M)Fw3?k1(3;WoQ5I4$ReLUVH{g8jW?~GQ51hL*#?cbF6dooc`IX7{M$t-K-(iU`rBxBYN9UhMy@9IZGsJ;rm z@>Puyg@j7x5IY=|gi>web5}d(?gcuX>OoL7V9p5%2JwZ^&&*#7tSQvU57pceJ9b3# zOgwAumw~Yd*bgipqhwvYlphctz-cN7ekyJ{q5+zhf=o$5jhnp=33KS47+yzTn_4p z-mGuceaF5Yg8+)k9 zw|_@`^0^~GPS$N?N&l%h&WSrie6wPA^9e*)lYFE_ZtyAA$Ro?KpqHT+40%V%x|d*g zo=&Wh1h4Zh;XS8oS-Ek;cP8XFNtpd-V)3 z$L!P}i5#hQ8!baL_XO|wppIbEPmZ+Qe!1%TSe3L1H z-+(0R4t_}Cvo^-Sd3OuP3;n^-*giTOqvfy4*AYk-8IP67Zv(D3{qA{M9iXaQ3L}QQ zV%Z0xI)HnJclNUX@B-X=#q6L?@Q}FbL19Z0*qEIceA<>~P7f#$HS^-s7I2DB1_a7U0ra&Yzw$V&sz=a)Css!9wq@@;_%Grfn+DG07`T8 z;Z(>vCeJDnt_q!SRy&pT{QebkNK9<_WHetJNHrVbDS(8l&B4sbbInPo>N+qd# z$Dkr=6-k5BgLMeNH65Hj42KXyrktz;MQ({!s8Bh(QZ&xFVDYt_opgBOJ5T9`lAD@P zQ(&mW=LE{O34EJF!->|Wa42yL>I^_l!uGDzIl+=aFY-jvA#Y&k*2V}KKu+K#0-cfw zoc=H}oT{UCz`O;L<~UDAPTaPqm3djov(gl*`P zcB9%qb8C=wx~->Ih5HRJyopH%zyFx88a2CG7q_! zbyql1!VuO9tcKdHVyLt(o_mQ;29$<7tlX|erenkGd8OUM32wM8(05QXY#??Ug0)+9*CB^<1-t>Q~XQ zn!LOI9?m;DtpuHy;?YO{p~izLyy5MRv4U4QrkaO^Be)yz7xR<~0echaz!(q{V4J}}0yds_;x zp2!M6hvB5KkTg%j3iUAxRSVf<##tpIoSm9D9Z#t)s=4I{A@I_0{)&oOyi9!OSI$1&*s2-KNVcGNBnxDPvEy1-ek zq%5fP#)|;Bi2lLC-v{tM?R6|42#&#ZQQBy=hjcox?@Ee+lf z&4Fe|+K-F7naVi?@}8O7XyolhdDQp}_ob+Lcpm_8@4q7~)UNwrPV-ug2SfCXh$v!I zWwiizOJ&QmNnK73@C_(obWHcV$7E)|pt}5>Y0MEG2(}oix(T$>-Y+{9W`r&| zr!c&B=-oSS8|dYQ1cv058X1^++e_G47vmIx1+VRF%rIUTlm9f$WZKotW61ZPyotjUuf57IKFSG+GU9MoDckmg_B{ z2w+kkoGm|5M9>vE*1*1azA~J1;0=2vqO{wP{*r@G-cAKR6lSM29gGWFZgRxvX2%)6 zAI(;Ub*MbpoZ!KQJ-(trDB*uiOvISttsi=G3%I3v!je5dE%sqIWbu7zZ7ep!ifexc zwaCGoG@b~aNzpM$yX+W7@ z!Iki`%-8?(-60J=@m%0-4|*ibH2L}z7p-Dd=2C`@Tp~z+HJ|J-24YT?t{6;%POZ?vL%AxPVVt6~N4u0J+ z0^&Z1(nuQi2xc`x>hzSBV+)9qsLYXG8^LvYcH^9!I&4EDAqzhptH;nS5UD5NO7+k1 zV{l>dc zC~>4bN5QkzZVbn>C3Il>p1EuVILOaxH(B1p;_m26i=RW6!sZ~wjh%VE3wN#iO)sNg zFI|(U4|;R_Ax|YYh97R%h{3FO0p9aQ-JBG->a{jbXliW0+#(Oaef<;QW5uADwTj3^ zB!voTJ%m*0mR6Oxs0*JVNO%-|C2I1K6Z4Dh@?n8}tnd|A8QX}DI5HhN@OwPo1;Op4 z#>t*Lr%4gXf5G4Bxq*?#bjw~k=>EH?EoL z?%SO_ifnC@YasCOl$4z!=z+Jn<*-n!xUqPuhCx!3Yk#$(gZ6Wqk)Elu+t0W{RKD{m zu4m=g&Cxw+%wyz}9zB9yd6f>!%?T;+21|nI#(;;V1D>BkN>Y-*50MLN^@Syd_GWMT zwy%|??J#OswIgqS{K8EvqmlW$GhAH_R`B9zA3C0Gj3V<_W^Ig&ZYv!h*C1pa9ow=A7&A{iCY>km;HS| z_LhspIs`1FW>pWcV}0$9n$icMC`gzprPM@Nq814XN!toQ7|6zR#Bc3OHWULcV%Mj# z`R2D5k2#hwUa(AEHj)+(6XzYiL2{7=uGctQC(`>83i$2`{>+(m`^A00u&7kKs2Og0 zH&0I-J;ECYQzT@CS-i@zb1$)#5+0p!Q>l|L+s_CN~f` zZF4yifz7Xxn@X9*`)vEb)$JTB^b;ZL{KD`gPZtL7Eu|tKN}U*{1p@=*l`L%9$^&kx z(Sv7e9uzC^eqS3WMBCVv1snOY1<;P`@g|wCoGH5Zv-mY=WrKi#trS2aw8e@_ey9=6 zEA{muQ(V;d`E4cyR&%CnzE~ieYwzqHOuXh2p?t@JUR4b;7sYi%7im@6MQ46^^Tvz~ zo!lOGTExG6E>5cgh$q0Pb-R9r=_!tvfMm?L_)qZmcF+FQ48jK41ZSpk(fG+wOmo_9 z_MTLE+9$pWI)Y#J1D)eJ8?U|!y7 z#G}_k%10Tb71K@sutbs zG-lwuTt)Xfor15e^+Mh(CH3#fh>Fg5D|a-s|JCq<^*wlHoEl-g4@+z<0ynu=TBUQ z2G}l?ot#e3s_ba;o5Z1iFubwjT(w5`nN{zSk(~i8;E|uUS=U-(`X37M(-Vjm&7x?- zMZ)ShqBu4!g0esUo3zMM;f;a34nGp+CirbT8gju`TspIn_iW_T$l zQdY2fs80482kGX!`nbBfuM7E`fJ6MI(6N*n41og?czms3x!9u0N+$FCwiO4GXxQ3w z29O~)CyJ}v=r&hZ_jB5GiL;{->GUyD0SoSp`!dU#9Ds~4fjX*|G7%#u61~BniH#=q z&7~c{=NrvEc`8n;yp+SGtYJp{Fj)C`SG&sg!uCqolbe}-j}4w!l}{Eyx=ZgD4UU_o zHfBs^EhY}s;LGJV8aRDVh`0}Ypo+>*-poHflCMEVkONy{RS6*#9q(G|AMu_RO7MeA zeQsBm@3s!y3MYxI8tx({qA-G9KN9zzURD>@obXG9OaYT^B7PNR30Y5=k0S)^GQ`9c zzgMM+qDy}9llDQ;a5>F;`hT{jA(Ey-jih0v?GT(C#1JU6^5N6wDZ zHh$N|H~DjtTB*aeiDQcZvtz8pt7cg<&(<`F-XdTmYlo873XYsNgV1WwJ=VJ>)O#fOVWI@U(l)Ovvnm__x2A=4a zvb5ofk)-fc2G@7XN!gry$=$?QLa5wFmR)HvuBazhDgvP;))=St1!iWG4s#wU+!Bhn zG5po)Ui762ElVEcCn|A~NEaIpRHsBkF??2MyPZJO2jH78U5_<(n=_1P4_L!oM*b64 zWgt>#ffOwVKMFS{J{CV87fH47ZFyNsIo@f;%{4*Sp1ahdBS zMOzVbxc!$DHpO|QKv~S&X+Wgek~S`bzr(iAX_Q@A?{()T(FQm5#JGn%;dGhOrJTY93K``cOX$>Isj8t4IJ1gK0=(KLuz_i}S zKWAXyb6v|Sw3z0eoVs>^SXZ&TeqCr!yEbzcH?eSrYAQ1bWItD(B3{?xP^a!UT1 zVV(XG`wnI&m6QkF(Rr@v&z3h+5FjzH!qkliqT8aj3bUFt*8hF#=C*;-<8|%0x#4Em zmqtG_=sYq(H@Ac^1BXRrL!D<*nfX(@+GNddpJlM<&-^fmq@aElqGI-i>v^j5LeIah zxLm(-+5boI#9X?@f5k{qjlPG&;*JRLo3X?ESGv-GkwC)6EB@$%jv&=;Kz5w61dXpd zrA+;iJV=(@9ypjsfBT?~myRj0|3k?t?%@9YVXs4RTgw`hoJDok|9>0e-_CaWnF4MZ z@&y&#RFIHTYI=~osP*tA8=|g&W*oZ-Vi3Tz{P;d6V2&$x0pw!I5~w+vCw!dwRU{S;^pO!UlQn57?j45NH`YgN%zAOUlpTDOgrb?F&4@#u8=$8uQUf~5pY5I5-cwdnRZ z+H87-=8Cs1pfho5r#L%SXe%n#(a!a@L8_d}<#oh9I&d5_ zOnpuPM|%K4-3qllJY2g3qCr#28Oz2*^DxKr_kd{4=47L04~fTrnQjdG9AYrxtzcTa z56JFD+rdGHkhe2~&!P8Lss<8+{K?vhXA8XD5f*yzzTet^^NS!s0|7(0%$|Lq^}9H^ z&c{FZjLF7yUt_w?&}ehBi07!8gG~Pq+p?PwLwvSXX(p!ZlP$SxYyNT1FuNMXYgHX< zro327$9tMU$$h~}MfLA0A4HF)L2!GC!g8~zGM=_R9TQ~4cMk5R@DpV}C}l_geg*oJoO5FB#x>G5JVvJ1%+M*^Y~ zie?8obq`>&Uy@1|NJLH*Y`RLO+sNK^rUGP;pMAHTH(`n2qz#H1a3OFrU0HTfF{Y6t z7Ko54wm>v|k=Di9JLnzltT9OD?US%|KsBz(>lrr{`HBJ!7wMdaLMfkwKmX7Tsu2YM z-i<{rkGn1A1-&vMJIc90tSO8Fy2DvRwnSl(kx51$4k1;^lUVZG4SA>UH&XqL6#K>( zOL$zhKRE5eLAc$s)I8v@>t}H~FqY1%rsGGqNS307@t$Kvoh0G<@hWhEmrky}w1z=g zB}LqzNAaP|OwcLo;Sg1W_bW~?v1ld^Jfzs9$FcWRnA`)rbLvf$R%VlY_$;+)q#wY` zc3=CMMy5j#`tdJ>_kX#cw*N}i(zk<`o2{KZK(i*Bycau@T!NakTyU(~E(QOp4x&4} z!G1LZ4&+z`pNn$dck?*A!g%A(Sm?uTc%^ovov%UvtFG15FN2^FRrHc;lV#lN8d1~$9MbZKmHah-#a`yku0v)+AftF1mnBDe zmvq<2JXe>qk`E&^J~K?s%**e8E@`A_)=#-YWj^rm$cr~s?2tWt+{YPciak`!vA!A} z(!H0|rJKs$Hs%cicqn|aLAL76o7Gpn!=X{g|l;1Z91 z>I<}x;)|(zu5J760WiDPS<=4(R(ou(FsIuLs(0)-w67G;D$z#He5!Q~n{wBn9x z8c!GCgb>^A02^x_XxYJ#+U`o|`PLj?ZokntIRThLk--f6 zg`)4Ac7d1$KhO;h?cqr7X`aVOVV;ge>+lxETFI;N+W6Y)M2_piep}IT9(>kd&LqKl zq12WIiM`ww7LfkS;)q6gjwq8(-Z0_|0;28G^y|6&JAp)#bUiv<)M@^&0yp!psvyl! z=k6DWQDp61Cmk3xx))4>2Kau0*Mi^TLM{X|Y|r%H#RMn1NtFpm%ZdlU&o2~TD4)YH zd_KOonLB=rUl?amS$Abnil_oyzg~C+kp?3>%}BZA;-z{)EeNfNjz#f7YC|ubfdy2h zrA2sX>~z&4CEg@0;@Z!h3dy+HEi3KCRWzIbcP;!>sk_Tw7L=TGO6I&+@HaKO=CFzf zpu*G6t}#s72{*XtucGjMD$d=)1300f6SoH<9D#B>(PaqkFI~%jjtAtpU4o0%HsAf= z>Zr)}%g@ATkN@2BVB8kRR`QNTmum6orJ%W=ZsW6tJ^?7+Y)INg?=l{t3Gl8^7k&FF zW2B8tD|EQeA6`)o0sMN!Y;Yn@PQ_CJ!F;3^B3c&E2M#7Igd&9ywS#>0V#jOBhw#q% zK-78q|MYVe@)A@9%-+`@q%j?uV~rKBySqxLz)t7j#H{>w&@mwDVtB}1F8^Y%slZm< ze@|hv9L)e{%Pb=9qy>n@E-&T%kp0#soso8YTry}XtH8j4%tQ+LZ3&1D=LwPimh~^M zGUY1X=sz{=L$h$`gk}m~^9)Qs0$^HntBKH+hC0DGHXSIb5W)c`i|yv`yBe zcML?4M#7X6uG~|%wou!-J{0_(108%Pds|%VfYs$WER%aH>kys=R@N2nCBZ7e-ft2} zRM_otcL8{L*8SiX*2JD~$?Y)=cKI%$5pLu5vR2jj3H0^4reX05A54-=5+cy2dZ(OH z{K-PndC3;><7=| zwPBVJx;5&uZpwoY0QGQ_rET6qW&GP&1CBdAc;(LUNVx#9_WTH#BME$+%qMroIV