package com.xw.yunwei.ui.task
|
|
import android.graphics.Rect
|
import android.os.Bundle
|
import android.view.Gravity
|
import android.view.KeyEvent
|
import android.view.LayoutInflater
|
import android.view.View
|
import android.view.ViewGroup
|
import android.view.WindowManager
|
import androidx.core.view.isVisible
|
import androidx.databinding.DataBindingUtil
|
import androidx.fragment.app.Fragment
|
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.withResumed
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.customview.customView
|
import com.afollestad.materialdialogs.customview.getCustomView
|
import com.amap.api.location.AMapLocation
|
import com.google.android.material.tabs.TabLayoutMediator
|
import com.xw.common.CommonDialog
|
import com.xw.common.R
|
import com.xw.common.TimeDialog
|
import com.xw.common.TimeDialog.Companion.getTime
|
import com.xw.common.base.BaseFragment
|
import com.xw.common.utils.GpsHelper
|
import com.xw.common.utils.ToastUtilCommon
|
import com.xw.common.utils.ToolUtil
|
import com.xw.yunwei.BR
|
import com.xw.yunwei.databinding.DialogDataFilterBinding
|
import com.xw.yunwei.databinding.FragmentTaskBinding
|
import com.xw.yunwei.databinding.ItemArrow2TextBinding
|
import com.xw.yunwei.extension.showCount
|
import com.xw.yunwei.model.FilterData
|
import com.xw.yunwei.model.LocationData
|
import com.xw.yunwei.model.SwitchTabTask
|
import com.xw.yunwei.ui.task.list.TaskListFragment
|
import com.xw.yunwei.ui.work.notice.NoticeActivity
|
import com.xw.yunwei.utils.DialogFactory
|
import kotlinx.coroutines.launch
|
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.ThreadMode
|
|
/**
|
* 任务中心 Fragment
|
*
|
* 负责展示任务列表的 ViewPager 页面(全部/待到达/待处理/已处理),
|
* 集成筛选弹窗(时间、优先级、类型、排序)、车牌号搜索、定位下发等功能。
|
*
|
* @author zmc
|
*/
|
class TaskFragment : BaseFragment<FragmentTaskBinding, TaskVM>(), GpsHelper.OnMapLocation {
|
override fun getBindingVariable(): Int = BR.viewModel
|
|
/** 标签页标题数组 */
|
private val tabTitles = arrayOf("全部", "待到达", "待处理", "已处理", "改派中")
|
|
/** 筛选弹窗实例 */
|
private var filterDialog: MaterialDialog? = null
|
|
/** 日期选择弹窗 */
|
val dialogTime: TimeDialog by lazy { TimeDialog(requireActivity()) }
|
|
/** GPS 定位辅助类 */
|
var gpsHelper: GpsHelper? = null
|
|
/**
|
* 当前筛选条件数据
|
* 根据 ViewModel 中用户选中的筛选项动态组装
|
*/
|
val filterData: FilterData
|
get() {
|
// 构建筛选数据
|
return FilterData(
|
distributeType = viewModel.getDistributeType(),
|
startTime = viewModel.startTime,
|
endTime = viewModel.endTime,
|
taskUrgencyLevel = viewModel.getUrgencyLevel(),
|
taskCategory = viewModel.getTaskCategory(),
|
sortType = viewModel.getSortType(),
|
sortRule = viewModel.taskSort,
|
plateNumber = viewModel.plateNumber.value
|
)
|
}
|
|
/**
|
* 当前定位数据
|
* 用于下发给子 Fragment
|
*/
|
val locationData: LocationData
|
get() {
|
return LocationData(
|
lat = viewModel.location?.latitude, lng = viewModel.location?.longitude
|
)
|
}
|
|
val adapter by lazy {
|
PagerAdapter(this)
|
}
|
private var tabLayoutMediator: TabLayoutMediator? = null
|
override fun onCreate(savedInstanceState: Bundle?) {
|
super.onCreate(savedInstanceState)
|
// 初始化一次性定位,用于后续筛选或下发位置
|
gpsHelper = GpsHelper(
|
requireActivity(),
|
this,
|
isOnceLocation = true,
|
isAutomatic = false,
|
savedInstanceState = savedInstanceState
|
)
|
lifecycle.addObserver(gpsHelper!!)
|
}
|
|
/**
|
* 初始化页面基础组件
|
* 注册 EventBus,设置 ViewPager 与 TabLayout 联动
|
*/
|
override fun init() {
|
super.init()
|
EventBus.getDefault().register(this)
|
}
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
super.onViewCreated(view, savedInstanceState)
|
binding.viewPager.adapter = adapter
|
binding.viewPager.offscreenPageLimit = 5 // 默认值,可根据需要调整
|
tabLayoutMediator =
|
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
|
tab.text = tabTitles[position]
|
}.apply {
|
attach()
|
}
|
view.post {
|
binding.viewPager.currentItem = 1
|
}
|
refreshFilterStatus()
|
}
|
|
//刷新筛选状态
|
private fun refreshFilterStatus() {
|
binding.iconFilter.setImageResource(if (viewModel.hadFilter()) com.xw.yunwei.R.mipmap.ic_filter_task2 else com.xw.yunwei.R.mipmap.ic_filter_task)
|
}
|
|
/**
|
* 设置各类点击及输入监听器
|
*/
|
override fun setListener() {
|
super.setListener()
|
|
// 点击筛选按钮弹出筛选弹窗
|
binding.llFilter.setOnClickListener {
|
showFilterDialog()
|
}
|
|
// 车牌号输入框监听回车键,触发搜索并下发筛选条件
|
binding.searchEt.setOnKeyListener { _, keyCode, event ->
|
if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
|
if (viewModel.plateNumber.value.isNullOrEmpty()) {
|
ToastUtilCommon.show("请输入车牌号")
|
} else {
|
EventBus.getDefault().post(filterData)
|
viewModel.statistic()
|
// requireActivity().supportFragmentManager.setFragmentResult(
|
// REQUEST_KEY_FILTER,
|
// bundleOf(KEY_FILTER_DATA to filterData)
|
// )
|
}
|
ToolUtil.closeKeybord(binding.searchEt, requireActivity())
|
}
|
false
|
}
|
// 监听软键盘收起,清除输入框焦点
|
binding.root.viewTreeObserver.addOnGlobalLayoutListener {
|
val rect = Rect()
|
binding.root.getWindowVisibleDisplayFrame(rect)
|
val screenHeight = binding.root.rootView.height
|
val keypadHeight = screenHeight - rect.bottom
|
// 当键盘高度小于屏幕高度的 15% 时,认为键盘已关闭
|
if (keypadHeight < screenHeight * 0.15) {
|
binding.searchEt.clearFocus()
|
}
|
}
|
|
// 点击通知入口跳转通知页面
|
binding.notice.setOnClickListener { startActivity(NoticeActivity::class.java) }
|
}
|
|
/**
|
* 设置 LiveData 等可观察数据监听
|
*/
|
override fun setObservable() {
|
super.setObservable()
|
TaskVM.needRefresh.observe(viewLifecycleOwner) {
|
if (it) {
|
TaskVM.needRefresh.value = false
|
// 刷新未读消息数量并控制角标显示
|
viewModel.getUnreadCount {
|
binding.count.isVisible = it.isNotEmpty() && (it.toInt() > 0)
|
}
|
}
|
}
|
viewModel.taskStatistic.observe(viewLifecycleOwner) {
|
val totalCountShow = it.totalTableCount.showCount(99)
|
val waitArriveCountShow = it.waitArriveCount.showCount(99)
|
val waitHandleCountShow = it.waitHandleCount.showCount(99)
|
val reassignmentCount = it.reassignmentCount.showCount(99)
|
tabTitles[0] = "全部${if (totalCountShow.isEmpty()) "" else " $totalCountShow"}"
|
tabTitles[1] =
|
"待到达${if (waitArriveCountShow.isEmpty()) "" else " $waitArriveCountShow"}"
|
tabTitles[2] =
|
"待处理${if (waitHandleCountShow.isEmpty()) "" else " $waitHandleCountShow"}"
|
tabTitles[3] = "已处理"
|
tabTitles[4] = "改派中${if (reassignmentCount.isEmpty()) "" else " $reassignmentCount"}"
|
adapter.notifyDataSetChanged()
|
binding.tvAllCount.visibility =
|
if ((it.totalCount ?: 0) > 0) View.VISIBLE else View.GONE
|
binding.tvAllCount.text = it.totalCount.showCount(99)
|
binding.tvAccidentCount.visibility =
|
if ((it.accidentCount ?: 0) > 0) View.VISIBLE else View.GONE
|
binding.tvAccidentCount.text = it.accidentCount.showCount(99)
|
binding.tvNormalCount.visibility =
|
if ((it.routineCount ?: 0) > 0) View.VISIBLE else View.GONE
|
binding.tvNormalCount.text = it.routineCount.showCount(99)
|
}
|
viewModel.needRefreshList.observe(viewLifecycleOwner) {
|
if (it == true) {
|
EventBus.getDefault().post(filterData)
|
viewModel.statistic()
|
}
|
}
|
}
|
|
override fun onResume() {
|
super.onResume()
|
viewModel.statistic()
|
}
|
|
/**
|
* 显示任务筛选弹窗
|
*
|
* 弹窗包含:派发时间、优先级、任务类型、自定义时间范围、排序方式等选项。
|
* 首次创建时初始化各选项的默认选中状态及点击逻辑。
|
*/
|
private fun showFilterDialog() {
|
filterDialog = DialogFactory.produce(requireActivity()).apply {
|
customView(
|
com.xw.yunwei.R.layout.dialog_data_filter,
|
noVerticalPadding = true,
|
horizontalPadding = false
|
)
|
cancelable(true)
|
cornerRadius(12f)
|
|
val bind: DialogDataFilterBinding? = DataBindingUtil.bind(getCustomView())
|
|
with(bind!!) {
|
// 移除默认的标题栏显示,改用自定义数据填充
|
bind.stllTaskTime.remove()
|
bind.stllTaskLevel.remove()
|
bind.stllTaskType.remove()
|
|
// 初始化派发时间筛选项
|
bind.stllTaskTime.setData(
|
viewModel.filterTaskTime,
|
requireActivity(),
|
arrayListOf(
|
viewModel.filterTaskTime.indexOf(viewModel.selectTaskTime).toString()
|
),
|
requireActivity().resources.getColor(R.color.blue_5),
|
"shape_blue1_bg_4"
|
)
|
bind.tvStartTime.text = viewModel.startTime
|
bind.tvEndTime.text = viewModel.endTime
|
// 初始化优先级筛选项
|
bind.stllTaskLevel.setData(
|
viewModel.filterTaskLevel,
|
requireActivity(),
|
arrayListOf(
|
viewModel.filterTaskLevel.indexOf(viewModel.selectTaskLevel).toString()
|
),
|
requireActivity().resources.getColor(R.color.blue_5),
|
"shape_blue1_bg_4"
|
)
|
// 初始化任务类型筛选项
|
bind.stllTaskType.setData(
|
viewModel.filterTaskType,
|
requireActivity(),
|
arrayListOf(
|
viewModel.filterTaskType.indexOf(viewModel.selectTaskType).toString()
|
),
|
requireActivity().resources.getColor(R.color.blue_5),
|
"shape_blue1_bg_4"
|
)
|
|
// 派发时间点击监听:单选切换并清空自定义时间
|
bind.stllTaskTime.setListener {
|
for (index in bind.stllTaskTime.selectIndexList) { // 先取消选中状态
|
bind.stllTaskTime.onTvClick(index)
|
}
|
val index = it.tag as Int // 当前点击的下标
|
bind.stllTaskTime.onTvClick(index)
|
viewModel.selectTaskTime = viewModel.filterTaskTime[index]
|
|
viewModel.startTime = ""
|
viewModel.endTime = ""
|
bind.tvStartTime.text = viewModel.startTime
|
bind.tvEndTime.text = viewModel.endTime
|
}
|
// 优先级点击监听:单选切换
|
bind.stllTaskLevel.setListener {
|
for (index in bind.stllTaskLevel.selectIndexList) { // 先取消选中状态
|
bind.stllTaskLevel.onTvClick(index)
|
}
|
val index = it.tag as Int // 当前点击的下标
|
bind.stllTaskLevel.onTvClick(index)
|
viewModel.selectTaskLevel = viewModel.filterTaskLevel[index]
|
}
|
// 任务类型点击监听:单选切换
|
bind.stllTaskType.setListener {
|
for (index in bind.stllTaskType.selectIndexList) { // 先取消选中状态
|
bind.stllTaskType.onTvClick(index)
|
}
|
val index = it.tag as Int // 当前点击的下标
|
bind.stllTaskType.onTvClick(index)
|
viewModel.changeTaskType(index)
|
}
|
val refresh: () -> Unit = {
|
viewModel.sortViews.forEach {
|
if (it.name.text == viewModel.selectTaskSort) {
|
it.layout.isSelected = true
|
it.name.isSelected = true
|
it.arrowUp.isSelected = viewModel.taskSort == 2
|
it.arrowDown.isSelected = viewModel.taskSort == 1
|
} else {
|
it.layout.isSelected = false
|
it.name.isSelected = false
|
it.arrowUp.isSelected = false
|
it.arrowDown.isSelected = false
|
}
|
}
|
}
|
|
/**
|
* 排序选项点击回调
|
* @param name 排序字段名称
|
*/
|
val onCheck: (String) -> Unit = { name ->
|
if (name == viewModel.selectTaskSort) {
|
// 同一字段再次点击,切换正序/倒序
|
viewModel.taskSort = if (viewModel.taskSort == 1) 2 else 1
|
} else {
|
// 切换排序字段,默认正序
|
viewModel.selectTaskSort = name
|
viewModel.taskSort = 1
|
}
|
// 更新所有排序项的选中状态及箭头方向
|
refresh()
|
}
|
|
// 动态构建排序选项视图
|
bind.tagLayout.removeAllViews()
|
viewModel.sortViews.clear()
|
viewModel.filterTaskSort.forEachIndexed { index, name ->
|
val itemBind: ItemArrow2TextBinding = DataBindingUtil.inflate(
|
LayoutInflater.from(activity),
|
com.xw.yunwei.R.layout.item_arrow2_text,
|
null,
|
false
|
)
|
itemBind.name.text = name
|
viewModel.sortViews.add(itemBind)
|
itemBind.root.setOnClickListener { onCheck(name) }
|
bind.tagLayout.addView(itemBind.root)
|
}
|
refresh()
|
viewModel.sortViews.forEach {
|
if (it.name.text == viewModel.selectTaskSort) {
|
it.layout.isSelected = true
|
it.name.isSelected = true
|
it.arrowUp.isSelected = viewModel.taskSort == 2
|
it.arrowDown.isSelected = viewModel.taskSort == 1
|
} else {
|
it.layout.isSelected = false
|
it.name.isSelected = false
|
it.arrowUp.isSelected = false
|
it.arrowDown.isSelected = false
|
}
|
}
|
// 开始时间选择:弹出日期选择器并校验不能大于结束时间
|
bind.tvStartTime.setOnClickListener {
|
val showTime = getTime(viewModel.startTime)
|
val minTime = getTime("2020-01-01")
|
dialogTime.show("选择日期", showTime, minTime) {
|
val odlIndex = mutableListOf<Int>()
|
for (index in bind.stllTaskTime.selectIndexList) { // 先取消选中状态
|
odlIndex.add(index)
|
}
|
odlIndex.forEach { bind.stllTaskTime.onTvClick(it) }
|
viewModel.selectTaskTime = ""
|
|
val selectTime = getTime(it)
|
if (viewModel.endTime.isNotEmpty()) {
|
if (selectTime.replace("-", "") > viewModel.endTime.replace(
|
"-", ""
|
)
|
) {
|
ToastUtilCommon.showWarn("开始时间不能大于结束时间")
|
return@show
|
}
|
}
|
viewModel.startTime = selectTime
|
bind.tvStartTime.text = viewModel.startTime
|
}
|
}
|
// 结束时间选择:弹出日期选择器并校验不能小于开始时间
|
bind.tvEndTime.setOnClickListener {
|
val showTime = getTime(viewModel.endTime)
|
val minTime = getTime("2020-01-01")
|
dialogTime.show("选择日期", showTime, minTime) {
|
val odlIndex = mutableListOf<Int>()
|
for (index in bind.stllTaskTime.selectIndexList) { // 先取消选中状态
|
odlIndex.add(index)
|
}
|
odlIndex.forEach { bind.stllTaskTime.onTvClick(it) }
|
viewModel.selectTaskTime = ""
|
|
val selectTime = getTime(it)
|
if (viewModel.startTime.isNotEmpty()) {
|
if (selectTime.replace("-", "") < viewModel.startTime.replace(
|
"-", ""
|
)
|
) {
|
ToastUtilCommon.showWarn("开始时间不能大于结束时间")
|
return@show
|
}
|
}
|
viewModel.endTime = getTime(it)
|
bind.tvEndTime.text = viewModel.endTime
|
}
|
}
|
|
// 取消:直接关闭弹窗
|
bind.close.setOnClickListener { dismiss() }
|
// 重置:恢复所有筛选项为默认状态
|
bind.btnLeft.setOnClickListener {
|
val odlIndex = mutableListOf<Int>()
|
for (index in bind.stllTaskTime.selectIndexList) { // 先取消选中状态
|
odlIndex.add(index)
|
}
|
odlIndex.forEach { bind.stllTaskTime.onTvClick(it) }
|
viewModel.selectTaskTime = viewModel.filterTaskTime[2]
|
bind.stllTaskTime.onTvClick(2)
|
|
odlIndex.clear()
|
for (index in bind.stllTaskLevel.selectIndexList) { // 先取消选中状态
|
odlIndex.add(index)
|
}
|
odlIndex.forEach { bind.stllTaskLevel.onTvClick(it) }
|
viewModel.selectTaskLevel = viewModel.filterTaskLevel[0]
|
bind.stllTaskLevel.onTvClick(0)
|
|
odlIndex.clear()
|
for (index in bind.stllTaskType.selectIndexList) { // 先取消选中状态
|
odlIndex.add(index)
|
}
|
odlIndex.forEach { bind.stllTaskType.onTvClick(it) }
|
viewModel.changeTaskType(0)
|
bind.stllTaskType.onTvClick(0)
|
|
viewModel.startTime = ""
|
bind.tvStartTime.text = viewModel.startTime
|
viewModel.endTime = ""
|
bind.tvEndTime.text = viewModel.endTime
|
|
viewModel.selectTaskSort = viewModel.filterTaskSort[0]
|
refresh()
|
}
|
// 确定:校验时间合法性后下发筛选条件
|
bind.btnRight.setOnClickListener {
|
if (viewModel.selectTaskTime.isEmpty() && (viewModel.startTime.isEmpty() || viewModel.endTime.isEmpty())) {
|
ToastUtilCommon.showWarn("请正确选择时间")
|
return@setOnClickListener
|
}
|
EventBus.getDefault().post(filterData)
|
viewModel.statistic()
|
// requireActivity().supportFragmentManager.setFragmentResult(
|
// REQUEST_KEY_FILTER,
|
// bundleOf(KEY_FILTER_DATA to filterData)
|
// )
|
filterDialog?.dismiss()
|
refreshFilterStatus()
|
}
|
}
|
}
|
|
filterDialog?.show()
|
// 关键:让软键盘把 BottomSheet 顶上去
|
filterDialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
filterDialog?.window?.setLayout(
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
|
)
|
filterDialog?.window?.setGravity(Gravity.BOTTOM)
|
filterDialog?.window?.setWindowAnimations(R.style.AnimUp)
|
}
|
|
/**
|
* 定位结果回调
|
*
|
* @param pLocation 定位结果,null 表示未获取到定位权限
|
*/
|
override fun location(pLocation: AMapLocation?) {
|
if (pLocation == null) {
|
CommonDialog.showLocationNotGrantedTipsDialog(requireActivity())
|
} else {
|
viewModel.location = pLocation
|
EventBus.getDefault().post(locationData)
|
// requireActivity().supportFragmentManager.setFragmentResult(
|
// REQUEST_KEY_LOCATION,
|
// bundleOf(KEY_LOCATION_DATA to locationData)
|
// )
|
}
|
}
|
|
override fun onDestroyView() {
|
super.onDestroyView()
|
tabLayoutMediator?.detach()
|
}
|
|
override fun onDestroy() {
|
super.onDestroy()
|
EventBus.getDefault().unregister(this)
|
}
|
|
/**
|
* EventBus 消息接收:切换任务 Tab
|
*
|
* @param event 切换目标 Tab 索引事件
|
*/
|
@Suppress("unused")
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
fun onMessageEvent(event: SwitchTabTask) {
|
lifecycleScope.launch {
|
lifecycle.withResumed {
|
binding.tabLayout.getTabAt(event.index)?.select()
|
}
|
}
|
}
|
|
/**
|
* ViewPager2 适配器
|
*
|
* 根据 position 返回对应状态的任务列表 Fragment
|
*/
|
inner class PagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
|
private val pages = listOf(
|
viewModel.allTaskFragment,
|
viewModel.togoTaskFragment,
|
viewModel.todoTaskFragment,
|
viewModel.finishTaskFragment,
|
viewModel.reassignmentTaskFragment
|
)
|
|
override fun getItemCount(): Int = tabTitles.size
|
|
override fun createFragment(position: Int): TaskListFragment {
|
return pages[position]
|
}
|
|
// 返回唯一的 ID,推荐使用数据的哈希值
|
override fun getItemId(position: Int): Long {
|
return pages[position].hashCode().toLong()
|
}
|
|
// 告诉 ViewPager2 某个 ID 是否仍然有效
|
override fun containsItem(itemId: Long): Boolean {
|
return pages.any { it.hashCode().toLong() == itemId }
|
}
|
}
|
|
companion object {
|
const val REQUEST_KEY_FILTER = "filter_request"
|
const val KEY_FILTER_DATA = "filter_data"
|
const val REQUEST_KEY_LOCATION = "location_request"
|
const val KEY_LOCATION_DATA = "location_data"
|
}
|
}
|