Forráskód Böngészése

feat: 钱包页面

DoggyZhang 3 hónapja
szülő
commit
9041bb6237
60 módosított fájl, 2739 hozzáadás és 512 törlés
  1. 16 401
      app/src/main/java/com/adealink/weparty/commonui/widget/wheel/WheelDatePickerDialog.kt
  2. 470 0
      app/src/main/java/com/adealink/weparty/commonui/widget/wheel/WheelDatePickerFragment.kt
  3. 8 0
      app/src/main/java/com/adealink/weparty/module/wallet/Router.kt
  4. 1 0
      app/src/main/java/com/adealink/weparty/module/wallet/data/WalletData.kt
  5. BIN
      app/src/main/res/drawable-xhdpi/common_app_wallet_detail_ic.png
  6. 6 0
      app/src/main/res/drawable-xhdpi/common_wallet_bean_bg.xml
  7. 0 0
      app/src/main/res/drawable-xhdpi/common_wallet_coin_bg.xml
  8. 0 0
      app/src/main/res/drawable-xhdpi/common_wallet_diamond_bg.xml
  9. 6 76
      app/src/main/res/layout/dialog_wheel_date_picker.xml
  10. 79 0
      app/src/main/res/layout/fragment_wheel_date_picker.xml
  11. 1 0
      app/src/main/res/values/colors.xml
  12. 1 0
      app/src/main/res/values/strings.xml
  13. 1 10
      module/profile/src/main/java/com/adealink/weparty/profile/me/comp/MeWalletComp.kt
  14. 2 2
      module/profile/src/main/res/layout/layout_me_wallet.xml
  15. 10 1
      module/wallet/src/main/AndroidManifest.xml
  16. 31 1
      module/wallet/src/main/java/com/adealink/weparty/wallet/WalletActivity.kt
  17. 55 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/comp/MyBalanceComp.kt
  18. 36 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/comp/MyIncomeComp.kt
  19. 35 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/comp/WalletActivityComp.kt
  20. 7 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/datasource/remote/WalletHttpService.kt
  21. 140 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/WalletDetailActivity.kt
  22. 46 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/bean/BeanDetailItemViewBinder.kt
  23. 60 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/bean/BeanDetailViewModel.kt
  24. 81 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/bean/BeanFragment.kt
  25. 46 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/coin/CoinDetailItemViewBinder.kt
  26. 60 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/coin/CoinDetailViewModel.kt
  27. 82 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/coin/CoinFragment.kt
  28. 37 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/data/DetailData.kt
  29. 46 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/diamond/DiamondDetailItemViewBinder.kt
  30. 60 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/diamond/DiamondDetailViewModel.kt
  31. 82 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/diamond/DiamondFragment.kt
  32. 84 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/filter/DetailDateFilterDialog.kt
  33. 38 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/tab/TabManager.kt
  34. 50 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/detail/tab/WalletTab.kt
  35. 3 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/manager/IWalletListener.kt
  36. 11 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/manager/IWalletManager.kt
  37. 59 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/manager/WalletManager.kt
  38. 20 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/viewmodel/WalletDetailViewModel.kt
  39. 23 17
      module/wallet/src/main/java/com/adealink/weparty/wallet/viewmodel/WalletViewModel.kt
  40. 15 0
      module/wallet/src/main/java/com/adealink/weparty/wallet/viewmodel/WalletViewModelFactory.kt
  41. 5 0
      module/wallet/src/main/res/color/wallet_filter_date_button_text_sel.xml
  42. BIN
      module/wallet/src/main/res/drawable-xhdpi/wallet_filter_ic.png
  43. 5 0
      module/wallet/src/main/res/drawable/wallet_detail_item_bg.xml
  44. 8 0
      module/wallet/src/main/res/drawable/wallet_detail_item_bottom_bg.xml
  45. 8 0
      module/wallet/src/main/res/drawable/wallet_detail_item_top_bg.xml
  46. 17 0
      module/wallet/src/main/res/drawable/wallet_filter_date_button_bg_sel.xml
  47. 93 4
      module/wallet/src/main/res/layout/activity_wallet.xml
  48. 94 0
      module/wallet/src/main/res/layout/activity_wallet_detail.xml
  49. 97 0
      module/wallet/src/main/res/layout/dialog_wallet_detail_filter.xml
  50. 29 0
      module/wallet/src/main/res/layout/fragment_detail_bean.xml
  51. 30 0
      module/wallet/src/main/res/layout/fragment_detail_coin.xml
  52. 29 0
      module/wallet/src/main/res/layout/fragment_detail_diamond.xml
  53. 80 0
      module/wallet/src/main/res/layout/item_bean_detail.xml
  54. 80 0
      module/wallet/src/main/res/layout/item_coin_detail.xml
  55. 80 0
      module/wallet/src/main/res/layout/item_diamond_detail.xml
  56. 4 0
      module/wallet/src/main/res/layout/layout_wallet_activity.xml
  57. 153 0
      module/wallet/src/main/res/layout/layout_wallet_balance.xml
  58. 23 0
      module/wallet/src/main/res/layout/layout_wallet_detail_tab_item.xml
  59. 90 0
      module/wallet/src/main/res/layout/layout_wallet_income.xml
  60. 6 0
      module/wallet/src/main/res/values/strings.xml

+ 16 - 401
app/src/main/java/com/adealink/weparty/commonui/widget/wheel/WheelDatePickerDialog.kt

@@ -3,18 +3,16 @@ package com.adealink.weparty.commonui.widget.wheel
 import android.os.Bundle
 import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.mvvm.view.viewBinding
-import com.adealink.frame.util.getBirthStr
 import com.adealink.frame.util.onClick
-import com.adealink.frame.util.timeToYMDHM
 import com.adealink.weparty.R
-import com.adealink.weparty.commonui.MIN_YEAR
 import com.adealink.weparty.commonui.dialogfragment.data.DatePickerType
-import com.adealink.weparty.commonui.dialogfragment.data.Month
-import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.widget.BottomDialogFragment
+import com.adealink.weparty.commonui.widget.wheel.WheelDatePickerFragment.Companion.EXTRA_CURR_MINUTE_SCOPE
+import com.adealink.weparty.commonui.widget.wheel.WheelDatePickerFragment.Companion.EXTRA_INIT_DATE
+import com.adealink.weparty.commonui.widget.wheel.WheelDatePickerFragment.Companion.EXTRA_TARGET_DAY_SCOPE
+import com.adealink.weparty.commonui.widget.wheel.WheelDatePickerFragment.Companion.EXTRA_TARGET_MONTH_SCOPE
+import com.adealink.weparty.commonui.widget.wheel.WheelDatePickerFragment.Companion.EXTRA_TYPE
 import com.adealink.weparty.databinding.DialogWheelDatePickerBinding
-import java.util.Calendar
-import java.util.Date
 
 /**
  * 日期选择控件;
@@ -22,16 +20,9 @@ import java.util.Date
 class WheelDatePickerDialog : BottomDialogFragment(R.layout.dialog_wheel_date_picker) {
 
     companion object {
-        val SIMPLE_NAME: String = WheelDatePickerDialog::class.java.simpleName
+        const val EXTRA_TIPS = "extra_tips"
+        const val EXTRA_CONFIRM_BUTTON_TEXT = "extra_confirm_button_text"
 
-        private const val EXTRA_INIT_DATE = "extra_init_date"
-        private const val EXTRA_TIPS = "extra_tips"
-        private const val EXTRA_TARGET_MONTH_SCOPE = "extra_target_month_scope"
-        private const val EXTRA_TARGET_DAY_SCOPE = "extra_target_day_scope"
-        private const val EXTRA_CURR_MINUTE_SCOPE = "extra_curr_minute_scope"
-        private const val EXTRA_TYPE = "extra_type"
-
-        private const val EXTRA_CONFIRM_BUTTON_TEXT = "extra_confirm_button_text"
         fun newInstance(
             timestamp: Long = System.currentTimeMillis(),
             tips: String? = null,
@@ -67,417 +58,41 @@ class WheelDatePickerDialog : BottomDialogFragment(R.layout.dialog_wheel_date_pi
 
     private val binding by viewBinding(DialogWheelDatePickerBinding::bind)
 
-    private val yearList = mutableListOf<String>()
-    private val monthList =
-        mutableListOf<Pair<Int, String>>()  //first用于索引及获取相应时间戳,second用于日期控件滚轮文案显示
-    private val dayList = mutableListOf<String>()
-    private var hourList = arrayListOf<String>()
-    private var minuteList = arrayListOf<String>()
-
-    private var initTimestamp = System.currentTimeMillis()
     private var title: String = getCompatString(R.string.common_choose_date)
-    private var targetMonthScope: Int = -2   //-N选取前N个月  N选取后N个月
-    private var targetDayScope: Int = 0   //-N选取前N天  N选取后N天
-    private var currMinuteScope: Int = 0   //选取开始的小时数,1表示选取当前时间一小时
-    override val dimAmount: Float = 0.2f
-
-    private var selectYear = 0
-    private var selectMonth = 0
-    private var selectDay = 0
-    private var selectHour = 0
-    private var selectMinute = 0
-
-    private var currYear = 0
-    private var targetYear = MIN_YEAR
-    private var currMonth = 1
-    private var targetMonth = 1
-    private var currDay = 1
-    private var targetDay = 1
-    private var targetHour = 1
-    private var targetMinute = 1
-    private var currHour = 1
-    private var currMinute = 1
-    private var type: Int = DatePickerType.YMD.ordinal
     private var confirmButtonText: String = getCompatString(R.string.commonui_confirm)
+
     var selectBirthdayCallback: ISelectDateCallback? = null
 
+    private lateinit var datePickerFragment: WheelDatePickerFragment
 
     private fun parseIntent() {
-        arguments?.getLong(EXTRA_INIT_DATE)?.let {
-            initTimestamp = it
-        }
-        arguments?.getInt(EXTRA_TARGET_MONTH_SCOPE)?.let {
-            targetMonthScope = it
-        }
-        arguments?.getInt(EXTRA_TARGET_DAY_SCOPE)?.let {
-            targetDayScope = it
-        }
-        arguments?.getInt(EXTRA_CURR_MINUTE_SCOPE)?.let {
-            currMinuteScope = it
-        }
-        arguments?.getInt(EXTRA_TYPE)?.let {
-            type = it
-        }
         arguments?.getString(EXTRA_CONFIRM_BUTTON_TEXT)?.let {
             confirmButtonText = it
         }
-        if (initTimestamp <= 0) {
-            initTimestamp = System.currentTimeMillis()
-        }
         arguments?.getString(EXTRA_TIPS)?.let {
             title = it
         }
     }
 
-    /**
-     * - get(Calendar.MONTH) 比真实的月份少1;
-     */
-    private fun initDataPicker() {
-        val cal = Calendar.getInstance()
-        // 当前的时间
-        if (currMinuteScope != 0) {
-            cal.add(Calendar.MINUTE, currMinuteScope)
-        }
-        currYear = cal.get(Calendar.YEAR)
-        currMonth = cal.get(Calendar.MONTH) + 1
-        currDay = cal.get(Calendar.DAY_OF_MONTH)
-        currHour = cal.get(Calendar.HOUR_OF_DAY)
-        currMinute = cal.get(Calendar.MINUTE)
-        val currTimestamp = cal.timeInMillis
-        // 选择月份或日
-        if (targetDayScope != 0) {
-            cal.add(Calendar.DATE, targetDayScope)
-        } else {
-            cal.add(Calendar.MONTH, targetMonthScope)
-        }
-//        targetYear = cal.get(Calendar.YEAR)
-        targetMonth = cal.get(Calendar.MONTH) + 1
-        targetDay = cal.get(Calendar.DAY_OF_MONTH)
-        targetHour = cal.get(Calendar.HOUR_OF_DAY)
-        targetMinute = cal.get(Calendar.MINUTE)
-        val targetTimestamp = cal.timeInMillis
-        //防越界
-        if (targetTimestamp > currTimestamp) {
-            if (currTimestamp > initTimestamp || initTimestamp > targetTimestamp) {
-                initTimestamp = currTimestamp
-            }
-        } else {
-            if (currTimestamp < initTimestamp || initTimestamp < targetTimestamp) {
-                initTimestamp = currTimestamp
-            }
-        }
-        // 初始化选中的时间
-        val date = Date(initTimestamp)
-        cal.time = date
-        selectYear = cal.get(Calendar.YEAR)
-        selectMonth = cal.get(Calendar.MONTH) + 1
-        selectDay = cal.get(Calendar.DAY_OF_MONTH)
-        selectHour = cal.get(Calendar.HOUR_OF_DAY)
-        selectMinute = cal.get(Calendar.MINUTE)
-        // 填充列表
-        if (targetYear < currYear) {
-            for (year in targetYear..currYear) {
-                yearList.add("$year")
-            }
-        } else {
-            for (year in currYear..targetYear) {
-                yearList.add("$year")
-            }
-        }
-    }
-
-    private fun inflateMonthList(month: Int) {
-        when (month) {
-            Month.JANUARY.value -> monthList.add(
-                Pair(
-                    1,
-                    getCompatString(R.string.commonui_january)
-                )
-            )
-            Month.FEBRUARY.value -> monthList.add(
-                Pair(
-                    2,
-                    getCompatString(R.string.commonui_february)
-                )
-            )
-            Month.MARCH.value -> monthList.add(Pair(3, getCompatString(R.string.commonui_march)))
-            Month.APRIL.value -> monthList.add(Pair(4, getCompatString(R.string.commonui_april)))
-            Month.MAY.value -> monthList.add(Pair(5, getCompatString(R.string.commonui_may)))
-            Month.JUNE.value -> monthList.add(Pair(6, getCompatString(R.string.commonui_june)))
-            Month.JULY.value -> monthList.add(Pair(7, getCompatString(R.string.commonui_july)))
-            Month.AUGUST.value -> monthList.add(Pair(8, getCompatString(R.string.commonui_august)))
-            Month.SEPTEMBER.value -> monthList.add(
-                Pair(
-                    9,
-                    getCompatString(R.string.commonui_september)
-                )
-            )
-            Month.OCTOBER.value -> monthList.add(
-                Pair(
-                    10,
-                    getCompatString(R.string.commonui_october)
-                )
-            )
-            Month.NOVEMBER.value -> monthList.add(
-                Pair(
-                    11,
-                    getCompatString(R.string.commonui_november)
-                )
-            )
-            else -> monthList.add(Pair(12, getCompatString(R.string.commonui_december)))
-        }
-    }
 
     override fun initViews() {
         parseIntent()
-        initDataPicker()
         binding.tvTitle.text = title
         binding.btnConfirm.text = confirmButtonText
-        binding.wheelYear.setEntries(yearList)
-        updateMonthList()
 
-        // 设置选中x
-        binding.wheelYear.currentIndex = selectYear - targetYear
-        binding.wheelMonth.currentIndex = monthList.indexOfFirst { it.first == selectMonth }
-        binding.wheelDay.currentIndex = dayList.indexOf(selectDay.toString())
-        binding.wheelHour.currentIndex = hourList.indexOf(selectHour.toString())
-        binding.wheelMinute.currentIndex = minuteList.indexOf(
-            if (selectMinute < 10) {
-                "0$selectMinute"
-            } else {
-                selectMinute.toString()
-            }
-        )
-        when (type) {
-            DatePickerType.YMDHM.ordinal -> {  //精确到分钟
-                binding.wheelDay.setItemWidth(100)
-                binding.wheelMonth.setItemWidth(200)
-                binding.wheelYear.setItemWidth(200)
-                binding.wheelHour.show()
-                binding.wheelMinute.show()
-                binding.wheelHour.setOnWheelChangedListener { _, _, newIndex ->
-                    selectHour = hourList.getOrNull(newIndex)?.toInt() ?: 0
-                    updateMinuteList()
-                    selectMinute = minuteList.getOrNull(0)?.toInt() ?: 0
-                }
-                binding.wheelMinute.setOnWheelChangedListener { _, _, newIndex ->
-                    selectMinute = minuteList.getOrNull(newIndex)?.toInt() ?: 0
-                }
-            }
+        datePickerFragment = WheelDatePickerFragment()
+        childFragmentManager.beginTransaction()
+            .replace(binding.flDatePicker.id, datePickerFragment, "WheelDatePickerFragment")
+            .commitAllowingStateLoss()
 
-            else -> {}
-        }
-        binding.wheelYear.setOnWheelChangedListener { _, _, newIndex ->
-            selectYear = yearList.getOrNull(newIndex)?.toInt() ?: 0
-            updateMonthList()
-            selectMonth = monthList.getOrNull(0)?.first ?: 0
-            updateDayList()
-            selectDay = dayList.getOrNull(0)?.toInt() ?: 0
-            selectHour = hourList.getOrNull(0)?.toInt() ?: 0
-            selectMinute = minuteList.getOrNull(0)?.toInt() ?: 0
-        }
-        binding.wheelMonth.setOnWheelChangedListener { _, _, newIndex ->
-            selectMonth = monthList.getOrNull(newIndex)?.first ?: 0
-            updateDayList()
-            selectDay = dayList.getOrNull(0)?.toInt() ?: 0
-            selectHour = hourList.getOrNull(0)?.toInt() ?: 0
-            selectMinute = minuteList.getOrNull(0)?.toInt() ?: 0
-        }
-        binding.wheelDay.setOnWheelChangedListener { _, _, newIndex ->
-            selectDay = dayList.getOrNull(newIndex)?.toInt() ?: 0
-            updateHourList()
-            selectHour = hourList.getOrNull(0)?.toInt() ?: 0
-            selectMinute = minuteList.getOrNull(0)?.toInt() ?: 0
-        }
         binding.btnConfirm.onClick {
-            val calendar = Calendar.getInstance()
-            calendar.clear()
-            if (type == DatePickerType.YMDHM.ordinal) {
-                calendar.set(selectYear, selectMonth - 1, selectDay, selectHour, selectMinute)
-            } else {
-                calendar.set(selectYear, selectMonth - 1, selectDay)
-            }
-            val timestamp = calendar.timeInMillis
-            selectBirthdayCallback?.onSelectComplete(
-                timestamp,
-                if (type == DatePickerType.YMDHM.ordinal) {
-                    timeToYMDHM(timestamp)
-                } else {
-                    getBirthStr(timestamp)
-                }
-            )
+            datePickerFragment.getSelectedDate({ timestamp: Long, timeStr: String ->
+                selectBirthdayCallback?.onSelectComplete(timestamp, timeStr)
+            })
             dismiss()
         }
     }
 
