app/build.gradle.kts
@@ -86,6 +86,7 @@ manifestPlaceholders["icon"] = "@mipmap/ic_launcher" buildConfigField("String", "API_HOST", "\"${API_UAT}\"") buildConfigField("String", "WS_URL", "\"ws://192.168.110.188:8888/websocket\"") } create("prd") { // 正式环境 @@ -96,6 +97,7 @@ manifestPlaceholders["icon"] = "@mipmap/ic_launcher" buildConfigField("String", "API_HOST", "\"${API_PRD}\"") buildConfigField("String", "WS_URL", "\"ws://192.168.110.188:8888/websocket\"") } } @@ -119,7 +121,7 @@ testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) implementation("com.aliyun.ams:alicloud-android-push:3.10.1") implementation(libs.alicloud) implementation(project(":common")) implementation(libs.dialog.pickerview) app/src/main/AndroidManifest.xml
@@ -40,6 +40,9 @@ android:supportsRtl="true" android:theme="@style/Theme.Yunwei"> <activity android:name=".ui.work.checkInMission.CheckInMissionPointsListActivity" android:exported="false" /> <activity android:name=".ui.work.site.SiteErrorUploadActivity" android:exported="false" /> <activity app/src/main/java/com/xw/yunwei/Constants.kt
@@ -18,4 +18,13 @@ return field } //改派原因 val reasons = listOf<String>( "任务分派冲突", "个人原因", "任务距离太远", "临时处理紧急情况", "交通拥堵", "其他" ) } app/src/main/java/com/xw/yunwei/MyApp.kt
@@ -16,6 +16,7 @@ import com.alibaba.sdk.android.push.noonesdk.PushServiceFactory import com.amap.api.maps.MapsInitializer import com.scwang.smart.refresh.layout.SmartRefreshLayout import com.tencent.bugly.crashreport.CrashReport import com.xw.common.custom.ClassicsFooter11 import com.xw.common.custom.ClassicsHeader11 import com.xw.common.utils.ActivityManager @@ -24,14 +25,18 @@ import com.xw.common.utils.MMKVManager import com.xw.yunwei.di.networkModule import com.xw.yunwei.model.AppPermissionModel import com.xw.yunwei.websocket.WebSocketManager import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.android.ext.koin.androidContext import org.koin.core.context.GlobalContext.startKoin import com.tencent.bugly.crashreport.CrashReport class MyApp : Application() { companion object { private const val TAG = "MyApp" lateinit var myApp: MyApp var pageShowNum = 0 // 可见页面数 @@ -99,6 +104,7 @@ SmartRefreshLayout.setDefaultRefreshFooterCreator { context, _ -> ClassicsFooter11(context) } initWebSocket() } fun initLog() { @@ -132,7 +138,7 @@ registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { ActivityManager.addActivity(activity) LogUtils.i("zmc-pageA", "onActivityCreated: " + activity.javaClass.name) LogUtils.i(TAG, "onActivityCreated: " + activity.javaClass.name) if (activity is AppCompatActivity) { activity.supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() { @@ -154,31 +160,31 @@ } override fun onActivityStarted(activity: Activity) { LogUtils.d("zmc-pageA", "onActivityStarted: " + activity.javaClass.name) LogUtils.d(TAG, "onActivityStarted: " + activity.javaClass.name) } override fun onActivityResumed(activity: Activity) { LogUtils.i("zmc-pageA", "onActivityResumed: " + activity.javaClass.name) LogUtils.i(TAG, "onActivityResumed: " + activity.javaClass.name) currentShowActivity!!.complete(activity) pageShowNum++ } override fun onActivityPaused(activity: Activity) { LogUtils.e("zmc-pageA", "onActivityPaused: " + activity.javaClass.name) LogUtils.e(TAG, "onActivityPaused: " + activity.javaClass.name) pageShowNum-- currentShowActivity = null } override fun onActivityStopped(activity: Activity) { LogUtils.e("zmc-pageA", "onActivityStopped: " + activity.javaClass.name) LogUtils.e(TAG, "onActivityStopped: " + activity.javaClass.name) } override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { LogUtils.d("zmc-pageA", "onActivitySaveInstanceState: " + activity.javaClass.name) LogUtils.d(TAG, "onActivitySaveInstanceState: " + activity.javaClass.name) } override fun onActivityDestroyed(activity: Activity) { LogUtils.e("zmc-pageA", "onActivityDestroyed: " + activity.javaClass.name) LogUtils.e(TAG, "onActivityDestroyed: " + activity.javaClass.name) ActivityManager.removeActivity(activity) } }) @@ -266,4 +272,44 @@ }) } //初始化websocket private fun initWebSocket() { WebSocketManager.initialize(this) CoroutineScope(Dispatchers.IO).launch { WebSocketManager.eventFlow.collect { event -> when (event) { is WebSocketManager.WebSocketEvent.Connected -> { } is WebSocketManager.WebSocketEvent.ConnectFailed -> { } is WebSocketManager.WebSocketEvent.Message -> { val currentShowActivity = MyApp.myApp.getCurrentShowActivity() LogUtils.d(TAG, "显示activity ${currentShowActivity.javaClass.name}") } is WebSocketManager.WebSocketEvent.BinaryMessage -> { } is WebSocketManager.WebSocketEvent.Disconnected -> { } } } } } //连接websocket fun connectWebSocket() { WebSocketManager.connect() } //断连websocket fun disconnectWebSocket() { WebSocketManager.disconnect() } } app/src/main/java/com/xw/yunwei/model/CheckInMissionPointInfo.kt
New file @@ -0,0 +1,10 @@ package com.xw.yunwei.model //巡游任务点信息 data class CheckInMissionPointInfo( val id: Int, val timeRange: String?, // 打卡限时,如"08:00~09:00" val address: String?, // 地址,如"蜀山区华侨中心A座2301" val status: String?, // 打卡状态 val checkTime: String? = null // 打卡时间,仅已打卡/超时打卡时有值, ) app/src/main/java/com/xw/yunwei/model/CruiseCheckInMissionInfo.kt
New file @@ -0,0 +1,21 @@ package com.xw.yunwei.model import android.os.Parcelable import kotlinx.parcelize.Parcelize //巡游打卡任务信息 @Parcelize data class CruiseCheckInMissionInfo( val checkInPointCount: Int, val checkInPointFinishCount: Int, val checkInPointUnFinishCount: Int, val cruiseCheckInTaskTime: String, val id: String ) : Parcelable { //数据是否有效 fun isEffective() = (id.toLongOrNull() ?: 0L) > 0L //获取完成进度 fun getFinishProgress() = (checkInPointFinishCount.toFloat() / checkInPointCount.toFloat()) * 100f } app/src/main/java/com/xw/yunwei/net/WorkApi.kt
@@ -13,6 +13,7 @@ import com.xw.yunwei.model.CarReportModel import com.xw.yunwei.model.ChargeRecordModel import com.xw.yunwei.model.CheckRecordModel import com.xw.yunwei.model.CruiseCheckInMissionInfo import com.xw.yunwei.model.FenceInfoModel import com.xw.yunwei.model.MaintainRecordModel import com.xw.yunwei.model.RepairRecordModel @@ -138,4 +139,8 @@ /* 现场情况上报 */ @POST("operation/maintenance-app/api/v1/pointSituationReport/addPointSituationReport") fun addPointSituationReport(@Body params: RequestBody): Call<HttpResponse<Any>> /* 获取个人中心巡游打卡任务 */ @GET("maintenance/maintenance-app/api/v1/cruiseCheckInRule/getMyXpCruiseCheckInRule") fun getMyCruiseCheckInMissionInfo(): Call<HttpResponse<CruiseCheckInMissionInfo>> } app/src/main/java/com/xw/yunwei/net/WorkRepository.kt
@@ -4,6 +4,7 @@ import com.xw.common.net.BaseHttpManager.Companion.getBody import com.xw.common.net.interceptAwait import com.xw.yunwei.model.CheckRecordModel import com.xw.yunwei.model.CruiseCheckInMissionInfo import com.xw.yunwei.model.MaintainRecordModel import com.xw.yunwei.model.SiteModel import com.xw.yunwei.model.UploadModel @@ -74,4 +75,9 @@ api.addPointSituationReport(getBody(map)).interceptAwait() return true } /* 获取个人中心巡游打卡任务 */ suspend fun getMyCruiseCheckInMissionInfo(): CruiseCheckInMissionInfo? { return api.getMyCruiseCheckInMissionInfo().interceptAwait() } } app/src/main/java/com/xw/yunwei/ui/MainActivity.kt
@@ -17,13 +17,13 @@ import com.xw.yunwei.MyApp import com.xw.yunwei.R import com.xw.yunwei.databinding.ActivityMainBinding import com.xw.yunwei.model.LocationData import com.xw.yunwei.model.SwitchTabMain import com.xw.yunwei.model.UserInfo import com.xw.yunwei.push.MessageHandler.showProtocolDialog import com.xw.yunwei.ui.task.TaskFragment import com.xw.yunwei.ui.user.mine.MineFragment import com.xw.yunwei.ui.work.WorkFragment import com.xw.yunwei.websocket.WebSocketManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.greenrobot.eventbus.EventBus @@ -44,6 +44,7 @@ super.init() val user = MMKVManager.getInstance().getModel<UserInfo>(KeyPreferences.USER) if (user == null) { MyApp.myApp.disconnectWebSocket() MMKVManager.getInstance().clear() ToastUtilCommon.showWarn("登录已过期,请重新登录") lifecycleScope.launch { @@ -62,7 +63,7 @@ override fun setObservable() { super.setObservable() viewModel.selectIndex.observe(this) { showFragment(it) } MyApp.permissions.observe(this){ MyApp.permissions.observe(this) { viewModel.workPermissions.value = MyApp.findPermissionByName("工作台")?.visible == "0" viewModel.taskPermissions.value = MyApp.findPermissionByName("任务中心")?.visible == "0" @@ -90,7 +91,7 @@ } // 检测是否有新协议 private fun checkNewProtocol(){ private fun checkNewProtocol() { val needRead = MMKVManager.getInstance().getBoolean(KeyPreferences.NEED_READ, false) if (needRead) { showProtocolDialog(this, true) @@ -98,7 +99,7 @@ } // 检测是否有强制升级版本 private fun checkNewForcedVersion(){ private fun checkNewForcedVersion() { viewModel.getVersion { if (it.forceUpdateFlag == 1) { UpdateManager.instance!! @@ -118,7 +119,11 @@ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val permissionList: MutableList<String> = ArrayList() for (permission in listOf("android.permission.POST_NOTIFICATIONS")) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission( this, permission ) != PackageManager.PERMISSION_GRANTED ) { permissionList.add(permission) } } @@ -126,10 +131,16 @@ if (!result) { // 无权限 申请 val pl = arrayOf("android.permission.POST_NOTIFICATIONS") XXPermissions.with(this).permission(pl).request(object : OnPermissionCallback { override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) { // 权限同意 override fun onGranted( permissions: MutableList<String>, allGranted: Boolean ) { // 权限同意 } override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) { override fun onDenied( permissions: MutableList<String>, doNotAskAgain: Boolean ) { super.onDenied(permissions, doNotAskAgain) ToastUtilCommon.showWarn("您未授权通知权限,可能会导致无法及时收到消息") } app/src/main/java/com/xw/yunwei/ui/dialog/ApplyForReassignDialog.kt
@@ -1,46 +1,220 @@ package com.xw.yunwei.ui.dialog import android.content.Context import android.os.Handler import android.os.Looper import android.view.Gravity import android.widget.Button import android.widget.LinearLayout import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import androidx.core.view.isVisible import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.customview.customView import com.xw.common.R import com.xw.common.utils.BindUtils.getResColor import com.xw.yunwei.R import com.xw.yunwei.model.TaskModel import java.time.Duration import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit import kotlin.math.max //申请改派弹窗 object ApplyForReassignDialog { fun show(context: Context){ // 倒计时Handler private val countdownHandler = Handler(Looper.getMainLooper()) //显示 fun show( context: Context, item: TaskModel, applyForReassignBlock: (dialog: MaterialDialog) -> Unit, receive: () -> Unit ) { var runnable: Runnable? = null val dialog = MaterialDialog(context).apply { customView( R.layout.dialog_confirm, noVerticalPadding = true, dialogWrapContent = false R.layout.dialog_apply_for_reassign, noVerticalPadding = true, dialogWrapContent = false ) cancelable(true) cornerRadius(15f) val tvContent = findViewById<AppCompatTextView>(R.id.content) val btnLeft = findViewById<Button>(R.id.btnLeft) val btnRight = findViewById<Button>(R.id.btnRight) tvContent.text = content btnLeft.text = leftText ?: "" btnRight.text = rightText ?: "" btnLeft.isVisible = !leftText.isNullOrEmpty() btnRight.isVisible = !rightText.isNullOrEmpty() btnLeft.setOnClickListener { if (leftBlock()) { dismiss() } cancelable(false) cancelOnTouchOutside(false) cornerRadius(4f) val levelId = when (item.taskUrgencyLevel) { 0 -> R.mipmap.ic_task_item_left_one 1 -> R.mipmap.ic_task_item_left_two 2 -> R.mipmap.ic_task_item_left_three 3 -> R.mipmap.ic_task_item_left_four else -> null } btnRight.setOnClickListener { if (rightBlock()) { dismiss() val level = findViewById<AppCompatImageView>(R.id.level) val countdownLayout = findViewById<LinearLayout>(R.id.countdownLayout) val countdownTips = findViewById<AppCompatTextView>(R.id.countdownTips) val hour = findViewById<AppCompatTextView>(R.id.hour) val colon1 = findViewById<AppCompatImageView>(R.id.colon1) val minutes = findViewById<AppCompatTextView>(R.id.minutes) val colon2 = findViewById<AppCompatImageView>(R.id.colon2) val second = findViewById<AppCompatTextView>(R.id.second) val type = findViewById<AppCompatTextView>(R.id.type) val plateNumber = findViewById<AppCompatTextView>(R.id.plateNumber) val status = findViewById<AppCompatTextView>(R.id.status) val address = findViewById<AppCompatTextView>(R.id.address) val tvApplyForReassignment = findViewById<AppCompatTextView>(R.id.tv_apply_for_reassignment) val tvControl = findViewById<AppCompatTextView>(R.id.tvControl) levelId?.let { level.setImageResource(levelId) } if (item.status == 0 && item.taskCategory == 2) { countdownLayout.isVisible = true val list = calculateTimeDifference(item.endTime) updateTime( hour, colon1, minutes, colon2, second, countdownTips, countdownLayout, list ) if (list.all { it == 0L }) { // 全0 countdownTips.text = "已超时" } else { // 注册到倒计时列表 countdownTips.text = "倒计时结束前,务必到达现场" runnable = object : Runnable { override fun run() { val list = calculateTimeDifference(item.endTime) if (list.count { it == 0L } == list.size) { countdownHandler.removeCallbacks(this) } else { updateTime( hour, colon1, minutes, colon2, second, countdownTips, countdownLayout, list ) countdownHandler.postDelayed(this, 1000) } } } countdownHandler.postDelayed(runnable, 1000) } } else { countdownLayout.isVisible = false } if (item.isAccidentTask()) { type.text = "事故" } else if (item.isRoutineTask()) { type.text = item.getTaskType() } plateNumber.text = item.plateNumber val setStatus: (Int, Int) -> Unit = { text, bg -> status.setTextColor(text) status.setBackgroundResource(bg) } setStatus( getResColor(com.xw.common.R.color.gray_8), com.xw.common.R.color.gray_3 ) // 0 = 待到达 1 = 待处理 2 = 已完成 3 = 已取消 4 = 已取消 when (item.status) { 0 -> status.text = "待到达" 1 -> status.text = "待处理" 2 -> status.text = "已处理" 3 -> status.text = "已取消" 4 -> status.text = "改派中" } address.text = item.vehicleLocation tvApplyForReassignment.setOnClickListener { applyForReassignBlock.invoke(this) } tvControl.setOnClickListener { receive.invoke() dismiss() } } dialog.setOnDismissListener { runnable?.also { countdownHandler.removeCallbacks(it) } } dialog.show() dialog.window?.setGravity(Gravity.CENTER) dialog.window?.setWindowAnimations(R.style.AnimLeft) dialog.window?.setWindowAnimations(com.xw.common.R.style.AnimLeft) } //更新时间 private fun updateTime( hour: AppCompatTextView, colon1: AppCompatImageView, minutes: AppCompatTextView, colon2: AppCompatImageView, second: AppCompatTextView, countdownTips: AppCompatTextView, countdownLayout: LinearLayout, list: List<Long> ) { hour.text = if (list[0] < 10) "0${list[0]}" else "${list[0]}" minutes.text = if (list[1] < 10) "0${list[1]}" else "${list[1]}" second.text = if (list[2] < 10) "0${list[2]}" else "${list[2]}" val showRed = list[0] == 0L && list[1] == 0L val countdownId = if (showRed) R.mipmap.img_task_item_top_bg_red else R.mipmap.img_task_item_top_bg_blue countdownLayout.setBackgroundResource(countdownId) val colorId = if (showRed) getResColor(com.xw.common.R.color.red_6) else getResColor(com.xw.common.R.color.blue_5) countdownTips.setTextColor(colorId) val bgId = if (showRed) R.drawable.shape_fa3e3e_bg_4 else R.drawable.shape_blue_5_bg_4 hour.setBackgroundResource(bgId) minutes.setBackgroundResource(bgId) second.setBackgroundResource(bgId) val imgId = if (showRed) R.mipmap.ic_task_item_colon_r else R.mipmap.ic_task_item_colon_b colon1.setImageResource(imgId) colon2.setImageResource(imgId) val hideTime = list[0] == 0L && list[1] == 0L && list[2] == 0L hour.isVisible = !hideTime minutes.isVisible = !hideTime second.isVisible = !hideTime colon1.isVisible = !hideTime colon2.isVisible = !hideTime } /** * 计算目标时间与当前时间的差值,返回时分秒格式 */ private fun calculateTimeDifference(targetTimeStr: String?): List<Long> { targetTimeStr?.let { // 定义输入格式 val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") // 解析目标时间 val targetTime = LocalDateTime.parse(targetTimeStr, formatter) // 获取当前时间 val now = LocalDateTime.now() // 计算时间差(毫秒) val duration = Duration.between(now, targetTime) val totalSeconds = max(duration.seconds, 0L) // 转换为时分秒 val hours = TimeUnit.SECONDS.toHours(totalSeconds) val minutes = TimeUnit.SECONDS.toMinutes(totalSeconds) % 60 val seconds = totalSeconds % 60 return listOf(hours, minutes, seconds) } return listOf(0L, 0L, 0L) } } app/src/main/java/com/xw/yunwei/ui/dialog/ApplyForReassignReasonDialog.kt
New file @@ -0,0 +1,139 @@ package com.xw.yunwei.ui.dialog import android.content.Context import android.util.TypedValue import android.view.Gravity import android.widget.EditText import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.widget.addTextChangedListener import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.customview.customView import com.google.android.flexbox.FlexboxLayout import com.scwang.smart.refresh.layout.util.SmartUtil import com.xw.common.utils.ToastUtilCommon import com.xw.yunwei.R //申请改派原因弹窗 object ApplyForReassignReasonDialog { fun show( context: Context, reasons: List<String>, submitBlock: (position: Int, input: String) -> Unit ) { val dialog = MaterialDialog(context).apply { customView( R.layout.dialog_apply_for_reassign_reason, noVerticalPadding = true, dialogWrapContent = false ) cornerRadius(4f) val flexboxLayout = findViewById<FlexboxLayout>(R.id.fl_tags) val etInput = findViewById<EditText>(R.id.et_input) val tvOperation = findViewById<TextView>(R.id.tv_operation) val set = mutableSetOf<String>() fillTag(flexboxLayout, reasons, set) { if (set.first() == "其他" && etInput.text.isNullOrEmpty()) { tvOperation.setTextColor( ContextCompat.getColor( context, com.xw.common.R.color.gray_6 ) ) } else { tvOperation.setTextColor( ContextCompat.getColor( context, com.xw.common.R.color.blue_5 ) ) } } etInput.addTextChangedListener { if ((it?.length ?: 0) >= 200) { ToastUtilCommon.showSuc("最多输入200字") } } tvOperation.setOnClickListener { if (set.isEmpty()) { ToastUtilCommon.showSuc("请选择原因") return@setOnClickListener } if (set.first() == "其他" && etInput.text.isNullOrEmpty()) { ToastUtilCommon.showSuc("请输入改派原因") return@setOnClickListener } submitBlock.invoke(reasons.indexOf(set.first()), etInput.text?.toString() ?: "") dismiss() } } dialog.show() dialog.window?.setGravity(Gravity.CENTER) dialog.window?.setWindowAnimations(com.xw.common.R.style.AnimLeft) } //填充标签 private fun fillTag( flexboxLayout: FlexboxLayout, reasons: List<String>, set: MutableSet<String>, refreshListener: () -> Unit, ) { flexboxLayout.removeAllViews() reasons.map { it -> getTagItemView(flexboxLayout.context, it, set.contains(it)) { if (set.contains(it)) { set.remove(it) } else { set.clear() set.add(it) } refreshListener.invoke() fillTag(flexboxLayout, reasons, set, refreshListener) } }.forEach { flexboxLayout.addView(it) } } //获取标签item控件 private fun getTagItemView( context: Context, tag: String, isChecked: Boolean, clickListener: (tag: String) -> Unit ): TextView { return TextView(context).apply { layoutParams = FlexboxLayout.LayoutParams( FlexboxLayout.LayoutParams.WRAP_CONTENT, FlexboxLayout.LayoutParams.WRAP_CONTENT ).apply { topMargin = SmartUtil.dp2px(4f) bottomMargin = SmartUtil.dp2px(4f) leftMargin = SmartUtil.dp2px(4f) rightMargin = SmartUtil.dp2px(4f) } setPadding( SmartUtil.dp2px(8f), SmartUtil.dp2px(6f), SmartUtil.dp2px(8f), SmartUtil.dp2px(6f) ) text = tag setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) setTextColor( ContextCompat.getColor( context, if (isChecked) com.xw.common.R.color.blue_5 else com.xw.common.R.color.gray_9 ) ) gravity = Gravity.CENTER setBackgroundResource(if (isChecked) R.drawable.shape_blue_1_bg_4 else R.drawable.shape_gray_3_bg_4) setOnClickListener { clickListener.invoke(tag) } } } } app/src/main/java/com/xw/yunwei/ui/task/accident/detail/AccidentDetailVM.kt
@@ -1,10 +1,10 @@ package com.xw.yunwei.ui.task.accident.detail import android.app.Application import android.content.Context import android.os.Handler import android.os.Looper import android.os.Message import com.xw.common.utils.LogUtils import android.view.View import androidx.core.os.bundleOf import androidx.lifecycle.MutableLiveData @@ -13,13 +13,16 @@ import com.amap.api.maps.model.LatLng import com.xw.common.base.BaseViewModel import com.xw.common.utils.CalculatorLineDistance import com.xw.common.utils.LogUtils import com.xw.common.utils.ToastUtilCommon import com.xw.yunwei.Constants import com.xw.yunwei.MyApp import com.xw.yunwei.R import com.xw.yunwei.model.TaskAccidentModel import com.xw.yunwei.net.TaskRepository import com.xw.yunwei.net.UserRepository import com.xw.yunwei.net.WorkRepository import com.xw.yunwei.ui.dialog.ApplyForReassignReasonDialog import com.xw.yunwei.ui.task.accident.handle.AccidentProcessActivity import com.xw.yunwei.ui.task.list.TaskListAdapter.Companion.MSG_COUNTDOWN import org.koin.core.component.inject @@ -27,7 +30,6 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit import kotlin.getValue import kotlin.math.max @@ -39,11 +41,11 @@ var taskId = 0L val taskDetail: MutableLiveData<TaskAccidentModel> = MutableLiveData() val title = taskDetail.map { if(it.status == 0) "待到达" else if(it.status == 1) "待处理" else "" if (it.status == 0) "待到达" else if (it.status == 1) "待处理" else "" } val levelId = taskDetail.map { LogUtils.e("zmc-L", "taskDetail ${it.taskUrgencyLevel}" ) when(it.taskUrgencyLevel){ LogUtils.e("zmc-L", "taskDetail ${it.taskUrgencyLevel}") when (it.taskUrgencyLevel) { 0 -> R.mipmap.ic_accident_one 1 -> R.mipmap.ic_accident_two 2 -> R.mipmap.ic_accident_three @@ -66,7 +68,7 @@ val actionPermissions = MutableLiveData(false) // 按钮权限 val actionName = taskDetail.map { if(it.status == 0) "到达现场" else if(it.status == 1) "事故处理" else "" if (it.status == 0) "到达现场" else if (it.status == 1) "事故处理" else "" } var canConfirmArrive = MutableLiveData(false) // 是否能提交到达操作 var location: AMapLocation? = null @@ -93,7 +95,7 @@ } } fun confirmArrive(){ fun confirmArrive() { launchRequest("操作中...") { taskRepository.confirmArrive("${taskDetail.value!!.id}") queryDetail() @@ -113,7 +115,7 @@ } } private fun handleAction(){ private fun handleAction() { when (taskDetail.value?.status) { 0 -> { if (canConfirmArrive.value == true) { @@ -139,13 +141,18 @@ val lat = location?.latitude ?: 0.0 val lng = location?.longitude ?: 0.0 if (lat != 0.0 && lng != 0.0 && (it.vehicleLat ?: 0.0) != 0.0 && (it.vehicleLon ?: 0.0) != 0.0) { if (lat != 0.0 && lng != 0.0 && (it.vehicleLat ?: 0.0) != 0.0 && (it.vehicleLon ?: 0.0) != 0.0 ) { val dis = CalculatorLineDistance.calculate( LatLng(lat, lng), LatLng(it.vehicleLat!!, it.vehicleLon!!) ) canConfirmArrive.postValue((it.canArriveRange ?: 0) > dis.toInt()) LogUtils.i("zmc-test-detail","${it.plateNumber} dis = $dis canArriveRange = ${it.canArriveRange} canClick = ${(it.canArriveRange ?: 0) > dis.toInt()}") LogUtils.i( "zmc-test-detail", "${it.plateNumber} dis = $dis canArriveRange = ${it.canArriveRange} canClick = ${(it.canArriveRange ?: 0) > dis.toInt()}" ) } } else { @@ -176,7 +183,7 @@ return listOf(hours, minutes, seconds) } } catch (e: Exception) { LogUtils.e(TAG, "calculateTimeDifference: $e", ) LogUtils.e(TAG, "calculateTimeDifference: $e") } return listOf(0L, 0L, 0L) } @@ -232,4 +239,14 @@ } } } //显示改派弹窗 fun showApplyForReassignDialog(context: Context) { ApplyForReassignReasonDialog.show( context, Constants.reasons, ) { position, input -> } } } app/src/main/java/com/xw/yunwei/ui/task/normal/NormalTaskDetailVM.kt
@@ -1,6 +1,7 @@ package com.xw.yunwei.ui.task.normal import android.app.Application import android.content.Context import android.util.Log import android.view.View import androidx.core.os.bundleOf @@ -13,6 +14,7 @@ import com.xw.common.base.ImageEventListener import com.xw.common.base.ImageSelectAdapter import com.xw.common.utils.ToastUtilCommon import com.xw.yunwei.Constants import com.xw.yunwei.Constants.user import com.xw.yunwei.R import com.xw.yunwei.model.TaskNormalChargeModel @@ -21,9 +23,10 @@ import com.xw.yunwei.model.TaskNormalMaintainModel import com.xw.yunwei.model.TaskNormalModel import com.xw.yunwei.model.TaskNormalRepairModel import com.xw.yunwei.net.CommonRepository import com.xw.yunwei.net.TaskRepository import com.xw.yunwei.net.WorkRepository import com.xw.yunwei.net.CommonRepository import com.xw.yunwei.ui.dialog.ApplyForReassignReasonDialog import com.xw.yunwei.ui.work.car.detail.StandardDescriptionActivity import com.xw.yunwei.ui.work.car.detail.operations.charge.ChargeCarActivity import com.xw.yunwei.ui.work.car.detail.operations.check.CheckCarActivity @@ -34,7 +37,6 @@ import okhttp3.RequestBody.Companion.asRequestBody import org.koin.core.component.inject import java.io.File import kotlin.getValue class NormalTaskDetailVM(application: Application) : BaseViewModel(application) { val taskRepository: TaskRepository by inject() @@ -46,16 +48,16 @@ var taskId = 0L val taskDetail: MutableLiveData<TaskNormalModel> = MutableLiveData() val title = taskDetail.map { if(it.status == 1) "待处理" else if(it.status == 2) "已处理" else "" if (it.status == 1) "待处理" else if (it.status == 2) "已处理" else "" } val levelId = taskDetail.map { when(it.taskUrgencyLevel){ 0 -> R.mipmap.ic_accident_one 1 -> R.mipmap.ic_accident_two 2 -> R.mipmap.ic_accident_three 3 -> R.mipmap.ic_accident_four else -> R.mipmap.ic_accident_one } when (it.taskUrgencyLevel) { 0 -> R.mipmap.ic_accident_one 1 -> R.mipmap.ic_accident_two 2 -> R.mipmap.ic_accident_three 3 -> R.mipmap.ic_accident_four else -> R.mipmap.ic_accident_one } } val btnDoingAction = MutableLiveData("") val showDoingBtn = MutableLiveData(true) // 显示按钮 @@ -107,7 +109,7 @@ fun queryDetail() { launchRequest { Log.e(TAG, "queryDetail: $taskId", ) Log.e(TAG, "queryDetail: $taskId") taskRepository.vehicleRoutineDetail("$taskId")?.let { runOnMainThread { if (taskDetail.value == null || taskDetail.value!!.status != it.status) { @@ -155,21 +157,25 @@ checkModel.postValue(it) } } "2" -> { // 充电 taskRepository.chargingDetail("$taskId")?.let { chargeModel.postValue(it) } } "3" -> { // 整备 taskRepository.preparationDetail("$taskId")?.let { maintainModel.postValue(it) } } "4" -> { // 洗车 taskRepository.cleanDetail("$taskId")?.let { cleanModel.postValue(it) } } "5" -> { // 维修 taskRepository.repairDetail("$taskId")?.let { repairModel.postValue(it) @@ -180,7 +186,7 @@ } } private fun submit(){ private fun submit() { taskDetail.value?.let { when (it.taskTypeId) { "1" -> { // 车辆检测 @@ -189,28 +195,33 @@ bundleOf("carId" to (it.vehicleId ?: 0L), "taskId" to (it.id ?: 0L)) ) } "2" -> { // 开始充电 startActivity( ChargeCarActivity::class.java, bundleOf("carId" to (it.vehicleId ?: 0L), "taskId" to (it.id ?: 0L)) ) } "3" -> { // 开始整备 startActivity( MaintainCarActivity::class.java, bundleOf("carId" to (it.vehicleId ?: 0L), "taskId" to (it.id ?: 0L)) ) } "4" -> { // 完成洗车任务 cleanCar() } "5" -> { // 去送修/取回 startActivity( RepairCarActivity::class.java, bundleOf("carId" to (it.vehicleId ?: 0L), "taskId" to (it.id ?: 0L)) ) } "6","7","8","9" -> { // 完成保养 完成续保 完成年检 车辆运营资质到期 "6", "7", "8", "9" -> { // 完成保养 完成续保 完成年检 车辆运营资质到期 saveBy() } } @@ -245,7 +256,7 @@ } /*完成保养/保险/年检*/ private fun saveBy(){ private fun saveBy() { if (taskDescription.value.isNullOrEmpty()) { ToastUtilCommon.showError("请录入完成任务情况说明") return @@ -295,4 +306,14 @@ } } } //显示改派弹窗 fun showApplyForReassignDialog(context: Context) { ApplyForReassignReasonDialog.show( context, Constants.reasons, ) { position, input -> } } } app/src/main/java/com/xw/yunwei/ui/user/login/LoginActivity.kt
@@ -28,11 +28,12 @@ override fun init() { super.init() if (Constants.token.isNotEmpty()) { MyApp.myApp.connectWebSocket() startActivity(MainActivity::class.java) finish() } else { MyApp.myApp.disconnectWebSocket() MyApp.myApp.unbindPushAccount() val tab1 = binding.tabLayout.newTab().apply { text = "验证码登录" } app/src/main/java/com/xw/yunwei/ui/user/login/LoginVM.kt
@@ -9,6 +9,7 @@ import com.xw.common.utils.KeyPreferences import com.xw.common.utils.MMKVManager import com.xw.common.utils.ToastUtilCommon import com.xw.yunwei.MyApp import com.xw.yunwei.R import com.xw.yunwei.model.AgreeInfo import com.xw.yunwei.net.UserRepository @@ -77,6 +78,7 @@ data?.let { Constants.token = it.token?.access_token ?: "" MMKVManager.getInstance().saveModel(KeyPreferences.USER, it.info?.xpOperationsUser) MyApp.myApp.connectWebSocket() startActivity(MainActivity::class.java) finish() } @@ -104,6 +106,7 @@ Constants.token = it.token?.access_token ?: "" MMKVManager.getInstance() .saveModel(KeyPreferences.USER, it.info?.xpOperationsUser) MyApp.myApp.connectWebSocket() startActivity(MainActivity::class.java) finish() } app/src/main/java/com/xw/yunwei/ui/user/mine/MineFragment.kt
@@ -24,6 +24,7 @@ super.onResume() viewModel.getInfo() viewModel.querySchedule() viewModel.getMyCruiseCheckInMissionInfo() } @SuppressLint("UseKtx") @@ -48,7 +49,9 @@ binding.nav.setOnClickListener { viewModel.clockModel.value?.let { if ((it.xpCheckLocation?.locationLat ?: 0.0) != 0.0 && (it.xpCheckLocation?.locationLon ?: 0.0) != 0.0) { if ((it.xpCheckLocation?.locationLat ?: 0.0) != 0.0 && (it.xpCheckLocation?.locationLon ?: 0.0) != 0.0 ) { navigate( end = LatLng( it.xpCheckLocation?.locationLat!!, @@ -73,11 +76,13 @@ binding.status.setTextColor(getResColor(com.xw.common.R.color.orange_5)) binding.status.setBackgroundResource(R.drawable.shape_orange_1_bg_4) } 2 -> { binding.status.text = "上班中" binding.status.setTextColor(getResColor(com.xw.common.R.color.green_5)) binding.status.setBackgroundResource(R.drawable.shape_green_1_bg_4) } 3 -> { binding.status.text = "休息" binding.status.setTextColor(getResColor(com.xw.common.R.color.gray_9)) @@ -85,6 +90,14 @@ } } } viewModel.cruiseCheckInMissionInfo.observe(this) { binding.tvMissionTime.text = viewModel.getFormateCruiseCheckInMissionTime() binding.cpvProgress.setProgress(viewModel.getCruiseCheckInProgress(), true) binding.tvMissionPointCount.text = it.checkInPointCount.toString() binding.tvHadReportNum.text = it.checkInPointFinishCount.toString() binding.tvNotReportNum.text = it.checkInPointUnFinishCount.toString() } } } app/src/main/java/com/xw/yunwei/ui/user/mine/MineVM.kt
@@ -14,22 +14,26 @@ import com.xw.common.viewAdapter.ImageDefault import com.xw.yunwei.R import com.xw.yunwei.model.ClockInStatusModel import com.xw.yunwei.model.CruiseCheckInMissionInfo import com.xw.yunwei.model.UserInfo import com.xw.yunwei.net.UserRepository import com.xw.yunwei.net.WorkRepository import com.xw.yunwei.ui.user.mine.checkin.CheckInActivity import com.xw.yunwei.ui.user.mine.info.UserInfoActivity import com.xw.yunwei.ui.user.mine.scheduling.SchedulingActivity import com.xw.yunwei.ui.user.mine.set.SetActivity import com.xw.yunwei.ui.user.mine.upload.CertUploadActivity import com.xw.yunwei.ui.work.checkInMission.CheckInMissionPointsListActivity import org.json.JSONObject import org.koin.core.component.inject import kotlin.getValue class MineVM(application: Application) : BaseViewModel(application) { val userRepository: UserRepository by inject() val workRepository: WorkRepository by inject() val userInfo = MutableLiveData(MMKVManager.getInstance().getModel<UserInfo>(KeyPreferences.USER)) val userInfo = MutableLiveData(MMKVManager.getInstance().getModel<UserInfo>(KeyPreferences.USER)) val avatar = MutableLiveData(ImageDefault("", com.xw.common.R.drawable.svg_avatar)) @@ -43,6 +47,9 @@ } val workFlag = MutableLiveData(0) //巡游打卡任务 val cruiseCheckInMissionInfo = MutableLiveData<CruiseCheckInMissionInfo>() var phone = "" // 客服电话 @@ -52,8 +59,12 @@ R.id.uploadLayout -> startActivity(CertUploadActivity::class.java) R.id.setLayout -> startActivity(SetActivity::class.java) R.id.schedulingLayout -> startActivity(SchedulingActivity::class.java) R.id.goCheck -> startActivity(CheckInActivity::class.java, bundleOf("clockModel" to clockModel.value)) R.id.goCheck -> startActivity( CheckInActivity::class.java, bundleOf("clockModel" to clockModel.value) ) R.id.ll_cruise_check_in_mission -> startActivity(CheckInMissionPointsListActivity::class.java) } } @@ -64,7 +75,7 @@ flowBlock = { userRepository.getUserInfo() }, onCollect = { avatar.postValue(avatar.value!!.apply { this.url = it?.avatar?.getUrl() ?: "" }) workFlag.postValue(it?.workFlag?:0) workFlag.postValue(it?.workFlag ?: 0) roleName.postValue(it?.roles?.getOrNull(0)?.roleName ?: "未分配") fleetLeaderName.postValue(it?.fleet?.fleetLeaderName ?: "无") fleetName.postValue(it?.fleet?.fleetName ?: "无") @@ -74,9 +85,10 @@ fun querySchedule() { launchRequest(showLoading = clockModel.value == null) { userRepository.querySchedule(mapOf("schedulingDate" to getTime(System.currentTimeMillis())))?.let { clockModel.postValue(it) } userRepository.querySchedule(mapOf("schedulingDate" to getTime(System.currentTimeMillis()))) ?.let { clockModel.postValue(it) } } } @@ -93,4 +105,22 @@ } } //获取个人中心巡游打卡任务 fun getMyCruiseCheckInMissionInfo() { launchRequest { workRepository.getMyCruiseCheckInMissionInfo()?.also { cruiseCheckInMissionInfo.postValue(it) } } } //获取个人中心巡游打卡任务时间 fun getFormateCruiseCheckInMissionTime(): String { return cruiseCheckInMissionInfo.value?.cruiseCheckInTaskTime ?: "" } //获取巡游打卡进度 fun getCruiseCheckInProgress(): Float { return cruiseCheckInMissionInfo.value?.getFinishProgress() ?: 0f } } app/src/main/java/com/xw/yunwei/ui/user/mine/set/SetActivity.kt
@@ -3,7 +3,8 @@ import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.net.Uri import android.provider.Settings import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope @@ -17,11 +18,10 @@ import com.xw.common.utils.MMKVManager import com.xw.common.utils.ToastUtilCommon import com.xw.yunwei.BR import com.xw.yunwei.MyApp import com.xw.yunwei.databinding.ActivitySetBinding import kotlinx.coroutines.delay import kotlinx.coroutines.launch import android.net.Uri import android.provider.Settings /** * 设置 @@ -58,11 +58,19 @@ if (binding.locationTips.text == "去授权") { val pl = arrayOf(Permission.ACCESS_FINE_LOCATION, Permission.ACCESS_COARSE_LOCATION) XXPermissions.with(this).permission(pl).request(object : OnPermissionCallback { override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) { // 权限同意 if(allGranted){ binding.locationTips.text = "已开启" } override fun onGranted( permissions: MutableList<String>, allGranted: Boolean ) { // 权限同意 if (allGranted) { binding.locationTips.text = "已开启" } } override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) { override fun onDenied( permissions: MutableList<String>, doNotAskAgain: Boolean ) { super.onDenied(permissions, doNotAskAgain) //权限禁止 } @@ -100,6 +108,7 @@ binding.logOut.setOnClickListener { CommonDialog.showTipsDialog(this, "确认退出当前账号登录吗?", rightBlock = { MMKVManager.getInstance().clear() MyApp.myApp.disconnectWebSocket() lifecycleScope.launch { delay(100) AppUtils.relaunchApp(true) @@ -115,8 +124,13 @@ binding.switchNotification.isChecked = isEnabled } private fun checkLocation(){ val result = checkPermission(listOf(Permission.ACCESS_FINE_LOCATION, Permission.ACCESS_COARSE_LOCATION)) private fun checkLocation() { val result = checkPermission( listOf( Permission.ACCESS_FINE_LOCATION, Permission.ACCESS_COARSE_LOCATION ) ) if (result) { binding.locationTips.text = "已开启" } else { @@ -124,10 +138,14 @@ } } private fun checkPermission(permission: List<String>): Boolean{ private fun checkPermission(permission: List<String>): Boolean { val permissionList: MutableList<String> = ArrayList() for (permission in permission) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission( this, permission ) != PackageManager.PERMISSION_GRANTED ) { permissionList.add(permission) } } app/src/main/java/com/xw/yunwei/ui/widget/CircleProgressView.kt
New file @@ -0,0 +1,219 @@ package com.xw.yunwei.ui.widget import android.animation.ValueAnimator import android.content.Context import android.graphics.* import android.util.AttributeSet import android.view.View import android.view.animation.AccelerateDecelerateInterpolator import com.xw.yunwei.R import androidx.core.graphics.toColorInt class CircularProgressView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { // 画笔 private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG) // 颜色 var backgroundProgressColor: Int = "#FF9800".toColorInt() set(value) { field = value backgroundPaint.color = value invalidate() } var progressColor: Int = "#4CAF50".toColorInt() set(value) { field = value progressPaint.color = value invalidate() } // 尺寸 var ringWidth: Float = 40f set(value) { field = value backgroundPaint.strokeWidth = value progressPaint.strokeWidth = value requestLayout() } // 进度 var progress: Float = 0f set(value) { field = value.coerceIn(0f, maxProgress) animateProgress() } var maxProgress: Float = 100f set(value) { field = value invalidate() } // 动画进度 private var animatedProgress: Float = 0f private var progressAnimator: ValueAnimator? = null // 坐标 private var centerX: Float = 0f private var centerY: Float = 0f private var radius: Float = 0f init { setupPaints() setupAttributes(attrs) } private fun setupAttributes(attrs: AttributeSet?) { attrs?.let { val typedArray = context.obtainStyledAttributes(it, R.styleable.CircularProgressView) try { ringWidth = typedArray.getDimension( R.styleable.CircularProgressView_ringWidth, ringWidth ) backgroundProgressColor = typedArray.getColor( R.styleable.CircularProgressView_backgroundProgressColor, backgroundProgressColor ) progressColor = typedArray.getColor( R.styleable.CircularProgressView_progressColor, progressColor ) progress = typedArray.getFloat( R.styleable.CircularProgressView_progress, progress ) maxProgress = typedArray.getFloat( R.styleable.CircularProgressView_maxProgress, maxProgress ) } finally { typedArray.recycle() } } } private fun setupPaints() { // 背景环画笔 backgroundPaint.style = Paint.Style.STROKE backgroundPaint.strokeCap = Paint.Cap.ROUND backgroundPaint.strokeWidth = ringWidth backgroundPaint.color = backgroundProgressColor // 进度环画笔 progressPaint.style = Paint.Style.STROKE progressPaint.strokeCap = Paint.Cap.ROUND progressPaint.strokeWidth = ringWidth progressPaint.color = progressColor } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) // 计算中心点和半径 centerX = w / 2f centerY = h / 2f radius = minOf(w, h) / 2f - ringWidth / 2f // 更新画笔宽度 backgroundPaint.strokeWidth = ringWidth progressPaint.strokeWidth = ringWidth } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // 绘制背景环 canvas.drawCircle(centerX, centerY, radius, backgroundPaint) // 绘制进度环 if (animatedProgress > 0) { val sweepAngle = 360f * (animatedProgress / maxProgress) val rect = RectF( centerX - radius, centerY - radius, centerX + radius, centerY + radius ) canvas.drawArc( rect, -90f, // 从顶部开始 -sweepAngle, false, progressPaint ) } } /** * 设置进度 * @param progress 进度值 * @param animate 是否开启动画 */ fun setProgress(progress: Float, animate: Boolean = true) { this.progress = progress.coerceIn(0f, maxProgress) if (animate) { animateProgress() } else { animatedProgress = this.progress invalidate() } } /** * 进度动画 */ private fun animateProgress() { progressAnimator?.cancel() progressAnimator = ValueAnimator.ofFloat(animatedProgress, progress).apply { duration = 800 interpolator = AccelerateDecelerateInterpolator() addUpdateListener { animation -> animatedProgress = animation.animatedValue as Float invalidate() } start() } } /** * 获取当前动画进度 */ fun getAnimatedProgress(): Float = animatedProgress /** * 重置进度 */ fun resetProgress(animate: Boolean = true) { setProgress(0f, animate) } /** * 设置进度到最大值 */ fun setProgressToMax(animate: Boolean = true) { setProgress(maxProgress, animate) } /** * 释放资源 */ fun release() { progressAnimator?.cancel() } } app/src/main/java/com/xw/yunwei/ui/work/checkInMission/CheckInMissionPointsListActivity.kt
New file @@ -0,0 +1,14 @@ package com.xw.yunwei.ui.work.checkInMission import com.xw.common.base.BaseActivity import com.xw.yunwei.BR import com.xw.yunwei.ui.work.checkInMission.viewmodel.CheckInMissionPointsListViewModel class CheckInMissionPointsListActivity : BaseActivity<ActivityCheckInMissionPointsListBinding, CheckInMissionPointsListViewModel>() { override fun getBindingVariable() = BR.viewModel } app/src/main/java/com/xw/yunwei/ui/work/checkInMission/adapter/CheckInMissionPointAdapter.kt
New file @@ -0,0 +1,16 @@ package com.xw.yunwei.ui.work.checkInMission.adapter import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.viewholder.BaseViewHolder import com.xw.yunwei.R import com.xw.yunwei.model.CheckInMissionPointInfo //巡游任务点适配器 class CheckInMissionPointAdapter():BaseQuickAdapter<CheckInMissionPointInfo, BaseViewHolder>(R.layout.item_check_in_mission_point) { override fun convert( holder: BaseViewHolder, item: CheckInMissionPointInfo ) { TODO("Not yet implemented") } } app/src/main/java/com/xw/yunwei/ui/work/checkInMission/viewmodel/CheckInMissionPointsListViewModel.kt
New file @@ -0,0 +1,7 @@ package com.xw.yunwei.ui.work.checkInMission.viewmodel import android.app.Application import com.xw.common.base.BaseViewModel class CheckInMissionPointsListViewModel(application: Application): BaseViewModel(application) { } app/src/main/java/com/xw/yunwei/websocket/IWebSocketListener.kt
New file @@ -0,0 +1,54 @@ package com.xw.yunwei.websocket import okio.ByteString interface IWebSocketListener { /** * WebSocket连接成功 */ fun onConnected() /** * WebSocket连接失败 * @param t 异常信息 */ fun onConnectFailed(t: Throwable) /** * 接收到消息 * @param text 文本消息 */ fun onMessage(text: String) /** * 接收到二进制消息 * @param bytes 二进制数据 */ fun onMessage(bytes: ByteString) /** * 连接断开 * @param code 断开码 * @param reason 断开原因 */ fun onDisconnected(code: Int, reason: String) /** * 网络状态变化 * @param isAvailable 网络是否可用 */ fun onNetworkStateChanged(isAvailable: Boolean) /** * 发送消息成功 * @param message 消息内容 */ fun onSendMessageSuccess(message: String) /** * 发送消息失败 * @param message 消息内容 * @param t 异常 */ fun onSendMessageFailed(message: String, t: Throwable) } app/src/main/java/com/xw/yunwei/websocket/WebSocketClient.kt
New file @@ -0,0 +1,412 @@ package com.xw.yunwei.websocket import com.xw.common.utils.LogUtils import com.xw.yunwei.BuildConfig import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener import okhttp3.logging.HttpLoggingInterceptor import okio.ByteString import org.json.JSONObject import java.util.LinkedList import java.util.concurrent.TimeUnit //WebSocket客户端 class WebSocketClient( private val config: WebSocketConfig = WebSocketConfig ) { private var webSocket: WebSocket? = null private var okHttpClient: OkHttpClient? = null private var isConnecting = false private var isConnected = false private var reconnectCount = 0 private val ioCoroutineScope = CoroutineScope(Dispatchers.IO) // 监听器列表 private val listeners = mutableListOf<IWebSocketListener>() // 心跳相关 private var heartbeatJob: Job? = null // 消息队列(连接失败时缓存) private val messageQueue = LinkedList<String>() private val maxQueueSize = 50 companion object { private const val TAG = "WebSocketClient" } /** * 初始化WebSocket客户端 */ private fun initClient(): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(config.WS_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(config.WS_TIMEOUT, TimeUnit.SECONDS) .readTimeout(config.WS_TIMEOUT, TimeUnit.SECONDS) .pingInterval(config.PING_INTERVAL, TimeUnit.SECONDS) // 自动Ping .retryOnConnectionFailure(true) .addInterceptor(HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) { HttpLoggingInterceptor.Level.BODY } else { HttpLoggingInterceptor.Level.NONE } }) .addInterceptor { chain -> val original = chain.request() val request = original.newBuilder() .header("User-Agent", "Android-App/${BuildConfig.VERSION_NAME}") .header("Connection", "Upgrade") .header("Upgrade", "websocket") .header("Sec-WebSocket-Version", "13") .apply { // 添加认证token val token = getAuthToken() if (token.isNotBlank()) { header("Authorization", "Bearer $token") } } .build() chain.proceed(request) } .build() } /** * 连接WebSocket */ fun connect(isFirstConnect: Boolean) { if (isConnecting || isConnected) { LogUtils.d(TAG, "WebSocket正在连接或已连接") return } isConnecting = true reconnectCount = 0 ioCoroutineScope.launch { try { val request = Request.Builder() .url(config.WS_URL) .build() okHttpClient = initClient() okHttpClient?.newWebSocket(request, createWebSocketListener()) LogUtils.i(TAG, "开始连接WebSocket: ${config.WS_URL}") } catch (e: Exception) { isConnecting = false LogUtils.e(TAG, "连接WebSocket失败: ${e.message}", e) notifyConnectFailed(e) } } } /** * 创建WebSocket监听器 */ private fun createWebSocketListener(): WebSocketListener { return object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { LogUtils.i(TAG, "WebSocket连接成功") isConnecting = false isConnected = true reconnectCount = 0 this@WebSocketClient.webSocket = webSocket // 启动心跳 startHeartbeat() // 发送积压的消息 flushMessageQueue() // 通知监听器 notifyConnected() } override fun onMessage(webSocket: WebSocket, text: String) { LogUtils.d(TAG, "收到文本消息: $text") handleReceivedMessage(text) } override fun onMessage(webSocket: WebSocket, bytes: ByteString) { LogUtils.d(TAG, "收到二进制消息, 长度: ${bytes.size}") handleReceivedMessage(bytes) } override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { LogUtils.w(TAG, "WebSocket正在关闭: code=$code, reason=$reason") isConnected = false stopHeartbeat() } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { LogUtils.w(TAG, "WebSocket已关闭: code=$code, reason=$reason") isConnected = false stopHeartbeat() notifyDisconnected(code, reason) // 根据关闭码决定是否重连 if (shouldReconnect(code)) { scheduleReconnect() } } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { LogUtils.e(TAG, "WebSocket连接失败: ${t.message}") isConnecting = false isConnected = false stopHeartbeat() notifyConnectFailed(t) // 失败时尝试重连 if (reconnectCount < config.MAX_RECONNECT_COUNT) { scheduleReconnect() } } } } /** * 发送消息 */ fun sendMessage(message: String): Boolean { return if (isConnected) { try { webSocket?.send(message) LogUtils.d(TAG, "发送消息成功: $message") notifySendMessageSuccess(message) true } catch (e: Exception) { LogUtils.e(TAG, "发送消息失败: ${e.message}", e) cacheMessage(message) notifySendMessageFailed(message, e) false } } else { LogUtils.w(TAG, "WebSocket未连接,缓存消息: $message") cacheMessage(message) false } } /** * 发送JSON消息 */ fun sendJsonMessage(type: String, data: Any = emptyMap<String, Any>()): Boolean { val json = try { val jsonObject = JSONObject().apply { put("type", type) put("data", JSONObject(data.toString())) put("timestamp", System.currentTimeMillis()) } jsonObject.toString() } catch (e: Exception) { LogUtils.e(TAG, "构建JSON失败: ${e.message}", e) return false } return sendMessage(json) } /** * 断开连接 */ fun disconnect(code: Int = 1000, reason: String = "normal closure") { LogUtils.i(TAG, "主动断开WebSocket连接") stopHeartbeat() isConnecting = false isConnected = false reconnectCount = config.MAX_RECONNECT_COUNT // 阻止重连 webSocket?.close(code, reason) webSocket = null try { okHttpClient?.dispatcher?.executorService?.shutdown() okHttpClient?.connectionPool?.evictAll() } catch (e: Exception) { LogUtils.e(TAG, "清理资源失败: ${e.message}", e) } } /** * 重新连接 */ fun reconnect() { if (isConnecting) { LogUtils.d(TAG, "已经在重连中...") return } disconnect() LogUtils.d(TAG, "开始重连...") ioCoroutineScope.launch { delay(1000) // 延迟1秒重连 connect(false) } } /** * 添加监听器 */ fun addListener(listener: IWebSocketListener) { if (!listeners.contains(listener)) { listeners.add(listener) } } /** * 移除监听器 */ fun removeListener(listener: IWebSocketListener) { listeners.remove(listener) } /** * 清除所有监听器 */ fun clearListeners() { listeners.clear() } /** * 获取连接状态 */ fun getConnectionState(): ConnectionState { return when { isConnected -> ConnectionState.CONNECTED isConnecting -> ConnectionState.CONNECTING else -> ConnectionState.DISCONNECTED } } // 私有方法 private fun startHeartbeat() { stopHeartbeat() heartbeatJob = ioCoroutineScope.launch { while (isActive && isConnected) { delay(config.PING_INTERVAL * 1000) if (isConnected) { sendJsonMessage(WebSocketConfig.MessageType.HEARTBEAT) } } } } private fun stopHeartbeat() { heartbeatJob?.cancel() heartbeatJob = null } private fun scheduleReconnect() { if (reconnectCount >= config.MAX_RECONNECT_COUNT) { LogUtils.w(TAG, "已达到最大重连次数: ${config.MAX_RECONNECT_COUNT}") return } reconnectCount++ val delay = config.RECONNECT_DELAY * reconnectCount // 退避策略 LogUtils.i(TAG, "计划 ${reconnectCount} 秒后重连") ioCoroutineScope.launch { delay(delay * 1000) if (!isConnected && !isConnecting) { connect(false) } } } private fun cacheMessage(message: String) { synchronized(messageQueue) { if (messageQueue.size >= maxQueueSize) { messageQueue.poll() // 移除最旧的消息 } messageQueue.offer(message) } } private fun flushMessageQueue() { synchronized(messageQueue) { while (messageQueue.isNotEmpty()) { messageQueue.poll()?.also { sendMessage(it) } } } } private fun handleReceivedMessage(text: String) { try { val json = JSONObject(text) val type = json.optString("type") when (type) { "pong" -> { // 心跳响应 LogUtils.d(TAG, "收到心跳响应") } else -> { listeners.forEach { it.onMessage(text) } } } } catch (e: Exception) { LogUtils.e(TAG, "解析消息失败: ${e.message}", e) listeners.forEach { it.onMessage(text) } } } private fun handleReceivedMessage(bytes: ByteString) { listeners.forEach { it.onMessage(bytes) } } private fun notifyConnected() { listeners.forEach { it.onConnected() } } private fun notifyConnectFailed(t: Throwable) { listeners.forEach { it.onConnectFailed(t) } } private fun notifyDisconnected(code: Int, reason: String) { listeners.forEach { it.onDisconnected(code, reason) } } private fun notifySendMessageSuccess(message: String) { listeners.forEach { it.onSendMessageSuccess(message) } } private fun notifySendMessageFailed(message: String, t: Throwable) { listeners.forEach { it.onSendMessageFailed(message, t) } } private fun shouldReconnect(code: Int): Boolean { // 正常关闭不重连 return false } private fun getAuthToken(): String { // 从SharedPreferences或安全存储获取 return "" } enum class ConnectionState { CONNECTING, CONNECTED, DISCONNECTED } } app/src/main/java/com/xw/yunwei/websocket/WebSocketConfig.kt
New file @@ -0,0 +1,21 @@ package com.xw.yunwei.websocket import com.xw.yunwei.BuildConfig // WebSocket配置 object WebSocketConfig { const val WS_URL = BuildConfig.WS_URL const val WS_TIMEOUT = 10L // 连接超时(秒) const val PING_INTERVAL = 25L // 心跳间隔(秒) const val RECONNECT_DELAY = 3L // 重连延迟(秒) const val MAX_RECONNECT_COUNT = Int.MAX_VALUE // 最大重连次数 // 消息类型 object MessageType { const val CONNECT = "connect" const val HEARTBEAT = "heartbeat" const val MESSAGE = "message" const val ERROR = "error" const val CLOSE = "close" } } app/src/main/java/com/xw/yunwei/websocket/WebSocketManager.kt
New file @@ -0,0 +1,237 @@ package com.xw.yunwei.websocket import android.content.Context import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.xw.common.utils.LogUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import okio.ByteString // WebSocket管理器(单例) object WebSocketManager : IWebSocketListener { private const val TAG = "WebSocketManager" private lateinit var webSocketClient: WebSocketClient private var isInitialized = false private var enableConnect = false private val mainCoroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main) // 连接状态LiveData private val _connectionState = MutableLiveData<WebSocketClient.ConnectionState>() val connectionState: LiveData<WebSocketClient.ConnectionState> = _connectionState // 消息LiveData private val _messageEvent = MutableLiveData<Event<String>>() val messageEvent: LiveData<Event<String>> = _messageEvent // 事件Flow private val _eventFlow = MutableSharedFlow<WebSocketEvent>() val eventFlow: SharedFlow<WebSocketEvent> = _eventFlow.asSharedFlow() /** * 初始化 */ fun initialize(context: Context) { if (isInitialized) return webSocketClient = WebSocketClient() webSocketClient.addListener(this) // 监听网络状态 observeNetworkState(context) isInitialized = true LogUtils.i(TAG, "WebSocket管理器初始化完成") } /** * 连接WebSocket */ fun connect() { if (!isInitialized) { LogUtils.e(TAG, "请先调用initialize()初始化") return } enableConnect = true webSocketClient.connect(true) } /** * 断开连接 */ fun disconnect() { if (isInitialized) { enableConnect = false webSocketClient.disconnect() } } /** * 发送消息 */ fun sendMessage(message: String): Boolean { return if (isInitialized) { webSocketClient.sendMessage(message) } else { false } } /** * 发送JSON消息 */ fun sendJson(type: String, data: Map<String, Any> = emptyMap()): Boolean { return if (isInitialized) { webSocketClient.sendJsonMessage(type, data) } else { false } } /** * 重新连接 */ fun reconnect() { if (isInitialized && enableConnect) { webSocketClient.reconnect() } } /** * 获取连接状态 */ fun getConnectionState(): WebSocketClient.ConnectionState { return if (isInitialized) { webSocketClient.getConnectionState() } else { WebSocketClient.ConnectionState.DISCONNECTED } } /** * 清理资源 */ fun release() { if (isInitialized) { webSocketClient.disconnect() webSocketClient.clearListeners() isInitialized = false } } // 实现IWebSocketListener接口 override fun onConnected() { LogUtils.i(TAG, "WebSocket已连接") _connectionState.postValue(WebSocketClient.ConnectionState.CONNECTED) mainCoroutineScope.launch { _eventFlow.emit(WebSocketEvent.Connected) } } override fun onConnectFailed(t: Throwable) { LogUtils.e(TAG, "连接失败: ${t.message}") _connectionState.postValue(WebSocketClient.ConnectionState.DISCONNECTED) mainCoroutineScope.launch { _eventFlow.emit(WebSocketEvent.ConnectFailed(t)) } } override fun onMessage(text: String) { LogUtils.d(TAG, "收到消息: $text") _messageEvent.postValue(Event(text)) mainCoroutineScope.launch { _eventFlow.emit(WebSocketEvent.Message(text)) } } override fun onMessage(bytes: ByteString) { LogUtils.d(TAG, "收到二进制消息, 长度: ${bytes.size}") mainCoroutineScope.launch { _eventFlow.emit(WebSocketEvent.BinaryMessage(bytes)) } } override fun onDisconnected(code: Int, reason: String) { LogUtils.w(TAG, "连接断开: code=$code, reason=$reason") _connectionState.postValue(WebSocketClient.ConnectionState.DISCONNECTED) mainCoroutineScope.launch { _eventFlow.emit(WebSocketEvent.Disconnected(code, reason)) } } override fun onNetworkStateChanged(isAvailable: Boolean) { if (isAvailable && getConnectionState() == WebSocketClient.ConnectionState.DISCONNECTED) { // 网络恢复时重连 reconnect() } } override fun onSendMessageSuccess(message: String) { LogUtils.d(TAG, "发送消息成功: $message") } override fun onSendMessageFailed(message: String, t: Throwable) { LogUtils.e(TAG, "发送消息失败: ${t.message}") } // 网络状态监听 private fun observeNetworkState(context: Context) { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { LogUtils.i(TAG, "网络已连接") onNetworkStateChanged(true) } override fun onLost(network: Network) { LogUtils.w(TAG, "网络已断开") onNetworkStateChanged(false) } } val request = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build() connectivityManager.registerNetworkCallback(request, networkCallback) } // Event包装类(避免LiveData重复触发) class Event<T>(private val content: T) { private var hasBeenHandled = false fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } } // WebSocket事件密封类 sealed class WebSocketEvent { object Connected : WebSocketEvent() data class ConnectFailed(val throwable: Throwable) : WebSocketEvent() data class Message(val text: String) : WebSocketEvent() data class BinaryMessage(val bytes: ByteString) : WebSocketEvent() data class Disconnected(val code: Int, val reason: String) : WebSocketEvent() } } app/src/main/res/drawable/circle_gray_6.xml
New file @@ -0,0 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" android:useLevel="false"> <solid android:color="@color/gray_6" /> </shape> app/src/main/res/drawable/layer_list_green_progress_bar.xml
New file @@ -0,0 +1,20 @@ <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 背景 --> <item android:id="@android:id/background"> <shape> <corners android:radius="@dimen/dp_8" /> <solid android:color="@color/gray_5" /> </shape> </item> <!-- 主进度 --> <item android:id="@android:id/progress"> <clip> <shape> <corners android:radius="@dimen/dp_8" /> <solid android:color="@color/green_5" /> </shape> </clip> </item> </layer-list> app/src/main/res/drawable/layer_list_orange_progress_bar.xml
New file @@ -0,0 +1,20 @@ <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 背景 --> <item android:id="@android:id/background"> <shape> <corners android:radius="@dimen/dp_8" /> <solid android:color="@color/gray_5" /> </shape> </item> <!-- 主进度 --> <item android:id="@android:id/progress"> <clip> <shape> <corners android:radius="@dimen/dp_8" /> <solid android:color="@color/orange_5" /> </shape> </clip> </item> </layer-list> app/src/main/res/drawable/shape_red_5_line_gray_3_bg_4.xml
New file @@ -0,0 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <stroke android:width="@dimen/dp_1" android:color="@color/red_5" /> <corners android:radius="@dimen/dp_4" /> <solid android:color="@color/gray_3" /> </shape> app/src/main/res/layout/activity_check_in_mission_points_list.xml
New file @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.work.checkInMission.CheckInMissionPointsListActivity"> </androidx.constraintlayout.widget.ConstraintLayout> app/src/main/res/layout/dialog_apply_for_reassign.xml
@@ -1,210 +1,234 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout <ImageView android:id="@+id/iv_bg" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/dp_4" android:layout_marginTop="@dimen/dp_4" android:background="@drawable/shape_white_bg_4" android:orientation="vertical" android:paddingBottom="@dimen/dp_20" app:layout_constraintTop_toTopOf="parent"> android:scaleType="fitXY" android:src="@mipmap/ic_apply_reassign_bg" /> <LinearLayout android:id="@+id/countdownLayout" android:layout_width="match_parent" android:layout_height="@dimen/dp_36" android:background="@mipmap/img_task_item_top_bg_blue" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_12"> <ImageView android:id="@+id/iv_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:src="@mipmap/icon_note" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/countdownTips" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:ellipsize="end" android:gravity="end" android:maxLines="1" android:paddingEnd="@dimen/dp_8" android:singleLine="true" android:text="倒计时结束前,务必到达现场" android:textColor="@color/blue_5" android:textSize="@dimen/sp_12" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/dp_16" android:layout_marginTop="@dimen/dp_20" android:text="新任务提示" android:textColor="@color/gray_9" android:textSize="@dimen/sp_16" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/hour" android:layout_width="@dimen/dp_30" android:layout_height="@dimen/dp_23" android:layout_marginStart="@dimen/dp_8" android:background="@drawable/shape_blue_5_bg_4" android:gravity="center" android:text="11" android:textColor="@color/white" android:textSize="@dimen/sp_16" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/colon1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_3" android:layout_marginTop="@dimen/dp_3" android:src="@mipmap/ic_task_item_colon_b" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/minutes" android:layout_width="@dimen/dp_30" android:layout_height="@dimen/dp_23" android:background="@drawable/shape_blue_5_bg_4" android:gravity="center" android:text="33" android:textColor="@color/white" android:textSize="@dimen/sp_16" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/colon2" android:layout_width="@dimen/dp_3" android:layout_height="@dimen/dp_10" android:layout_marginHorizontal="@dimen/dp_3" android:layout_marginTop="@dimen/dp_3" android:src="@mipmap/ic_task_item_colon_b" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/second" android:layout_width="@dimen/dp_30" android:layout_height="@dimen/dp_23" android:background="@drawable/shape_blue_5_bg_4" android:gravity="center" android:text="09" android:textColor="@color/white" android:textSize="@dimen/sp_16" android:textStyle="bold" /> </LinearLayout> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_alignStart="@id/iv_bg" android:layout_alignTop="@id/iv_bg" android:layout_alignEnd="@id/iv_bg" android:layout_marginHorizontal="@dimen/dp_4" android:layout_marginTop="@dimen/dp_60"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_16" android:layout_marginTop="@dimen/dp_20" android:gravity="center_vertical" android:orientation="horizontal"> android:layout_marginStart="@dimen/dp_4" android:layout_marginTop="@dimen/dp_4" android:orientation="vertical" android:paddingBottom="@dimen/dp_20" app:layout_constraintTop_toTopOf="parent"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/type" android:layout_width="wrap_content" <LinearLayout android:id="@+id/countdownLayout" android:layout_width="match_parent" android:layout_height="@dimen/dp_36" android:layout_marginEnd="@dimen/dp_4" android:background="@mipmap/img_task_item_top_bg_blue" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_12"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/countdownTips" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:ellipsize="end" android:gravity="end" android:maxLines="1" android:paddingEnd="@dimen/dp_8" android:singleLine="true" android:text="倒计时结束前,务必到达现场" android:textColor="@color/blue_5" android:textSize="@dimen/sp_12" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/hour" android:layout_width="@dimen/dp_30" android:layout_height="@dimen/dp_23" android:layout_marginStart="@dimen/dp_8" android:background="@drawable/shape_blue_5_bg_4" android:gravity="center" android:text="11" android:textColor="@color/white" android:textSize="@dimen/sp_16" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/colon1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_3" android:layout_marginTop="@dimen/dp_3" android:src="@mipmap/ic_task_item_colon_b" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/minutes" android:layout_width="@dimen/dp_30" android:layout_height="@dimen/dp_23" android:background="@drawable/shape_blue_5_bg_4" android:gravity="center" android:text="33" android:textColor="@color/white" android:textSize="@dimen/sp_16" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/colon2" android:layout_width="@dimen/dp_3" android:layout_height="@dimen/dp_10" android:layout_marginHorizontal="@dimen/dp_3" android:layout_marginTop="@dimen/dp_3" android:src="@mipmap/ic_task_item_colon_b" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/second" android:layout_width="@dimen/dp_30" android:layout_height="@dimen/dp_23" android:background="@drawable/shape_blue_5_bg_4" android:gravity="center" android:text="09" android:textColor="@color/white" android:textSize="@dimen/sp_16" android:textStyle="bold" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/shape_gray_7_r_4" android:paddingHorizontal="@dimen/dp_8" android:paddingVertical="@dimen/dp_3" android:text="事故" android:textColor="@color/gray_7" android:textSize="@dimen/sp_14" /> android:layout_marginHorizontal="@dimen/dp_16" android:layout_marginTop="@dimen/dp_20" android:gravity="center_vertical" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/plateNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginHorizontal="@dimen/dp_8" android:layout_weight="1" android:text="粤A 888WM试" android:textColor="@color/gray_9" android:textSize="@dimen/sp_18" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/shape_gray_7_r_4" android:paddingHorizontal="@dimen/dp_8" android:paddingVertical="@dimen/dp_3" android:text="事故" android:textColor="@color/gray_7" android:textSize="@dimen/sp_14" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/status" android:layout_width="wrap_content" <androidx.appcompat.widget.AppCompatTextView android:id="@+id/plateNumber" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginHorizontal="@dimen/dp_8" android:layout_weight="1" android:text="粤A 888WM试" android:textColor="@color/gray_9" android:textSize="@dimen/sp_18" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/shape_gray_3_bg_2" android:paddingHorizontal="@dimen/dp_8" android:paddingVertical="@dimen/dp_3" android:text="待到达" android:textColor="@color/gray_8" android:textSize="@dimen/sp_12" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/shape_gray_3_bg_2" android:paddingHorizontal="@dimen/dp_8" android:paddingVertical="@dimen/dp_3" android:text="待到达" android:textColor="@color/gray_8" android:textSize="@dimen/sp_12" /> </LinearLayout> android:layout_marginHorizontal="@dimen/dp_16" android:layout_marginTop="@dimen/dp_16" android:background="@mipmap/img_map" android:minHeight="@dimen/dp_58" android:orientation="horizontal"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_16" android:layout_marginTop="@dimen/dp_16" android:background="@mipmap/img_map" android:minHeight="@dimen/dp_58" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/address" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="@dimen/dp_12" android:layout_weight="1" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" tools:text="蜀山区华侨中心A座2301" /> </LinearLayout> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/address" android:layout_width="0dp" <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="@dimen/dp_12" android:layout_weight="1" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" tools:text="蜀山区华侨中心A座2301" /> android:layout_marginHorizontal="@dimen/dp_16" android:layout_marginTop="@dimen/dp_16" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/nav" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|center_vertical" android:layout_margin="@dimen/dp_12" android:src="@drawable/svg_map_nav" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tv_apply_for_reassignment" android:layout_width="0dp" android:layout_height="@dimen/dp_40" android:layout_marginEnd="@dimen/dp_6" android:layout_weight="1" android:background="@drawable/shape_gray_5_line_4" android:gravity="center" android:text="申请改派" android:textColor="@color/gray_9" android:textSize="@dimen/sp_16" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tvControl" android:layout_width="0dp" android:layout_height="@dimen/dp_40" android:layout_marginStart="@dimen/dp_6" android:layout_weight="1" android:background="@drawable/shape_blue_5_bg_4" android:gravity="center" android:text="接受任务" android:textColor="@color/white" android:textSize="@dimen/sp_16" android:textStyle="bold" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_16" android:layout_marginTop="@dimen/dp_16" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tv_apply_for_reassignment" android:layout_width="0dp" android:layout_height="@dimen/dp_40" android:layout_marginEnd="@dimen/dp_6" android:layout_weight="1" android:background="@drawable/shape_gray_5_line_4" android:gravity="center" android:text="申请改派" android:textColor="@color/gray_9" android:textSize="@dimen/sp_16" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tvControl" android:layout_width="0dp" android:layout_height="@dimen/dp_40" android:layout_marginStart="@dimen/dp_6" android:layout_weight="1" android:background="@drawable/shape_blue_5_bg_4" android:gravity="center" android:text="接受任务" android:textColor="@color/white" android:textSize="@dimen/sp_16" android:textStyle="bold" /> </LinearLayout> </LinearLayout> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/level" android:layout_width="@dimen/dp_33" android:layout_height="@dimen/dp_24" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" tools:src="@mipmap/ic_task_item_left_one" /> </androidx.constraintlayout.widget.ConstraintLayout> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/level" android:layout_width="@dimen/dp_33" android:layout_height="@dimen/dp_24" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" tools:src="@mipmap/ic_task_item_left_one" /> </androidx.constraintlayout.widget.ConstraintLayout> </RelativeLayout> app/src/main/res/layout/dialog_apply_for_reassign_reason.xml
New file @@ -0,0 +1,57 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/shape_while_bg_5" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_20" android:layout_marginTop="@dimen/dp_16" android:gravity="center" android:text="请选择改派原因" android:textColor="@color/gray_10" android:textSize="@dimen/sp_18" android:textStyle="bold" /> <com.google.android.flexbox.FlexboxLayout android:id="@+id/fl_tags" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_16" android:layout_marginVertical="@dimen/dp_12" app:alignContent="stretch" app:alignItems="stretch" app:flexWrap="wrap" app:justifyContent="flex_start" /> <EditText android:id="@+id/et_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_20" android:layout_marginTop="@dimen/dp_4" android:layout_marginBottom="@dimen/dp_8" android:background="@drawable/shape_red_5_line_gray_3_bg_4" android:gravity="top|start" android:hint="请输入改派原因" android:maxLength="200" android:minHeight="@dimen/dp_95" android:padding="@dimen/dp_12" android:textColor="@color/gray_9" android:textColorLink="@color/gray_6" android:textSize="@dimen/sp_14" /> <TextView android:id="@+id/tv_operation" android:layout_width="match_parent" android:layout_height="@dimen/dp_60" android:gravity="center" android:text="提交" android:textColor="@color/blue_5" android:textSize="@dimen/sp_16" android:textStyle="bold" /> </LinearLayout> app/src/main/res/layout/fragment_mine.xml
@@ -68,9 +68,9 @@ android:text="@{viewModel.userInfo.phoneNumber}" android:textColor="@color/blue_5" android:textSize="@dimen/sp_14" app:layout_constraintTop_toTopOf="@+id/name" app:layout_constraintBottom_toBottomOf="@+id/name" app:layout_constraintStart_toEndOf="@+id/name" app:layout_constraintTop_toTopOf="@+id/name" tools:text="13488881203" /> <androidx.appcompat.widget.AppCompatImageView @@ -164,335 +164,596 @@ tools:text="未打卡" /> </androidx.constraintlayout.widget.ConstraintLayout> <androidx.cardview.widget.CardView android:id="@+id/layout" <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_12" android:layout_height="0dp" android:layout_marginTop="@dimen/dp_25" app:cardCornerRadius="@dimen/dp_8" app:cardElevation="@dimen/dp_2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/infoLayout"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <LinearLayout android:id="@+id/schedulingLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:onClick="@{viewModel.onClick}" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="排班考勤" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:background="@drawable/shape_f8f9fa_bg_4" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_9" android:paddingVertical="@dimen/dp_7"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="20dp" android:layout_height="20dp" android:src="@drawable/svg_banci" app:tint="#1976D2" /> <androidx.appcompat.widget.AppCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" show="@{viewModel.clockModel!=null}" android:text='@{viewModel.clockModel.xpShift.shiftName+""}' android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" tools:text="早班" /> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1" /> <androidx.appcompat.widget.AppCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.clockTime}" show="@{viewModel.clockModel!=null}" android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" android:textStyle="bold" tools:text="2026-09-21 09:00~17:00" /> </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:gravity="center_vertical" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="打卡地点" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1" /> <!-- show="@{viewModel.clockModel.clockIn != true || viewModel.clockModel.clockOut != true}"--> <androidx.appcompat.widget.AppCompatImageView show="@{viewModel.clockModel!=null && (viewModel.clockModel.clockIn != true || viewModel.clockModel.clockOut != true)}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_location" /> <!-- show="@{viewModel.clockModel.clockIn != true || viewModel.clockModel.clockOut != true}"--> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/goCheck" android:layout_width="wrap_content" android:layout_height="wrap_content" show="@{viewModel.clockModel!=null && (viewModel.clockModel.clockIn != true || viewModel.clockModel.clockOut != true)}" android:layout_marginStart="4dp" android:onClick="@{viewModel.onClick}" android:text="去打卡" android:textColor="@color/blue_5" android:textSize="@dimen/sp_14" android:textStyle="bold" /> </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_12" android:background="@mipmap/img_map"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginVertical="@dimen/dp_10" android:layout_marginStart="@dimen/dp_12" android:layout_marginEnd="@dimen/dp_60" android:text="@{viewModel.clockModel.xpCheckLocation.locationAddress}" show="@{viewModel.clockModel!=null}" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" tools:text="蜀山区华侨中心A座2301" /> <!-- 导航按钮 --> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/nav" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|center_vertical" android:layout_margin="@dimen/dp_12" android:src="@drawable/svg_map_nav" /> </FrameLayout> </LinearLayout> </androidx.cardview.widget.CardView> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_12" android:layout_marginTop="@dimen/dp_12" android:layout_marginBottom="@dimen/dp_25" app:cardCornerRadius="@dimen/dp_8" app:cardElevation="@dimen/dp_2" app:layout_constraintTop_toBottomOf="@+id/layout"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:id="@+id/setLayout" <androidx.cardview.widget.CardView android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="@dimen/dp_56" android:gravity="center_vertical" android:onClick="@{viewModel.onClick}" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_16"> android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_12" app:cardCornerRadius="@dimen/dp_8" app:cardElevation="@dimen/dp_2"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:src="@drawable/svg_mine_set" /> android:orientation="vertical" android:padding="16dp"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_weight="1" android:text="设置" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <LinearLayout android:id="@+id/schedulingLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:onClick="@{viewModel.onClick}" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="排班考勤" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> </LinearLayout> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> <View </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:background="@drawable/shape_f8f9fa_bg_4" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_9" android:paddingVertical="@dimen/dp_7"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="20dp" android:layout_height="20dp" android:src="@drawable/svg_banci" app:tint="#1976D2" /> <androidx.appcompat.widget.AppCompatTextView show="@{viewModel.clockModel!=null}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text='@{viewModel.clockModel.xpShift.shiftName+""}' android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" tools:text="早班" /> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1" /> <androidx.appcompat.widget.AppCompatTextView show="@{viewModel.clockModel!=null}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.clockTime}" android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" android:textStyle="bold" tools:text="2026-09-21 09:00~17:00" /> </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:gravity="center_vertical" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="打卡地点" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1" /> <!-- show="@{viewModel.clockModel.clockIn != true || viewModel.clockModel.clockOut != true}"--> <androidx.appcompat.widget.AppCompatImageView show="@{viewModel.clockModel!=null && (viewModel.clockModel.clockIn != true || viewModel.clockModel.clockOut != true)}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_location" /> <!-- show="@{viewModel.clockModel.clockIn != true || viewModel.clockModel.clockOut != true}"--> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/goCheck" show="@{viewModel.clockModel!=null && (viewModel.clockModel.clockIn != true || viewModel.clockModel.clockOut != true)}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" android:onClick="@{viewModel.onClick}" android:text="去打卡" android:textColor="@color/blue_5" android:textSize="@dimen/sp_14" android:textStyle="bold" /> </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_12" android:background="@mipmap/img_map"> <androidx.appcompat.widget.AppCompatTextView show="@{viewModel.clockModel!=null}" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginVertical="@dimen/dp_10" android:layout_marginStart="@dimen/dp_12" android:layout_marginEnd="@dimen/dp_60" android:text="@{viewModel.clockModel.xpCheckLocation.locationAddress}" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" tools:text="蜀山区华侨中心A座2301" /> <!-- 导航按钮 --> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/nav" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|center_vertical" android:layout_margin="@dimen/dp_12" android:src="@drawable/svg_map_nav" /> </FrameLayout> </LinearLayout> </androidx.cardview.widget.CardView> <androidx.cardview.widget.CardView android:id="@+id/cv_task" show="@{viewModel.cruiseCheckInMissionInfo.effective}" android:layout_width="match_parent" android:layout_height="1px" android:layout_marginHorizontal="@dimen/dp_16" android:background="@color/gray_4" /> android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_12" android:layout_marginTop="@dimen/dp_16" android:visibility="gone" app:cardCornerRadius="@dimen/dp_8" app:cardElevation="@dimen/dp_2" app:layout_constraintTop_toBottomOf="@+id/layout" tools:visibility="visible"> <LinearLayout android:id="@+id/tiaokuan" <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <LinearLayout android:id="@+id/ll_cruise_check_in_mission" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:onClick="@{viewModel.onClick}" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="巡游打卡任务" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:background="@drawable/shape_f8f9fa_bg_4" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_9" android:paddingVertical="@dimen/dp_7"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="20dp" android:layout_height="20dp" android:src="@mipmap/ic_clock_note" /> <androidx.appcompat.widget.AppCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/dp_4" android:text="任务时间" android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" /> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1" /> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tv_mission_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" tools:text="2026-09-21 09:00~17:00" /> </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:gravity="center_vertical" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="打卡情况" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout> <!-- show="@{viewModel.clockModel!=null}"--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_12" android:gravity="center" android:orientation="horizontal"> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"> <com.xw.yunwei.ui.widget.CircularProgressView android:id="@+id/cpv_progress" android:layout_width="@dimen/dp_100" android:layout_height="@dimen/dp_100" app:backgroundProgressColor="@color/orange_5" app:maxProgress="100" app:progress="0" app:progressColor="@color/green_5" app:ringWidth="@dimen/dp_10" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/tv_mission_point_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0" android:textColor="@color/color_3d3d3d" android:textSize="@dimen/sp_20" android:textStyle="bold" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="打卡点位" android:textColor="@color/color_3d3d3d" android:textSize="@dimen/sp_12" android:textStyle="bold" /> </LinearLayout> </FrameLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/dp_24" android:gravity="center" android:orientation="vertical"> <LinearLayout android:layout_width="@dimen/dp_124" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="已打卡" android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" android:textStyle="bold" /> <View android:layout_width="0dp" android:layout_height="@dimen/dp_1" android:layout_weight="1" /> <TextView android:id="@+id/tv_had_report_num" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/green_5" android:textSize="@dimen/sp_16" android:textStyle="bold" tools:text="16" /> </LinearLayout> <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="@dimen/dp_124" android:layout_height="@dimen/dp_8" android:max="@{viewModel.cruiseCheckInMissionInfo.checkInPointCount}" android:maxHeight="@dimen/dp_8" android:minHeight="@dimen/dp_8" android:progress="@{viewModel.cruiseCheckInMissionInfo.checkInPointFinishCount}" android:progressDrawable="@drawable/layer_list_green_progress_bar" /> <LinearLayout android:layout_width="@dimen/dp_124" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_20" android:gravity="center" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="未打卡" android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" android:textStyle="bold" /> <View android:layout_width="0dp" android:layout_height="@dimen/dp_1" android:layout_weight="1" /> <TextView android:id="@+id/tv_not_report_num" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/orange_5" android:textSize="@dimen/sp_16" android:textStyle="bold" tools:text="16" /> </LinearLayout> <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="@dimen/dp_124" android:layout_height="@dimen/dp_8" android:max="@{viewModel.cruiseCheckInMissionInfo.checkInPointCount}" android:maxHeight="@dimen/dp_8" android:minHeight="@dimen/dp_8" android:progress="@{viewModel.cruiseCheckInMissionInfo.checkInPointFinishCount}" android:progressDrawable="@drawable/layer_list_orange_progress_bar" /> </LinearLayout> </LinearLayout> </LinearLayout> </androidx.cardview.widget.CardView> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="@dimen/dp_56" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_16"> android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_12" android:layout_marginTop="@dimen/dp_16" android:layout_marginBottom="@dimen/dp_25" app:cardCornerRadius="@dimen/dp_8" app:cardElevation="@dimen/dp_2" app:layout_constraintTop_toBottomOf="@+id/cv_task"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:src="@drawable/svg_mine_tiaokuan" /> android:orientation="vertical"> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_weight="1" android:text="服务条款" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <LinearLayout android:id="@+id/setLayout" android:layout_width="match_parent" android:layout_height="@dimen/dp_56" android:gravity="center_vertical" android:onClick="@{viewModel.onClick}" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_16"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_mine_set" /> </LinearLayout> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_weight="1" android:text="设置" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginHorizontal="@dimen/dp_16" android:background="@color/gray_4" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> <LinearLayout android:id="@+id/contact" android:layout_width="match_parent" android:layout_height="@dimen/dp_56" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_16"> </LinearLayout> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_mine_kefu" /> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginHorizontal="@dimen/dp_16" android:background="@color/gray_4" /> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_weight="1" android:text="联系客服" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <LinearLayout android:id="@+id/tiaokuan" android:layout_width="match_parent" android:layout_height="@dimen/dp_56" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_16"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_mine_tiaokuan" /> </LinearLayout> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_weight="1" android:text="服务条款" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginHorizontal="@dimen/dp_16" android:background="@color/gray_4" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> <LinearLayout android:id="@+id/uploadLayout" android:layout_width="match_parent" android:layout_height="@dimen/dp_56" android:gravity="center_vertical" android:onClick="@{viewModel.onClick}" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_16"> </LinearLayout> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_mine_card" /> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginHorizontal="@dimen/dp_16" android:background="@color/gray_4" /> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_weight="1" android:text="证件上传" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <LinearLayout android:id="@+id/contact" android:layout_width="match_parent" android:layout_height="@dimen/dp_56" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_16"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_mine_kefu" /> </LinearLayout> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_weight="1" android:text="联系客服" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginHorizontal="@dimen/dp_16" android:background="@color/gray_4" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginHorizontal="@dimen/dp_16" android:background="@color/gray_4" /> <LinearLayout android:id="@+id/uploadLayout" android:layout_width="match_parent" android:layout_height="@dimen/dp_56" android:gravity="center_vertical" android:onClick="@{viewModel.onClick}" android:orientation="horizontal" android:paddingHorizontal="@dimen/dp_16"> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_mine_card" /> <androidx.appcompat.widget.AppCompatTextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_weight="1" android:text="证件上传" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" /> <androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/svg_arrow_r" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginHorizontal="@dimen/dp_16" android:background="@color/gray_4" /> </LinearLayout> </androidx.cardview.widget.CardView> </LinearLayout> </androidx.cardview.widget.CardView> </ScrollView> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.core.widget.NestedScrollView> app/src/main/res/layout/item_check_in_mission_point.xml
New file @@ -0,0 +1,117 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/dp_20" android:orientation="horizontal"> <LinearLayout android:id="@+id/ll_no" android:layout_width="wrap_content" android:layout_height="0dp" android:gravity="center" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="@id/ll_content" app:layout_constraintEnd_toStartOf="@id/ll_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <!-- 序号 --> <TextView android:id="@+id/tv_index" android:layout_width="@dimen/dp_16" android:layout_height="@dimen/dp_16" android:background="@drawable/circle_gray_6" android:gravity="center" android:text="1" android:textColor="@color/white" android:textSize="@dimen/sp_10" android:textStyle="bold" /> <View android:layout_width="@dimen/dp_1" android:layout_height="0dp" android:layout_marginVertical="@dimen/dp_6" android:layout_weight="1" /> </LinearLayout> <LinearLayout android:id="@+id/ll_content" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/dp_12" android:orientation="vertical" android:paddingBottom="@dimen/dp_12" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/ll_no" app:layout_constraintTop_toTopOf="parent"> <!-- 顶部信息行 --> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="打卡限时" android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" android:textStyle="bold" /> <TextView android:id="@+id/tv_time_range" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_title" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" tools:text="08:00~09:00" /> <TextView android:id="@+id/tv_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_title" android:layout_alignParentEnd="true" android:paddingHorizontal="@dimen/dp_8" android:paddingVertical="@dimen/dp_1" android:textColor="@color/gray_9" android:textSize="@dimen/sp_12" tools:text="08:41:57 已打卡" /> </RelativeLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_8" android:background="@mipmap/img_map" android:minHeight="@dimen/dp_58" android:orientation="horizontal"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/address" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="@dimen/dp_12" android:layout_weight="1" android:textColor="@color/gray_9" android:textSize="@dimen/sp_14" android:textStyle="bold" tools:text="蜀山区华侨中心A座2301" /> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/nav" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|center_vertical" android:layout_margin="@dimen/dp_12" android:src="@drawable/svg_map_nav" /> </LinearLayout> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout> app/src/main/res/mipmap-xhdpi/ic_apply_reassign_bg.png
app/src/main/res/mipmap-xhdpi/ic_clock_note.png
app/src/main/res/mipmap-xhdpi/icon_note.png
app/src/main/res/values/attrs.xml
New file @@ -0,0 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CircularProgressView"> <!-- 环宽度 --> <attr name="ringWidth" format="dimension" /> <!-- 背景环颜色 --> <attr name="backgroundProgressColor" format="color" /> <!-- 进度环颜色 --> <attr name="progressColor" format="color" /> <!-- 当前进度 --> <attr name="progress" format="float" /> <!-- 最大进度 --> <attr name="maxProgress" format="float" /> </declare-styleable> </resources> app/src/main/res/values/colors.xml
@@ -7,4 +7,5 @@ <color name="teal_700">#FF018786</color> <color name="black">#FF000000</color> <color name="white">#FFFFFFFF</color> <color name="color_3d3d3d">#FF3D3D3D</color> </resources> common/build.gradle.kts
@@ -60,6 +60,7 @@ api(libs.kotlinx.coroutines.core) api(libs.kotlinx.coroutines.android) api(libs.okhttp) api(libs.okhttp.logging.interceptor) api(libs.retrofit) api(libs.retrofit.converter.gson) api(libs.retrofit.converter.scalars) gradle/libs.versions.toml
@@ -24,6 +24,7 @@ lifecycleVersion = "2.10.0" mmkv = "2.3.0" okhttp = "4.9.3" # 升级有兼容性问题 okhttpLoggingInterceptor = "4.9.3" # 升级有兼容性问题 pdfrendererview = "1.0.10" photoview = "2.3.0" pictureselector = "v3.11.2" @@ -35,6 +36,7 @@ xxpermissions = "20.0" flexbox = "3.0.0" crashreport = "latest.release" alicloud = "3.10.1" [libraries] @@ -56,6 +58,8 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } # okhttp okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttpLoggingInterceptor" } # retrofit retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" } @@ -106,6 +110,7 @@ # 腾讯bugly crashreport = { module = "com.tencent.bugly:crashreport", version.ref = "crashreport" } nativecrashreport = { module = "com.tencent.bugly:nativecrashreport", version.ref = "crashreport" } alicloud = { module = "com.aliyun.ams:alicloud-android-push", version.ref = "alicloud" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }