cloudroam
2025-03-03 85a2d7f51a32cd747e27ab68bc8699a54419b6c7
fix :包裹列表
已修改5个文件
已添加9个文件
754 ■■■■■ 文件已修改
app/build.gradle 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/PackageAdapter.kt 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/dao/PackageDao.kt 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/AppDatabase.kt 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/CourierStat.kt 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/DailyStat.kt 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/PackageInfo.kt 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/repository/PackageRepository.kt 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/resource_package.xml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_dashboard.xml 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_package_dashboard.xml 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/layout_week_stats.xml 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle
@@ -270,6 +270,7 @@
    // 滚轮滑动控件级联
    implementation 'com.contrarywind:Android-PickerView:4.1.9'
    //图形化
    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
app/src/main/java/com/example/firstapp/adapter/PackageAdapter.kt
对比新文件
@@ -0,0 +1,51 @@
package com.example.firstapp.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.firstapp.R
import com.example.firstapp.model.PackageInfo
import java.text.SimpleDateFormat
import java.util.Locale
class PackageAdapter : RecyclerView.Adapter<PackageAdapter.PackageViewHolder>() {
    private var packages = listOf<PackageInfo>()
    fun updatePackages(newPackages: List<PackageInfo>) {
        packages = newPackages
        notifyDataSetChanged()
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PackageViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_package_dashboard, parent, false)
        return PackageViewHolder(view)
    }
    override fun onBindViewHolder(holder: PackageViewHolder, position: Int) {
        holder.bind(packages[position])
    }
    override fun getItemCount() = packages.size
    class PackageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        private val imgCourier: ImageView = view.findViewById(R.id.img_courier)
        private val textCourierName: TextView = view.findViewById(R.id.text_courier_name)
        private val textTrackingNumber: TextView = view.findViewById(R.id.text_tracking_number)
        private val textTime: TextView = view.findViewById(R.id.text_time)
        fun bind(packageInfo: PackageInfo) {
            imgCourier.setImageResource(packageInfo.courierIcon)
            textCourierName.text = packageInfo.courierName
            textTrackingNumber.text = packageInfo.trackingNumber
            textTime.text = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
                .format(packageInfo.receivedTime)
        }
    }
}
app/src/main/java/com/example/firstapp/dao/PackageDao.kt
对比新文件
@@ -0,0 +1,49 @@
package com.example.firstapp.dao
import androidx.room.*
import com.example.firstapp.model.PackageInfo
import com.example.firstapp.model.CourierStat
import com.example.firstapp.model.DailyStat
import kotlinx.coroutines.flow.Flow
@Dao
interface PackageDao {
    @Query("SELECT * FROM packages WHERE date(receivedTime/1000, 'unixepoch', 'localtime') = date(:date/1000, 'unixepoch', 'localtime') ORDER BY receivedTime DESC")
    fun getPackagesByDay(date: Long): Flow<List<PackageInfo>>
    @Query("""
        SELECT * FROM packages
        WHERE strftime('%Y-%W', receivedTime/1000, 'unixepoch', 'localtime') =
              strftime('%Y-%W', :date/1000, 'unixepoch', 'localtime')
        ORDER BY receivedTime DESC
    """)
    fun getPackagesByWeek(date: Long): Flow<List<PackageInfo>>
    @Query("""
        SELECT * FROM CourierStat
        WHERE EXISTS (
            SELECT 1 FROM packages p
            WHERE p.courierName = CourierStat.courierName
            AND strftime('%Y-%W', p.receivedTime/1000, 'unixepoch', 'localtime') =
                strftime('%Y-%W', :date/1000, 'unixepoch', 'localtime')
        )
    """)
    fun getCourierStatsByWeek(date: Long): Flow<List<CourierStat>>
    @Query("""
        SELECT * FROM DailyStat
        WHERE EXISTS (
            SELECT 1 FROM packages p
            WHERE date(p.receivedTime/1000, 'unixepoch', 'localtime') = DailyStat.date
            AND strftime('%Y-%W', p.receivedTime/1000, 'unixepoch', 'localtime') =
                strftime('%Y-%W', :date/1000, 'unixepoch', 'localtime')
        )
    """)
    fun getDailyStatsByWeek(date: Long): Flow<List<DailyStat>>
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(packageInfo: PackageInfo)
    @Update
    fun update(packageInfo: PackageInfo)
}
app/src/main/java/com/example/firstapp/database/AppDatabase.kt
@@ -7,6 +7,7 @@
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.firstapp.dao.PackageDao
import com.example.firstapp.database.dao.CodeDao
import com.example.firstapp.database.dao.KeywordDao
import com.example.firstapp.database.dao.MsgDao
@@ -20,11 +21,23 @@
import com.example.firstapp.utils.TAG_LIST
import com.example.firstapp.database.ext.ConvertersDate
import com.example.firstapp.model.PackageInfo
import com.example.firstapp.model.CourierStat
import com.example.firstapp.model.DailyStat
@Database(
    entities = [ Msg::class, Code::class, KeywordEntity::class, Reminder::class],
    entities = [
        Msg::class,
        Code::class,
        KeywordEntity::class,
        Reminder::class,
        PackageInfo::class
    ],
    views = [
        CourierStat::class,
        DailyStat::class
    ],
    version = 20,
    exportSchema = false
)
@@ -34,6 +47,7 @@
    abstract fun codeDao(): CodeDao
    abstract fun keywordDao(): KeywordDao
    abstract fun reminderDao(): ReminderDao
    abstract fun packageDao(): PackageDao
    companion object {
        @Volatile
@@ -71,16 +85,6 @@
        private val MIGRATION_MSG = object : Migration(19, 20) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //database.execSQL("Create table Msg as Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
@@ -110,6 +114,23 @@
                        `isEnabled` INTEGER NOT NULL
                    )
                """)
                // 创建 CourierStat 视图
                database.execSQL("""
                    CREATE VIEW IF NOT EXISTS CourierStat AS
                    SELECT courierName, COUNT(*) as count
                    FROM packages
                    GROUP BY courierName
                """)
                // 创建 DailyStat 视图
                database.execSQL("""
                    CREATE VIEW IF NOT EXISTS DailyStat AS
                    SELECT date(receivedTime/1000, 'unixepoch', 'localtime') as date,
                           COUNT(*) as count
                    FROM packages
                    GROUP BY date(receivedTime/1000, 'unixepoch', 'localtime')
                """)
//                database.execSQL("""
//                   CREATE TABLE   IF NOT EXISTS `reminders` (
//                    id INTEGER PRIMARY KEY AUTOINCREMENT,
app/src/main/java/com/example/firstapp/model/CourierStat.kt
对比新文件
@@ -0,0 +1,15 @@
package com.example.firstapp.model
import androidx.room.DatabaseView
@DatabaseView(
    """
    SELECT courierName, COUNT(*) as count
    FROM packages
    GROUP BY courierName
    """
)
data class CourierStat(
    val courierName: String,
    val count: Int
)
app/src/main/java/com/example/firstapp/model/DailyStat.kt
对比新文件
@@ -0,0 +1,16 @@
package com.example.firstapp.model
import androidx.room.DatabaseView
@DatabaseView(
    """
    SELECT date(receivedTime/1000, 'unixepoch', 'localtime') as date,
           COUNT(*) as count
    FROM packages
    GROUP BY date(receivedTime/1000, 'unixepoch', 'localtime')
    """
)
data class DailyStat(
    val date: String,
    val count: Int
)
app/src/main/java/com/example/firstapp/model/PackageInfo.kt
对比新文件
@@ -0,0 +1,15 @@
package com.example.firstapp.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "packages")
data class PackageInfo(
    @PrimaryKey
    val trackingNumber: String,     // 快递单号
    val courierName: String,        // 快递公司名称
    val receivedTime: Long,         // 收件时间
    val courierIcon: Int,          // 快递公司图标资源ID
    val status: Int = 0            // 取件状态: 0-未取件, 1-已取件
)
app/src/main/java/com/example/firstapp/repository/PackageRepository.kt
对比新文件
@@ -0,0 +1,34 @@
package com.example.firstapp.repository
import com.example.firstapp.dao.PackageDao
import com.example.firstapp.model.PackageInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class PackageRepository(private val packageDao: PackageDao) {
    fun getPackages(date: Long, dateType: String): Flow<List<PackageInfo>> {
        return when(dateType) {
            "DAY" -> packageDao.getPackagesByDay(date)
            "WEEK" -> packageDao.getPackagesByWeek(date)
            else -> packageDao.getPackagesByDay(date)
        }
    }
    fun getCourierStats(date: Long) = packageDao.getCourierStatsByWeek(date)
    fun getDailyStats(date: Long) = packageDao.getDailyStatsByWeek(date)
    suspend fun insert(packageInfo: PackageInfo) {
        withContext(Dispatchers.IO) {
            packageDao.insert(packageInfo)
        }
    }
    suspend fun update(packageInfo: PackageInfo) {
        withContext(Dispatchers.IO) {
            packageDao.update(packageInfo)
        }
    }
}
app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
@@ -1,5 +1,6 @@
package com.example.firstapp.ui.dashboard
import com.example.firstapp.R
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -7,7 +8,20 @@
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.fragment.app.viewModels
import com.example.firstapp.databinding.FragmentDashboardBinding
import com.google.android.material.tabs.TabLayout
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.firstapp.adapter.PackageAdapter
import com.example.firstapp.model.PackageInfo
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.components.Legend
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.*
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter
import java.util.*
import java.text.SimpleDateFormat
class DashboardFragment : Fragment() {
@@ -17,24 +31,216 @@
    // onDestroyView.
    private val binding get() = _binding!!
    private val packageAdapter = PackageAdapter()
    private var currentDate = Calendar.getInstance()
    private var currentDateType = DateType.DAY
    private lateinit var barChart: BarChart
    private lateinit var pieChart: PieChart
    enum class DateType {
        DAY, WEEK, MONTH, YEAR
    }
    private val viewModel: DashboardViewModel by viewModels()
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val dashboardViewModel =
            ViewModelProvider(this).get(DashboardViewModel::class.java)
        _binding = FragmentDashboardBinding.inflate(inflater, container, false)
        val root: View = binding.root
        val textView: TextView = binding.textDashboard
        dashboardViewModel.text.observe(viewLifecycleOwner) {
            textView.text = it
        }
        return root
        return binding.root
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupRecyclerView()
        setupTabLayout()
        setupDatePicker()
        updateDateDisplay()
        setupWeekView(view)
        loadPackages()
    }
    private fun setupRecyclerView() {
        binding.recyclerPackages.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = packageAdapter
        }
    }
    private fun setupTabLayout() {
        binding.tabDateRange.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab?) {
                currentDateType = when(tab?.position) {
                    0 -> DateType.DAY
                    1 -> DateType.WEEK
                    2 -> DateType.MONTH
                    3 -> DateType.YEAR
                    else -> DateType.DAY
                }
                // 切换视图
                binding.viewFlipperStats.displayedChild = when(currentDateType) {
                    DateType.DAY -> 0
                    DateType.WEEK -> 1
                    else -> 0
                }
                updateDateDisplay()
//                loadPackages()
                observePackages()
            }
            override fun onTabUnselected(tab: TabLayout.Tab?) {}
            override fun onTabReselected(tab: TabLayout.Tab?) {}
        })
    }
    private fun setupDatePicker() {
        binding.btnPreviousDate.setOnClickListener {
            adjustDate(-1)
        }
        binding.btnNextDate.setOnClickListener {
            adjustDate(1)
        }
    }
    private fun adjustDate(amount: Int) {
        when (currentDateType) {
            DateType.DAY -> currentDate.add(Calendar.DAY_OF_MONTH, amount)
            DateType.WEEK -> currentDate.add(Calendar.WEEK_OF_YEAR, amount)
            DateType.MONTH -> currentDate.add(Calendar.MONTH, amount)
            DateType.YEAR -> currentDate.add(Calendar.YEAR, amount)
        }
        updateDateDisplay()
        loadPackages()
    }
    private fun updateDateDisplay() {
        val dateFormat = when (currentDateType) {
            DateType.DAY -> "yyyy年MM月dd日"
            DateType.WEEK -> "yyyy年第ww周"
            DateType.MONTH -> "yyyy年MM月"
            DateType.YEAR -> "yyyy年"
        }
        binding.textCurrentDate.text = SimpleDateFormat(dateFormat, Locale.getDefault())
            .format(currentDate.time)
    }
    private fun setupWeekView(view: View) {
        val weekStatsView = binding.layoutWeekStats.root
        barChart = weekStatsView.findViewById(R.id.chart_daily_packages)
        pieChart = weekStatsView.findViewById(R.id.chart_courier_distribution)
        setupBarChart()
        setupPieChart()
    }
    private fun setupBarChart() {
        // 配置柱状图
        barChart.apply {
            description.isEnabled = false
            setDrawGridBackground(false)
            legend.isEnabled = false
            // X轴设置
            xAxis.apply {
                position = XAxis.XAxisPosition.BOTTOM
                setDrawGridLines(false)
                valueFormatter = IndexAxisValueFormatter(getDayLabels())
            }
            // Y轴设置
            axisLeft.apply {
                setDrawGridLines(true)
                axisMinimum = 0f
            }
            axisRight.isEnabled = false
        }
        updateBarChartData()
    }
    private fun updateBarChartData() {
        viewModel.getDailyStats(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats ->
            val entries = stats.mapIndexed { index, stat ->
                BarEntry(index.toFloat(), stat.count.toFloat())
            }
            val dataSet = BarDataSet(entries, "包裹数量")
            dataSet.color = resources.getColor(R.color.purple_500)
            barChart.data = BarData(dataSet)
            barChart.invalidate()
        }
    }
    private fun setupPieChart() {
        // 配置饼图
        pieChart.apply {
            description.isEnabled = false
            setUsePercentValues(true)
            setDrawEntryLabels(false)
            legend.isEnabled = true
            legend.verticalAlignment = Legend.LegendVerticalAlignment.CENTER
            legend.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT
            legend.orientation = Legend.LegendOrientation.VERTICAL
        }
        updatePieChartData()
    }
    private fun updatePieChartData() {
        viewModel.getCourierStats(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats ->
            val entries = stats.map { stat ->
                PieEntry(stat.count.toFloat(), stat.courierName)
            }
            val dataSet = PieDataSet(entries, "快递公司分布")
            dataSet.colors = listOf(
                resources.getColor(R.color.purple_500),
                resources.getColor(R.color.teal_200)
            )
            pieChart.data = PieData(dataSet)
            pieChart.invalidate()
        }
    }
    private fun getDayLabels(): Array<String> {
        return arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日")
    }
    private fun loadPackages() {
        // 这里应该从数据库或网络加载数据
        // 这里使用模拟数据作为示例
        val mockPackages = listOf(
            PackageInfo(
                trackingNumber = "14-6-7023",
                courierName = "某快递",
                receivedTime = System.currentTimeMillis(),
                courierIcon = R.drawable.data
            ),
            PackageInfo(
                trackingNumber = "230721",
                courierName = "京东",
                receivedTime = System.currentTimeMillis() - 3600000,
                courierIcon = R.drawable.data
            )
        )
        packageAdapter.updatePackages(mockPackages)
        binding.textPackageCount.text = "${mockPackages.size}个"
    }
    private fun observePackages() {
        viewModel.getPackages(
            currentDate.timeInMillis,
            currentDateType.name
        ).observe(viewLifecycleOwner) { packages ->
            when (currentDateType) {
                DateType.WEEK -> {
                    // 更新图表数据
                    updateBarChartData()
                    updatePieChartData()
                }
                else -> {
                    packageAdapter.updatePackages(packages)
                }
            }
            binding.textPackageCount.text = "${packages.size}个"
        }
    }
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt
@@ -1,13 +1,35 @@
package com.example.firstapp.ui.dashboard
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.example.firstapp.AppDatabase
import com.example.firstapp.model.PackageInfo
import com.example.firstapp.repository.PackageRepository
import kotlinx.coroutines.launch
class DashboardViewModel : ViewModel() {
    private val _text = MutableLiveData<String>().apply {
        value = "This is dashboard Fragment"
class DashboardViewModel(application: Application) : AndroidViewModel(application) {
    private val repository: PackageRepository
    init {
        val packageDao = AppDatabase.getInstance(application).packageDao()
        repository = PackageRepository(packageDao)
    }
    val text: LiveData<String> = _text
    fun getPackages(date: Long, dateType: String) =
        repository.getPackages(date, dateType).asLiveData()
    fun getCourierStats(date: Long) = repository.getCourierStats(date).asLiveData()
    fun getDailyStats(date: Long) = repository.getDailyStats(date).asLiveData()
    fun updatePackageStatus(packageInfo: PackageInfo) = viewModelScope.launch {
        repository.update(packageInfo)
    }
    fun insert(packageInfo: PackageInfo) = viewModelScope.launch {
        repository.insert(packageInfo)
    }
}
app/src/main/res/drawable/resource_package.xml
对比新文件
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="48dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="48dp">
    <path android:fillColor="#FFE89C" android:pathData="M787.6,387.8v157.7c0,16.6 -13.4,30 -30,30s-30,-13.4 -30,-30v-137.3l58.7,-29c0.6,1.8 0.9,3.6 1.1,5.6 0.1,1 0.2,2 0.2,3z"/>
    <path android:fillColor="#FFC477" android:pathData="M1024,299.6v424.7c0,26.9 -14.9,51 -39,63l-452.4,229.2a70.6,70.6 0,0 1,-31.4 7.4h-0.4c-11.1,-0.1 -22.1,-2.7 -32.2,-8l-0.2,-0.1 -430.7,-228.6C14.5,775.1 0,751.2 0,724.9V299.1a70.4,70.4 0,0 1,8.5 -33.6,70.4 70.4,0 0,1 29.3,-28.8L468.7,8c20,-10.4 43.9,-10.6 64,-0.6l0.2,0.1 452.2,229.1c13.3,6.6 23.8,17 30.5,29.4 5.5,10.1 8.5,21.6 8.5,33.6z"/>
    <path android:fillColor="#FFB655" android:pathData="M1024,299.6v424.7c0,26.9 -14.9,51 -39,63l-452.4,229.2a70.6,70.6 0,0 1,-31.4 7.4h-0.4V520.4l514.7,-254.3c5.5,10.1 8.5,21.6 8.5,33.6z"/>
    <path android:fillColor="#FFD399" android:pathData="M1015.5,266l-229.2,113.3 -56.4,27.9 -229.1,113.2L8.5,265.5a70.4,70.4 0,0 1,29.3 -28.8l184.7,-98 64,-34 182.1,-96.6A70.3,70.3 0,0 1,500.8 0a70.5,70.5 0,0 1,31.8 7.4l0.2,0.1 452.2,229.1c13.3,6.6 23.8,17 30.5,29.4z"/>
    <path android:fillColor="#FFFCE5" android:pathData="M787.6,387.8v157.7c0,16.6 -13.4,30 -30,30s-30,-13.4 -30,-30v-139.6l-226.8,-120 -278.3,-147.3 64,-34 214.3,113.4 270.8,143.3 0.1,0.1c0.6,0.3 1.1,0.7 1.7,1 0.3,0.2 0.6,0.4 0.9,0.6l0,0c2.3,1.6 4.4,3.5 6.1,5.5 0.1,0.1 0.2,0.2 0.2,0.3 0.4,0.5 0.8,1.1 1.2,1.6 0.2,0.3 0.4,0.5 0.6,0.8 0.3,0.4 0.5,0.8 0.8,1.2 0.3,0.4 0.5,0.9 0.8,1.3 0.1,0.3 0.3,0.5 0.4,0.8 0.3,0.6 0.6,1.3 0.9,1.9 0,0.1 0.1,0.2 0.1,0.3 0.3,0.8 0.6,1.7 0.9,2.5 0.6,1.8 0.9,3.6 1.1,5.6 0.1,1 0.2,2 0.2,3z"/>
    <path android:fillColor="#B5B51F" android:pathData="M352.2,602.8c-8.4,-14.3 -26.8,-19.1 -41,-10.7l-92.9,54.5 -19.5,-35.3c-8,-14.5 -26.2,-19.8 -40.7,-11.8s-19.8,26.2 -11.8,40.7l34.4,62.3c0.1,0.1 0.2,0.3 0.3,0.4 0,0.1 0.1,0.2 0.1,0.3 0.1,0.2 0.2,0.4 0.4,0.6 0.2,0.3 0.4,0.6 0.5,0.9 0.2,0.3 0.4,0.6 0.6,0.8 0.2,0.3 0.4,0.6 0.6,0.8 0.2,0.3 0.4,0.5 0.6,0.8 0.2,0.3 0.4,0.5 0.7,0.8 0.2,0.3 0.5,0.5 0.7,0.7 0.2,0.2 0.5,0.5 0.7,0.7 0.2,0.2 0.5,0.5 0.7,0.7 0.2,0.2 0.5,0.4 0.7,0.6 0.3,0.2 0.5,0.5 0.8,0.7 0.2,0.2 0.5,0.4 0.7,0.6 0.3,0.2 0.6,0.4 0.9,0.6 0.2,0.2 0.5,0.3 0.7,0.5 0.3,0.2 0.6,0.4 0.9,0.6 0.2,0.2 0.5,0.3 0.7,0.4 0.3,0.2 0.6,0.3 1,0.5 0.3,0.1 0.5,0.3 0.8,0.4 0.3,0.2 0.7,0.3 1,0.5 0.3,0.1 0.5,0.2 0.8,0.3 0.3,0.1 0.7,0.3 1,0.4 0.3,0.1 0.5,0.2 0.8,0.3 0.3,0.1 0.7,0.2 1,0.3 0.3,0.1 0.6,0.2 0.8,0.2 0.3,0.1 0.7,0.2 1,0.2 0.3,0.1 0.6,0.1 0.9,0.2 0.3,0.1 0.6,0.1 1,0.2 0.3,0.1 0.6,0.1 1,0.2 0.3,0 0.6,0.1 0.9,0.1 0.3,0 0.7,0.1 1,0.1 0.3,0 0.5,0 0.8,0 0.4,0 0.8,0 1.1,0 0.6,0 1.3,-0 1.9,-0.1 0.2,-0 0.3,-0 0.5,-0 0.5,-0 0.9,-0.1 1.4,-0.1 0.2,-0 0.5,-0.1 0.7,-0.1 0.4,-0.1 0.8,-0.1 1.2,-0.2 0.3,-0.1 0.6,-0.1 0.8,-0.2 0.3,-0.1 0.7,-0.2 1,-0.2 0.3,-0.1 0.6,-0.2 0.9,-0.3 0.3,-0.1 0.6,-0.2 1,-0.3 0.3,-0.1 0.6,-0.2 0.9,-0.3 0.3,-0.1 0.6,-0.2 0.9,-0.4 0.3,-0.1 0.6,-0.3 0.9,-0.4 0.3,-0.1 0.6,-0.3 0.9,-0.4 0.3,-0.1 0.6,-0.3 0.9,-0.4 0.2,-0.1 0.4,-0.2 0.6,-0.3 0.1,-0 0.2,-0.1 0.2,-0.1 0.2,-0.1 0.3,-0.2 0.4,-0.2l119.4,-70.1c14.3,-8.4 19.1,-26.8 10.7,-41z"/>
</vector>
app/src/main/res/layout/fragment_dashboard.xml
@@ -6,17 +6,122 @@
    android:layout_height="match_parent"
    tools:context=".ui.dashboard.DashboardFragment">
    <TextView
        android:id="@+id/text_dashboard"
    <!-- 时间范围选择器 -->
    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_date_range"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:textAlignment="center"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
        app:layout_constraintTop_toTopOf="parent">
        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="天" />
        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="周" />
        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="月" />
        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="年" />
    </com.google.android.material.tabs.TabLayout>
    <!-- 日期选择器 -->
    <LinearLayout
        android:id="@+id/layout_date_picker"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="16dp"
        app:layout_constraintTop_toBottomOf="@id/tab_date_range">
        <ImageButton
            android:id="@+id/btn_previous_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="?attr/selectableItemBackground"
            android:src="@android:drawable/ic_media_previous" />
        <TextView
            android:id="@+id/text_current_date"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:textSize="16sp"
            tools:text="2025年1月14日" />
        <ImageButton
            android:id="@+id/btn_next_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="?attr/selectableItemBackground"
            android:src="@android:drawable/ic_media_next" />
    </LinearLayout>
    <!-- 包裹统计卡片 -->
    <androidx.cardview.widget.CardView
        android:id="@+id/card_package_stats"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        app:cardCornerRadius="8dp"
        app:cardElevation="4dp"
        app:layout_constraintTop_toBottomOf="@id/layout_date_picker">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="16dp">
            <ImageView
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:src="@drawable/resource_package" />
            <TextView
                android:id="@+id/text_package_count"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginStart="16dp"
                android:textSize="24sp"
                android:textStyle="bold"
                tools:text="4个" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
    <!-- 包裹列表 -->
    <ViewFlipper
        android:id="@+id/view_flipper_stats"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/card_package_stats">
        <!-- 日视图 -->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_packages"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <!-- 周视图 -->
        <include
            android:id="@+id/layout_week_stats"
            layout="@layout/layout_week_stats"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <!-- 月视图和年视图可以根据需要添加 -->
    </ViewFlipper>
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/layout/item_package_dashboard.xml
对比新文件
@@ -0,0 +1,57 @@
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="16dp"
    android:layout_marginVertical="4dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="2dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="12dp">
        <ImageView
            android:id="@+id/img_courier"
            android:layout_width="40dp"
            android:layout_height="40dp"
            tools:src="@android:drawable/ic_menu_gallery" />
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="12dp"
            android:layout_weight="1"
            android:orientation="vertical">
            <TextView
                android:id="@+id/text_courier_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="16sp"
                android:textStyle="bold"
                tools:text="某快递" />
            <TextView
                android:id="@+id/text_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:textColor="@android:color/darker_gray"
                android:textSize="12sp"
                tools:text="2025-01-14 10:30" />
        </LinearLayout>
        <TextView
            android:id="@+id/text_tracking_number"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/darker_gray"
            tools:text="14-6-7023" />
    </LinearLayout>
</androidx.cardview.widget.CardView>
app/src/main/res/layout/layout_week_stats.xml
对比新文件
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <!-- 包裹总数统计卡片 -->
    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="16dp">
            <ImageView
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:src="@drawable/resource_package" />
            <TextView
                android:id="@+id/text_total_packages"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginStart="16dp"
                android:textSize="24sp"
                android:textStyle="bold"
                android:text="4个" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
    <!-- 每日包裹数量柱状图 -->
    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginHorizontal="16dp"
        android:layout_marginBottom="16dp">
        <com.github.mikephil.charting.charts.BarChart
            android:id="@+id/chart_daily_packages"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="8dp" />
    </androidx.cardview.widget.CardView>
    <!-- 快递公司占比饼图 -->
    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginHorizontal="16dp"
        android:layout_marginBottom="16dp">
        <com.github.mikephil.charting.charts.PieChart
            android:id="@+id/chart_courier_distribution"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="8dp" />
    </androidx.cardview.widget.CardView>
</LinearLayout>