-
-    /**
-     * 更新可选择的月份;
-     * 1. 只有1年:直接从小到大填充月份;注意目标月份是否大于当前月份
-     * 2. 跨年:[targetMonth, 12] 、[1, currMonth]
-     */
-    private fun updateMonthList() {
-        monthList.clear()
-        val startMonth = if (yearList.size < 2) {
-            if (targetMonth > currMonth) currMonth else targetMonth
-        } else {
-            if (currYear < targetYear) {
-                if (selectYear.toString() == yearList.getOrNull(0)) currMonth else 1
-            } else {
-                if (selectYear.toString() == yearList.getOrNull(0)) targetMonth else 1
-            }
-
-        }
-
-        val endMonth = if (yearList.size < 2) {
-            if (targetMonth > currMonth) targetMonth else currMonth
-        } else {
-            if (currYear < targetYear) {
-                if (selectYear.toString() == yearList.getOrNull(0)) 12 else targetMonth
-            } else {
-                if (selectYear.toString() == yearList.getOrNull(0)) 12 else currMonth
-            }
-
-        }
-
-        for (month in startMonth until endMonth + 1) {
-            inflateMonthList(month)
-        }
-        binding.wheelMonth.setEntries(monthList.map { it.second })
-        updateDayList()
-    }
-
-    /**
-     * 更新可选择的日子;
-     */
-    private fun updateDayList() {
-        val calendar = Calendar.getInstance()
-        calendar[Calendar.YEAR] = selectYear
-        calendar[Calendar.MONTH] = selectMonth - 1
-        val days = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
-        dayList.clear()
-        try {
-            when (selectMonth) {
-                targetMonth -> {
-                    if (targetMonth <= currMonth) {
-                        if (yearList.size < 2) {
-                            if (targetDayScope != 0) {
-                                for (i in currDay..targetDay) {
-                                    dayList.add("$i")
-                                }
-                            } else {
-                                for (i in targetDay..days) {
-                                    dayList.add("$i")
-                                }
-                            }
-                        } else {
-                            if (targetDayScope != 0) {
-                                for (i in 1..targetDay) {
-                                    dayList.add("$i")
-                                }
-                            } else {
-                                for (i in currDay..days) {
-                                    dayList.add("$i")
-                                }
-                            }
-                        }
-                    } else {
-                        if (targetYear > currYear) {
-                            for (i in 1..targetDay) {
-                                dayList.add("$i")
-                            }
-                        } else {
-                            for (i in currDay..days) {
-                                dayList.add("$i")
-                            }
-                        }
-
-                    }
-                }
-
-                currMonth -> {
-                    if (targetMonth <= currMonth) {
-                        if (yearList.size < 2) {
-                            for (i in 1..currDay) {
-                                dayList.add("$i")
-                            }
-                        } else {
-                            for (i in currDay..days) {
-                                dayList.add("$i")
-                            }
-                        }
-                    } else {
-                        if (targetYear > currYear) {
-                            for (i in currDay..days) {
-                                dayList.add("$i")
-                            }
-                        } else {
-                            for (i in 1..currDay) {
-                                dayList.add("$i")
-                            }
-                        }
-                    }
-
-                }
-
-                else -> {
-                    for (i in 1..days) {
-                        dayList.add("$i")
-                    }
-                }
-            }
-        } catch (e: Exception) {
-            dayList.clear()
-            for (i in 1..days) {
-                dayList.add("$i")
-            }
-        }
-
-        binding.wheelDay.setEntries(dayList)
-        updateHourList()
-    }
-
-    private fun updateHourList() {
-        hourList.clear()
-
-        val startHour =
-            if (selectDay == currDay && selectMonth == currMonth) currHour
-            else if (selectDay == targetDay && selectMonth == targetMonth) targetHour
-            else 0
-        if (selectDay == targetDay && selectMonth == targetMonth) {
-            for (i in 0..startHour) {
-                hourList.add("$i")
-            }
-        } else {
-            for (i in startHour..23) {
-                hourList.add("$i")
-            }
-        }
-
-        binding.wheelHour.setEntries(hourList)
-        updateMinuteList()
-    }
-
-    private fun updateMinuteList() {
-        minuteList.clear()
-        val startMinute =
-            if (selectHour == currHour && selectDay == currDay && selectMonth == currMonth) currMinute
-            else if (selectHour == targetHour && selectDay == targetDay && selectMonth == targetMonth) targetMinute
-            else 0
-        if (selectHour == targetHour && selectDay == targetDay && selectMonth == targetMonth) {
-            for (i in 0..startMinute) {
-                minuteList.add(if (i < 10) "0$i" else "$i")
-            }
-        } else {
-            for (i in startMinute..59) {
-                minuteList.add(if (i < 10) "0$i" else "$i")
-            }
-        }
-
-        binding.wheelMinute.setEntries(minuteList)
-    }
-
-
     interface ISelectDateCallback {
         /**
          * @param timeStr "yyyy-MM-dd" 格式 或者“yyyy-MM-DD-HH-MM-SS”

+ 470 - 0
app/src/main/java/com/adealink/weparty/commonui/widget/wheel/WheelDatePickerFragment.kt

@@ -0,0 +1,470 @@
+package com.adealink.weparty.commonui.widget.wheel
+
+import android.os.Bundle
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.getBirthStr
+import com.adealink.frame.util.timeToYMDHM
+import com.adealink.weparty.R
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.MIN_YEAR
+import com.adealink.weparty.commonui.dialogfragment.data.DatePickerType
+import com.adealink.weparty.commonui.dialogfragment.data.Month
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.databinding.FragmentWheelDatePickerBinding
+import java.util.Calendar
+import java.util.Date
+
+/**
+ * 日期选择控件;
+ */
+class WheelDatePickerFragment : BaseFragment(R.layout.fragment_wheel_date_picker) {
+
+    companion object {
+        const val EXTRA_INIT_DATE = "extra_init_date"
+        const val EXTRA_TARGET_MONTH_SCOPE = "extra_target_month_scope"
+        const val EXTRA_TARGET_DAY_SCOPE = "extra_target_day_scope"
+        const val EXTRA_CURR_MINUTE_SCOPE = "extra_curr_minute_scope"
+        const val EXTRA_TYPE = "extra_type"
+
+        fun newInstance(
+            timestamp: Long = System.currentTimeMillis(),
+            tips: String? = null,
+            targetMonthScope: Int? = -2,
+            targetDayScope: Int? = 0,
+            currMinuteScope: Int? = 0,
+            type: Int? = DatePickerType.YMD.ordinal,
+        ): WheelDatePickerFragment {
+            val fragment = WheelDatePickerFragment()
+            val bundle = Bundle()
+            if (targetMonthScope != null) {
+                bundle.putInt(EXTRA_TARGET_MONTH_SCOPE, targetMonthScope)
+            }
+            if (targetDayScope != null) {
+                bundle.putInt(EXTRA_TARGET_DAY_SCOPE, targetDayScope)
+            }
+            if (currMinuteScope != null) {
+                bundle.putInt(EXTRA_CURR_MINUTE_SCOPE, currMinuteScope)
+            }
+            bundle.putLong(EXTRA_INIT_DATE, timestamp)
+            if (type != null) {
+                bundle.putInt(EXTRA_TYPE, type)
+            }
+
+            fragment.arguments = bundle
+            return fragment
+        }
+    }
+
+    private val binding by viewBinding(FragmentWheelDatePickerBinding::bind)
+
+    private val yearList = mutableListOf<String>()
+    private val monthList =
+        mutableListOf<Pair<Int, String>>()  //first用于索引及获取相应时间戳,second用于日期控件滚轮文案显示
+    private val dayList = mutableListOf<String>()
+    private var hourList = arrayListOf<String>()
+    private var minuteList = arrayListOf<String>()
+
+    private var initTimestamp = System.currentTimeMillis()
+    private var targetMonthScope: Int = -2   //-N选取前N个月  N选取后N个月
+    private var targetDayScope: Int = 0   //-N选取前N天  N选取后N天
+    private var currMinuteScope: Int = 0   //选取开始的小时数,1表示选取当前时间一小时
+
+    private var selectYear = 0
+    private var selectMonth = 0
+    private var selectDay = 0
+    private var selectHour = 0
+    private var selectMinute = 0
+
+    private var currYear = 0
+    private var targetYear = MIN_YEAR
+    private var currMonth = 1
+    private var targetMonth = 1
+    private var currDay = 1
+    private var targetDay = 1
+    private var targetHour = 1
+    private var targetMinute = 1
+    private var currHour = 1
+    private var currMinute = 1
+    private var type: Int = DatePickerType.YMD.ordinal
+
+    private fun parseIntent() {
+        arguments?.getLong(EXTRA_INIT_DATE)?.let {
+            initTimestamp = it
+        }
+        arguments?.getInt(EXTRA_TARGET_MONTH_SCOPE)?.let {
+            targetMonthScope = it
+        }
+        arguments?.getInt(EXTRA_TARGET_DAY_SCOPE)?.let {
+            targetDayScope = it
+        }
+        arguments?.getInt(EXTRA_CURR_MINUTE_SCOPE)?.let {
+            currMinuteScope = it
+        }
+        arguments?.getInt(EXTRA_TYPE)?.let {
+            type = it
+        }
+        if (initTimestamp <= 0) {
+            initTimestamp = System.currentTimeMillis()
+        }
+    }
+
+    /**
+     * - get(Calendar.MONTH) 比真实的月份少1;
+     */
+    private fun initDataPicker() {
+        val cal = Calendar.getInstance()
+        // 当前的时间
+        if (currMinuteScope != 0) {
+            cal.add(Calendar.MINUTE, currMinuteScope)
+        }
+        currYear = cal.get(Calendar.YEAR)
+        currMonth = cal.get(Calendar.MONTH) + 1
+        currDay = cal.get(Calendar.DAY_OF_MONTH)
+        currHour = cal.get(Calendar.HOUR_OF_DAY)
+        currMinute = cal.get(Calendar.MINUTE)
+        val currTimestamp = cal.timeInMillis
+        // 选择月份或日
+        if (targetDayScope != 0) {
+            cal.add(Calendar.DATE, targetDayScope)
+        } else {
+            cal.add(Calendar.MONTH, targetMonthScope)
+        }
+//        targetYear = cal.get(Calendar.YEAR)
+        targetMonth = cal.get(Calendar.MONTH) + 1
+        targetDay = cal.get(Calendar.DAY_OF_MONTH)
+        targetHour = cal.get(Calendar.HOUR_OF_DAY)
+        targetMinute = cal.get(Calendar.MINUTE)
+        val targetTimestamp = cal.timeInMillis
+        //防越界
+        if (targetTimestamp > currTimestamp) {
+            if (currTimestamp > initTimestamp || initTimestamp > targetTimestamp) {
+                initTimestamp = currTimestamp
+            }
+        } else {
+            if (currTimestamp < initTimestamp || initTimestamp < targetTimestamp) {
+                initTimestamp = currTimestamp
+            }
+        }
+        // 初始化选中的时间
+        val date = Date(initTimestamp)
+        cal.time = date
+        selectYear = cal.get(Calendar.YEAR)
+        selectMonth = cal.get(Calendar.MONTH) + 1
+        selectDay = cal.get(Calendar.DAY_OF_MONTH)
+        selectHour = cal.get(Calendar.HOUR_OF_DAY)
+        selectMinute = cal.get(Calendar.MINUTE)
+        // 填充列表
+        if (targetYear < currYear) {
+            for (year in targetYear..currYear) {
+                yearList.add("$year")
+            }
+        } else {
+            for (year in currYear..targetYear) {
+                yearList.add("$year")
+            }
+        }
+    }
+
+    private fun inflateMonthList(month: Int) {
+        when (month) {
+            Month.JANUARY.value -> monthList.add(
+                Pair(
+                    1,
+                    getCompatString(R.string.commonui_january)
+                )
+            )
+
+            Month.FEBRUARY.value -> monthList.add(
+                Pair(
+                    2,
+                    getCompatString(R.string.commonui_february)
+                )
+            )
+
+            Month.MARCH.value -> monthList.add(Pair(3, getCompatString(R.string.commonui_march)))
+            Month.APRIL.value -> monthList.add(Pair(4, getCompatString(R.string.commonui_april)))
+            Month.MAY.value -> monthList.add(Pair(5, getCompatString(R.string.commonui_may)))
+            Month.JUNE.value -> monthList.add(Pair(6, getCompatString(R.string.commonui_june)))
+            Month.JULY.value -> monthList.add(Pair(7, getCompatString(R.string.commonui_july)))
+            Month.AUGUST.value -> monthList.add(Pair(8, getCompatString(R.string.commonui_august)))
+            Month.SEPTEMBER.value -> monthList.add(
+                Pair(
+                    9,
+                    getCompatString(R.string.commonui_september)
+                )
+            )
+
+            Month.OCTOBER.value -> monthList.add(
+                Pair(
+                    10,
+                    getCompatString(R.string.commonui_october)
+                )
+            )
+
+            Month.NOVEMBER.value -> monthList.add(
+                Pair(
+                    11,
+                    getCompatString(R.string.commonui_november)
+                )
+            )
+
+            else -> monthList.add(Pair(12, getCompatString(R.string.commonui_december)))
+        }
+    }
+
+    override fun initViews() {
+        parseIntent()
+        initDataPicker()
+        binding.wheelYear.setEntries(yearList)
+        updateMonthList()
+
+        // 设置选中x
+        binding.wheelYear.currentIndex = selectYear - targetYear
+        binding.wheelMonth.currentIndex = monthList.indexOfFirst { it.first == selectMonth }
+        binding.wheelDay.currentIndex = dayList.indexOf(selectDay.toString())
+        binding.wheelHour.currentIndex = hourList.indexOf(selectHour.toString())
+        binding.wheelMinute.currentIndex = minuteList.indexOf(
+            if (selectMinute < 10) {
+                "0$selectMinute"
+            } else {
+                selectMinute.toString()
+            }
+        )
+        when (type) {
+            DatePickerType.YMDHM.ordinal -> {  //精确到分钟
+                binding.wheelDay.setItemWidth(100)
+                binding.wheelMonth.setItemWidth(200)
+                binding.wheelYear.setItemWidth(200)
+                binding.wheelHour.show()
+                binding.wheelMinute.show()
+                binding.wheelHour.setOnWheelChangedListener { _, _, newIndex ->
+                    selectHour = hourList.getOrNull(newIndex)?.toInt() ?: 0
+                    updateMinuteList()
+                    selectMinute = minuteList.getOrNull(0)?.toInt() ?: 0
+                }
+                binding.wheelMinute.setOnWheelChangedListener { _, _, newIndex ->
+                    selectMinute = minuteList.getOrNull(newIndex)?.toInt() ?: 0
+                }
+            }
+
+            else -> {}
+        }
+        binding.wheelYear.setOnWheelChangedListener { _, _, newIndex ->
+            selectYear = yearList.getOrNull(newIndex)?.toInt() ?: 0
+            updateMonthList()
+            selectMonth = monthList.getOrNull(0)?.first ?: 0
+            updateDayList()
+            selectDay = dayList.getOrNull(0)?.toInt() ?: 0
+            selectHour = hourList.getOrNull(0)?.toInt() ?: 0
+            selectMinute = minuteList.getOrNull(0)?.toInt() ?: 0
+        }
+        binding.wheelMonth.setOnWheelChangedListener { _, _, newIndex ->
+            selectMonth = monthList.getOrNull(newIndex)?.first ?: 0
+            updateDayList()
+            selectDay = dayList.getOrNull(0)?.toInt() ?: 0
+            selectHour = hourList.getOrNull(0)?.toInt() ?: 0
+            selectMinute = minuteList.getOrNull(0)?.toInt() ?: 0
+        }
+        binding.wheelDay.setOnWheelChangedListener { _, _, newIndex ->
+            selectDay = dayList.getOrNull(newIndex)?.toInt() ?: 0
+            updateHourList()
+            selectHour = hourList.getOrNull(0)?.toInt() ?: 0
+            selectMinute = minuteList.getOrNull(0)?.toInt() ?: 0
+        }
+    }
+
+    fun getSelectedDate(callback: (timestamp: Long, timeStr: String) -> Unit) {
+        val calendar = Calendar.getInstance()
+        calendar.clear()
+        if (type == DatePickerType.YMDHM.ordinal) {
+            calendar.set(selectYear, selectMonth - 1, selectDay, selectHour, selectMinute)
+        } else {
+            calendar.set(selectYear, selectMonth - 1, selectDay)
+        }
+        val timestamp = calendar.timeInMillis
+        callback.invoke(
+            timestamp,
+            if (type == DatePickerType.YMDHM.ordinal) {
+                timeToYMDHM(timestamp)
+            } else {
+                getBirthStr(timestamp)
+            }
+        )
+    }
+
+
+    /**
+     * 更新可选择的月份;
+     * 1. 只有1年:直接从小到大填充月份;注意目标月份是否大于当前月份
+     * 2. 跨年:[targetMonth, 12] 、[1, currMonth]
+     */
+    private fun updateMonthList() {
+        monthList.clear()
+        val startMonth = if (yearList.size < 2) {
+            if (targetMonth > currMonth) currMonth else targetMonth
+        } else {
+            if (currYear < targetYear) {
+                if (selectYear.toString() == yearList.getOrNull(0)) currMonth else 1
+            } else {
+                if (selectYear.toString() == yearList.getOrNull(0)) targetMonth else 1
+            }
+
+        }
+
+        val endMonth = if (yearList.size < 2) {
+            if (targetMonth > currMonth) targetMonth else currMonth
+        } else {
+            if (currYear < targetYear) {
+                if (selectYear.toString() == yearList.getOrNull(0)) 12 else targetMonth
+            } else {
+                if (selectYear.toString() == yearList.getOrNull(0)) 12 else currMonth
+            }
+
+        }
+
+        for (month in startMonth until endMonth + 1) {
+            inflateMonthList(month)
+        }
+        binding.wheelMonth.setEntries(monthList.map { it.second })
+        updateDayList()
+    }
+
+    /**
+     * 更新可选择的日子;
+     */
+    private fun updateDayList() {
+        val calendar = Calendar.getInstance()
+        calendar[Calendar.YEAR] = selectYear
+        calendar[Calendar.MONTH] = selectMonth - 1
+        val days = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
+        dayList.clear()
+        try {
+            when (selectMonth) {
+                targetMonth -> {
+                    if (targetMonth <= currMonth) {
+                        if (yearList.size < 2) {
+                            if (targetDayScope != 0) {
+                                for (i in currDay..targetDay) {
+                                    dayList.add("$i")
+                                }
+                            } else {
+                                for (i in targetDay..days) {
+                                    dayList.add("$i")
+                                }
+                            }
+                        } else {
+                            if (targetDayScope != 0) {
+                                for (i in 1..targetDay) {
+                                    dayList.add("$i")
+                                }
+                            } else {
+                                for (i in currDay..days) {
+                                    dayList.add("$i")
+                                }
+                            }
+                        }
+                    } else {
+                        if (targetYear > currYear) {
+                            for (i in 1..targetDay) {
+                                dayList.add("$i")
+                            }
+                        } else {
+                            for (i in currDay..days) {
+                                dayList.add("$i")
+                            }
+                        }
+
+                    }
+                }
+
+                currMonth -> {
+                    if (targetMonth <= currMonth) {
+                        if (yearList.size < 2) {
+                            for (i in 1..currDay) {
+                                dayList.add("$i")
+                            }
+                        } else {
+                            for (i in currDay..days) {
+                                dayList.add("$i")
+                            }
+                        }
+                    } else {
+                        if (targetYear > currYear) {
+                            for (i in currDay..days) {
+                                dayList.add("$i")
+                            }
+                        } else {
+                            for (i in 1..currDay) {
+                                dayList.add("$i")
+                            }
+                        }
+                    }
+
+                }
+
+                else -> {
+                    for (i in 1..days) {
+                        dayList.add("$i")
+                    }
+                }
+            }
+        } catch (e: Exception) {
+            dayList.clear()
+            for (i in 1..days) {
+                dayList.add("$i")
+            }
+        }
+
+        binding.wheelDay.setEntries(dayList)
+        updateHourList()
+    }
+
+    private fun updateHourList() {
+        hourList.clear()
+
+        val startHour =
+            if (selectDay == currDay && selectMonth == currMonth) currHour
+            else if (selectDay == targetDay && selectMonth == targetMonth) targetHour
+            else 0
+        if (selectDay == targetDay && selectMonth == targetMonth) {
+            for (i in 0..startHour) {
+                hourList.add("$i")
+            }
+        } else {
+            for (i in startHour..23) {
+                hourList.add("$i")
+            }
+        }
+
+        binding.wheelHour.setEntries(hourList)
+        updateMinuteList()
+    }
+
+    private fun updateMinuteList() {
+        minuteList.clear()
+        val startMinute =
+            if (selectHour == currHour && selectDay == currDay && selectMonth == currMonth) currMinute
+            else if (selectHour == targetHour && selectDay == targetDay && selectMonth == targetMonth) targetMinute
+            else 0
+        if (selectHour == targetHour && selectDay == targetDay && selectMonth == targetMonth) {
+            for (i in 0..startMinute) {
+                minuteList.add(if (i < 10) "0$i" else "$i")
+            }
+        } else {
+            for (i in startMinute..59) {
+                minuteList.add(if (i < 10) "0$i" else "$i")
+            }
+        }
+
+        binding.wheelMinute.setEntries(minuteList)
+    }
+
+
+    interface ISelectDateCallback {
+        /**
+         * @param timeStr "yyyy-MM-dd" 格式 或者“yyyy-MM-DD-HH-MM-SS”
+         */
+        fun onSelectComplete(timestamp: Long, timeStr: String)
+    }
+}

+ 8 - 0
app/src/main/java/com/adealink/weparty/module/wallet/Router.kt

@@ -17,6 +17,14 @@ interface Wallet {
         }
     }
 
