Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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?) {
Expand Down Expand Up @@ -178,6 +188,10 @@ abstract class CallbackActivity : ParentActivity(), OnUnreadCountInvalidated, No

getUnreadNotificationCount()

if (RemoteConfigUtils.getBoolean(RemoteConfigParam.TODO_REDESIGN)) {
getToDoCount()
}

initialCoreDataLoadingComplete()
} catch {
initialCoreDataLoadingComplete()
Expand Down Expand Up @@ -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<List<UnreadNotificationCount>>() {
override fun onResponse(data: Call<List<UnreadNotificationCount>>, response: Response<List<UnreadNotificationCount>>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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!!)
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/
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)
}
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/
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
}
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/
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())
}
}
}
Loading
Loading