puzhibing
2026-08-21 5fa94b3718b4a78a4fcea4c97652b0ef20b7de3a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
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"
    }
}