+    interface Detail {
+        companion object {
+            const val PATH = "${Common.PATH}/detail"
+
+            const val EXTRA_TAB = "extra_tab"
+        }
+    }
+
     interface TopUpDialog {
         companion object {
             const val PATH = "${Common.PATH}/top_up_dialog"

+ 1 - 0
app/src/main/java/com/adealink/weparty/module/wallet/data/WalletData.kt

@@ -3,4 +3,5 @@ package com.adealink.weparty.module.wallet.data
 enum class Currency(val type: Int) {
     COIN(0),
     DIAMOND(1),
+    BEAN(2),
 }

BIN
app/src/main/res/drawable-xhdpi/common_app_wallet_detail_ic.png


+ 6 - 0
app/src/main/res/drawable-xhdpi/common_wallet_bean_bg.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#0DFF7300" />
+    <corners android:radius="12dp" />
+</shape>

+ 0 - 0
module/profile/src/main/res/drawable/profile_me_wallet_coin_bg.xml → app/src/main/res/drawable-xhdpi/common_wallet_coin_bg.xml


+ 0 - 0
module/profile/src/main/res/drawable/profile_me_wallet_diamond_bg.xml → app/src/main/res/drawable-xhdpi/common_wallet_diamond_bg.xml


+ 6 - 76
app/src/main/res/layout/dialog_wheel_date_picker.xml

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="@drawable/common_bottom_dialog_bg"
@@ -21,83 +22,12 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
-    <View
-        android:layout_width="0dp"
-        android:layout_height="40dp"
-        android:background="@drawable/common_date_picker_highlight_bg"
-        app:layout_constraintBottom_toBottomOf="@id/bottom_barrier"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/tv_title" />
-
-    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
-        android:id="@+id/wheel_day"
-        android:layout_width="0dp"
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fl_date_picker"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        app:layout_constraintEnd_toStartOf="@id/wheel_month"
-        app:layout_constraintHorizontal_weight="1"
-        app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/tv_title"
-        app:wheelItemHeight="40dp"
-        app:wheelItemWidth="100dp" />
-
-    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
-        android:id="@+id/wheel_month"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        app:layout_constraintEnd_toStartOf="@id/wheel_year"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintHorizontal_weight="1"
-        app:layout_constraintStart_toEndOf="@id/wheel_day"
-        app:layout_constraintTop_toBottomOf="@id/tv_title"
-        app:wheelItemHeight="40dp"
-        app:wheelItemWidth="100dp" />
-
-    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
-        android:id="@+id/wheel_year"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        app:layout_constraintEnd_toStartOf="@+id/wheel_hour"
-        app:layout_constraintHorizontal_weight="1"
-        app:layout_constraintStart_toEndOf="@id/wheel_month"
-        app:layout_constraintTop_toBottomOf="@id/tv_title"
-        app:wheelItemHeight="40dp"
-        app:wheelItemWidth="100dp" />
-
-    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
-        android:id="@+id/wheel_hour"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:layout_constraintEnd_toStartOf="@+id/wheel_minute"
-        app:layout_constraintHorizontal_weight="1"
-        app:layout_constraintStart_toEndOf="@id/wheel_year"
-        app:layout_constraintTop_toBottomOf="@id/tv_title"
-        app:wheelItemHeight="40dp"
-        app:wheelItemWidth="70dp" />
-
-    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
-        android:id="@+id/wheel_minute"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintHorizontal_weight="1"
-        app:layout_constraintTop_toBottomOf="@id/tv_title"
-        app:wheelItemHeight="40dp"
-        app:wheelItemWidth="70dp" />
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/bottom_barrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierDirection="bottom"
-        app:constraint_referenced_ids="wheel_day,
-        wheel_month,
-        wheel_year,
-        wheel_hour,
-        wheel_minute" />
+        tools:layout_height="200dp" />
 
     <com.adealink.weparty.commonui.widget.CommonButton
         android:id="@+id/btn_confirm"
@@ -111,6 +41,6 @@
         android:textSize="16sp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/bottom_barrier" />
+        app:layout_constraintTop_toBottomOf="@id/fl_date_picker" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 79 - 0
app/src/main/res/layout/fragment_wheel_date_picker.xml

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <View
+        android:layout_width="0dp"
+        android:layout_height="40dp"
+        android:background="@drawable/common_date_picker_highlight_bg"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
+        android:id="@+id/wheel_day"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/wheel_month"
+        app:layout_constraintHorizontal_weight="1"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:wheelItemHeight="40dp"
+        app:wheelItemWidth="100dp" />
+
+    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
+        android:id="@+id/wheel_month"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/wheel_year"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintHorizontal_weight="1"
+        app:layout_constraintStart_toEndOf="@id/wheel_day"
+        app:layout_constraintTop_toTopOf="parent"
+        app:wheelItemHeight="40dp"
+        app:wheelItemWidth="100dp" />
+
+    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
+        android:id="@+id/wheel_year"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/wheel_hour"
+        app:layout_constraintHorizontal_weight="1"
+        app:layout_constraintStart_toEndOf="@id/wheel_month"
+        app:layout_constraintTop_toTopOf="parent"
+        app:wheelItemHeight="40dp"
+        app:wheelItemWidth="100dp" />
+
+    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
+        android:id="@+id/wheel_hour"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/wheel_minute"
+        app:layout_constraintHorizontal_weight="1"
+        app:layout_constraintStart_toEndOf="@id/wheel_year"
+        app:layout_constraintTop_toTopOf="parent"
+        app:wheelItemHeight="40dp"
+        app:wheelItemWidth="70dp" />
+
+    <com.adealink.weparty.commonui.widget.wheel.Wheel3DView
+        android:id="@+id/wheel_minute"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintHorizontal_weight="1"
+        app:layout_constraintTop_toTopOf="parent"
+        app:wheelItemHeight="40dp"
+        app:wheelItemWidth="70dp" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 0
app/src/main/res/values/colors.xml

@@ -1188,4 +1188,5 @@
 
     <color name="color_FFB0F5EA">#FFB0F5EA</color>
     <color name="color_FFA3CDF9">#FFA3CDF9</color>
+    <color name="color_FF1789FF">#FF1789FF</color>
 </resources>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -166,6 +166,7 @@
     <string name="common_search_record">Search record</string>
     <string name="common_coin">Coins</string>
     <string name="common_diamond">Diamonds</string>
+    <string name="common_bean">Beans</string>
     <string name="common_exp" translatable="false">EXP</string>
     <string name="common_end">End</string>
     <string name="common_copy">Copy</string>

+ 1 - 10
module/profile/src/main/java/com/adealink/weparty/profile/me/comp/MeWalletComp.kt

@@ -52,15 +52,6 @@ class MeWalletComp(
 
     private fun goWallet(currency: Currency) {
         val act = activity ?: return
-        when (currency) {
-            Currency.COIN -> {
-                Router.build(act, Wallet.Wallet.PATH).start()
-
-            }
-
-            Currency.DIAMOND -> {
-                Router.build(act, Wallet.Wallet.PATH).start()
-            }
-        }
+        Router.build(act, Wallet.Wallet.PATH).start()
     }
 }

+ 2 - 2
module/profile/src/main/res/layout/layout_me_wallet.xml

@@ -26,7 +26,7 @@
         android:id="@+id/cl_coin"
         android:layout_width="0dp"
         android:layout_height="64dp"
-        android:background="@drawable/profile_me_wallet_coin_bg"
+        android:background="@drawable/common_wallet_coin_bg"
         app:layout_constraintEnd_toStartOf="@id/cl_diamond"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/tv_wallet">
@@ -79,7 +79,7 @@
         android:layout_width="0dp"
         android:layout_height="64dp"
         android:layout_marginStart="10dp"
-        android:background="@drawable/profile_me_wallet_diamond_bg"
+        android:background="@drawable/common_wallet_diamond_bg"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toEndOf="@id/cl_coin"
         app:layout_constraintTop_toBottomOf="@id/tv_wallet">

+ 10 - 1
module/wallet/src/main/AndroidManifest.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:dist="http://schemas.android.com/apk/distribution"
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:dist="http://schemas.android.com/apk/distribution"
     xmlns:tools="http://schemas.android.com/tools"
     tools:ignore="LockedOrientationActivity">
 
@@ -15,5 +16,13 @@
     </dist:module>
 
     <application>
+        <activity
+            android:name=".WalletActivity"
+            android:screenOrientation="portrait"
+            android:theme="@style/AppTheme" />
+        <activity
+            android:name=".detail.WalletDetailActivity"
+            android:screenOrientation="portrait"
+            android:theme="@style/AppTheme" />
     </application>
 </manifest>

+ 31 - 1
module/wallet/src/main/java/com/adealink/weparty/wallet/WalletActivity.kt

@@ -3,15 +3,21 @@ package com.adealink.weparty.wallet
 import androidx.activity.viewModels
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.updateLayoutParams
+import com.adealink.frame.aab.util.getCompatDimension
 import com.adealink.frame.mvvm.view.viewBinding
 import com.adealink.frame.router.Router
 import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.frame.util.onClick
 import com.adealink.frame.util.statusBarHeight
 import com.adealink.weparty.commonui.BaseActivity
 import com.adealink.weparty.module.wallet.Wallet
+import com.adealink.weparty.wallet.comp.MyBalanceComp
+import com.adealink.weparty.wallet.comp.MyIncomeComp
+import com.adealink.weparty.wallet.comp.WalletActivityComp
 import com.adealink.weparty.wallet.databinding.ActivityWalletBinding
 import com.adealink.weparty.wallet.viewmodel.WalletViewModel
 import com.adealink.weparty.wallet.viewmodel.WalletViewModelFactory
+import com.adealink.weparty.R as APP_R
 
 @RouterUri(
     path = [Wallet.Wallet.PATH],
@@ -30,10 +36,34 @@ class WalletActivity : BaseActivity() {
     override fun initViews() {
         super.initViews()
         setContentView(binding.root)
+        val statusBarHeight = this@WalletActivity.statusBarHeight()
+        binding.topBar.setPadding(0, statusBarHeight, 0, 0)
         binding.topBar.updateLayoutParams<ConstraintLayout.LayoutParams> {
-            topMargin = this@WalletActivity.statusBarHeight()
+            height = statusBarHeight + getCompatDimension(APP_R.dimen.common_top_bar_height).toInt()
         }
 
+        binding.btnBack.onClick {
+            finish()
+        }
+        binding.btnDetail.onClick {
+            goDetail()
+        }
+    }
+
+    override fun initComponents() {
+        super.initComponents()
+        MyBalanceComp(this, binding.vBalance).attach()
+        MyIncomeComp(this, binding.vIncome).attach()
+        WalletActivityComp(this, binding.vActivity).attach()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        viewModel.refreshWalletData()
+    }
+
+    private fun goDetail() {
+        Router.build(this, Wallet.Detail.PATH).start()
     }
 
 }

+ 55 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/comp/MyBalanceComp.kt

@@ -0,0 +1,55 @@
+package com.adealink.weparty.wallet.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.activityViewModels
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.util.formatNumberStr
+import com.adealink.weparty.wallet.databinding.LayoutWalletBalanceBinding
+import com.adealink.weparty.wallet.viewmodel.WalletViewModel
+import com.adealink.weparty.wallet.viewmodel.WalletViewModelFactory
+
+class MyBalanceComp(
+    lifecycleOwner: LifecycleOwner,
+    val binding: LayoutWalletBalanceBinding
+) : ViewComponent(lifecycleOwner) {
+
+    private val viewModel by activityViewModels<WalletViewModel> { WalletViewModelFactory() }
+
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+        loadData()
+    }
+
+    private fun initView() {
+        binding.btnTopUpCoin.onClick {
+            topUpCoin()
+        }
+        binding.btnTopUpDiamond.onClick {
+            topUpDiamond()
+        }
+    }
+
+    private fun observeViewModel() {
+        viewModel.coinLD.observe(viewLifecycleOwner) {
+            binding.tvCoin.text = formatNumberStr(it)
+        }
+        viewModel.diamondLD.observe(viewLifecycleOwner) {
+            binding.tvDiamond.text = formatNumberStr(it)
+        }
+    }
+
+    private fun loadData() {
+        viewModel.getWalletData()
+    }
+
+    private fun topUpCoin() {
+
+    }
+
+    private fun topUpDiamond() {
+
+    }
+}

+ 36 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/comp/MyIncomeComp.kt

@@ -0,0 +1,36 @@
+package com.adealink.weparty.wallet.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.activityViewModels
+import com.adealink.weparty.wallet.databinding.LayoutWalletIncomeBinding
+import com.adealink.weparty.wallet.viewmodel.WalletViewModel
+import com.adealink.weparty.wallet.viewmodel.WalletViewModelFactory
+
+class MyIncomeComp(
+    lifecycleOwner: LifecycleOwner,
+    val binding: LayoutWalletIncomeBinding
+) : ViewComponent(lifecycleOwner) {
+
+    private val viewModel by activityViewModels<WalletViewModel> { WalletViewModelFactory() }
+
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+        loadData()
+    }
+
+    private fun initView() {
+
+    }
+
+    private fun observeViewModel() {
+
+    }
+
+    private fun loadData() {
+
+    }
+
+}

+ 35 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/comp/WalletActivityComp.kt

@@ -0,0 +1,35 @@
+package com.adealink.weparty.wallet.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.activityViewModels
+import com.adealink.weparty.wallet.databinding.LayoutWalletActivityBinding
+import com.adealink.weparty.wallet.viewmodel.WalletViewModel
+import com.adealink.weparty.wallet.viewmodel.WalletViewModelFactory
+
+class WalletActivityComp(
+    lifecycleOwner: LifecycleOwner,
+    val binding: LayoutWalletActivityBinding
+) : ViewComponent(lifecycleOwner) {
+
+    private val viewModel by activityViewModels<WalletViewModel> { WalletViewModelFactory() }
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+        loadData()
+    }
+
+    private fun initView() {
+
+    }
+
+    private fun observeViewModel() {
+
+    }
+
+    private fun loadData() {
+
+    }
+
+}

