日报2025526

团队作业开发
完成测试任务派发与接取

package com.example.sanpaias.activity

import android.app.AlertDialog
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.sanpaias.R
import com.example.sanpaias.entity.Device
import com.example.sanpaias.entity.TestingPlan
import com.example.sanpaias.network.RetrofitClient
import com.google.gson.Gson
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import java.text.SimpleDateFormat
import java.util.*

class TestingPlanListActivity : AppCompatActivity() {

    private lateinit var rvTestingPlans: RecyclerView
    private lateinit var tvNoData: TextView
    private lateinit var btnAddPlan: Button
    private lateinit var adapter: TestingPlanAdapter
    private val testingPlans = mutableListOf<TestingPlan>()
    private val devices = mutableListOf<Device>()
    private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
    
    companion object {
        private const val TAG = "TestingPlanListActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_testing_plan_list)
        
        initViews()
        setupRecyclerView()
        loadDevices()
        loadTestingPlans()
    }

    private fun initViews() {
        rvTestingPlans = findViewById(R.id.rvTestingPlans)
        tvNoData = findViewById(R.id.tvNoData)
        btnAddPlan = findViewById(R.id.btnAddPlan)
        
        btnAddPlan.setOnClickListener {
            showAddTestingPlanDialog()
        }
    }

    private fun setupRecyclerView() {
        adapter = TestingPlanAdapter(testingPlans) { plan, action ->
            when (action) {
                "edit" -> showEditTestingPlanDialog(plan)
                "delete" -> showDeleteConfirmDialog(plan)
                "toggle_status" -> togglePlanStatus(plan)
                "enable" -> enablePlan(plan)
                "disable" -> disablePlan(plan)
            }
        }
        rvTestingPlans.layoutManager = LinearLayoutManager(this)
        rvTestingPlans.adapter = adapter
    }

