Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ class InitActivity : BasePresenterActivity<InitActivityPresenter, InitActivityVi
private var drawerItemSelectedJob: Job? = null

private val mTabSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
// 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CoursesView, DashboardPresenter> {
override fun create() = DashboardPresenter()
class DashboardPresenterFactory(
private val userApi: UserAPI.UsersInterface,
private val networkStateProvider: NetworkStateProvider
) : PresenterFactory<CoursesView, DashboardPresenter> {
override fun create() = DashboardPresenter(userApi, networkStateProvider)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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<Course, DashboardPresenter, CoursesView, CoursesViewHolder, CoursesAdapter>(), CoursesView {

@Inject
lateinit var userApi: UserAPI.UsersInterface

@Inject
lateinit var networkStateProvider: NetworkStateProvider

private val binding by viewBinding(FragmentDashboardBinding::bind)

private lateinit var mGridLayoutManager: GridLayoutManager
Expand All @@ -82,7 +108,7 @@ class DashboardFragment : BaseSyncFragment<Course, DashboardPresenter, CoursesVi
override fun perPageCount() = ApiPrefs.perPageCount
override fun withPagination() = false

override fun getPresenterFactory() = DashboardPresenterFactory()
override fun getPresenterFactory() = DashboardPresenterFactory(userApi, networkStateProvider)

override fun onAttach(context: Context) {
super.onAttach(context)
Expand Down Expand Up @@ -141,6 +167,7 @@ class DashboardFragment : BaseSyncFragment<Course, DashboardPresenter, CoursesVi
if(courseRecyclerView.adapter == null) {
courseRecyclerView.adapter = adapter
}
addItemTouchHelperForCardReorder()
presenter.loadData(mNeedToForceNetwork)
mNeedToForceNetwork = false
}
Expand Down Expand Up @@ -234,6 +261,86 @@ class DashboardFragment : BaseSyncFragment<Course, DashboardPresenter, CoursesVi
RecyclerViewUtils.checkIfEmpty(emptyCoursesView, courseRecyclerView, swipeRefreshLayout, adapter, presenter.isEmpty)
}

private fun addItemTouchHelperForCardReorder() {
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.START or ItemTouchHelper.END or ItemTouchHelper.DOWN or ItemTouchHelper.UP,
0
) {
private var draggedFromPosition: Int? = null

override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)

if (viewHolder != null && actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
draggedFromPosition = viewHolder.bindingAdapterPosition
}
}

override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val fromPosition = viewHolder.bindingAdapterPosition
val toPosition = target.bindingAdapterPosition

if (fromPosition in 0 until adapter.size() && toPosition in 0 until adapter.size()) {
adapter.notifyItemMoved(fromPosition, toPosition)
}

return true
}

override fun getDragDirs(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
return if (viewHolder is CoursesViewHolder && presenter.isOnline()) {
ItemTouchHelper.START or ItemTouchHelper.END or ItemTouchHelper.DOWN or ItemTouchHelper.UP
} else 0
}

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit

override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)

val finishingPosition = viewHolder.bindingAdapterPosition
val startPosition = draggedFromPosition

if (finishingPosition == RecyclerView.NO_POSITION || startPosition == null) {
draggedFromPosition = null
return
}

if (startPosition != finishingPosition) {
presenter.moveCourse(startPosition, finishingPosition)
adapter.notifyDataSetChanged()

lifecycleScope.launch {
val result = presenter.saveDashboardPositions()
if (result.isFail) {
toast(R.string.failedToUpdateDashboardOrder)
}
}
}

draggedFromPosition = null
}
})

itemTouchHelper.attachToRecyclerView(binding.courseRecyclerView)
}

fun cancelCardDrag() {
val cancelEvent = MotionEvent.obtain(0L, 0L, ACTION_CANCEL, 0f, 0f, 0)
binding.courseRecyclerView.onTouchEvent(cancelEvent)
cancelEvent.recycle()
}

companion object {
fun getInstance() = DashboardFragment()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,31 @@
*/
package com.instructure.teacher.presenters

import com.instructure.canvasapi2.CanvasRestAdapter
import com.instructure.canvasapi2.apis.UserAPI
import com.instructure.canvasapi2.builders.RestParams
import com.instructure.canvasapi2.managers.CourseManager
import com.instructure.canvasapi2.models.Course
import com.instructure.canvasapi2.models.DashboardCard
import com.instructure.canvasapi2.models.DashboardPositions
import com.instructure.canvasapi2.models.Tab
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.canvasapi2.utils.DataResult
import com.instructure.canvasapi2.utils.weave.apiAsync
import com.instructure.pandarecycler.util.toList
import com.instructure.pandautils.blueprint.SyncPresenter
import com.instructure.pandautils.utils.ColorApiHelper
import com.instructure.pandautils.utils.NetworkStateProvider
import com.instructure.teacher.viewinterface.CoursesView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

class DashboardPresenter : SyncPresenter<Course, CoursesView>(Course::class.java) {
class DashboardPresenter(
private val userApi: UserAPI.UsersInterface,
private val networkStateProvider: NetworkStateProvider
) : SyncPresenter<Course, CoursesView>(Course::class.java) {

private var dashboardJob: Job? = null

Expand Down Expand Up @@ -113,4 +123,36 @@ class DashboardPresenter : SyncPresenter<Course, CoursesView>(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<DashboardPositions> {
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()
}
}
Loading