+ 7 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/datasource/remote/WalletHttpService.kt

@@ -3,6 +3,9 @@ package com.adealink.weparty.wallet.datasource.remote
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.network.data.Res
 import com.adealink.weparty.wallet.data.WalletRes
+import com.adealink.weparty.wallet.detail.data.DetailReq
+import com.adealink.weparty.wallet.detail.data.DetailRes
+import retrofit2.http.Body
 import retrofit2.http.Core
 import retrofit2.http.POST
 
@@ -11,4 +14,8 @@ interface WalletHttpService {
     @Core
     @POST("user/my/wallet")
     suspend fun pullWallet(): Rlt<Res<WalletRes>>
+
+
+    @POST("user/my/wallet")
+    suspend fun pullDetail(@Body req: DetailReq): Rlt<Res<DetailRes>>
 }

+ 140 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/WalletDetailActivity.kt

@@ -0,0 +1,140 @@
+package com.adealink.weparty.wallet.detail
+
+import androidx.activity.viewModels
+import androidx.fragment.app.Fragment
+import com.adealink.frame.aab.util.getCompatColor
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.Router
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.frame.util.onClick
+import com.adealink.frame.util.statusBarHeight
+import com.adealink.weparty.commonui.BaseActivity
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.recycleview.adapter.BaseActivityTabFragmentStateAdapter
+import com.adealink.weparty.commonui.widget.EmptyFragment
+import com.adealink.weparty.module.wallet.Wallet
+import com.adealink.weparty.wallet.R
+import com.adealink.weparty.wallet.databinding.ActivityWalletDetailBinding
+import com.adealink.weparty.wallet.databinding.LayoutWalletDetailTabItemBinding
+import com.adealink.weparty.wallet.detail.filter.DetailDateFilterDialog
+import com.adealink.weparty.wallet.detail.tab.DETAIL_TABS
+import com.adealink.weparty.wallet.viewmodel.WalletDetailViewModel
+import com.adealink.weparty.wallet.viewmodel.WalletViewModelFactory
+import com.google.android.material.tabs.TabLayout
+import com.google.android.material.tabs.TabLayoutMediator
+import com.adealink.weparty.R as APP_R
+
+@RouterUri(
+    path = [Wallet.Detail.PATH],
+    desc = "钱包首页"
+)
+class WalletDetailActivity : BaseActivity() {
+
+    private val binding by viewBinding(ActivityWalletDetailBinding::inflate)
+    private val viewModel by viewModels<WalletDetailViewModel> { WalletViewModelFactory() }
+    private lateinit var pagerAdapter: PageAdapter
+
+    override fun onBeforeCreate() {
+        super.onBeforeCreate()
+        Router.bind(this)
+    }
+
+    override fun initViews() {
+        super.initViews()
+        setContentView(binding.root)
+        val statusBarHeight = this@WalletDetailActivity.statusBarHeight()
+        binding.clTop.setPadding(0, statusBarHeight, 0, 10.dp())
+
+        binding.btnBack.onClick {
+            finish()
+        }
+        binding.btnFilter.onClick {
+            clickFilter()
+        }
+
+        pagerAdapter = PageAdapter()
+        binding.vpContent.offscreenPageLimit = 1
+        binding.vpContent.adapter = pagerAdapter
+        TabLayoutMediator(
+            binding.tlTab,
+            binding.vpContent,
+            true,
+            false
+        ) { tabLayout, position ->
+            tabLayout.setCustomView(R.layout.layout_wallet_detail_tab_item)
+            tabLayout.customView?.let { customView ->
+                val tabViewBinding = LayoutWalletDetailTabItemBinding.bind(customView)
+                onConfigureTab(tabViewBinding, position)
+                updateTabView(
+                    tabLayout,
+                    0 == position,
+                )
+            }
+        }.attach()
+        binding.tlTab.addOnTabSelectedListener(
+            object : TabLayout.OnTabSelectedListener {
+
+                override fun onTabSelected(tab: TabLayout.Tab?) {
+                    updateTabView(tab, true)
+                }
+
+                override fun onTabUnselected(tab: TabLayout.Tab?) {
+                    updateTabView(tab, false)
+                }
+
+                override fun onTabReselected(tab: TabLayout.Tab?) {
+                }
+
+            }
+        )
+    }
+
+    private fun onConfigureTab(binding: LayoutWalletDetailTabItemBinding, position: Int) {
+        binding.tvTab.text = pagerAdapter.getTabName(position)
+    }
+
+    private fun updateTabView(
+        tabView: TabLayout.Tab?,
+        isSelected: Boolean
+    ) {
+        tabView?.customView?.let { tabView ->
+            val tabViewBinding = LayoutWalletDetailTabItemBinding.bind(tabView)
+            if (isSelected) {
+                tabViewBinding.root.setBackgroundResource(APP_R.drawable.common_tab_item_bg)
+                tabViewBinding.tvTab.setTextColor(getCompatColor(APP_R.color.color_FF1D2129))
+            } else {
+                tabViewBinding.root.setBackgroundResource(0)
+                tabViewBinding.tvTab.setTextColor(getCompatColor(APP_R.color.color_FF4E5969))
+            }
+        }
+    }
+
+    private fun clickFilter() {
+        DetailDateFilterDialog().apply {
+            //setStartEnd()
+            setListener(object : DetailDateFilterDialog.OnFilterListener {
+                override fun onDateSelected(fromTs: Long, toTs: Long) {
+                    viewModel.changedDate(fromTs, toTs)
+                }
+            })
+        }.show(supportFragmentManager, "DetailDateFilterDialog")
+    }
+
+
+    internal inner class PageAdapter : BaseActivityTabFragmentStateAdapter(this) {
+        override fun getTabName(pos: Int): String {
+            return DETAIL_TABS.getOrNull(pos)?.name?.invoke() ?: ""
+        }
+
+        override fun getItemCount(): Int {
+            return DETAIL_TABS.size
+        }
+
+        override fun createFragment(position: Int): Fragment {
+            val homeTab = DETAIL_TABS.getOrNull(position) ?: return EmptyFragment()
+            return homeTab.fragmentBuilder.invoke()
+        }
+
+    }
+
+}

