From 85a2d7f51a32cd747e27ab68bc8699a54419b6c7 Mon Sep 17 00:00:00 2001
From: cloudroam <cloudroam>
Date: 星期一, 03 三月 2025 10:02:04 +0800
Subject: [PATCH] fix :包裹列表

---
 app/src/main/java/com/example/firstapp/repository/PackageRepository.kt    |   34 ++
 app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt  |  226 ++++++++++++++++
 app/src/main/res/layout/layout_week_stats.xml                             |   63 ++++
 app/src/main/java/com/example/firstapp/adapter/PackageAdapter.kt          |   51 +++
 app/src/main/java/com/example/firstapp/dao/PackageDao.kt                  |   49 +++
 app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt |   38 ++
 app/src/main/java/com/example/firstapp/model/DailyStat.kt                 |   16 +
 app/src/main/res/drawable/resource_package.xml                            |   15 +
 app/src/main/java/com/example/firstapp/model/CourierStat.kt               |   15 +
 app/src/main/java/com/example/firstapp/model/PackageInfo.kt               |   15 +
 app/src/main/java/com/example/firstapp/database/AppDatabase.kt            |   45 ++
 app/src/main/res/layout/fragment_dashboard.xml                            |  127 ++++++++
 app/src/main/res/layout/item_package_dashboard.xml                        |   57 ++++
 app/build.gradle                                                          |    3 
 14 files changed, 712 insertions(+), 42 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 82e8b39..75f7708 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -270,6 +270,7 @@
     // 滚轮滑动控件级联
     implementation 'com.contrarywind:Android-PickerView:4.1.9'
 
-
+    //图形化
+    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/adapter/PackageAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/PackageAdapter.kt
new file mode 100644
index 0000000..4abce71
--- /dev/null
+++ b/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)
+        }
+    }
+}
+
diff --git a/app/src/main/java/com/example/firstapp/dao/PackageDao.kt b/app/src/main/java/com/example/firstapp/dao/PackageDao.kt
new file mode 100644
index 0000000..e156386
--- /dev/null
+++ b/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)
+} 
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/database/AppDatabase.kt b/app/src/main/java/com/example/firstapp/database/AppDatabase.kt
index 2251240..b520136 100644
--- a/app/src/main/java/com/example/firstapp/database/AppDatabase.kt
+++ b/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,
diff --git a/app/src/main/java/com/example/firstapp/model/CourierStat.kt b/app/src/main/java/com/example/firstapp/model/CourierStat.kt
new file mode 100644
index 0000000..c112e7c
--- /dev/null
+++ b/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
+) 
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/model/DailyStat.kt b/app/src/main/java/com/example/firstapp/model/DailyStat.kt
new file mode 100644
index 0000000..dc61551
--- /dev/null
+++ b/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
+) 
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/model/PackageInfo.kt b/app/src/main/java/com/example/firstapp/model/PackageInfo.kt
new file mode 100644
index 0000000..d1e2005
--- /dev/null
+++ b/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-已取件
+)
+
diff --git a/app/src/main/java/com/example/firstapp/repository/PackageRepository.kt b/app/src/main/java/com/example/firstapp/repository/PackageRepository.kt
new file mode 100644
index 0000000..082178c
--- /dev/null
+++ b/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)
+        }
+    }
+} 
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
index 0be390f..992fcb1 100644
--- a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
+++ b/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
diff --git a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt
index e47a4a6..dae2211 100644
--- a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt
+++ b/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)
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/res/drawable/resource_package.xml b/app/src/main/res/drawable/resource_package.xml
new file mode 100644
index 0000000..d41543f
--- /dev/null
+++ b/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>
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
index 166ab0e..4d90e0c 100644
--- a/app/src/main/res/layout/fragment_dashboard.xml
+++ b/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>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_package_dashboard.xml b/app/src/main/res/layout/item_package_dashboard.xml
new file mode 100644
index 0000000..b0990bb
--- /dev/null
+++ b/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> 
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_week_stats.xml b/app/src/main/res/layout/layout_week_stats.xml
new file mode 100644
index 0000000..0a2d857
--- /dev/null
+++ b/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> 
\ No newline at end of file

--
Gitblit v1.9.3