    private fun loadDevices() {
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.getAllDevices()
                if (response.isSuccessful && response.body()?.code == 200) {
                    devices.clear()
                    devices.addAll(response.body()?.data ?: emptyList())
                }
            } catch (e: Exception) {
                Log.e(TAG, "加载设备列表失败", e)
            }
        }
    }

    private fun loadTestingPlans() {
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.getTestingPlanList()
                if (response.isSuccessful && response.body()?.code == 200) {
                    testingPlans.clear()
                    testingPlans.addAll(response.body()?.data ?: emptyList())
                    adapter.notifyDataSetChanged()
                    
                    if (testingPlans.isEmpty()) {
                        tvNoData.visibility = View.VISIBLE
                        rvTestingPlans.visibility = View.GONE
                    } else {
                        tvNoData.visibility = View.GONE
                        rvTestingPlans.visibility = View.VISIBLE
                    }
                } else {
                    Log.e(TAG, "获取检测计划失败: ${response.message()}")
                }
            } catch (e: Exception) {
                Log.e(TAG, "加载检测计划异常", e)
            }
        }
    }

    private fun showAddTestingPlanDialog() {
        val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_add_testing_plan, null)
        val dialog = AlertDialog.Builder(this)
            .setView(dialogView)
            .create()

        val spinnerDeviceId = dialogView.findViewById<Spinner>(R.id.spinnerDeviceId)
        val etLocation = dialogView.findViewById<EditText>(R.id.etLocation)
        val spinnerFrequency = dialogView.findViewById<Spinner>(R.id.spinnerFrequency)
        val etStartTime = dialogView.findViewById<EditText>(R.id.etStartTime)
        val etEndTime = dialogView.findViewById<EditText>(R.id.etEndTime)
        val btnCancel = dialogView.findViewById<Button>(R.id.btnCancel)
        val btnConfirm = dialogView.findViewById<Button>(R.id.btnConfirm)

        // 设置设备下拉列表
        val deviceAdapter = ArrayAdapter(
            this,
            android.R.layout.simple_spinner_item,
            devices.map { "${it.deviceId} - ${it.location}" }
        )
        deviceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        spinnerDeviceId.adapter = deviceAdapter

        // 设置频率下拉列表
        val frequencyAdapter = ArrayAdapter(
            this,
            android.R.layout.simple_spinner_item,
            arrayOf("月", "季度", "半年", "年")
        )
        frequencyAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        spinnerFrequency.adapter = frequencyAdapter

        // 设备选择监听器
        spinnerDeviceId.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                if (position < devices.size) {
                    etLocation.setText(devices[position].location)
                }
            }
            override fun onNothingSelected(parent: AdapterView<*>?) {}
        }

        // 设置时间选择
        etStartTime.setOnClickListener {
            showDateTimePicker { dateTime ->
                etStartTime.setText(dateFormat.format(dateTime.time))
            }
        }

        etEndTime.setOnClickListener {
            showDateTimePicker { dateTime ->
                etEndTime.setText(dateFormat.format(dateTime.time))
            }
        }

        btnCancel.setOnClickListener {
            dialog.dismiss()
        }

        btnConfirm.setOnClickListener {
            val selectedDeviceIndex = spinnerDeviceId.selectedItemPosition
            if (selectedDeviceIndex < 0 || selectedDeviceIndex >= devices.size) {
                Toast.makeText(this, "请选择设备", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            val startTimeText = etStartTime.text.toString()
            if (startTimeText.isEmpty()) {
                Toast.makeText(this, "请选择开始时间", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            val startTimeIso = convertToIsoFormat(startTimeText)
            val endTimeText = etEndTime.text.toString()
            val endTimeIso = if (endTimeText.isNotEmpty()) convertToIsoFormat(endTimeText) else null

            val newPlan = TestingPlan(
                deviceId = devices[selectedDeviceIndex].deviceId,
                location = etLocation.text.toString(),
                frequency = spinnerFrequency.selectedItem.toString(),
                startTime = startTimeIso,
                endTime = endTimeIso
            )

            // 提交新计划
            addTestingPlan(newPlan)
            dialog.dismiss()
        }

        dialog.show()
    }

    private fun showDateTimePicker(callback: (Calendar) -> Unit) {
        val calendar = Calendar.getInstance()
        
        DatePickerDialog(
            this,
            { _, year, month, dayOfMonth ->
                calendar.set(Calendar.YEAR, year)
                calendar.set(Calendar.MONTH, month)
                calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
                
                TimePickerDialog(
                    this,
                    { _, hourOfDay, minute ->
                        calendar.set(Calendar.HOUR_OF_DAY, hourOfDay)
                        calendar.set(Calendar.MINUTE, minute)
                        calendar.set(Calendar.SECOND, 0)
                        callback(calendar)
                    },
                    calendar.get(Calendar.HOUR_OF_DAY),
                    calendar.get(Calendar.MINUTE),
                    true
                ).show()
            },
            calendar.get(Calendar.YEAR),
            calendar.get(Calendar.MONTH),
            calendar.get(Calendar.DAY_OF_MONTH)
        ).show()
    }

    private fun addTestingPlan(plan: TestingPlan) {
        lifecycleScope.launch {
            try {
                Log.d(TAG, "发送的检测计划数据: $plan")
                
                val response = RetrofitClient.instance.addTestingPlan(plan)
                if (response.isSuccessful) {
                    Toast.makeText(this@TestingPlanListActivity, "添加检测计划成功", Toast.LENGTH_SHORT).show()
                    loadTestingPlans()
                } else {
                    Toast.makeText(this@TestingPlanListActivity, "添加检测计划失败", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                Log.e(TAG, "添加检测计划异常", e)
                Toast.makeText(this@TestingPlanListActivity, "网络错误", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun showEditTestingPlanDialog(plan: TestingPlan) {
        val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_add_testing_plan, null)
        val dialog = AlertDialog.Builder(this)
            .setView(dialogView)
            .create()

        val spinnerDeviceId = dialog.findViewById<Spinner>(R.id.spinnerDeviceId)
        val etLocation = dialog.findViewById<EditText>(R.id.etLocation)
        val spinnerFrequency = dialog.findViewById<Spinner>(R.id.spinnerFrequency)
        val etStartTime = dialog.findViewById<EditText>(R.id.etStartTime)
        val etEndTime = dialog.findViewById<EditText>(R.id.etEndTime)
        val btnCancel = dialog.findViewById<Button>(R.id.btnCancel)
        val btnConfirm = dialog.findViewById<Button>(R.id.btnConfirm)

        // 设置设备下拉列表
        val deviceAdapter = ArrayAdapter(
            this,
            android.R.layout.simple_spinner_item,
            devices.map { "${it.deviceId} - ${it.location}" }
        )
        deviceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        spinnerDeviceId.adapter = deviceAdapter

        // 设置频率下拉列表
        val frequencyAdapter = ArrayAdapter(
            this,
            android.R.layout.simple_spinner_item,
            arrayOf("月", "季度", "半年", "年")
        )
        frequencyAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        spinnerFrequency.adapter = frequencyAdapter

        // 填充现有数据
        val devicePosition = devices.indexOfFirst { it.deviceId == plan.deviceId }
        if (devicePosition >= 0) {
            spinnerDeviceId.setSelection(devicePosition)
        }
        
        etLocation.setText(plan.location)
        
        val frequencyPosition = arrayOf("月", "季度", "半年", "年").indexOf(plan.frequency)
        if (frequencyPosition >= 0) {
            spinnerFrequency.setSelection(frequencyPosition)
        }
        
        etStartTime.setText(convertFromIsoFormat(plan.startTime))
        if (!plan.endTime.isNullOrEmpty()) {
            etEndTime.setText(convertFromIsoFormat(plan.endTime))
        }

        // 设置时间选择
        etStartTime.setOnClickListener {
            showDateTimePicker { dateTime ->
                etStartTime.setText(dateFormat.format(dateTime.time))
            }
        }

        etEndTime.setOnClickListener {
            showDateTimePicker { dateTime ->
                etEndTime.setText(dateFormat.format(dateTime.time))
            }
        }

        btnCancel.setOnClickListener {
            dialog.dismiss()
        }

        btnConfirm.setOnClickListener {
            val selectedDeviceIndex = spinnerDeviceId.selectedItemPosition
            if (selectedDeviceIndex < 0 || selectedDeviceIndex >= devices.size) {
                Toast.makeText(this, "请选择设备", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            val startTimeText = etStartTime.text.toString()
            if (startTimeText.isEmpty()) {
                Toast.makeText(this, "请选择开始时间", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            val startTimeIso = convertToIsoFormat(startTimeText)
            val endTimeText = etEndTime.text.toString()
            val endTimeIso = if (endTimeText.isNotEmpty()) convertToIsoFormat(endTimeText) else null

            val updatedPlan = plan.copy(
                deviceId = devices[selectedDeviceIndex].deviceId,
                location = etLocation.text.toString(),
                frequency = spinnerFrequency.selectedItem.toString(),
                startTime = startTimeIso,
                endTime = endTimeIso
            )

            updateTestingPlan(updatedPlan)
            dialog.dismiss()
        }

        dialog.show()
    }

    private fun updateTestingPlan(plan: TestingPlan) {
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.updateTestingPlan(plan)
                if (response.isSuccessful) {
                    Toast.makeText(this@TestingPlanListActivity, "更新检测计划成功", Toast.LENGTH_SHORT).show()
                    loadTestingPlans()
                } else {
                    Toast.makeText(this@TestingPlanListActivity, "更新检测计划失败", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                Log.e(TAG, "更新检测计划异常", e)
                Toast.makeText(this@TestingPlanListActivity, "网络错误", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun showDeleteConfirmDialog(plan: TestingPlan) {
        AlertDialog.Builder(this)
            .setTitle("确认删除")
            .setMessage("确定要删除这个检测计划吗?")
            .setPositiveButton("删除") { _, _ ->
                deleteTestingPlan(plan)
            }
            .setNegativeButton("取消", null)
            .show()
    }

    private fun deleteTestingPlan(plan: TestingPlan) {
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.deleteTestingPlan(plan.planId!!)
                if (response.isSuccessful) {
                    Toast.makeText(this@TestingPlanListActivity, "删除检测计划成功", Toast.LENGTH_SHORT).show()
                    loadTestingPlans()
                } else {
                    Toast.makeText(this@TestingPlanListActivity, "删除检测计划失败", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                Log.e(TAG, "删除检测计划异常", e)
                Toast.makeText(this@TestingPlanListActivity, "网络错误", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun togglePlanStatus(plan: TestingPlan) {
        val newStatus = when (plan.status) {
            "待启用" -> "启用"
            "启用" -> "停用"
            "停用" -> "启用"
            else -> "启用"
        }
        
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.updateTestingPlanStatus(plan.planId!!, newStatus)
                if (response.isSuccessful) {
                    Toast.makeText(this@TestingPlanListActivity, "状态更新成功", Toast.LENGTH_SHORT).show()
                    loadTestingPlans()
                } else {
                    Toast.makeText(this@TestingPlanListActivity, "状态更新失败", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                Log.e(TAG, "更新状态异常", e)
                Toast.makeText(this@TestingPlanListActivity, "网络错误", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun enablePlan(plan: TestingPlan) {
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.updateTestingPlanStatus(plan.planId!!, "启用")
                if (response.isSuccessful) {
                    Toast.makeText(this@TestingPlanListActivity, "状态更新成功", Toast.LENGTH_SHORT).show()
                    
                    // 启用成功后生成对应的检测工单
                    generateTestingOrder(plan.planId!!)
                } else {
                    Toast.makeText(this@TestingPlanListActivity, "状态更新失败", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                Log.e(TAG, "更新状态异常", e)
                Toast.makeText(this@TestingPlanListActivity, "网络错误", Toast.LENGTH_SHORT).show()
            }
        }
    }
    
    private fun disablePlan(plan: TestingPlan) {
        updatePlanStatus(plan, "停用")
    }
    
    private fun updatePlanStatus(plan: TestingPlan, newStatus: String) {
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.updateTestingPlanStatus(plan.planId!!, newStatus)
                if (response.isSuccessful) {
                    Toast.makeText(this@TestingPlanListActivity, "状态更新成功", Toast.LENGTH_SHORT).show()
                    loadTestingPlans()
                } else {
                    Toast.makeText(this@TestingPlanListActivity, "状态更新失败", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                Log.e(TAG, "更新状态异常", e)
                Toast.makeText(this@TestingPlanListActivity, "网络错误", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun convertToIsoFormat(dateTimeString: String): String {
        return try {
            val date = dateFormat.parse(dateTimeString)
            val isoFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
            isoFormat.format(date!!)
        } catch (e: Exception) {
            dateTimeString
        }
    }

    private fun convertFromIsoFormat(isoString: String): String {
        return try {
            val isoFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
            val date = isoFormat.parse(isoString)
            dateFormat.format(date!!)
        } catch (e: Exception) {
            isoString
        }
    }
    // 生成检测工单
    private fun generateTestingOrder(planId: Int) {
        lifecycleScope.launch {
            try {
                val request = mapOf("planIds" to listOf(planId))
                val gson = Gson()
                val json = gson.toJson(request)
                val requestBody = json.toRequestBody("application/json".toMediaTypeOrNull())
                val response = RetrofitClient.instance.generateTestingOrders(requestBody)

                if (response.isSuccessful) {
                    Toast.makeText(this@TestingPlanListActivity, "启用成功,已生成检测工单", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this@TestingPlanListActivity, "启用成功,但工单生成失败", Toast.LENGTH_SHORT).show()
                }

                // 重新加载检测计划列表
                loadTestingPlans()

            } catch (e: Exception) {
                Log.e(TAG, "生成工单异常", e)
                Toast.makeText(this@TestingPlanListActivity, "生成工单失败: ${e.message}", Toast.LENGTH_SHORT).show()
            }
        }
    }

    // RecyclerView Adapter
    class TestingPlanAdapter(
        private val plans: MutableList<TestingPlan>,
        private val onItemAction: (TestingPlan, String) -> Unit
    ) : RecyclerView.Adapter<TestingPlanAdapter.ViewHolder>() {

        class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val tvPlanId: TextView = itemView.findViewById(R.id.tvPlanId)
            val tvDeviceId: TextView = itemView.findViewById(R.id.tvDeviceId)
            val tvLocation: TextView = itemView.findViewById(R.id.tvLocation)
            val tvFrequency: TextView = itemView.findViewById(R.id.tvFrequency)
            val tvStatus: TextView = itemView.findViewById(R.id.tvStatus)
            val tvTimeRange: TextView = itemView.findViewById(R.id.tvTimeRange)
            val btnEnable: Button = itemView.findViewById(R.id.btnEnable)
            val btnDisable: Button = itemView.findViewById(R.id.btnDisable)
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_testing_plan, parent, false)
            return ViewHolder(view)
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val plan = plans[position]
            
            holder.tvPlanId.text = "计划ID: ${plan.planId ?: position + 1}"
            holder.tvDeviceId.text = "设备ID: ${plan.deviceId}"
            holder.tvLocation.text = "检测位置: ${plan.location}"
            holder.tvFrequency.text = "检测频率: ${plan.frequency}"
            holder.tvStatus.text = "状态: ${plan.status}"
            
            // 格式化时间范围
            val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
            val isoFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
            
            val startTimeStr = try {
                val startDate = isoFormat.parse(plan.startTime)
                dateFormat.format(startDate!!)
            } catch (e: Exception) {
                plan.startTime.substring(0, 10) // 取前10位作为日期
            }
            
            val endTimeStr = if (!plan.endTime.isNullOrEmpty()) {
                try {
                    val endDate = isoFormat.parse(plan.endTime)
                    dateFormat.format(endDate!!)
                } catch (e: Exception) {
                    plan.endTime.substring(0, 10)
                }
            } else {
                "无限期"
            }
            
            holder.tvTimeRange.text = "时间范围: $startTimeStr ~ $endTimeStr"
            
            // 根据状态设置颜色
            when (plan.status) {
                "启用" -> holder.tvStatus.setTextColor(0xFF4CAF50.toInt())
                "停用" -> holder.tvStatus.setTextColor(0xFFF44336.toInt())
                "待启用" -> holder.tvStatus.setTextColor(0xFFFF9800.toInt())
            }
            
            // 设置按钮点击事件
            holder.btnEnable.setOnClickListener {
                onItemAction(plan, "enable")
            }
            
            holder.btnDisable.setOnClickListener {
                onItemAction(plan, "disable")
            }
            
            // 根据当前状态调整按钮可用性
            when (plan.status) {
                "启用" -> {
                    holder.btnEnable.isEnabled = false
                    holder.btnDisable.isEnabled = true
                }
                "停用", "待启用" -> {
                    holder.btnEnable.isEnabled = true
                    holder.btnDisable.isEnabled = false
                }
            }
        }

        override fun getItemCount() = plans.size
    }
}

package com.example.sanpaias.activity

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Base64
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.sanpaias.R
import com.example.sanpaias.adapter.PhotoAdapter
import com.example.sanpaias.entity.TestingOrder
import com.example.sanpaias.network.RetrofitClient
import kotlinx.coroutines.launch
import java.io.ByteArrayOutputStream
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

// 位置相关导入
import android.location.Location
import android.location.Geocoder
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices

class TestingTaskEditActivity : AppCompatActivity() {

    companion object {
        private const val CAMERA_PERMISSION_REQUEST_CODE = 100
        private const val REQUEST_LOCATION_PERMISSION = 102
    }

    private lateinit var tvOrderId: TextView
    private lateinit var tvPlanId: TextView
    private lateinit var tvStatus: TextView
    private lateinit var etTestingDesc: EditText
    private lateinit var btnTakePhoto: Button
    private lateinit var btnSave: Button
    private lateinit var btnSubmit: Button
    private lateinit var rvPhotos: RecyclerView
    private lateinit var photoAdapter: PhotoAdapter
    private lateinit var testingOrder: TestingOrder

    // 位置相关变量
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private var currentLocation: Location? = null

    private val photoList = mutableListOf<String>()
    private var currentPhotoFile: File? = null

    private val cameraLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == RESULT_OK) {
            currentPhotoFile?.let { file ->
                if (file.exists()) {
                    val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, Uri.fromFile(file))
                    val timestampedBitmap = addTimestampToBitmap(bitmap)
                    val base64Image = bitmapToBase64(timestampedBitmap)
                    photoList.add(base64Image)
                    photoAdapter.notifyDataSetChanged()
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_task_edit)

        // 初始化位置服务
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        
        // 获取当前位置
        getCurrentLocation()
        
        // 获取传递的ID并加载完整数据
        val orderId = intent.getStringExtra("order_id") ?: return
        val planId = intent.getIntExtra("plan_id", -1)
        
        loadTestingOrderById(orderId)
        
        initViews()
        setupRecyclerView()
        setupButtonListeners()
    }

    private fun loadTestingOrderById(orderId: String) {
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.getTestingOrderList()
                if (response.isSuccessful) {
                    val orders = response.body()?.data ?: emptyList()
                    val order = orders.find { it.orderId == orderId }
                    if (order != null) {
                        testingOrder = order
                        displayOrderInfo()
                    } else {
                        Toast.makeText(this@TestingTaskEditActivity, "未找到工单信息", Toast.LENGTH_SHORT).show()
                        finish()
                    }
                } else {
                    Toast.makeText(this@TestingTaskEditActivity, "加载工单失败", Toast.LENGTH_SHORT).show()
                    finish()
                }
            } catch (e: Exception) {
                Log.e("TestingTaskEditActivity", "加载工单异常", e)
                Toast.makeText(this@TestingTaskEditActivity, "加载工单异常", Toast.LENGTH_SHORT).show()
                finish()
            }
        }
    }

    private fun initViews() {
        tvOrderId = findViewById(R.id.tvOrderId)
        tvPlanId = findViewById(R.id.tvFaultId)
        tvStatus = findViewById(R.id.tvStatus)
        etTestingDesc = findViewById(R.id.etRepairDesc)
        btnTakePhoto = findViewById(R.id.btnTakePhoto)
        btnSave = findViewById(R.id.btnSave)
        btnSubmit = findViewById(R.id.btnSubmit)
        rvPhotos = findViewById(R.id.recyclerViewPhotos)
    }

    private fun setupRecyclerView() {
        photoAdapter = PhotoAdapter(photoList) { position ->
            photoList.removeAt(position)
            photoAdapter.notifyItemRemoved(position)
        }
        rvPhotos.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        rvPhotos.adapter = photoAdapter
    }

    private fun displayOrderInfo() {
        tvOrderId.text = "工单编号: ${testingOrder.orderId}"
        tvPlanId.text = "计划编号: ${testingOrder.planId}"
        tvStatus.text = "状态: ${testingOrder.status}"
        etTestingDesc.setText(testingOrder.testingDesc ?: "")

        // 加载现有照片
        testingOrder.photos?.let { photos ->
            if (photos.isNotEmpty()) {
                val photosList = photos.split(",")
                photoList.addAll(photosList)
                photoAdapter.notifyDataSetChanged()
            }
        }
    }

    private fun setupButtonListeners() {
        btnTakePhoto.setOnClickListener {
            if (checkCameraPermission()) {
                dispatchTakePictureIntent()
            } else {
                requestCameraPermission()
            }
        }

        btnSave.setOnClickListener {
            saveTestingOrder()
        }

        btnSubmit.setOnClickListener {
            submitTestingOrder()
        }
    }

    private fun checkCameraPermission(): Boolean {
        val cameraPermission = ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED
        
        val locationPermission = ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
        
        return cameraPermission && locationPermission
    }

    private fun requestCameraPermission() {
        val permissionsNeeded = mutableListOf<String>()
        
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            permissionsNeeded.add(Manifest.permission.CAMERA)
        }
        
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION)
        }
        
        if (permissionsNeeded.isNotEmpty()) {
            ActivityCompat.requestPermissions(this, permissionsNeeded.toTypedArray(), CAMERA_PERMISSION_REQUEST_CODE)
        }
    }

    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            takePictureIntent.resolveActivity(packageManager)?.also {
                currentPhotoFile = createImageFile()
                currentPhotoFile?.also {
                    val photoURI: Uri = FileProvider.getUriForFile(
                        this,
                        "com.example.sanpaias.fileprovider",
                        it
                    )
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                    cameraLauncher.launch(takePictureIntent)
                }
            }
        }
    }

    private fun createImageFile(): File {
        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
        val storageDir: File = getExternalFilesDir("Pictures")!!
        return File.createTempFile(
            "JPEG_${timeStamp}_",
            ".jpg",
            storageDir
        )
    }

    private fun getCurrentLocation() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            fusedLocationClient.lastLocation
                .addOnSuccessListener { location: Location? ->
                    currentLocation = location
                    if (location == null) {
                        Log.w("TestingTaskEditActivity", "无法获取位置信息")
                    }
                }
                .addOnFailureListener { e ->
                    Log.e("TestingTaskEditActivity", "获取位置失败", e)
                }
        }
    }

    private fun getLocationText(): String {
        return currentLocation?.let { location ->
            try {
                val geocoder = Geocoder(this, Locale.getDefault())
                val addresses = geocoder.getFromLocation(location.latitude, location.longitude, 1)
                if (addresses?.isNotEmpty() == true) {
                    val address = addresses[0]
                    // 构建地址字符串,优先显示详细地址
                    val addressParts = mutableListOf<String>()
                    
                    // 添加街道地址
                    address.getAddressLine(0)?.let { line: String -> addressParts.add(line) }
                    
                    // 如果没有详细地址,则使用行政区划信息
                    if (addressParts.isEmpty()) {
                        address.locality?.let { city: String -> addressParts.add(city) } // 城市
                        address.subLocality?.let { district: String -> addressParts.add(district) } // 区县
                        address.thoroughfare?.let { street: String -> addressParts.add(street) } // 街道
                    }
                    
                    val locationText = if (addressParts.isNotEmpty()) {
                        addressParts.joinToString(" ")
                    } else {
                        "${address.locality ?: ""} ${address.subAdminArea ?: ""}".trim()
                    }
                    
                    if (locationText.isNotEmpty()) "位置: $locationText" else ""
                } else {
                    // 如果地理编码失败,显示经纬度作为备选
                    val latitude = String.format("%.4f", location.latitude)
                    val longitude = String.format("%.4f", location.longitude)
                    "位置: $latitude, $longitude"
                }
            } catch (e: Exception) {
                // 地理编码异常时,显示经纬度作为备选
                val latitude = String.format("%.4f", location.latitude)
                val longitude = String.format("%.4f", location.longitude)
                "位置: $latitude, $longitude"
            }
        } ?: ""
    }

    private fun addTimestampToBitmap(bitmap: Bitmap): Bitmap {
        val width = bitmap.width
        val height = bitmap.height
        
        val config = bitmap.config ?: Bitmap.Config.ARGB_8888
        val result = Bitmap.createBitmap(width, height, config)
        
        val canvas = Canvas(result)
        canvas.drawBitmap(bitmap, 0f, 0f, null)
        
        // 设置检测时间水印
        val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())
        val locationText = getLocationText()
        
        val paint = Paint().apply {
            color = Color.WHITE
            textSize = width / 35f  // 与维修工单保持一致
            isAntiAlias = true
            style = Paint.Style.FILL
            setShadowLayer(3f, 1f, 1f, Color.BLACK)  // 添加阴影
        }
        
        // 计算时间文本位置(右下角)
        val timeBounds = Rect()
        paint.getTextBounds(timestamp, 0, timestamp.length, timeBounds)
        val timeTextWidth = paint.measureText(timestamp)
        val timeTextHeight = timeBounds.height()
        
        val timeX = width - timeTextWidth - width / 50f
        val timeY = height - timeTextHeight - height / 50f
        
        // 绘制时间文本
        canvas.drawText(timestamp, timeX, timeY, paint)
        
        // 如果有位置信息,绘制位置文本(时间上方)
        if (locationText.isNotEmpty()) {
            val locationBounds = Rect()
            paint.getTextBounds(locationText, 0, locationText.length, locationBounds)
            val locationTextWidth = paint.measureText(locationText)
            val locationTextHeight = locationBounds.height()
            
            val locationX = width - locationTextWidth - width / 50f
            val locationY = timeY - locationTextHeight - height / 100f  // 在时间上方,留出间距
            
            // 绘制位置文本
            canvas.drawText(locationText, locationX, locationY, paint)
        }
        
        return result
    }

    private fun bitmapToBase64(bitmap: Bitmap): String {
        val byteArrayOutputStream = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.JPEG, 90, byteArrayOutputStream)
        val byteArray = byteArrayOutputStream.toByteArray()
        return Base64.encodeToString(byteArray, Base64.DEFAULT)
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            CAMERA_PERMISSION_REQUEST_CODE -> {
                var cameraGranted = false
                var locationGranted = false
                
                for (i in permissions.indices) {
                    when (permissions[i]) {
                        Manifest.permission.CAMERA -> {
                            cameraGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED
                        }
                        Manifest.permission.ACCESS_FINE_LOCATION -> {
                            locationGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED
                            if (locationGranted) {
                                getCurrentLocation()
                            }
                        }
                    }
                }
                
                if (cameraGranted) {
                    dispatchTakePictureIntent()
                } else {
                    Toast.makeText(this, "需要相机权限才能拍照", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    private fun saveTestingOrder() {
        val updatedOrder = TestingOrder(
            orderId = testingOrder.orderId,
            planId = testingOrder.planId,
            engineerId = testingOrder.engineerId,
            planTime = testingOrder.planTime,
            testingDesc = etTestingDesc.text.toString(),
            status = testingOrder.status,
            photos = if (photoList.isNotEmpty()) photoList.joinToString(",") else null
        )

        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.updateTestingOrder(updatedOrder)
                if (response.isSuccessful) {
                    Toast.makeText(this@TestingTaskEditActivity, "保存成功", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this@TestingTaskEditActivity, "保存失败: ${response.message()}", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                Log.e("TestingTaskEditActivity", "保存异常", e)
                Toast.makeText(this@TestingTaskEditActivity, "保存异常", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun submitTestingOrder() {
        val updatedOrder = TestingOrder(
            orderId = testingOrder.orderId,
            planId = testingOrder.planId,
            engineerId = testingOrder.engineerId,
            planTime = testingOrder.planTime,
            testingDesc = etTestingDesc.text.toString(),
            status = "待审批",
            photos = if (photoList.isNotEmpty()) photoList.joinToString(",") else null
        )

        lifecycleScope.launch {
            try {
                val response = RetrofitClient.instance.updateTestingOrder(updatedOrder)
                if (response.isSuccessful) {
                    Toast.makeText(this@TestingTaskEditActivity, "提交成功,状态已更改为待审批", Toast.LENGTH_SHORT).show()
                    finish()
                } else {
                    Toast.makeText(this@TestingTaskEditActivity, "提交失败: ${response.message()}", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                Log.e("TestingTaskEditActivity", "提交异常", e)
                Toast.makeText(this@TestingTaskEditActivity, "提交异常", Toast.LENGTH_SHORT).show()
            }
        }
    }
}
package com.example.sanpaias.adapter

import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import com.example.sanpaias.R
import com.example.sanpaias.entity.TestingOrder
import com.example.sanpaias.activity.TestingTaskEditActivity

class TestingTaskAdapter(
    private val onTakeTask: (TestingOrder) -> Unit
) : RecyclerView.Adapter<TestingTaskAdapter.TaskViewHolder>() {

    private var tasks: List<TestingOrder> = emptyList()

    class TaskViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val cardView: CardView = itemView.findViewById(R.id.cardView)
        val tvOrderId: TextView = itemView.findViewById(R.id.tvOrderId)
        val tvFaultId: TextView = itemView.findViewById(R.id.tvFaultId)
        val tvStatus: TextView = itemView.findViewById(R.id.tvStatus)
        val tvRepairDesc: TextView = itemView.findViewById(R.id.tvRepairDesc)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_my_task, parent, false)
        return TaskViewHolder(view)
    }

    override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
        val task = tasks[position]
        
        holder.tvOrderId.text = "工单编号: ${task.orderId}"
        holder.tvFaultId.text = "计划编号: ${task.planId}"
        holder.tvStatus.text = "状态: ${task.status ?: "待处理"}"
        holder.tvRepairDesc.text = "检测描述: ${task.testingDesc ?: "暂无描述"}"
        
        // 修改点击事件,跳转到检测工单编辑页面
        holder.cardView.setOnClickListener {
            val context = holder.itemView.context
            val intent = Intent(context, TestingTaskEditActivity::class.java)
            // 为避免TransactionTooLargeException,只传递必要的ID
            intent.putExtra("order_id", task.orderId)
            intent.putExtra("plan_id", task.planId)
            context.startActivity(intent)
        }
    }

    override fun getItemCount() = tasks.size

    fun updateData(newTasks: List<TestingOrder>) {
        tasks = newTasks
        notifyDataSetChanged()
    }
}
package com.example.sanpaias.entity

import java.time.LocalDateTime

data class TestingPlan(
    val planId: Int? = null,
    val deviceId: String,
    val startTime: String,
    val endTime: String? = null,
    val frequency: String,
    val status: String = "待启用",
    val location: String
)
package com.example.sanpaias.entity

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class TestingOrder(
    val orderId: String,
    val planId: Int,
    val engineerId: String?,
    val planTime: String?,
    val testingDesc: String?,
    val status: String,
    val photos: String?
) : Parcelable
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:layout_margin="8dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <TextView
            android:id="@+id/tvPlanId"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="计划ID: 1"
            android:textSize="14sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/tvDeviceId"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:text="设备ID: DEV001"
            android:textSize="14sp" />

        <TextView
            android:id="@+id/tvLocation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:text="检测位置:"
            android:textSize="14sp" />

        <TextView
            android:id="@+id/tvFrequency"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:text="检测频率: 月"
            android:textSize="14sp" />

        <TextView
            android:id="@+id/tvStatus"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:text="状态: 启用"
            android:textSize="14sp" />

        <TextView
            android:id="@+id/tvTimeRange"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:text="时间范围: 2024-01-01 ~ 2024-12-31"
            android:textSize="14sp" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btnEnable"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_weight="1"
                android:text="启用" />

            <Button
                android:id="@+id/btnDisable"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="停用" />

        </LinearLayout>

    </LinearLayout>

</androidx.cardview.widget.CardView>
posted @ 2025-05-26 22:17  花落水无痕  阅读(17)  评论(0)    收藏  举报