+ 46 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/bean/BeanDetailItemViewBinder.kt

@@ -0,0 +1,46 @@
+package com.adealink.weparty.wallet.detail.bean
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.wallet.R
+import com.adealink.weparty.wallet.databinding.ItemBeanDetailBinding
+import com.adealink.weparty.wallet.detail.data.BeanDetailItemData
+
+class BeanDetailItemViewBinder() :
+    ItemViewBinder<BeanDetailItemData, BindingViewHolder<ItemBeanDetailBinding>>() {
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<ItemBeanDetailBinding>,
+        item: BeanDetailItemData,
+    ) {
+        if (holder.layoutPosition == 0) {
+            //第一个
+            holder.binding.root.setPadding(12.dp(), 10.dp(), 12.dp(), 0)
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_top_bg)
+        } else if (holder.layoutPosition == adapter.itemCount - 1) {
+            //最后一个
+            holder.binding.root.setPadding(12.dp(), 0, 12.dp(), 10.dp())
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_bottom_bg)
+        } else {
+            holder.binding.root.setPadding(12.dp(), 0, 12.dp(), 0)
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_bg)
+        }
+
+    }
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup,
+    ): BindingViewHolder<ItemBeanDetailBinding> {
+        return BindingViewHolder(
+            ItemBeanDetailBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        )
+    }
+}

+ 60 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/bean/BeanDetailViewModel.kt

@@ -0,0 +1,60 @@
+package com.adealink.weparty.wallet.detail.bean
+
+import androidx.lifecycle.MutableLiveData
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.frame.network.data.PageReq
+import com.adealink.weparty.App
+import com.adealink.weparty.module.wallet.data.Currency
+import com.adealink.weparty.util.PageHandler
+import com.adealink.weparty.wallet.datasource.remote.WalletHttpService
+import com.adealink.weparty.wallet.detail.data.BeanDetailItemData
+import com.adealink.weparty.wallet.detail.data.DetailReq
+import kotlinx.coroutines.launch
+
+class BeanDetailViewModel : BaseViewModel() {
+
+    private val walletHttpService by lazy {
+        App.instance.networkService.getHttpService(WalletHttpService::class.java)
+    }
+
+    private val detailList = mutableListOf<BeanDetailItemData>()
+    private val pageHandler = PageHandler()
+
+    val detailListRltLD = ExtMutableLiveData<Rlt<Any>>()
+    val detailListLD = MutableLiveData<List<BeanDetailItemData>>()
+    fun pullFollowList() {
+        detailList.clear()
+        pageHandler.reset()
+        loadMoreFollowList()
+    }
+
+    fun loadMoreFollowList() {
+        viewModelScope.launch {
+            val rlt = walletHttpService.pullDetail(
+                DetailReq(
+                    currency = Currency.BEAN.type,
+                    page = PageReq(size = pageHandler.pageSize, next = pageHandler.currentPage)
+                )
+            )
+            when (rlt) {
+                is Rlt.Failed -> {
+                    detailListRltLD.send(rlt)
+                }
+
+                is Rlt.Success -> {
+                    pageHandler.nextPage(rlt.data.data?.next)
+                    detailList.addAll(
+                        rlt.data.data?.list?.map {
+                            BeanDetailItemData(it)
+                        } ?: emptyList()
+                    )
+                    detailListRltLD.send(rlt)
+                    detailListLD.send(detailList)
+                }
+            }
+        }
+    }
+
+}

+ 81 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/bean/BeanFragment.kt

@@ -0,0 +1,81 @@
+package com.adealink.weparty.wallet.detail.bean
+
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.itemdecoration.VerticalSpaceItemDecoration
+import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.wallet.R
+import com.adealink.weparty.wallet.databinding.FragmentDetailBeanBinding
+import com.adealink.weparty.wallet.detail.data.BeanDetailItemData
+import com.adealink.weparty.wallet.viewmodel.WalletViewModelFactory
+import com.scwang.smart.refresh.layout.api.RefreshLayout
+import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener
+import com.adealink.weparty.R as APP_R
+
+class BeanFragment: BaseFragment(R.layout.fragment_detail_bean) {
+
+    private val binding by viewBinding(FragmentDetailBeanBinding::bind)
+    private val viewModel by viewModels<BeanDetailViewModel> { WalletViewModelFactory() }
+    private val listAdapter by fastLazy { MultiTypeListAdapter<BeanDetailItemData>() }
+    override fun initViews() {
+        super.initViews()
+        binding.vRefresh.setEnableRefresh(true)
+        binding.vRefresh.setEnableLoadMore(true)
+        binding.vRefresh.setEnableAutoLoadMore(true)
+        binding.vRefresh.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
+            override fun onRefresh(refreshLayout: RefreshLayout) {
+                viewModel.pullFollowList()
+            }
+
+
+            override fun onLoadMore(refreshLayout: RefreshLayout) {
+                viewModel.loadMoreFollowList()
+            }
+        })
+
+        listAdapter.register(BeanDetailItemViewBinder())
+        binding.rvList.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+        binding.rvList.adapter = listAdapter
+        binding.rvList.addItemDecoration(
+            VerticalSpaceItemDecoration(
+                1.dp(),
+                firstSpaceHeight = 8.dp()
+            )
+        )
+    }
+    override fun loadData() {
+        super.loadData()
+        viewModel.pullFollowList()
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        viewModel.detailListLD.observe(viewLifecycleOwner) {
+            listAdapter.submitList(it)
+            if (it.isNullOrEmpty()) {
+                binding.rvList.gone()
+                binding.vErrorView.show(
+                    errorEmptyResId = APP_R.drawable.common_list_empty_ic,
+                    title = APP_R.string.commonui_list_empty
+                )
+            } else {
+                binding.rvList.show()
+                binding.vErrorView.gone()
+            }
+        }
+        viewModel.detailListRltLD.observe(viewLifecycleOwner) { rlt ->
+            binding.vRefresh.finishRefresh()
+            binding.vRefresh.finishLoadMore()
+            showFailedToast(rlt)
+        }
+    }
+
+}

+ 46 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/coin/CoinDetailItemViewBinder.kt

