| | |
| | | // 滚轮滑动控件级联 |
| | | implementation 'com.contrarywind:Android-PickerView:4.1.9' |
| | | |
| | | |
| | | //图形化 |
| | | implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' |
| | | |
| | | } |
对比新文件 |
| | |
| | | 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) |
| | | } |
| | | } |
| | | } |
| | | |
对比新文件 |
| | |
| | | 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) |
| | | } |
| | |
| | | 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 |
| | |
| | | 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 |
| | | ) |
| | |
| | | abstract fun codeDao(): CodeDao |
| | | abstract fun keywordDao(): KeywordDao |
| | | abstract fun reminderDao(): ReminderDao |
| | | abstract fun packageDao(): PackageDao |
| | | |
| | | companion object { |
| | | @Volatile |
| | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | 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") |
| | |
| | | `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, |
对比新文件 |
| | |
| | | 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 |
| | | ) |
对比新文件 |
| | |
| | | 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 |
| | | ) |
对比新文件 |
| | |
| | | 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-已取件 |
| | | ) |
| | | |
对比新文件 |
| | |
| | | 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) |
| | | } |
| | | } |
| | | } |
| | |
| | | package com.example.firstapp.ui.dashboard |
| | | |
| | | import com.example.firstapp.R |
| | | import android.os.Bundle |
| | | import android.view.LayoutInflater |
| | | import android.view.View |
| | |
| | | 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() { |
| | | |
| | |
| | | // 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 |
| | |
| | | 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) |
| | | } |
| | | } |
对比新文件 |
| | |
| | | <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> |
| | |
| | | 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> |
对比新文件 |
| | |
| | | <?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> |
对比新文件 |
| | |
| | | <?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> |