@@ -0,0 +1,46 @@
+package com.adealink.weparty.wallet.detail.coin
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.wallet.R
+import com.adealink.weparty.wallet.databinding.ItemCoinDetailBinding
+import com.adealink.weparty.wallet.detail.data.CoinDetailItemData
+
+class CoinDetailItemViewBinder() :
+    ItemViewBinder<CoinDetailItemData, BindingViewHolder<ItemCoinDetailBinding>>() {
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<ItemCoinDetailBinding>,
+        item: CoinDetailItemData,
+    ) {
+        if (holder.layoutPosition == 0) {
+            //第一个
+            holder.binding.root.setPadding(12.dp(), 10.dp(), 12.dp(), 0)
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_top_bg)
+        } else if (holder.layoutPosition == adapter.itemCount - 1) {
+            //最后一个
+            holder.binding.root.setPadding(12.dp(), 0, 12.dp(), 10.dp())
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_bottom_bg)
+        } else {
+            holder.binding.root.setPadding(12.dp(), 0, 12.dp(), 0)
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_bg)
+        }
+
+    }
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup,
+    ): BindingViewHolder<ItemCoinDetailBinding> {
+        return BindingViewHolder(
+            ItemCoinDetailBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        )
+    }
+}

+ 60 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/coin/CoinDetailViewModel.kt

@@ -0,0 +1,60 @@
+package com.adealink.weparty.wallet.detail.coin
+
+import androidx.lifecycle.MutableLiveData
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.frame.network.data.PageReq
+import com.adealink.weparty.App
+import com.adealink.weparty.module.wallet.data.Currency
+import com.adealink.weparty.util.PageHandler
+import com.adealink.weparty.wallet.datasource.remote.WalletHttpService
+import com.adealink.weparty.wallet.detail.data.CoinDetailItemData
+import com.adealink.weparty.wallet.detail.data.DetailReq
+import kotlinx.coroutines.launch
+
+class CoinDetailViewModel : BaseViewModel() {
+
+    private val walletHttpService by lazy {
+        App.instance.networkService.getHttpService(WalletHttpService::class.java)
+    }
+
+    private val detailList = mutableListOf<CoinDetailItemData>()
+    private val pageHandler = PageHandler()
+
+    val detailListRltLD = ExtMutableLiveData<Rlt<Any>>()
+    val detailListLD = MutableLiveData<List<CoinDetailItemData>>()
+    fun pullFollowList() {
+        detailList.clear()
+        pageHandler.reset()
+        loadMoreFollowList()
+    }
+
+    fun loadMoreFollowList() {
+        viewModelScope.launch {
+            val rlt = walletHttpService.pullDetail(
+                DetailReq(
+                    currency = Currency.COIN.type,
+                    page = PageReq(size = pageHandler.pageSize, next = pageHandler.currentPage)
+                )
+            )
+            when (rlt) {
+                is Rlt.Failed -> {
+                    detailListRltLD.send(rlt)
+                }
+
+                is Rlt.Success -> {
+                    pageHandler.nextPage(rlt.data.data?.next)
+                    detailList.addAll(
+                        rlt.data.data?.list?.map {
+                            CoinDetailItemData(it)
+                        } ?: emptyList()
+                    )
+                    detailListRltLD.send(rlt)
+                    detailListLD.send(detailList)
+                }
+            }
+        }
+    }
+
+}

+ 82 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/coin/CoinFragment.kt

@@ -0,0 +1,82 @@
+package com.adealink.weparty.wallet.detail.coin
+
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.itemdecoration.VerticalSpaceItemDecoration
+import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.wallet.R
+import com.adealink.weparty.wallet.databinding.FragmentDetailCoinBinding
+import com.adealink.weparty.wallet.detail.data.CoinDetailItemData
+import com.adealink.weparty.wallet.viewmodel.WalletViewModelFactory
+import com.scwang.smart.refresh.layout.api.RefreshLayout
+import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener
+import com.adealink.weparty.R as APP_R
+
+class CoinFragment : BaseFragment(R.layout.fragment_detail_coin) {
+
+    private val binding by viewBinding(FragmentDetailCoinBinding::bind)
+    private val viewModel by viewModels<CoinDetailViewModel> { WalletViewModelFactory() }
+    private val listAdapter by fastLazy { MultiTypeListAdapter<CoinDetailItemData>() }
+    override fun initViews() {
+        super.initViews()
+        binding.vRefresh.setEnableRefresh(true)
+        binding.vRefresh.setEnableLoadMore(true)
+        binding.vRefresh.setEnableAutoLoadMore(true)
+        binding.vRefresh.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
+            override fun onRefresh(refreshLayout: RefreshLayout) {
+                viewModel.pullFollowList()
+            }
+
+
+            override fun onLoadMore(refreshLayout: RefreshLayout) {
+                viewModel.loadMoreFollowList()
+            }
+        })
+
+        listAdapter.register(CoinDetailItemViewBinder())
+        binding.rvList.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+        binding.rvList.adapter = listAdapter
+        binding.rvList.addItemDecoration(
+            VerticalSpaceItemDecoration(
+                1.dp(),
+                firstSpaceHeight = 8.dp()
+            )
+        )
+    }
+
+    override fun loadData() {
+        super.loadData()
+        viewModel.pullFollowList()
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        viewModel.detailListLD.observe(viewLifecycleOwner) {
+            listAdapter.submitList(it)
+            if (it.isNullOrEmpty()) {
+                binding.rvList.gone()
+                binding.vErrorView.show(
+                    errorEmptyResId = APP_R.drawable.common_list_empty_ic,
+                    title = APP_R.string.commonui_list_empty
+                )
+            } else {
+                binding.rvList.show()
+                binding.vErrorView.gone()
+            }
+        }
+        viewModel.detailListRltLD.observe(viewLifecycleOwner) { rlt ->
+            binding.vRefresh.finishRefresh()
+            binding.vRefresh.finishLoadMore()
+            showFailedToast(rlt)
+        }
+    }
+
+}

+ 37 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/data/DetailData.kt

@@ -0,0 +1,37 @@
+package com.adealink.weparty.wallet.detail.data
+
+import com.adealink.frame.network.data.PageReq
+import com.adealink.weparty.commonui.recycleview.diffutil.BaseListItemData
+import com.google.gson.annotations.GsonNullable
+import com.google.gson.annotations.SerializedName
+
+
+data class DetailReq(
+    @SerializedName("type") val currency: Int,
+    @SerializedName("page") val page: PageReq
+)
+
+data class DetailRes(
+    @SerializedName("list") val list: List<WalletDetailData>,
+    @GsonNullable
+    @SerializedName("next") val next: String? = null
+)
+
+data class WalletDetailData(
+    @SerializedName("id") val id: String
+)
+
+sealed class BaseWalletDetail : BaseListItemData
+
+data class CoinDetailItemData(
+    val data: WalletDetailData
+) : BaseWalletDetail()
+
+
+data class DiamondDetailItemData(
+    val data: WalletDetailData
+) : BaseWalletDetail()
+
+data class BeanDetailItemData(
+    val data: WalletDetailData
+) : BaseWalletDetail()

+ 46 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/diamond/DiamondDetailItemViewBinder.kt

@@ -0,0 +1,46 @@
+package com.adealink.weparty.wallet.detail.diamond
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.wallet.R
+import com.adealink.weparty.wallet.databinding.ItemDiamondDetailBinding
+import com.adealink.weparty.wallet.detail.data.DiamondDetailItemData
+
+class DiamondDetailItemViewBinder() :
+    ItemViewBinder<DiamondDetailItemData, BindingViewHolder<ItemDiamondDetailBinding>>() {
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<ItemDiamondDetailBinding>,
+        item: DiamondDetailItemData,
+    ) {
+        if (holder.layoutPosition == 0) {
+            //第一个
+            holder.binding.root.setPadding(12.dp(), 10.dp(), 12.dp(), 0)
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_top_bg)
+        } else if (holder.layoutPosition == adapter.itemCount - 1) {
+            //最后一个
+            holder.binding.root.setPadding(12.dp(), 0, 12.dp(), 10.dp())
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_bottom_bg)
+        } else {
+            holder.binding.root.setPadding(12.dp(), 0, 12.dp(), 0)
+            holder.binding.root.setBackgroundResource(R.drawable.wallet_detail_item_bg)
+        }
+
+    }
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup,
+    ): BindingViewHolder<ItemDiamondDetailBinding> {
+        return BindingViewHolder(
+            ItemDiamondDetailBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        )
+    }
+}

+ 60 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/diamond/DiamondDetailViewModel.kt

@@ -0,0 +1,60 @@
+package com.adealink.weparty.wallet.detail.diamond
+
+import androidx.lifecycle.MutableLiveData
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.frame.network.data.PageReq
+import com.adealink.weparty.App
+import com.adealink.weparty.module.wallet.data.Currency
+import com.adealink.weparty.util.PageHandler
+import com.adealink.weparty.wallet.datasource.remote.WalletHttpService
+import com.adealink.weparty.wallet.detail.data.DetailReq
+import com.adealink.weparty.wallet.detail.data.DiamondDetailItemData
+import kotlinx.coroutines.launch
+
+class DiamondDetailViewModel : BaseViewModel() {
+
+    private val walletHttpService by lazy {
+        App.instance.networkService.getHttpService(WalletHttpService::class.java)
+    }
+
+    private val detailList = mutableListOf<DiamondDetailItemData>()
+    private val pageHandler = PageHandler()
+
+    val detailListRltLD = ExtMutableLiveData<Rlt<Any>>()
+    val detailListLD = MutableLiveData<List<DiamondDetailItemData>>()
+    fun pullFollowList() {
+        detailList.clear()
+        pageHandler.reset()
+        loadMoreFollowList()
+    }
+
+    fun loadMoreFollowList() {
+        viewModelScope.launch {
+            val rlt = walletHttpService.pullDetail(
+                DetailReq(
+                    currency = Currency.DIAMOND.type,
+                    page = PageReq(size = pageHandler.pageSize, next = pageHandler.currentPage)
+                )
+            )
+            when (rlt) {
+                is Rlt.Failed -> {
+                    detailListRltLD.send(rlt)
+                }
+
+                is Rlt.Success -> {
+                    pageHandler.nextPage(rlt.data.data?.next)
+                    detailList.addAll(
+                        rlt.data.data?.list?.map {
+                            DiamondDetailItemData(it)
+                        } ?: emptyList()
+                    )
+                    detailListRltLD.send(rlt)
+                    detailListLD.send(detailList)
+                }
+            }
+        }
+    }
+
+}

+ 82 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/diamond/DiamondFragment.kt

@@ -0,0 +1,82 @@
+package com.adealink.weparty.wallet.detail.diamond
+
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.itemdecoration.VerticalSpaceItemDecoration
+import com.adealink.weparty.commonui.toast.util.showFailedToast
+import com.adealink.weparty.wallet.R
+import com.adealink.weparty.wallet.databinding.FragmentDetailDiamondBinding
+import com.adealink.weparty.wallet.detail.data.DiamondDetailItemData
+import com.adealink.weparty.wallet.viewmodel.WalletViewModelFactory
+import com.scwang.smart.refresh.layout.api.RefreshLayout
+import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener
+import com.adealink.weparty.R as APP_R
+
+class DiamondFragment: BaseFragment(R.layout.fragment_detail_diamond) {
+
+    private val binding by viewBinding(FragmentDetailDiamondBinding::bind)
+    private val viewModel by viewModels<DiamondDetailViewModel> { WalletViewModelFactory() }
+    private val listAdapter by fastLazy { MultiTypeListAdapter<DiamondDetailItemData>() }
+    override fun initViews() {
+        super.initViews()
+        binding.vRefresh.setEnableRefresh(true)
+        binding.vRefresh.setEnableLoadMore(true)
+        binding.vRefresh.setEnableAutoLoadMore(true)
+        binding.vRefresh.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
+            override fun onRefresh(refreshLayout: RefreshLayout) {
+                viewModel.pullFollowList()
+            }
+
+
+            override fun onLoadMore(refreshLayout: RefreshLayout) {
+                viewModel.loadMoreFollowList()
+            }
+        })
+
+        listAdapter.register(DiamondDetailItemViewBinder())
+        binding.rvList.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+        binding.rvList.adapter = listAdapter
+        binding.rvList.addItemDecoration(
+            VerticalSpaceItemDecoration(
+                1.dp(),
+                firstSpaceHeight = 8.dp()
+            )
+        )
+    }
+
+    override fun loadData() {
+        super.loadData()
+        viewModel.pullFollowList()
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        viewModel.detailListLD.observe(viewLifecycleOwner) {
+            listAdapter.submitList(it)
+            if (it.isNullOrEmpty()) {
+                binding.rvList.gone()
+                binding.vErrorView.show(
+                    errorEmptyResId = APP_R.drawable.common_list_empty_ic,
+                    title = APP_R.string.commonui_list_empty
+                )
+            } else {
+                binding.rvList.show()
+                binding.vErrorView.gone()
+            }
+        }
+        viewModel.detailListRltLD.observe(viewLifecycleOwner) { rlt ->
+            binding.vRefresh.finishRefresh()
+            binding.vRefresh.finishLoadMore()
+            showFailedToast(rlt)
+        }
+    }
+
+}

+ 84 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/filter/DetailDateFilterDialog.kt

@@ -0,0 +1,84 @@
+package com.adealink.weparty.wallet.detail.filter
+
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.widget.BottomDialogFragment
+import com.adealink.weparty.commonui.widget.wheel.WheelDatePickerFragment
+import com.adealink.weparty.wallet.R
+import com.adealink.weparty.wallet.databinding.DialogWalletDetailFilterBinding
+
+class DetailDateFilterDialog : BottomDialogFragment(R.layout.dialog_wallet_detail_filter) {
+
+    private val binding by viewBinding(DialogWalletDetailFilterBinding::bind)
+
+    private var isEditFromDate = true
+
+    private var listener: OnFilterListener? = null
+
+    private var fromInitTs: Long = 0
+    private var fromTs: Long = 0
+
+    private var endInitTs: Long = 0
+    private var endTs: Long = 0
+
+    private lateinit var datePickerFragment: WheelDatePickerFragment
+
+    fun setInitTs(fromTs: Long, endTs: Long) {
+        this.fromInitTs = fromTs
+        this.fromTs = fromTs
+        this.endInitTs = endTs
+        this.endTs = endTs
+    }
+
+    fun setListener(listener: OnFilterListener?) {
+        this.listener = listener
+    }
+
+    override fun initViews() {
+        super.initViews()
+        binding.tvFromDate.onClick {
+            isEditFromDate = true
+            datePickerFragment.getSelectedDate({ timestamp: Long, timeStr: String ->
+                this.fromTs = timestamp
+            })
+            updateUI()
+        }
+        binding.tvToDate.onClick {
+            isEditFromDate = false
+            datePickerFragment.getSelectedDate({ timestamp: Long, timeStr: String ->
+                this.endTs = timestamp
+            })
+            updateUI()
+        }
+
+        datePickerFragment = WheelDatePickerFragment()
+        childFragmentManager.beginTransaction()
+            .replace(binding.flDatePicker.id, datePickerFragment, "WheelDatePickerFragment")
+            .commitAllowingStateLoss()
+
+        binding.btnConfirm.onClick {
+            confirm()
+        }
+    }
+
+    private fun updateUI() {
+        if (isEditFromDate) {
+            binding.tvFromDate.isSelected = true
+            binding.tvToDate.isSelected = false
+        } else {
+            binding.tvFromDate.isSelected = false
+            binding.tvToDate.isSelected = true
+        }
+    }
+
+    private fun confirm() {
+        if (fromTs != fromInitTs || endTs != endInitTs) {
+            listener?.onDateSelected(fromTs, endTs)
+        }
+        dismiss()
+    }
+
+    interface OnFilterListener {
+        fun onDateSelected(fromTs: Long, toTs: Long)
+    }
+}

+ 38 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/tab/TabManager.kt

@@ -0,0 +1,38 @@
+package com.adealink.weparty.wallet.detail.tab
+
+import com.adealink.weparty.ui.main.tab.Tab
+
+interface ITabManager {
+    fun initTabs(tabs: List<Tab>) {}
+
+    fun getTabs(): List<Tab> = arrayListOf()
+
+    fun getCount(): Int = 0
+
+    fun getTab(index: Int): Tab? = null
+
+    fun getTabIndex(tab: Tab): Int = 0
+}
+
+class TabManager : ITabManager {
+    private var tabs = mutableListOf<Tab>()
+
+    override fun initTabs(tabs: List<Tab>) {
+        this.tabs.clear()
+        this.tabs.addAll(tabs)
+    }
+
+    override fun getTabs() = tabs
+
+    override fun getCount(): Int {
+        return tabs.size
+    }
+
+    override fun getTab(index: Int): Tab? {
+        return if (index in tabs.indices) tabs[index] else null
+    }
+
+    override fun getTabIndex(tab: Tab): Int {
+        return tabs.indexOf(tab)
+    }
+}

+ 50 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/detail/tab/WalletTab.kt

@@ -0,0 +1,50 @@
+package com.adealink.weparty.wallet.detail.tab
+
+import androidx.fragment.app.Fragment
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.weparty.module.wallet.data.Currency
+import com.adealink.weparty.wallet.detail.bean.BeanFragment
+import com.adealink.weparty.wallet.detail.coin.CoinFragment
+import com.adealink.weparty.wallet.detail.diamond.DiamondFragment
+import com.adealink.weparty.R as APP_R
+
+data class Tab(
+    val type: Currency,
+    val name: (() -> String),
+    val fragmentBuilder: () -> Fragment
+)
+
+val COIN_TAB = Tab(
+    type = Currency.COIN,
+    name = {
+        getCompatString(APP_R.string.common_coin)
+    },
+    fragmentBuilder = {
+        CoinFragment()
+    }
+)
+
+val DIAMOND_TAB = Tab(
+    type = Currency.DIAMOND,
+    name = {
+        getCompatString(APP_R.string.common_diamond)
+    },
+    fragmentBuilder = {
+        DiamondFragment()
+    }
+)
+
+val BEAN_TAB = Tab(
+    type = Currency.BEAN,
+    name = {
+        getCompatString(APP_R.string.common_bean)
+    },
+    fragmentBuilder = {
+        BeanFragment()
+    }
+)
+
+
+val DETAIL_TABS = listOf(
+    COIN_TAB, DIAMOND_TAB, BEAN_TAB
+)

+ 3 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/manager/IWalletListener.kt

@@ -1,6 +1,9 @@
 package com.adealink.weparty.wallet.manager
 
 import com.adealink.frame.frame.IListener
+import com.adealink.weparty.module.wallet.data.Currency
 
 interface IWalletListener : IListener {
+    fun onCurrencyChanged(currency: Map<Currency, Long>)
+
 }

+ 11 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/manager/IWalletManager.kt

@@ -1,6 +1,17 @@
 package com.adealink.weparty.wallet.manager
 
+import com.adealink.frame.base.Rlt
 import com.adealink.frame.frame.IBaseFrame
+import com.adealink.weparty.module.wallet.data.Currency
 
 interface IWalletManager : IBaseFrame<IWalletListener> {
+
+    //cache
+    fun getWalletData()
+
+    fun refreshWalletDat()
+
+    suspend fun pullWalletData(): Rlt<Map<Currency, Long>>
+
+
 }

+ 59 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/manager/WalletManager.kt

@@ -1,9 +1,68 @@
 package com.adealink.weparty.wallet.manager
 
+import com.adealink.frame.base.Rlt
 import com.adealink.frame.frame.BaseFrame
+import com.adealink.weparty.App
+import com.adealink.weparty.module.wallet.data.Currency
+import com.adealink.weparty.wallet.datasource.remote.WalletHttpService
+import kotlinx.coroutines.launch
+
+
+val walletManager: IWalletManager by lazy { WalletManager() }
 
 class WalletManager : BaseFrame<IWalletListener>(), IWalletManager {
 
+    private val walletHttpService by lazy {
+        App.instance.networkService.getHttpService(WalletHttpService::class.java)
+    }
+
+    private var coin: Long = 0
+    private var diamond: Long = 0
+    private var bean: Long = 0
+
+    override fun getWalletData() {
+        launch {
+            notifyCurrencyChanged()
+        }
+    }
+
+    override fun refreshWalletDat() {
+        launch {
+            pullWalletData()
+            notifyCurrencyChanged()
+        }
+    }
+
+    override suspend fun pullWalletData(): Rlt<Map<Currency, Long>> {
+        val rlt = walletHttpService.pullWallet()
+        when (rlt) {
+            is Rlt.Failed -> {
+                return rlt
+            }
+
+            is Rlt.Success -> {
+                this.coin = rlt.data.data?.coin ?: 0
+                this.diamond = rlt.data.data?.diamond ?: 0
+                return Rlt.Success(
+                    mapOf(
+                        Currency.COIN to this.coin,
+                        Currency.DIAMOND to this.diamond
+                    )
+                )
+            }
+        }
+    }
+
+    private fun notifyCurrencyChanged() {
+        dispatch {
+            it.onCurrencyChanged(
+                mapOf(
+                    Currency.COIN to this.coin,
+                    Currency.DIAMOND to this.diamond
+                )
+            )
+        }
+    }
 
 
 }

+ 20 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/viewmodel/WalletDetailViewModel.kt

@@ -0,0 +1,20 @@
+package com.adealink.weparty.wallet.viewmodel
+
+import com.adealink.frame.mvvm.livedata.ExtMutableLiveData
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.weparty.App
+import com.adealink.weparty.wallet.datasource.remote.WalletHttpService
+
+class WalletDetailViewModel : BaseViewModel() {
+
+    private val walletHttpService by lazy {
+        App.instance.networkService.getHttpService(WalletHttpService::class.java)
+    }
+
+    val onDetailDateChangedLD = ExtMutableLiveData<Pair<Long, Long>>()
+
+    fun changedDate(fromkTs: Long, endTs: Long) {
+        onDetailDateChangedLD.send(Pair(fromkTs, endTs))
+    }
+
+}

+ 23 - 17
module/wallet/src/main/java/com/adealink/weparty/wallet/viewmodel/WalletViewModel.kt

@@ -2,35 +2,41 @@ package com.adealink.weparty.wallet.viewmodel
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import com.adealink.frame.base.Rlt
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
-import com.adealink.weparty.App
+import com.adealink.weparty.module.wallet.data.Currency
 import com.adealink.weparty.module.wallet.viewmodel.IWalletViewModel
-import com.adealink.weparty.wallet.datasource.remote.WalletHttpService
+import com.adealink.weparty.wallet.manager.IWalletListener
+import com.adealink.weparty.wallet.manager.walletManager
 import kotlinx.coroutines.launch
 
-class WalletViewModel : BaseViewModel(), IWalletViewModel {
+class WalletViewModel : BaseViewModel(), IWalletViewModel, IWalletListener {
     override val coinLD: LiveData<Long> = MutableLiveData()
     override val diamondLD: LiveData<Long> = MutableLiveData()
 
-    private val walletHttpService by lazy {
-        App.instance.networkService.getHttpService(WalletHttpService::class.java)
+    init {
+        walletManager.addListener(this)
     }
 
-    override fun refreshWalletData() {
-        viewModelScope.launch {
-            val rlt = walletHttpService.pullWallet()
-            when (rlt) {
-                is Rlt.Failed -> {
+    override fun onCleared() {
+        super.onCleared()
+        walletManager.removeListener(this)
+    }
 
-                }
+    fun getWalletData(){
+        viewModelScope.launch {
+            walletManager.getWalletData()
+        }
+    }
 
-                is Rlt.Success -> {
-                    coinLD.send(rlt.data.data?.coin ?: 0)
-                    diamondLD.send(rlt.data.data?.diamond ?: 0)
-                }
-            }
+    override fun refreshWalletData() {
+        viewModelScope.launch {
+            walletManager.refreshWalletDat()
         }
     }
 
+    override fun onCurrencyChanged(currency: Map<Currency, Long>) {
+        coinLD.send(currency[Currency.COIN] ?: 0)
+        diamondLD.send(currency[Currency.DIAMOND] ?: 0)
+    }
+
 }

+ 15 - 0
module/wallet/src/main/java/com/adealink/weparty/wallet/viewmodel/WalletViewModelFactory.kt

@@ -2,6 +2,9 @@ package com.adealink.weparty.wallet.viewmodel
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
+import com.adealink.weparty.wallet.detail.bean.BeanDetailViewModel
+import com.adealink.weparty.wallet.detail.coin.CoinDetailViewModel
+import com.adealink.weparty.wallet.detail.diamond.DiamondDetailViewModel
 
 @Suppress("UNCHECKED_CAST")
 class WalletViewModelFactory : ViewModelProvider.NewInstanceFactory() {
@@ -12,6 +15,18 @@ class WalletViewModelFactory : ViewModelProvider.NewInstanceFactory() {
                 isAssignableFrom(WalletViewModel::class.java) ->
                     WalletViewModel()
 
+                isAssignableFrom(WalletDetailViewModel::class.java) ->
+                    WalletDetailViewModel()
+
+                isAssignableFrom(CoinDetailViewModel::class.java) ->
+                    CoinDetailViewModel()
+
+                isAssignableFrom(DiamondDetailViewModel::class.java) ->
+                    DiamondDetailViewModel()
+
+                isAssignableFrom(BeanDetailViewModel::class.java) ->
+                    BeanDetailViewModel()
+
                 else ->
                     throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
             } as T

+ 5 - 0
module/wallet/src/main/res/color/wallet_filter_date_button_text_sel.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/color_FF1D2129" android:state_selected="true" />
+    <item android:color="@color/color_FFC9CDD4" />
+</selector>

BIN
module/wallet/src/main/res/drawable-xhdpi/wallet_filter_ic.png


+ 5 - 0
module/wallet/src/main/res/drawable/wallet_detail_item_bg.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/white" />
+</shape>

+ 8 - 0
module/wallet/src/main/res/drawable/wallet_detail_item_bottom_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/white" />
+    <corners
+        android:bottomLeftRadius="12dp"
+        android:bottomRightRadius="12dp" />
+</shape>

+ 8 - 0
module/wallet/src/main/res/drawable/wallet_detail_item_top_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/white" />
+    <corners
+        android:topLeftRadius="12dp"
+        android:topRightRadius="12dp" />
+</shape>

+ 17 - 0
module/wallet/src/main/res/drawable/wallet_filter_date_button_bg_sel.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true">
+        <shape>
+            <solid android:color="@color/white" />
+            <corners android:radius="30dp" />
+            <stroke android:width="1dp" android:color="@color/color_FF1789FF" />
+        </shape>
+    </item>
+
+    <item>
+        <shape>
+            <solid android:color="@color/color_FFF9FAFB" />
+            <corners android:radius="30dp" />
+        </shape>
+    </item> <!-- default -->
+</selector>

+ 93 - 4
module/wallet/src/main/res/layout/activity_wallet.xml

@@ -2,14 +2,103 @@
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:background="@color/color_FFF1F2F5">
 
-    <com.adealink.weparty.commonui.widget.CommonTopBar
+    <androidx.appcompat.widget.AppCompatImageView
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintDimensionRatio="375:173"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/common_app_top_bg_ic" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/top_bar"
-        android:layout_width="match_parent"
+        android:layout_width="0dp"
         android:layout_height="@dimen/common_top_bar_height"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/btn_back"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_marginStart="16dp"
+            android:rotationY="@integer/locale_mirror_flip"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/commonui_back_black_48_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:text="@string/wallet_title"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="16sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/btn_detail"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_marginEnd="16dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/common_app_wallet_detail_ic" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.core.widget.NestedScrollView
+        style="@style/CommonVerticalFade"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginHorizontal="16dp"
+        android:layout_marginTop="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/top_bar">
+
+        <androidx.appcompat.widget.LinearLayoutCompat
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <include
+                android:id="@+id/v_balance"
+                layout="@layout/layout_wallet_balance"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <include
+                android:id="@+id/v_income"
+                layout="@layout/layout_wallet_income"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="16dp" />
+
+            <include
+                android:id="@+id/v_activity"
+                layout="@layout/layout_wallet_activity"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="16dp" />
+
+        </androidx.appcompat.widget.LinearLayoutCompat>
+
+
+    </androidx.core.widget.NestedScrollView>
+
 
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 94 - 0
module/wallet/src/main/res/layout/activity_wallet_detail.xml

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_top"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:paddingBottom="10dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/top_bar"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/common_top_bar_height"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/btn_back"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_marginStart="16dp"
+                android:rotationY="@integer/locale_mirror_flip"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:srcCompat="@drawable/commonui_back_black_48_ic" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:fontFamily="@font/poppins_semibold"
+                android:includeFontPadding="false"
+                android:text="@string/wallet_detail_title"
+                android:textColor="@color/color_FF1D2129"
+                android:textSize="16sp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/btn_filter"
+                android:layout_width="32dp"
+                android:layout_height="32dp"
+                android:layout_marginEnd="16dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:srcCompat="@drawable/wallet_filter_ic" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <com.google.android.material.tabs.TabLayout
+            android:id="@+id/tl_tab"
+            android:layout_width="0dp"
+            android:layout_height="32dp"
+            android:layout_marginHorizontal="16dp"
+            android:background="@drawable/common_tab_bg"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/top_bar"
+            app:tabBackground="@null"
+            app:tabGravity="fill"
+            app:tabIndicatorHeight="0dp"
+            app:tabMaxWidth="0dp"
+            app:tabMode="fixed"
+            app:tabPaddingBottom="4dp"
+            app:tabPaddingEnd="4dp"
+            app:tabPaddingStart="4dp"
+            app:tabPaddingTop="4dp"
+            app:tabRippleColor="@null" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/vp_content"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@color/color_FFF1F2F5"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/cl_top" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 97 - 0
module/wallet/src/main/res/layout/dialog_wallet_detail_filter.xml

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/common_bottom_dialog_bg"
+    android:paddingHorizontal="24dp"
+    android:paddingBottom="12dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="0dp"
+        android:layout_height="50dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:text="@string/common_choose_date"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="16sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_from_to_date"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingVertical="6dp"
+        app:layout_constraintTop_toBottomOf="@id/tv_title">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_from_date"
+            android:layout_width="0dp"
+            android:layout_height="36dp"
+            android:background="@drawable/wallet_filter_date_button_bg_sel"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:textColor="@color/wallet_filter_date_button_text_sel"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/v_to"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="01-12-2025" />
+
+        <View
+            android:id="@+id/v_to"
+            android:layout_width="16dp"
+            android:layout_height="2dp"
+            android:layout_marginHorizontal="14.5dp"
+            android:background="@color/color_FF86909C"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/tv_to_date"
+            app:layout_constraintStart_toEndOf="@id/tv_from_date"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_to_date"
+            android:layout_width="0dp"
+            android:layout_height="36dp"
+            android:background="@drawable/wallet_filter_date_button_bg_sel"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:textColor="@color/wallet_filter_date_button_text_sel"
+            android:textSize="14sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/v_to"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="01-12-2025" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fl_date_picker"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toBottomOf="@id/cl_from_to_date"
+        tools:layout_height="200dp" />
+
+    <com.adealink.weparty.commonui.widget.CommonButton
+        android:id="@+id/btn_confirm"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/common_button_height"
+        android:layout_marginHorizontal="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/fl_date_picker"
+        app:text="@string/commonui_confirm"
+        app:textColor="@color/white"
+        app:textSize="16sp" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 29 - 0
module/wallet/src/main/res/layout/fragment_detail_bean.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.scwang.smart.refresh.layout.SmartRefreshLayout
+        android:id="@+id/v_refresh"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingHorizontal="16dp" />
+    </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+
+    <com.adealink.weparty.commonui.widget.CommonEmptyErrorView
+        android:id="@+id/v_error_view"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 30 - 0
module/wallet/src/main/res/layout/fragment_detail_coin.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <com.scwang.smart.refresh.layout.SmartRefreshLayout
+        android:id="@+id/v_refresh"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingHorizontal="16dp" />
+    </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+
+    <com.adealink.weparty.commonui.widget.CommonEmptyErrorView
+        android:id="@+id/v_error_view"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 29 - 0
module/wallet/src/main/res/layout/fragment_detail_diamond.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <com.scwang.smart.refresh.layout.SmartRefreshLayout
+        android:id="@+id/v_refresh"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingHorizontal="16dp" />
+    </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+
+    <com.adealink.weparty.commonui.widget.CommonEmptyErrorView
+        android:id="@+id/v_error_view"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 80 - 0
module/wallet/src/main/res/layout/item_bean_detail.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/wallet_detail_item_bg"
+    android:paddingHorizontal="12dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="充值" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/iv_currency"
+        android:layout_width="12dp"
+        android:layout_height="12dp"
+        android:layout_marginEnd="2dp"
+        app:layout_constraintBottom_toBottomOf="@id/tv_changed"
+        app:layout_constraintEnd_toStartOf="@id/tv_changed"
+        app:layout_constraintTop_toTopOf="@id/tv_changed"
+        app:srcCompat="@drawable/common_wallet_bean_32_ic" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_changed"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="end|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="+100" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/barrier_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="tv_title, iv_currency, tv_changed" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:layout_marginTop="4dp"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="12sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/barrier_top"
+        tools:text="通过[支付方式]充值" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_time"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:layout_marginTop="4dp"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="12sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc"
+        tools:text="08/12/2025 19:00" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 80 - 0
module/wallet/src/main/res/layout/item_coin_detail.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/wallet_detail_item_bg"
+    android:paddingHorizontal="12dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="充值" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/iv_currency"
+        android:layout_width="12dp"
+        android:layout_height="12dp"
+        android:layout_marginEnd="2dp"
+        app:layout_constraintBottom_toBottomOf="@id/tv_changed"
+        app:layout_constraintEnd_toStartOf="@id/tv_changed"
+        app:layout_constraintTop_toTopOf="@id/tv_changed"
+        app:srcCompat="@drawable/common_wallet_coin_32_ic" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_changed"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="end|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="+100" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/barrier_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="tv_title, iv_currency, tv_changed" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:layout_marginTop="4dp"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="12sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/barrier_top"
+        tools:text="通过[支付方式]充值" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_time"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:layout_marginTop="4dp"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="12sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc"
+        tools:text="08/12/2025 19:00" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 80 - 0
module/wallet/src/main/res/layout/item_diamond_detail.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/wallet_detail_item_bg"
+    android:paddingHorizontal="12dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_title"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="充值" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/iv_currency"
+        android:layout_width="12dp"
+        android:layout_height="12dp"
+        android:layout_marginEnd="2dp"
+        app:layout_constraintBottom_toBottomOf="@id/tv_changed"
+        app:layout_constraintEnd_toStartOf="@id/tv_changed"
+        app:layout_constraintTop_toTopOf="@id/tv_changed"
+        app:srcCompat="@drawable/common_wallet_diamond_32_ic" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_changed"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="end|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="+100" />
+
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/barrier_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="bottom"
+        app:constraint_referenced_ids="tv_title, iv_currency, tv_changed" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_desc"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:layout_marginTop="4dp"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="12sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/barrier_top"
+        tools:text="通过[支付方式]充值" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_time"
+        android:layout_width="wrap_content"
+        android:layout_height="30dp"
+        android:layout_marginTop="4dp"
+        android:gravity="start|center_vertical"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="12sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_desc"
+        tools:text="08/12/2025 19:00" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 4 - 0
module/wallet/src/main/res/layout/layout_wallet_activity.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.adealink.weparty.commonui.widget.banner.Banner xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="70dp" />

+ 153 - 0
module/wallet/src/main/res/layout/layout_wallet_balance.xml

@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/common_white_radius_16_bg"
+    android:paddingHorizontal="16dp"
+    android:paddingVertical="12dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_balance_title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="start"
+        android:includeFontPadding="false"
+        android:text="@string/wallet_my_balance"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="16sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!--金币-->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_coin"
+        android:layout_width="0dp"
+        android:layout_height="64dp"
+        android:layout_marginTop="12dp"
+        android:background="@drawable/common_wallet_coin_bg"
+        android:paddingHorizontal="14dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_balance_title">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:layout_width="34dp"
+            android:layout_height="34dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/common_wallet_coin_64_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_coin"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="54dp"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="start|center_vertical"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="22sp"
+            app:layout_constraintBottom_toTopOf="@id/tv_coin_unit"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:text="12k" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_coin_unit"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="start|center_vertical"
+            android:includeFontPadding="false"
+            android:text="@string/common_coin"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="@id/tv_coin"
+            app:layout_constraintTop_toBottomOf="@id/tv_coin" />
+
+        <com.adealink.weparty.commonui.widget.CommonButton
+            android:id="@+id/btn_top_up_coin"
+            android:layout_width="wrap_content"
+            android:layout_height="24dp"
+            android:paddingHorizontal="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_min="55dp"
+            app:text="@string/wallet_go_top_up"
+            app:textSize="12sp" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <!--钻石-->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_diamond"
+        android:layout_width="0dp"
+        android:layout_height="64dp"
+        android:layout_marginTop="10dp"
+        android:background="@drawable/common_wallet_diamond_bg"
+        android:paddingHorizontal="14dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/cl_coin">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:layout_width="34dp"
+            android:layout_height="34dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/common_wallet_diamond_64_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_diamond"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="54dp"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="start|center_vertical"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="22sp"
+            app:layout_constraintBottom_toTopOf="@id/tv_diamond_unit"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="packed"
+            tools:text="12k" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_diamond_unit"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="start|center_vertical"
+            android:includeFontPadding="false"
+            android:text="@string/common_diamond"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="@id/tv_diamond"
+            app:layout_constraintTop_toBottomOf="@id/tv_diamond" />
+
+        <com.adealink.weparty.commonui.widget.CommonButton
+            android:id="@+id/btn_top_up_diamond"
+            android:layout_width="wrap_content"
+            android:layout_height="24dp"
+            android:paddingHorizontal="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_min="55dp"
+            app:text="@string/wallet_go_top_up"
+            app:textSize="12sp" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 23 - 0
module/wallet/src/main/res/layout/layout_wallet_detail_tab_item.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="26dp"
+    android:background="@drawable/common_tab_item_bg">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_tab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_FF1D2129"
+        android:textSize="14sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="1500/2hour" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 90 - 0
module/wallet/src/main/res/layout/layout_wallet_income.xml

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/common_white_radius_16_bg"
+    android:paddingHorizontal="16dp"
+    android:paddingVertical="12dp">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_balance_title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:fontFamily="@font/poppins_semibold"
+        android:gravity="start"
+        android:includeFontPadding="false"
+        android:text="@string/wallet_my_income"
+        android:textColor="@color/color_FF4E5969"
+        android:textSize="16sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!--金豆-->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_coin"
+        android:layout_width="0dp"
+        android:layout_height="64dp"
+        android:layout_marginTop="12dp"
+        android:background="@drawable/common_wallet_bean_bg"
+        android:paddingHorizontal="14dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tv_balance_title">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:layout_width="34dp"
+            android:layout_height="34dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/common_wallet_bean_64_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_bean_unit"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="start|center_vertical"
+            android:includeFontPadding="false"
+            android:text="@string/common_coin"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toTopOf="@id/tv_bean"
+            app:layout_constraintStart_toStartOf="@id/tv_bean"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_chainStyle="packed" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_bean"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="54dp"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="start|center_vertical"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FF1D2129"
+            android:textSize="22sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/tv_bean_unit"
+            tools:text="12k" />
+
+        <com.adealink.weparty.commonui.widget.CommonButton
+            android:id="@+id/btn_top_up_coin"
+            android:layout_width="wrap_content"
+            android:layout_height="24dp"
+            android:paddingHorizontal="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_min="55dp"
+            app:text="@string/wallet_go_detail"
+            app:textSize="12sp" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 6 - 0
module/wallet/src/main/res/values/strings.xml

@@ -4,4 +4,10 @@
     <string name="wallet_balance">Balance:</string>
     <string name="wallet_payment_title">Please choose your payment method</string>
     <string name="wallet_top_up_button">Recharge</string>
+    <string name="wallet_title">My Wallet</string>
+    <string name="wallet_my_balance">My Balance</string>
+    <string name="wallet_my_income">My Income</string>
+    <string name="wallet_go_top_up"><![CDATA[Top-up >]]></string>
+    <string name="wallet_go_detail"><![CDATA[Detail >]]></string>
+    <string name="wallet_detail_title">Details</string>
 </resources>