Просмотр исходного кода

feat:房间火箭

* feat: 房间火箭;

* fix:房间火箭 - 房间列表,封面没有显示出火箭图标

* fix:房间火箭 - 规则页文案修改

* fix:房间火箭 - 游戏组件的展示顺序:首充or充值福利--》火箭--》概率游戏

* fix:房间火箭 - 中奖记录为空时,页面需要有缺省图

* fix:房间火箭 - 每日重置火箭的时间用utc+2.5的24点

* fix:房间火箭 - 不保留5级火箭;

* fix:房间火箭 - 部分机型上开奖弹窗的关闭按钮只显示一半(测试机:vivo Y52s) ;

---------

Co-authored-by: duxuefu <duxuefu@zhenxinmail.com>
dxf 8 месяцев назад
Родитель
Сommit
f2198911d3
100 измененных файлов с 2672 добавлено и 287 удалено
  1. BIN
      app/src/main/assets/room_rocket_entrance_level1.svga
  2. BIN
      app/src/main/assets/room_rocket_entrance_level2.svga
  3. BIN
      app/src/main/assets/room_rocket_entrance_level3.svga
  4. BIN
      app/src/main/assets/room_rocket_entrance_level4.svga
  5. BIN
      app/src/main/assets/room_rocket_entrance_level5.svga
  6. 128 28
      app/src/main/java/com/adealink/weparty/commonui/game/GameTextView.kt
  7. 10 0
      app/src/main/java/com/adealink/weparty/commonui/widget/CommonEmptyErrorView.kt
  8. 2 0
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/FloatViewFactory.kt
  9. 2 3
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/data/IFloatData.kt
  10. 91 50
      app/src/main/java/com/adealink/weparty/commonui/widget/progress/SinWaveProgressBar.kt
  11. 35 2
      app/src/main/java/com/adealink/weparty/module/game/GameModule.kt
  12. 7 0
      app/src/main/java/com/adealink/weparty/module/game/IGameService.kt
  13. 68 0
      app/src/main/java/com/adealink/weparty/module/game/Router.kt
  14. 17 2
      app/src/main/java/com/adealink/weparty/module/game/rocket/Data.kt
  15. 3 4
      app/src/main/java/com/adealink/weparty/module/game/rocket/effect/RocketHeadlineEffectView.kt
  16. 68 101
      app/src/main/java/com/adealink/weparty/module/game/rocket/util/UIUtil.kt
  17. 23 67
      app/src/main/java/com/adealink/weparty/module/game/rocket/view/RocketHeadlineView.kt
  18. 9 1
      app/src/main/java/com/adealink/weparty/module/game/rocket/viewmodel/IRocketViewModel.kt
  19. 1 0
      app/src/main/java/com/adealink/weparty/ui/MainStartUpFragment.kt
  20. 24 0
      app/src/main/java/com/qmuiteam/qmui/widget/QMUIProgressBar.java
  21. BIN
      app/src/main/res/drawable-xhdpi/common_content_empty_ic_1.webp
  22. BIN
      app/src/main/res/drawable-xhdpi/common_loading_iv.png
  23. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_entrance_1_ic.webp
  24. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_entrance_2_ic.webp
  25. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_entrance_3_ic.webp
  26. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_entrance_4_ic.webp
  27. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_entrance_5_ic.webp
  28. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_headline_1_ic.webp
  29. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_headline_2_ic.webp
  30. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_headline_3_ic.webp
  31. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_headline_4_ic.webp
  32. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_headline_5_ic.webp
  33. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_1_ic.webp
  34. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_1_large_ic.webp
  35. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_2_ic.webp
  36. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_2_large_ic.webp
  37. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_3_ic.webp
  38. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_3_large_ic.webp
  39. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_4_ic.webp
  40. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_4_large_ic.webp
  41. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_5_ic.webp
  42. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_level_5_large_ic.webp
  43. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_small_level_1_ic.webp
  44. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_small_level_2_ic.webp
  45. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_small_level_3_ic.webp
  46. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_small_level_4_ic.webp
  47. BIN
      app/src/main/res/drawable-xhdpi/game_rocket_small_level_5_ic.webp
  48. 2 2
      app/src/main/res/layout/game_layout_rocket_headline.xml
  49. 95 0
      app/src/main/res/layout/layout_rocket_headline.xml
  50. 1 0
      app/src/main/res/values-ar/strings.xml
  51. 1 0
      app/src/main/res/values-bn/strings.xml
  52. 1 0
      app/src/main/res/values-es/strings.xml
  53. 1 0
      app/src/main/res/values-hi/strings.xml
  54. 1 0
      app/src/main/res/values-in/strings.xml
  55. 3 1
      app/src/main/res/values-kk/strings.xml
  56. 3 1
      app/src/main/res/values-ky/strings.xml
  57. 1 0
      app/src/main/res/values-ms/strings.xml
  58. 1 0
      app/src/main/res/values-pt/strings.xml
  59. 1 0
      app/src/main/res/values-ru/strings.xml
  60. 1 0
      app/src/main/res/values-ta/strings.xml
  61. 1 0
      app/src/main/res/values-te/strings.xml
  62. 3 1
      app/src/main/res/values-tg/strings.xml
  63. 3 2
      app/src/main/res/values-th/strings.xml
  64. 3 1
      app/src/main/res/values-tk/strings.xml
  65. 1 0
      app/src/main/res/values-tl/strings.xml
  66. 2 1
      app/src/main/res/values-tr/strings.xml
  67. 1 0
      app/src/main/res/values-ur/strings.xml
  68. 3 1
      app/src/main/res/values-uz/strings.xml
  69. 2 1
      app/src/main/res/values-vi/strings.xml
  70. 1 0
      app/src/main/res/values-zh-rTW/strings.xml
  71. 1 0
      app/src/main/res/values-zh/strings.xml
  72. 16 3
      app/src/main/res/values/attrs.xml
  73. 64 1
      app/src/main/res/values/colors.xml
  74. 1 0
      app/src/main/res/values/strings.xml
  75. 7 2
      module/game/src/main/AndroidManifest.xml
  76. 41 12
      module/game/src/main/java/com/adealink/weparty/game/GameServiceImpl.kt
  77. 156 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/activity/RocketKingActivity.kt
  78. 60 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/LevelRocketTabItemViewBinder.kt
  79. 45 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/RocketKingItemViewBinder.kt
  80. 54 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/RocketLevelRewardItemViewBinder.kt
  81. 29 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/RocketRecordGiftItemViewBinder.kt
  82. 64 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/RocketRecordItemViewBinder.kt
  83. 30 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/MyRewardEmptyItemViewBinder.kt
  84. 43 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/MyRewardGotItemViewBinder.kt
  85. 52 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/MyRewardInfoViewBinder.kt
  86. 49 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/RewardEmptyItemViewBinder.kt
  87. 40 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/RewardTitleItemViewBinder.kt
  88. 99 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/RewardUserDetailItemViewBinder.kt
  89. 32 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/UserGotRewardItemViewBinder.kt
  90. 213 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/comp/RocketLevelRewardComp.kt
  91. 69 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/comp/RocketRandListComp.kt
  92. 75 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/data/Data.kt
  93. 5 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/data/Tags.kt
  94. 49 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/datasource/remote/RocketHttpService.kt
  95. 36 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketKingRuleDialog.kt
  96. 77 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketLimitTimeGiftDialog.kt
  97. 353 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketPanelDialog.kt
  98. 119 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketRecordDialog.kt
  99. 39 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketRuleDialog.kt
  100. 134 0
      module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketUserRewardDialog.kt

BIN
app/src/main/assets/room_rocket_entrance_level1.svga


BIN
app/src/main/assets/room_rocket_entrance_level2.svga


BIN
app/src/main/assets/room_rocket_entrance_level3.svga


BIN
app/src/main/assets/room_rocket_entrance_level4.svga


BIN
app/src/main/assets/room_rocket_entrance_level5.svga


+ 128 - 28
app/src/main/java/com/adealink/weparty/commonui/game/GameTextView.kt

@@ -10,17 +10,28 @@ import android.graphics.PorterDuffXfermode
 import android.graphics.Shader
 import android.util.AttributeSet
 import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.content.withStyledAttributes
 import com.adealink.frame.aab.util.getCompatColor
 import com.adealink.weparty.R
 
-class GameTextView @JvmOverloads constructor(
+/**
+ * 可设置文字描边的TextView
+ * 默认文字描边颜色 #FF0A8399
+ */
+open class GameTextView @JvmOverloads constructor(
     context: Context, attrs: AttributeSet? = null
 ) : AppCompatTextView(context, attrs) {
+
     //描边
     private val strokePaint = Paint()
     private var strokeColor: Int = 0
     private var strokeWidth: Float = 0f
 
+    //描边渐变色
+    private var strokeColors: Array<Int>? = null
+    private var strokeColorPositions: Array<Float>? = null
+    private var strokeOrientation = VERTICAL_GRADIENT
+
     //阴影
     private val shadowPaint = Paint()
     private var shadowDx: Float = 0f
@@ -34,6 +45,11 @@ class GameTextView @JvmOverloads constructor(
     private var gradientColors: Array<Int>? = null
     private var gradientColorPositions: Array<Float>? = null
 
+    // 斜线
+    private val diagonalPaint = Paint()
+    private var diagonalColor: Int = Color.TRANSPARENT
+    private var diagonalWidth: Float = 0f
+
     private val SRC_ATOP_X_FERMODE = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
 
 
@@ -58,33 +74,49 @@ class GameTextView @JvmOverloads constructor(
         if (isInEditMode) {
             return
         }
-        context.theme.obtainStyledAttributes(
-            attrs,
-            R.styleable.GameTextView,
-            0, 0
-        ).apply {
-            strokeColor = getColor(R.styleable.GameTextView_strokeColor, getCompatColor(R.color.black))
+        context.withStyledAttributes(attrs, R.styleable.GameTextView) {
+            strokeColor =
+                getColor(R.styleable.GameTextView_strokeColor, getCompatColor(R.color.black))
             strokeWidth = getDimension(R.styleable.GameTextView_strokeWidth, 0f)
-
+            val strokeColors = getString(R.styleable.GameTextView_strokeColors)
+            val strokePositions = getString(R.styleable.GameTextView_strokePosition)
+            strokeOrientation =
+                getInt(R.styleable.GameTextView_gradientOrientation, VERTICAL_GRADIENT)
+            if (strokeColors != null && strokePositions != null) {
+                val cc =
+                    strokeColors.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+                val pp = strokePositions.split(",".toRegex()).dropLastWhile { it.isEmpty() }
+                    .toTypedArray()
+                if (cc.isNotEmpty() && cc.size == pp.size) {
+                    this@GameTextView.strokeColors = cc.map { Color.parseColor(it) }.toTypedArray()
+                    strokeColorPositions = pp.map { it.toFloat() }.toTypedArray()
+                    setTextColor(Color.WHITE)
+                }
+            }
             shadowDx = getFloat(R.styleable.GameTextView_shadowDx, 0f)
             shadowDy = getFloat(R.styleable.GameTextView_shadowDy, 0f)
             shadowRadius = getFloat(R.styleable.GameTextView_shadowRadius, 0f)
-            shadowColor = getColor(R.styleable.GameTextView_shadowColor, getCompatColor(R.color.black))
+            shadowColor =
+                getColor(R.styleable.GameTextView_shadowColor, getCompatColor(R.color.black))
 
             val gradientColors = getString(R.styleable.GameTextView_gradientColors)
             val gradientPositions = getString(R.styleable.GameTextView_gradientPosition)
-            gradientOrientation = getInt(R.styleable.GameTextView_gradientOrientation, VERTICAL_GRADIENT)
+            gradientOrientation =
+                getInt(R.styleable.GameTextView_gradientOrientation, VERTICAL_GRADIENT)
             if (gradientColors != null && gradientPositions != null) {
-                val cc = gradientColors.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
-                val pp = gradientPositions.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+                val cc = gradientColors.split(",".toRegex()).dropLastWhile { it.isEmpty() }
+                    .toTypedArray()
+                val pp = gradientPositions.split(",".toRegex()).dropLastWhile { it.isEmpty() }
+                    .toTypedArray()
                 if (cc.isNotEmpty() && cc.size == pp.size) {
-                    this@GameTextView.gradientColors = cc.map { Color.parseColor(it) }.toTypedArray()
+                    this@GameTextView.gradientColors =
+                        cc.map { Color.parseColor(it) }.toTypedArray()
                     gradientColorPositions = pp.map { it.toFloat() }.toTypedArray()
                     setTextColor(Color.WHITE)
                 }
             }
-
-            recycle()
+            diagonalColor = getColor(R.styleable.GameTextView_diagonalColor, Color.TRANSPARENT)
+            diagonalWidth = getDimension(R.styleable.GameTextView_diagonalWidth, 0f)
         }
         updatePaintAndInvalidate()
     }
@@ -116,7 +148,11 @@ class GameTextView @JvmOverloads constructor(
         updatePaintAndInvalidate()
     }
 
-    fun setGradient(colors: List<Int>, positions: List<Float>, orientation: Int = VERTICAL_GRADIENT) {
+    fun setGradient(
+        colors: List<Int>,
+        positions: List<Float>,
+        orientation: Int = VERTICAL_GRADIENT
+    ) {
         gradientOrientation = orientation
         gradientColors = colors.toTypedArray()
         gradientColorPositions = positions.toTypedArray()
@@ -143,18 +179,52 @@ class GameTextView @JvmOverloads constructor(
         strokePaint.isDither = true
         strokePaint.isFilterBitmap = true
         strokePaint.strokeWidth = strokeWidth
-        strokePaint.shader = LinearGradient(
-            0f, 0f,
-            0f, lineHeight.toFloat(),
-            strokeColor,
-            strokeColor,
-            Shader.TileMode.CLAMP
-        )
+        val strokeColors = strokeColors
+        val strokePositions = strokeColorPositions
+
+        if (strokeColors != null && strokePositions != null
+            && strokeColors.isNotEmpty()
+            && strokeColors.size == strokePositions.size
+        ) {
+            strokePaint.shader = if (strokeOrientation == VERTICAL_GRADIENT) {
+                LinearGradient(
+                    0f,
+                    0f,
+                    0f,
+                    lineHeight.toFloat(),
+                    strokeColors.toIntArray(),
+                    strokePositions.toFloatArray(),
+                    Shader.TileMode.CLAMP
+                )
+            } else {
+                LinearGradient(
+                    0f,
+                    0f,
+                    width.toFloat(),
+                    0f,
+                    strokeColors.toIntArray(),
+                    strokePositions.toFloatArray(),
+                    Shader.TileMode.CLAMP
+                )
+            }
+        } else {
+            strokePaint.shader = LinearGradient(
+                0f, 0f,
+                0f, lineHeight.toFloat(),
+                strokeColor,
+                strokeColor,
+                Shader.TileMode.CLAMP
+            )
+        }
+
         strokePaint.strokeJoin = Paint.Join.ROUND
         strokePaint.strokeCap = Paint.Cap.ROUND
         strokePaint.style = Paint.Style.STROKE
         strokePaint.xfermode = SRC_ATOP_X_FERMODE
-
+        if (strokeWidth != 0f && !text.endsWith(" ")) {
+            //描边时给文字末尾加个空格,防止被切割
+            text = text.padEnd(text.length + 1)
+        }
         //渐变画笔
         gradientPaint.isAntiAlias = true
         gradientPaint.isDither = true
@@ -193,7 +263,14 @@ class GameTextView @JvmOverloads constructor(
         gradientPaint.strokeCap = Paint.Cap.ROUND
         gradientPaint.style = Paint.Style.FILL
         strokePaint.xfermode = SRC_ATOP_X_FERMODE
-
+        // 斜线
+        diagonalPaint.apply {
+            isAntiAlias = true
+            strokeWidth = diagonalWidth
+            strokeCap = Paint.Cap.ROUND
+            color = diagonalColor
+            style = Paint.Style.STROKE
+        }
         postInvalidate()
     }
 
@@ -201,8 +278,8 @@ class GameTextView @JvmOverloads constructor(
         return shadowDx > 0 || shadowDy > 0
     }
 
-    private fun isDrawStroke(): Boolean {
-        return strokeWidth > 0
+    private fun isDiagonal(): Boolean {
+        return diagonalWidth > 0
     }
 
     private fun isDrawGradient(): Boolean {
@@ -213,6 +290,10 @@ class GameTextView @JvmOverloads constructor(
                 && gradientColors.size == gradientPositions.size
     }
 
+    private fun isDrawStroke(): Boolean {
+        return strokeWidth > 0
+    }
+
     fun setShadowLayer(radius: Float, dx: Float, dy: Float, color: String?) {
         shadowPaint.setShadowLayer(radius, dx, dy, Color.parseColor(color))
     }
@@ -231,7 +312,10 @@ class GameTextView @JvmOverloads constructor(
                 super.onDraw(canvas)
             }
         }
-
+        // 斜线
+        if (isDiagonal()) {
+            drawDiagonal(canvas)
+        }
         if (isDrawGradient()) {
             //渐变
             drawGradient {
@@ -334,8 +418,24 @@ class GameTextView @JvmOverloads constructor(
         paint.style = originStyle
     }
 
+    private fun drawDiagonal(canvas: Canvas) {
+        if (diagonalWidth > 0) {
+            diagonalPaint.color = diagonalColor
+            diagonalPaint.strokeWidth = diagonalWidth
+            diagonalPaint.style = Paint.Style.STROKE
+            diagonalPaint.strokeCap = Paint.Cap.ROUND
+            diagonalPaint.isAntiAlias = true
+            canvas.drawLine(
+                0f,
+                (height.toFloat() * 0.4).toFloat(), width.toFloat(),
+                (height.toFloat() * 0.6).toFloat(), diagonalPaint
+            )
+        }
+    }
+
     companion object {
         const val VERTICAL_GRADIENT = 0
         const val HORIZONTAL_GRADIENT = 1
     }
+
 }

+ 10 - 0
app/src/main/java/com/adealink/weparty/commonui/widget/CommonEmptyErrorView.kt

@@ -8,6 +8,7 @@ import androidx.annotation.ColorInt
 import androidx.annotation.DrawableRes
 import androidx.annotation.StringRes
 import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.updateLayoutParams
 import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.util.DisplayUtil
 import com.adealink.weparty.databinding.LayoutCommonEmptyErrorBinding
@@ -67,6 +68,15 @@ class CommonEmptyErrorView @JvmOverloads constructor(
         binding.ivErrorEmpty.layoutParams = lp
     }
 
+    /**
+     * 设置图片大小
+     */
+    fun setImageSize(size: Int) {
+        binding.ivErrorEmpty.updateLayoutParams<LayoutParams> {
+            width = size
+        }
+    }
+
     fun hide() {
         visibility = View.GONE
     }

+ 2 - 0
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/FloatViewFactory.kt

@@ -9,6 +9,7 @@ import com.adealink.weparty.debug.SysMemoryUsageFloatView
 import com.adealink.weparty.module.call.CallModule
 import com.adealink.weparty.module.couple.CoupleModule
 import com.adealink.weparty.module.follow.FollowModule
+import com.adealink.weparty.module.game.GameModule
 import com.adealink.weparty.module.level.LevelModule
 import com.adealink.weparty.module.message.MessageModule
 import com.adealink.weparty.module.profile.ProfileModule
@@ -42,6 +43,7 @@ class FloatViewFactory : IFloatViewFactory {
             FloatWindowType.HOME_INCOME -> HomeIncomeFloatView(data as HomeIncomeFloatData)
             FloatWindowType.HOME_NEW_USER_LOTTERY -> HomeBannerEntranceFloatView(data as HomeBannerEntranceFloatData)
             FloatWindowType.MEMORY_USAGE -> SysMemoryUsageFloatView(data as SysMemoryUsageFloatData)
+            FloatWindowType.ROCKET_HEADLINE -> GameModule.getRocketHeadlineFloatView(data)
         }
         return floatView as? V
     }

+ 2 - 3
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/data/IFloatData.kt

@@ -1,7 +1,5 @@
 package com.adealink.weparty.commonui.widget.floatview.data
 
-import com.adealink.weparty.BuildConfig
-
 enum class FloatWindowType(val type: String) {
     NETWORK_DISCONNECT_TIP("network_disconnect_tip"),
     MINIMIZE_ROOM("minimize_room"),
@@ -17,7 +15,8 @@ enum class FloatWindowType(val type: String) {
     HOME_TASK_COUNT_DOWN("home_task_count_down"),
     HOME_INCOME("home_income"),
     HOME_NEW_USER_LOTTERY("home_new_user_lottery"),
-    MEMORY_USAGE("memory_usage")
+    MEMORY_USAGE("memory_usage"),
+    ROCKET_HEADLINE("rocket_headline")
 }
 
 

+ 91 - 50
app/src/main/java/com/adealink/weparty/commonui/widget/progress/SinWaveProgressBar.kt

@@ -2,48 +2,81 @@ package com.adealink.weparty.commonui.widget.progress
 
 import android.animation.ValueAnimator
 import android.content.Context
-import android.content.res.TypedArray
 import android.graphics.Canvas
+import android.graphics.LinearGradient
 import android.graphics.Paint
 import android.graphics.Path
+import android.graphics.Shader
 import android.util.AttributeSet
 import android.view.View
 import android.view.animation.LinearInterpolator
+import androidx.core.content.withStyledAttributes
+import com.adealink.frame.aab.util.getCompatColor
+import com.adealink.frame.base.fastLazy
 import com.adealink.frame.util.DisplayUtil
 import com.adealink.weparty.R
+import kotlin.math.sin
 
 class SinWaveProgressBar @JvmOverloads constructor(
     context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,
 ) : View(context, attrs, defStyleAttr) {
     private var viewWidth = 0
     private var viewHeight = 0
-    private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG)
-    private val linePath: Path
-    var color: Int = 0
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-            invalidate()
+
+    //背景
+    private val bgLinePath by fastLazy { Path() }
+    private val bgLinePaint by fastLazy {
+        Paint(Paint.ANTI_ALIAS_FLAG).apply {
+            style = Paint.Style.FILL
+            isAntiAlias = true
+            color = getCompatColor(R.color.color_FFDE2AEF)
+        }
+    }
+
+    //前景
+    private val fgLinePath by fastLazy { Path() }
+    private val fgLinePaint by fastLazy {
+        Paint(Paint.ANTI_ALIAS_FLAG).apply {
+            style = Paint.Style.FILL
+            isAntiAlias = true
+            shader = LinearGradient(
+                0F, 0F,  // 渐变起点坐标
+                0F, viewHeight.toFloat(),      // 渐变终点坐标
+                intArrayOf(
+                    getCompatColor(R.color.color_FFF683FF),
+                    getCompatColor(R.color.color_FFFAB4FF),
+                    getCompatColor(R.color.color_FFF9AEFF),  // 渐变颜色数组
+                    getCompatColor(R.color.color_FFF683FF),
+                ),
+                floatArrayOf(0.25F, 0.4F, 0.6F, 0.75F),           // 颜色位置(null表示均匀分布)
+                Shader.TileMode.CLAMP  // 渐变填充模式
+            )
         }
+    }
 
     //振幅,单位px
-    private var amplitude: Float
+    private var amplitude: Float = 0F
 
     //角频率, 控制周期
-    private var angularFrequency: Float
+    private var angularFrequency: Float = 0F
 
     //初次相位角,
     private var phaseAngle = 0 * Math.PI / 180 + Math.PI / 2 * -1
 
     //相位动画持续时间
-    private var animationDuration: Int
+    private var animationDuration: Int = 0
+
+    //波浪起始点的y坐标
     private val waveStartY: Float
-        //波浪起始点的y坐标
         get() {
             return viewHeight * (1 - progress) + amplitude * 2
         }
+
+    //波浪起始点的y坐标
+    private val waveStartX: Float
+        get() {
+            return viewWidth * (progress) + amplitude * 2
+        }
     var progress: Float = 0f
         set(value) {
             if (field == value) {
@@ -55,34 +88,17 @@ class SinWaveProgressBar @JvmOverloads constructor(
     var animator: ValueAnimator? = null
 
     init {
-        val typedArray: TypedArray = context.obtainStyledAttributes(
-            attrs, R.styleable.SinWaveProgressBar
-        )
-        color = typedArray.getColor(
-            R.styleable.SinWaveProgressBar_waveColor, 0
-        )
-        amplitude =
-            typedArray.getDimension(
+        context.withStyledAttributes(attrs, R.styleable.SinWaveProgressBar) {
+            amplitude = getDimension(
                 R.styleable.SinWaveProgressBar_amplitude,
                 DisplayUtil.dp2px(5f).toFloat()
             )
-
-        val frequency = typedArray.getFloat(
-            R.styleable.SinWaveProgressBar_frequency,
-            8f
-        )
-        angularFrequency = frequency * 1.0f / 4
-
-        val radian = typedArray.getFloat(R.styleable.SinWaveProgressBar_phaseAngleRadian, 0f)
-        this.phaseAngle = radian * Math.PI / 180 + Math.PI / 2 * -1
-
-        animationDuration =
-            typedArray.getInt(R.styleable.SinWaveProgressBar_animationDuration, 1000)
-
-        typedArray.recycle()
-        linePaint.style = Paint.Style.FILL
-        linePaint.isAntiAlias = true
-        linePath = Path()
+            val frequency = getFloat(R.styleable.SinWaveProgressBar_frequency, 8f)
+            angularFrequency = frequency * 1.0f / 4
+            val radian = getFloat(R.styleable.SinWaveProgressBar_phaseAngleRadian, 0f)
+            phaseAngle = radian * Math.PI / 180 + Math.PI / 2 * -1
+            animationDuration = getInt(R.styleable.SinWaveProgressBar_animationDuration, 1000)
+        }
     }
 
     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
@@ -93,18 +109,43 @@ class SinWaveProgressBar @JvmOverloads constructor(
 
     override fun onDraw(canvas: Canvas) {
         super.onDraw(canvas)
-        linePaint.color = color
-        linePath.reset()
-        linePath.moveTo(0f, waveStartY)
-        for (i in 0 until viewWidth) {
-            val angle = i * 1f / viewWidth * 2 * Math.PI
-            val y = amplitude * Math.sin(angle * angularFrequency + phaseAngle)
-            linePath.lineTo(i.toFloat(), (y + waveStartY - amplitude).toFloat())
+        when {
+            progress >= 100 -> drawComplete(canvas)
+            progress <= 0 -> {} //不绘制
+            else -> drawRunning(canvas)
         }
-        linePath.lineTo(viewWidth.toFloat(), (viewHeight + 1).toFloat())
-        linePath.lineTo(0f, (viewHeight + 1).toFloat())
-        linePath.close()
-        canvas.drawPath(linePath, linePaint)
+    }
+
+    private fun drawComplete(canvas: Canvas) {
+        canvas.drawRect(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat(), fgLinePaint)
+    }
+
+    private fun drawRunning(canvas: Canvas) {
+        bgLinePath.reset()
+        fgLinePath.reset()
+
+        // 水平波浪从左侧开始
+        bgLinePath.moveTo(waveStartX, 0f)
+        fgLinePath.moveTo(waveStartX, 0f)
+
+        // 计算波浪路径
+        for (i in 0 until viewHeight) {
+            val angle = i * 1f / viewHeight * 2 * Math.PI
+            val x1 = amplitude * sin(angle * angularFrequency + phaseAngle)
+            bgLinePath.lineTo((x1 + waveStartX - amplitude).toFloat(), i.toFloat())
+            fgLinePath.lineTo((-x1 + waveStartX - amplitude).toFloat(), i.toFloat())
+        }
+
+        // 闭合路径形成填充区域
+        bgLinePath.lineTo(0f, viewHeight.toFloat())
+        bgLinePath.lineTo(0f, 0f)
+        bgLinePath.close()
+        fgLinePath.lineTo(0f, viewHeight.toFloat())
+        fgLinePath.lineTo(0f, 0f)
+        fgLinePath.close()
+
+        canvas.drawPath(bgLinePath, bgLinePaint)
+        canvas.drawPath(fgLinePath, fgLinePaint)
     }
 
     /**

+ 35 - 2
app/src/main/java/com/adealink/weparty/module/game/GameModule.kt

@@ -5,11 +5,14 @@ import com.adealink.frame.aab.BaseDynamicModule
 import com.adealink.frame.base.Rlt
 import com.adealink.weparty.R
 import com.adealink.weparty.cocosgame.data.Game
+import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
+import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
 import com.adealink.weparty.module.game.data.CommonActivityRewardInfoReq
 import com.adealink.weparty.module.game.data.GameShowConfig
 import com.adealink.weparty.module.game.data.GameType
 import com.adealink.weparty.module.game.data.ReceiveRewardReq
 import com.adealink.weparty.module.game.data.UserGameLevelInfoResult
+import com.adealink.weparty.module.game.rocket.viewmodel.IRocketViewModel
 import com.adealink.weparty.module.game.viewmodel.IGameViewModel
 import com.adealink.weparty.module.game.viewmodel.IMicCharmPKViewModel
 import com.adealink.weparty.module.game.viewmodel.IRedPacketViewModel
@@ -43,6 +46,10 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
         return getService().getMicCharmPKViewModel(owner)
     }
 
+    override fun getRocketViewModel(owner: ViewModelStoreOwner): IRocketViewModel? {
+        return getService().getRocketViewModel(owner)
+    }
+
     override fun updateCurRedPacketInfo(redPacketInfo: RedPacketInfo?) {
         return getService().updateCurRedPacketInfo(redPacketInfo)
     }
@@ -79,7 +86,10 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
         return getService().logout()
     }
 
-    override suspend fun getGameBetCoinsConfig(game: Game, gameType: Int?): Map<Int, Map<Int, List<Int>>> {
+    override suspend fun getGameBetCoinsConfig(
+        game: Game,
+        gameType: Int?
+    ): Map<Int, Map<Int, List<Int>>> {
         return getService().getGameBetCoinsConfig(game, gameType)
     }
 
@@ -87,6 +97,14 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
         getService().checkPlayingGame()
     }
 
+    override fun initRocket() {
+        getService().initRocket()
+    }
+
+    override fun getRocketHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
+        return getService().getRocketHeadlineFloatView(data)
+    }
+
     override fun emptyService(): IGameService {
         return object : IGameService {
 
@@ -106,6 +124,10 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
                 return null
             }
 
+            override fun getRocketViewModel(owner: ViewModelStoreOwner): IRocketViewModel? {
+                return null
+            }
+
             override fun updateCurRedPacketInfo(redPacketInfo: RedPacketInfo?) {
             }
 
@@ -141,7 +163,10 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
 
             }
 
-            override suspend fun getGameBetCoinsConfig(game: Game, gameType: Int?): Map<Int, Map<Int, List<Int>>> {
+            override suspend fun getGameBetCoinsConfig(
+                game: Game,
+                gameType: Int?
+            ): Map<Int, Map<Int, List<Int>>> {
                 return emptyMap()
             }
 
@@ -149,6 +174,14 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
 
             }
 
+            override fun initRocket() {
+
+            }
+
+            override fun getRocketHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
+                return null
+            }
+
             override fun getService(): IGameService? {
                 return null
             }

+ 7 - 0
app/src/main/java/com/adealink/weparty/module/game/IGameService.kt

@@ -4,12 +4,15 @@ import androidx.lifecycle.ViewModelStoreOwner
 import com.adealink.frame.aab.IService
 import com.adealink.frame.base.Rlt
 import com.adealink.weparty.cocosgame.data.Game
+import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
+import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
 import com.adealink.weparty.module.game.data.CommonActivityRewardInfoReq
 import com.adealink.weparty.module.game.data.GameActivityRewardInfo
 import com.adealink.weparty.module.game.data.GameShowConfig
 import com.adealink.weparty.module.game.data.GameType
 import com.adealink.weparty.module.game.data.ReceiveRewardReq
 import com.adealink.weparty.module.game.data.UserGameLevelInfoResult
+import com.adealink.weparty.module.game.rocket.viewmodel.IRocketViewModel
 import com.adealink.weparty.module.game.viewmodel.IGameViewModel
 import com.adealink.weparty.module.game.viewmodel.IMicCharmPKViewModel
 import com.adealink.weparty.module.game.viewmodel.IRedPacketViewModel
@@ -25,6 +28,7 @@ interface IGameService : IService<IGameService> {
     fun getRedPacketViewModel(owner: ViewModelStoreOwner): IRedPacketViewModel?
     fun getRouletteViewModel(owner: ViewModelStoreOwner): IRouletteViewModel?
     fun getMicCharmPKViewModel(owner: ViewModelStoreOwner): IMicCharmPKViewModel?
+    fun getRocketViewModel(owner: ViewModelStoreOwner): IRocketViewModel?
     fun updateCurRedPacketInfo(redPacketInfo: RedPacketInfo?)
     suspend fun isShowRoulette(roomId: Long): Boolean
     suspend fun isGameShow(configTypes: List<Int>): Map<Int, GameShowConfig>
@@ -41,4 +45,7 @@ interface IGameService : IService<IGameService> {
      */
     suspend fun getGameBetCoinsConfig(game: Game, gameType: Int? = null): Map<Int, Map<Int, List<Int>>>
     fun checkPlayingGame()
+
+    fun initRocket()
+    fun getRocketHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>?
 }

+ 68 - 0
app/src/main/java/com/adealink/weparty/module/game/Router.kt

@@ -80,4 +80,72 @@ interface Game {
         }
 
     }
+
+    interface Rocket {
+
+        interface RocketPanel {
+            companion object {
+                const val PATH = "/game/rocket"
+            }
+        }
+
+        interface RocketKing {
+            companion object {
+                const val PATH = "/game/rocket_king"
+            }
+        }
+
+        interface RocketLevelRewardFragment {
+            companion object {
+                const val PATH = "/game/rocket_level_reward"
+
+                const val LIMIT_GIFT_PATH = "/game/rocket_limit_gift"
+
+                const val EXTRA_REWARD_TAB_INDEX = "extra_reward_tab_index"
+            }
+        }
+
+        interface RocketRule {
+            companion object {
+                const val PATH = "/game/rocket/rule"
+            }
+        }
+
+        interface RocketKingRule {
+            companion object {
+                const val PATH = "/game/rocket/king/rule"
+            }
+        }
+
+
+        interface RocketRecord {
+            companion object {
+                const val PATH = "/game/rocket/record"
+            }
+        }
+
+        interface RocketUserRewardPre {
+            companion object {
+                const val TAG = "RocketUserRewardPreDialog"
+                const val PATH = "/game/rocket/user_reward_pre"
+            }
+        }
+
+        interface RocketUserReward {
+            companion object {
+                const val TAG = "RocketUserRewardDialog"
+                const val PATH = "/game/rocket/user_reward"
+                const val EXTRA_REWARD_LEVEL = "extra_reward_level"
+            }
+        }
+
+        interface RocketLimitTimeGift {
+            companion object {
+                const val PATH = "/game/rocket/limit_time_gift"
+                const val EXTRA_REWARD_INFO = "extra_reward_info"
+            }
+        }
+
+    }
+
 }

+ 17 - 2
app/src/main/java/com/adealink/weparty/module/game/rocket/Data.kt

@@ -21,8 +21,9 @@ enum class RocketLevel(val level: Int) {
     LEVE1(1),
     LEVE2(2),
     LEVE3(3),
-    LEVE4(4),
-    LEVE5(5);
+    LEVE4(4);
+    //    LEVE5(5);
+
     companion object {
         fun map(level: Int): RocketLevel {
             return values().firstOrNull { it.level == level } ?: LEVE1
@@ -35,6 +36,18 @@ enum class RocketLevel(val level: Int) {
         fun get(level: Int): RocketLevel? {
             return values().firstOrNull { it.level == level }
         }
+
+        fun isReachMaxRocketLevel(level: Int): Boolean {
+            return level == LEVE4.level
+        }
+
+        fun mapToRocketLevelWithLimit(level: Int): RocketLevel {
+            return if (level > LEVE4.level) {
+                LEVE4
+            } else {
+                RocketLevel.map(level)
+            }
+        }
     }
 }
 
@@ -130,6 +143,8 @@ data class RocketRewardIssueNotify(
     val roomId: Long,
     @SerializedName("level")
     val level: Int,
+    @SerializedName("picurl")
+    val picUrl: String,
 )
 
 data class GetRocketKingReq(

+ 3 - 4
app/src/main/java/com/adealink/weparty/module/game/rocket/effect/RocketHeadlineEffectView.kt

@@ -26,7 +26,6 @@ class RocketHeadlineEffectView @JvmOverloads constructor(
     private val screenWidth by fastLazy { DisplayUtil.getScreenWidth() }
     private val curHeadlineView by fastLazy {
         RocketHeadlineView(context).apply {
-            setKeepTime(ROCKET_HEAD_LINE_KEEP_TIME)
             headlineOpListener = this@RocketHeadlineEffectView.headlineOpListener
         }
     }
@@ -52,8 +51,8 @@ class RocketHeadlineEffectView @JvmOverloads constructor(
     }
 
     override fun play(entity: IEffectEntity<out IEffectView>?, l: IPlayListener?) {
-        val entity = entity as? RocketHeadlineEffectEntity
-        if (entity?.levelUpgradeNotify == null) {
+        val rocketEntity = entity as? RocketHeadlineEffectEntity
+        if (rocketEntity?.levelUpgradeNotify == null) {
             l?.onError(ERROR_ENTITY_NULL)
             return
         }
@@ -63,7 +62,7 @@ class RocketHeadlineEffectView @JvmOverloads constructor(
         }
         curHeadlineView.show()
         curHeadlineView.translationX = transX
-        curHeadlineView.updateUI(entity.levelUpgradeNotify)
+        curHeadlineView.updateUI(rocketEntity.levelUpgradeNotify)
         curHeadlineView.animate().translationX(0f).setDuration(300).withEndAction {
             curHeadlineView.animate().setDuration(ROCKET_HEAD_LINE_KEEP_TIME).withEndAction {
                 curHeadlineView.animate().translationX(-transX).setDuration(300).withEndAction {

+ 68 - 101
app/src/main/java/com/adealink/weparty/module/game/rocket/util/UIUtil.kt

@@ -1,7 +1,7 @@
 package com.adealink.weparty.module.game.rocket.util
 
-import android.annotation.SuppressLint
 import androidx.appcompat.widget.AppCompatTextView
+import com.adealink.frame.aab.util.getCompatColor
 import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.oss.ossService
 import com.adealink.weparty.R
@@ -18,119 +18,85 @@ fun getRankTopName(rank: Int): String {
     }
 }
 
-fun getRocketSmallResId(level: Int): Int {
+fun getRocketHeadlineResId(level: Int): Int {
     return when (level) {
-        RocketLevel.LEVE2.level -> {
-            //Level 2
-            R.drawable.game_rocket_small_level_2_ic
-        }
-
-        RocketLevel.LEVE3.level -> {
-            //Level 3
-            R.drawable.game_rocket_small_level_3_ic
-        }
-
-        RocketLevel.LEVE4.level -> {
-            //Level 4
-            R.drawable.game_rocket_small_level_4_ic
-        }
+        RocketLevel.LEVE2.level -> R.drawable.game_rocket_headline_2_ic //Level 2
+        RocketLevel.LEVE3.level -> R.drawable.game_rocket_headline_3_ic //Level 3
+        in RocketLevel.LEVE4.level..Int.MAX_VALUE -> R.drawable.game_rocket_headline_4_ic //Level 4
+//        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> R.drawable.game_rocket_headline_5_ic //Level 5~MAX
+        else -> R.drawable.game_rocket_headline_1_ic //Level 1
+    }
+}
 
-        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> {
-            //Level 5~MAX
-            R.drawable.game_rocket_small_level_5_ic
-        }
+fun getRocketSmallResId(level: Int): Int {
+    return when (level) {
+        RocketLevel.LEVE2.level -> R.drawable.game_rocket_level_2_ic //Level 2
+        RocketLevel.LEVE3.level -> R.drawable.game_rocket_level_3_ic //Level 3
+        in RocketLevel.LEVE4.level..Int.MAX_VALUE -> R.drawable.game_rocket_level_4_ic //Level 4
+//        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> R.drawable.game_rocket_level_5_ic //Level 5~MAX
+        else -> R.drawable.game_rocket_level_1_ic //Level 1
+    }
+}
 
-        else -> {
-            //Level 1
-            R.drawable.game_rocket_small_level_1_ic
-        }
+/**
+ * 房间火箭入口
+ */
+fun getRoomRocketEntranceResId(level: Int): Int {
+    return when (level) {
+        RocketLevel.LEVE2.level -> R.drawable.game_rocket_entrance_2_ic //Level 2
+        RocketLevel.LEVE3.level -> R.drawable.game_rocket_entrance_3_ic//Level 3
+        in RocketLevel.LEVE4.level..Int.MAX_VALUE -> R.drawable.game_rocket_entrance_4_ic //Level 4
+//        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> R.drawable.game_rocket_entrance_5_ic //Level 5~MAX
+        else -> R.drawable.game_rocket_entrance_1_ic //Level 1
     }
 }
 
 fun getProgressBgColor(level: Int): Int {
     return when (level) {
-        RocketLevel.LEVE2.level -> {
-            //Level 2
-            R.color.color_FF0228A9
-        }
-
-        RocketLevel.LEVE3.level -> {
-            //Level 3
-            R.color.color_FF980096
-        }
-
-        RocketLevel.LEVE4.level -> {
-            //Level 4
-            R.color.color_FF9C0000
-        }
-
-        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> {
-            //Level 5~MAX
-            R.color.color_FFDB9100
-        }
-
-        else -> {
-            //Level 1
-            R.color.color_FF126B00
-        }
+        RocketLevel.LEVE2.level -> R.color.color_E00A1559
+        RocketLevel.LEVE3.level -> R.color.color_E0400A59
+        in RocketLevel.LEVE4.level..Int.MAX_VALUE -> R.color.color_E0590A0A
+//        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> R.color.color_E059320A
+        else -> R.color.color_E00A2359
     }
 }
 
-fun getProgressColor(level: Int): Int {
+fun getProgressColor(level: Int): IntArray {
     return when (level) {
-        RocketLevel.LEVE2.level -> {
-            //Level 2
-            R.color.color_FF1FCEFF
-        }
-
-        RocketLevel.LEVE3.level -> {
-            //Level 3
-            R.color.color_FFFE4BFF
-        }
-
-        RocketLevel.LEVE4.level -> {
-            //Level 4
-            R.color.color_FFFF7549
-        }
-
-        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> {
-            //Level 5~MAX
-            R.color.color_FFFFE900
-        }
-
-        else -> {
-            //Level 1
-            R.color.color_FF8AE933
-        }
+        RocketLevel.LEVE2.level -> intArrayOf(
+            getCompatColor(R.color.color_FF3041FF),
+            getCompatColor(R.color.color_FFA19EFF)
+        )
+
+        RocketLevel.LEVE3.level -> intArrayOf(
+            getCompatColor(R.color.color_FFA42CFF),
+            getCompatColor(R.color.color_FFCDA9FF)
+        )
+
+        in RocketLevel.LEVE4.level..Int.MAX_VALUE -> intArrayOf(
+            getCompatColor(R.color.color_FFFF4830),
+            getCompatColor(R.color.color_FFFFAD9E)
+        )
+
+//        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> intArrayOf(
+//            getCompatColor(R.color.color_FFFF9730),
+//            getCompatColor(R.color.color_FFFFE59E)
+//        )
+
+        else -> intArrayOf(
+            getCompatColor(R.color.color_FF3041FF),
+            getCompatColor(R.color.color_FF9EE5FF)
+        )
     }
 }
 
 fun getToLaunchRocketSvgaName(level: Int): String {
     return when (level) {
-        RocketLevel.LEVE2.level -> {
-            //Level 2
-            "room_rocket_entrance_level2.svga"
-        }
-
-        RocketLevel.LEVE3.level -> {
-            //Level 3
-            "room_rocket_entrance_level3.svga"
-        }
-
-        RocketLevel.LEVE4.level -> {
-            //Level 4
-            "room_rocket_entrance_level4.svga"
-        }
-
-        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> {
-            //Level 5~MAX
-            "room_rocket_entrance_level5.svga"
-        }
-
-        else -> {
-            //Level 1
-            "room_rocket_entrance_level1.svga"
-        }
+        RocketLevel.LEVE2.level -> "room_rocket_entrance_level2.svga"
+        RocketLevel.LEVE3.level -> "room_rocket_entrance_level3.svga"
+        in RocketLevel.LEVE4.level..Int.MAX_VALUE -> "room_rocket_entrance_level4.svga"
+//        in RocketLevel.LEVE5.level..Int.MAX_VALUE -> "room_rocket_entrance_level5.svga"
+        else -> "room_rocket_entrance_level1.svga"
     }
 }
 
@@ -138,28 +104,29 @@ fun getToLaunchRocketSvgaName(level: Int): String {
  * 火箭发射动效
  */
 fun getRocketLaunchResUrl(level: RocketLevel): String {
-    return ossService.getUrlByPath("/activity/rocket/level${level.level}_rocket_launch_v3.mp4")
+    return ossService.getUrlByPath("/activity/rocket/level${level.level}_rocket_launch_v7.mp4")
 }
 
 /**
  * 发射台的火箭动效
  */
 fun getLaunchPadRocketResUrl(level: RocketLevel): String {
-    return ossService.getUrlByPath("/activity/rocket/level${level.level}_rocket_launch_pod_v3.mp4")
+    return ossService.getUrlByPath("/activity/rocket/level${level.level}_rocket_launch_pod_v5.mp4")
 }
 
 /**
  * 火箭发射台动效
  */
 fun getRocketLaunchPadResUrl(): String {
-    return ossService.getUrlByPath("/activity/rocket/rocket_launch_pod.mp4")
+    return ossService.getUrlByPath("/activity/rocket/rocket_launch_pod_1.mp4")
 }
 
 /**
  * 火箭头条动效
  */
 fun getRocketHeadLineResUrl(level: RocketLevel): String {
-    return ossService.getUrlByPath("/activity/rocket/rocket_level${level.level}_headline_v2.svga")
+    // 参考Fungo:各等级使用了统一资源
+    return ossService.getUrlByPath("/activity/rocket/rocket_headline_v3.svga")
 }
 
 

+ 23 - 67
app/src/main/java/com/adealink/weparty/module/game/rocket/view/RocketHeadlineView.kt

@@ -1,6 +1,7 @@
 package com.adealink.weparty.module.game.rocket.view
 
 import android.content.Context
+import android.graphics.BitmapFactory
 import android.util.AttributeSet
 import android.view.LayoutInflater
 import androidx.constraintlayout.widget.ConstraintLayout
@@ -8,40 +9,28 @@ import com.adealink.frame.aab.util.getCompatString
 import com.adealink.frame.router.Router
 import com.adealink.frame.util.AppUtil
 import com.adealink.weparty.R
-import com.adealink.weparty.commonui.ext.gone
-import com.adealink.weparty.commonui.ext.show
-import com.adealink.weparty.databinding.GameLayoutRocketHeadlineBinding
+import com.adealink.weparty.databinding.LayoutRocketHeadlineBinding
 import com.adealink.weparty.module.game.rocket.RocketLevel
 import com.adealink.weparty.module.game.rocket.RocketUpgradeNotify
 import com.adealink.weparty.module.game.rocket.effect.IHeadlineBtnClickListener
 import com.adealink.weparty.module.game.rocket.util.getRocketHeadLineResUrl
+import com.adealink.weparty.module.game.rocket.util.getRocketHeadlineResId
 import com.adealink.weparty.module.room.Room
 import com.adealink.weparty.room.data.EnterRoomInfo
 import com.adealink.weparty.room.data.JoinRoomFrom
+import com.opensource.svgaplayer.SVGADynamicEntity
+import com.opensource.svgaplayer.utils.Supplier
 import java.io.File
-import kotlin.math.max
 
 class RocketHeadlineView @JvmOverloads constructor(
     context: Context, attrs: AttributeSet? = null,
 ) : ConstraintLayout(context, attrs) {
-    val binding = GameLayoutRocketHeadlineBinding.inflate(LayoutInflater.from(context), this, true)
+    val binding = LayoutRocketHeadlineBinding.inflate(LayoutInflater.from(context), this, true)
 
     private var notify: RocketUpgradeNotify? = null
 
-    private var keepTime = MAX_KEEP_TIME_MS
-    private var countTimeLeft = 0L
     var headlineOpListener: IHeadlineBtnClickListener? = null
 
-    private val countDownUpdateTask = Runnable {
-        countTimeLeft--
-        updateCountDown()
-        startCountDownTimer()
-    }
-
-    fun setKeepTime(keepTime: Long) {
-        this.keepTime = keepTime
-    }
-
     fun updateUI(notify: RocketUpgradeNotify) {
         this.notify = notify
         playBackgroundSvga(notify.level, notify.effectPath)
@@ -62,69 +51,36 @@ class RocketHeadlineView @JvmOverloads constructor(
                 )
                 .start()
         }
-        updateDelayTimeAndStart()
     }
 
 
     private fun playBackgroundSvga(level: Int, effectPath: String?) {
         if (effectPath.isNullOrEmpty()) {
-            binding.svgaBg.setUrl(getRocketHeadLineResUrl(RocketLevel.map(level)))
-        } else {
-            binding.svgaBg.setFile(File(effectPath))
-        }
-    }
-
-    private fun updateDelayTimeAndStart() {
-        val notify = this.notify ?: return
-        val timePassMs = max(0, System.currentTimeMillis() - notify.receiveNotifyTime)
-        countTimeLeft = (MAX_KEEP_TIME_MS - timePassMs + 1000L) / 1000L
-        updateCountDown()
-        startCountDownTimer()
-    }
-
-    private fun updateCountDown() {
-        if (!isAttachedToWindow) {
-            return
-        }
-        if (countTimeLeft >= 0) {
-            binding.tvCountdown.show()
-            binding.tvCountdown.text = max(0, countTimeLeft).toString()
+            binding.svgaBg.setUrl(
+                getRocketHeadLineResUrl(RocketLevel.map(level)),
+                supplier = Supplier {
+                    val dynamicEntity = SVGADynamicEntity()
+                    val bitmap =
+                        BitmapFactory.decodeResource(resources, getRocketHeadlineResId(level))
+                    dynamicEntity.setDynamicImage(bitmap, KEY_ROCKET_ICON)
+                    return@Supplier dynamicEntity
+                }
+            )
         } else {
-            binding.tvCountdown.gone()
+            binding.svgaBg.setFile(File(effectPath), supplier = Supplier {
+                val dynamicEntity = SVGADynamicEntity()
+                val bitmap = BitmapFactory.decodeResource(resources, getRocketHeadlineResId(level))
+                dynamicEntity.setDynamicImage(bitmap, KEY_ROCKET_ICON)
+                return@Supplier dynamicEntity
+            })
         }
     }
 
-    private fun startCountDownTimer() {
-        if (countTimeLeft <= 0) {
-            if (isAttachedToWindow) {
-                binding.tvCountdown.gone()
-            }
-            return
-        }
-        removeCallbacks(countDownUpdateTask)
-        postDelayed(countDownUpdateTask, 1000)
-    }
-
-    private fun stopCountDownTimer() {
-        removeCallbacks(countDownUpdateTask)
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        updateDelayTimeAndStart()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        stopCountDownTimer()
-    }
-
     fun stop() {
         binding.svgaBg.stopAnimation(true)
-        stopCountDownTimer()
     }
 
     companion object {
-        private const val MAX_KEEP_TIME_MS = 10_000L //10秒
+        private const val KEY_ROCKET_ICON = "image" //火箭
     }
 }

+ 9 - 1
app/src/main/java/com/adealink/weparty/module/game/rocket/viewmodel/IRocketViewModel.kt

@@ -3,7 +3,13 @@ package com.adealink.weparty.module.game.rocket.viewmodel
 import androidx.lifecycle.LiveData
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.mvvm.livedata.ExtLiveData
-import com.adealink.weparty.module.game.rocket.*
+import com.adealink.weparty.module.game.rocket.RocketKingRes
+import com.adealink.weparty.module.game.rocket.RocketKingUserInfo
+import com.adealink.weparty.module.game.rocket.RocketLevel
+import com.adealink.weparty.module.game.rocket.RocketRankUserInfo
+import com.adealink.weparty.module.game.rocket.RocketRewardIssueNotify
+import com.adealink.weparty.module.game.rocket.RocketUpgradeNotify
+import com.adealink.weparty.module.game.rocket.RocketUserRewardInfo
 import com.adealink.weparty.module.operation.data.RewardDetailData
 
 /**
@@ -38,6 +44,8 @@ interface IRocketViewModel {
 
     fun getRocketRewardInfoByLevel(level: RocketLevel): LiveData<Rlt<RocketUserRewardInfo>>
 
+    fun getDownloadedLaunchPadRocketEffectPath(level: RocketLevel): String?
+
     fun getLaunchPadRocketEffectPath(level: RocketLevel): LiveData<Rlt<String>>
 
     fun getLaunchPadEffectPath(): LiveData<Rlt<String>>

+ 1 - 0
app/src/main/java/com/adealink/weparty/ui/MainStartUpFragment.kt

@@ -116,6 +116,7 @@ class MainStartUpFragment : BaseFragment() {
         walletViewModel?.addGlobalConfigListener()
         walletViewModel?.getUserChargeNotify()
         globalConfigManager.getAllGlobalConfig(true)
+        GameModule.initRocket()
     }
 
     private val otherTask by fastLazy {

+ 24 - 0
app/src/main/java/com/qmuiteam/qmui/widget/QMUIProgressBar.java

@@ -20,9 +20,11 @@ import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.LinearGradient;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.RectF;
+import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.view.View;
 
@@ -211,6 +213,28 @@ public class QMUIProgressBar extends View {
         invalidate();
     }
 
+
+    /**
+     * @param isHorizontal 渐变方向
+     * @param progress     进度,计算当前实际显示大小
+     * @param colors
+     * @param positions
+     */
+    public void setBarGradientColor(boolean isHorizontal, int progress, int[] colors, float[] positions) {
+        post(() -> { //延迟渲染,等待view测量完成
+            if (!isAttachedToWindow()) return;
+            LinearGradient linearGradient;
+            if (isHorizontal) { //水平渐变
+                linearGradient = new LinearGradient(0f, 0f, mWidth * (progress * 0.01f), 0f, colors, positions, Shader.TileMode.CLAMP);
+            } else { //垂直渐变
+                linearGradient = new LinearGradient(0f, 0f, 0, mHeight * (progress * 0.01f), colors, positions, Shader.TileMode.CLAMP);
+            }
+            // 将渐变应用到 Paint 对象
+            mPaint.setShader(linearGradient);
+            invalidate();
+        });
+    }
+
     @Override
     public void setBackgroundColor(int backgroundColor) {
         mBackgroundColor = backgroundColor;

BIN
app/src/main/res/drawable-xhdpi/common_content_empty_ic_1.webp


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


BIN
app/src/main/res/drawable-xhdpi/game_rocket_entrance_1_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_entrance_2_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_entrance_3_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_entrance_4_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_entrance_5_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_headline_1_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_headline_2_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_headline_3_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_headline_4_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_headline_5_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_1_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_1_large_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_2_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_2_large_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_3_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_3_large_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_4_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_4_large_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_5_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_level_5_large_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_small_level_1_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_small_level_2_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_small_level_3_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_small_level_4_ic.webp


BIN
app/src/main/res/drawable-xhdpi/game_rocket_small_level_5_ic.webp


+ 2 - 2
app/src/main/res/layout/game_layout_rocket_headline.xml

@@ -4,8 +4,8 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="98dp"
-    tools:background="@color/black"
-    android:layout_marginHorizontal="10dp">
+    android:layout_marginHorizontal="10dp"
+    tools:background="@color/black">
 
     <com.opensource.svgaplayer.WenextSvgaView
         android:id="@+id/svga_bg"

+ 95 - 0
app/src/main/res/layout/layout_rocket_headline.xml

@@ -0,0 +1,95 @@
+<?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="98dp"
+    android:layout_marginHorizontal="10dp"
+    tools:background="@color/color_80000000">
+
+    <com.opensource.svgaplayer.WenextSvgaView
+        android:id="@+id/svga_bg"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:rotationY="@integer/locale_mirror_flip"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.adealink.weparty.commonui.imageview.AvatarView
+        android:id="@+id/avatar_iv"
+        android:layout_width="33dp"
+        android:layout_height="33dp"
+        android:layout_marginStart="52dp"
+        app:layout_constraintBottom_toBottomOf="@id/svga_bg"
+        app:layout_constraintDimensionRatio="1:1"
+        app:layout_constraintStart_toStartOf="@id/svga_bg"
+        app:layout_constraintTop_toTopOf="@id/svga_bg"
+        app:layout_constraintVertical_bias="0.56"
+        app:placeholderImage="@drawable/common_default_avatar_ic"
+        app:roundedCornerRadius="2dp"
+        app:roundingBorderColor="@color/color_FFFFF66B"
+        app:roundingBorderWidth="1dp"
+        tools:src="@drawable/common_default_avatar_ic" />
+
+    <com.adealink.weparty.commonui.text.WenextTextView
+        android:id="@+id/title_tv"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="4dp"
+        android:layout_marginEnd="2dp"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textColor="@color/white"
+        android:textDirection="locale"
+        android:textSize="12sp"
+        app:layout_constraintBottom_toTopOf="@id/desc_tv"
+        app:layout_constraintEnd_toStartOf="@id/btn_go"
+        app:layout_constraintStart_toEndOf="@id/avatar_iv"
+        app:layout_constraintTop_toTopOf="@id/avatar_iv"
+        tools:text="title" />
+
+    <com.adealink.weparty.commonui.widget.AutoMarqueeTextView
+        android:id="@+id/desc_tv"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="4dp"
+        android:layout_marginEnd="2dp"
+        android:ellipsize="marquee"
+        android:focusable="false"
+        android:focusableInTouchMode="false"
+        android:gravity="start"
+        android:marqueeRepeatLimit="marquee_forever"
+        android:scrollHorizontally="true"
+        android:singleLine="true"
+        android:textColor="@color/white"
+        android:textSize="11sp"
+        app:layout_constraintBottom_toBottomOf="@id/avatar_iv"
+        app:layout_constraintEnd_toStartOf="@id/btn_go"
+        app:layout_constraintStart_toEndOf="@id/avatar_iv"
+        app:layout_constraintTop_toBottomOf="@id/title_tv"
+        tools:text="content content content content" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/btn_go"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="36dp"
+        android:background="@drawable/game_rocket_headline_go_bg"
+        android:gravity="center"
+        android:paddingHorizontal="12dp"
+        android:paddingVertical="2dp"
+        android:paddingStart="8dp"
+        android:paddingEnd="8dp"
+        android:singleLine="true"
+        android:text="@string/game_go"
+        android:textColor="@color/color_FFA56E00"
+        android:textSize="14sp"
+        android:textStyle="bold"
+        app:layout_constraintBottom_toBottomOf="@id/avatar_iv"
+        app:layout_constraintEnd_toEndOf="@id/svga_bg"
+        app:layout_constraintTop_toTopOf="@id/avatar_iv"
+        app:layout_constraintVertical_bias="0.55" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -682,4 +682,5 @@
    <string name="time_hours_ago">منذ %1$s ساعة</string>
    <string name="time_days_ago">منذ %1$s يومًا</string>
    <string name="time_month_ago">منذ شهر</string>
+    <string name="common_no_records">لا يوجد سجلات</string>
 </resources>

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

@@ -599,4 +599,5 @@
     <string name="commonui_no_message">কোনও বার্তা নেই</string>
     <string name="level_upgrades_to_level">LV%s লেভেলে আপগ্রেড করুন</string>
     <string name="common_view_details">বিস্তারিত দেখুন</string>
+    <string name="common_no_records">কোনও রেকর্ড নেই</string>
 </resources>

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

@@ -596,4 +596,5 @@
     <string name="level_upgrades_to_level">Actualiza a LV%s</string>
 
     <string name="common_view_details">Ver detalles</string>
+    <string name="common_no_records">No hay registros</string>
 </resources>

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

@@ -601,4 +601,5 @@
     <string name="level_upgrades_to_level">LV%s पर अपग्रेड</string>
 
     <string name="common_view_details">विवरण देखें</string>
+    <string name="common_no_records">कोई रिकॉर्ड नहीं</string>
 </resources>

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

@@ -599,4 +599,5 @@
     <string name="commonui_no_message">Tak ada pesan</string>
     <string name="level_upgrades_to_level">Naik ke LV%s</string>
     <string name="common_view_details">Lihat Detail</string>
+    <string name="common_no_records">Tidak ada catatan</string>
 </resources>

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

@@ -582,7 +582,8 @@
     <string name="game_not_exist_error">Ойын жүктелмеді, ойын ресурсы пакеті жүктелуде…</string>
     <string name="game_version_not_match_error">Ойын жаңартылуы керек, соңғы ойын ресурсы бумасын жүктеп алыңыз…</string>
     <string name="game_version_not_match">Ағымдағы ойын нұсқасы тым ескі және жаңа нұсқасы жүктелуде.</string>
-    <string name="game_app_version_old_can_play_game_error">Жаңа ойын жаңартуы бар, бірақ ағымдағы APP нұсқасы тым төмен. Жаңартуды тек APP</string>
+    <string
+        name="game_app_version_old_can_play_game_error">Жаңа ойын жаңартуы бар, бірақ ағымдағы APP нұсқасы тым төмен. Жаңартуды тек APP</string>
     жаңартқаннан кейін қолдана аласыз
     <string name="game_app_version_old_need_upgrade_error">Қолданбаның ағымдағы нұсқасы тым төмен, ойынды ойнамас бұрын қолданбаны жаңартыңыз</string>
     <string name="game_downloading_tips">Ойын жүктеп алынуда…</string>
@@ -670,4 +671,5 @@
     <string name="commonui_no_message">Хабарлама жоқ</string>
     <string name="level_upgrades_to_level">LV%s деңгейіне көтерілу</string>
     <string name="common_view_details">Толық ақпаратты көру</string>
+    <string name="common_no_records">Жазбалар жоқ</string>
 </resources>

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

@@ -582,7 +582,8 @@
     <string name="game_not_exist_error">Оюн жүктөлүп алынган жок, оюн ресурс топтому жүктөлүп алынууда…</string>
     <string name="game_version_not_match_error">Оюн жаңыртылышы керек, эң акыркы оюн ресурс пакетин жүктөп алыңыз…</string>
     <string name="game_version_not_match">Учурдагы оюндун версиясы өтө эски жана жаңы версия жүктөлүп алынууда.</string>
-    <string name="game_app_version_old_can_play_game_error">Жаңы оюн жаңыртылды, бирок учурдагы APP версиясы өтө төмөн. Жаңыртууну APP</string> жаңыртылгандан кийин гана колдоно аласыз
+    <string
+        name="game_app_version_old_can_play_game_error">Жаңы оюн жаңыртылды, бирок учурдагы APP версиясы өтө төмөн. Жаңыртууну APP</string> жаңыртылгандан кийин гана колдоно аласыз
     <string name="game_app_version_old_need_upgrade_error">Учурдагы APP версиясы өтө төмөн, сураныч, оюнду ойноодон мурун APP\'ди жаңыртыңыз</string>
     <string name="game_downloading_tips">Оюн жүктөлүп алынууда…</string>
     <string name="game_download_error">Оюнду жүктөп алуу катасы, кайра аракет кылуу үчүн чыкылдатыңыз</string>
@@ -670,4 +671,5 @@
     <string name="level_upgrades_to_level">LV%s деңгелине өсүү</string>
 
     <string name="common_view_details">Толук маалыматты карап чыгыңыз</string>
+    <string name="common_no_records">Жазуулар жок</string>
 </resources>

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

@@ -600,4 +600,5 @@
     <string name="level_upgrades_to_level">Tingkat naik ke LV%s</string>
 
     <string name="common_view_details">Lihat Butiran</string>
+    <string name="common_no_records">Tiada rekod</string>
 </resources>

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

@@ -598,4 +598,5 @@
     <string name="level_upgrades_to_level">Atualiza para LV%s</string>
 
     <string name="common_view_details">Ver detalhes</string>
+    <string name="common_no_records">Nenhum registro</string>
 </resources>

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

@@ -597,4 +597,5 @@
     <string name="level_upgrades_to_level">Повышение до LV%s</string>
 
     <string name="common_view_details">Смотреть детали</string>
+    <string name="common_no_records">Нет записей</string>
 </resources>

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

@@ -594,4 +594,5 @@
     <string name="level_upgrades_to_level">LV%s நிலைக்கு மேம்படுத்துக</string>
 
     <string name="common_view_details">விவரங்களைப் பார்வையிடவும்</string>
+    <string name="common_no_records">பதிவுகள் இல்லை</string>
 </resources>

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

@@ -594,4 +594,5 @@
     <string name="level_upgrades_to_level">LV%s స్థాయికి నవీకరణలు</string>
 
     <string name="common_view_details">వివరాలు చూడండి</string>
+    <string name="common_no_records">రికార్డులు లేవు</string>
 </resources>

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

@@ -582,7 +582,8 @@
     <string name="game_not_exist_error">Бозӣ бор карда нашудааст, бастаи захираҳои бозӣ зеркашӣ карда мешавад…</string>
     <string name="game_version_not_match_error">Бозӣ бояд навсозӣ шавад, бастаи охирини захираҳои бозиро зеркашӣ кунед…</string>
     <string name="game_version_not_match">Нусхаи кунунии бозӣ хеле кӯҳна аст ва версияи нав зеркашӣ карда мешавад.</string>
-    <string name="game_app_version_old_can_play_game_error">Навсозии бозӣ мавҷуд аст, аммо версияи кунунии APP хеле паст аст. Шумо метавонед навсозиро танҳо пас аз навсозии APP</string>
+    <string
+        name="game_app_version_old_can_play_game_error">Навсозии бозӣ мавҷуд аст, аммо версияи кунунии APP хеле паст аст. Шумо метавонед навсозиро танҳо пас аз навсозии APP</string>
     татбиқ кунед
     <string name="game_app_version_old_need_upgrade_error">Нусхаи кунунии APP хеле паст аст, лутфан пеш аз бозӣ кардан APP-ро навсозӣ кунед</string>
     <string name="game_downloading_tips">Боргирии бозӣ…</string>
@@ -671,4 +672,5 @@
     <string name="level_upgrades_to_level">Ба LV%s сатҳ такмил додан</string>
 
     <string name="common_view_details">Маълумотро бинед</string>
+    <string name="common_no_records">Ягон сабт</string>
 </resources>

+ 3 - 2
app/src/main/res/values-th/strings.xml

@@ -419,7 +419,7 @@
     <string name="rank_top_3">Top 3</string>
     <string name="rank_day">วัน</string>
     <string name="rank_last_month">เดือนล่าสุด</string>
-    <string name="rank_top_game">🔥 เกม</string>
+    <string name="rank_top_game">&#x1f525; เกม</string>
     <string name="rank_top_1">Top 1</string>
     <string name="rank_top_5">Top 5</string>
     <string name="rank_last_week">สัปดาห์ล่าสุด</string>
@@ -600,4 +600,5 @@
     <string name="level_upgrades_to_level">อัปเกรดเป็น LV%s</string>
 
     <string name="common_view_details">ดูรายละเอียด</string>
-</resources>
+    <string name="common_no_records">ไม่มีบันทึก</string>
+</resources>

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

@@ -582,7 +582,8 @@
     <string name="game_not_exist_error"> Oýun göçürilmedi, oýun çeşmesi bukjasyny göçürip aldy … </string>
     <string name="game_version_not_match_error"> Oýun täzelenmeli, iň täze oýun çeşmesi bukjasyny göçürip almaly … </string>
     <string name="game_version_not_match"> Häzirki oýun wersiýasy gaty köne we täze wersiýasy göçürilýär. </string>
-    <string name="game_app_version_old_can_play_game_error"> Täze oýun täzelenmesi bar, ýöne häzirki APP wersiýasy gaty pes. Täzelenmäni diňe APP </string>
+    <string
+        name="game_app_version_old_can_play_game_error"> Täze oýun täzelenmesi bar, ýöne häzirki APP wersiýasy gaty pes. Täzelenmäni diňe APP </string>
     täzelenenden soň ulanyp bilersiňiz
     <string name="game_app_version_old_need_upgrade_error"> Häzirki APP wersiýasy gaty pes, oýun oýnamazdan ozal APP-ni täzeläň </string>
     <string name="game_downloading_tips"> Göçürip alýan oýun … </string>
@@ -671,4 +672,5 @@
     <string name="level_upgrades_to_level">LV%s derejesine ýokarlandyryň</string>
 
     <string name="common_view_details">Jümişi görmek</string>
+    <string name="common_no_records">Recordsazgy ýok</string>
 </resources>

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

@@ -598,4 +598,5 @@
     <string name="level_upgrades_to_level">Nag-upgrade sa LV%s</string>
 
     <string name="common_view_details">Tingnan ang mga detalye</string>
+    <string name="common_no_records">Walang record</string>
 </resources>

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

@@ -415,7 +415,7 @@
     <string name="rank_top_3">Top 3</string>
     <string name="rank_day">Gün</string>
     <string name="rank_last_month">Geçen ay</string>
-    <string name="rank_top_game">🔥 Oyun</string>
+    <string name="rank_top_game">&#x1f525; Oyun</string>
     <string name="rank_top_1">Top 1</string>
     <string name="rank_top_5">Top 5</string>
     <string name="rank_last_week">Geçen hafta</string>
@@ -601,4 +601,5 @@
     <string name="level_upgrades_to_level">LV%s seviyesine yükseltir</string>
 
     <string name="common_view_details">Ayrıntıları Görüntüle</string>
+    <string name="common_no_records">Kayıt yok</string>
 </resources>

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

@@ -600,4 +600,5 @@
     <string name="level_upgrades_to_level">&#x200F;LV%s پر اپ گریڈ</string>
 
     <string name="common_view_details">تفصیلات دیکھیں</string>
+    <string name="common_no_records">کوئی ریکارڈ نہیں۔</string>
 </resources>

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

@@ -582,7 +582,8 @@
     <string name="game_not_exist_error">O‘yin yuklab olinmadi, o‘yin resurs paketi yuklab olinmoqda…</string>
     <string name="game_version_not_match_error">O‘yin yangilanishi kerak, eng so‘nggi o‘yin resurs paketini yuklab oling…</string>
     <string name="game_version_not_match">Joriy o‘yin versiyasi juda eski va yangi versiya yuklab olinmoqda.</string>
-    <string name="game_app_version_old_can_play_game_error">Yangi oʻyin yangilandi, lekin joriy APP versiyasi juda past. Yangilanishni faqat APP</string>
+    <string
+        name="game_app_version_old_can_play_game_error">Yangi oʻyin yangilandi, lekin joriy APP versiyasi juda past. Yangilanishni faqat APP</string>
     yangilangandan keyin qo‘llashingiz mumkin
     <string name="game_app_version_old_need_upgrade_error">ILOVAning joriy versiyasi juda past, o‘yinni o‘ynashdan oldin Ilovani yangilang</string>
     <string name="game_downloading_tips">O‘yin yuklab olinmoqda…</string>
@@ -671,4 +672,5 @@
     <string name="level_upgrades_to_level">LV%s darajasiga yangilanish</string>
 
     <string name="common_view_details">Tafsilotlarni ko\'rish</string>
+    <string name="common_no_records">Yo\'q</string>
 </resources>

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

@@ -405,7 +405,7 @@
     <string name="rank_top_party">Phòng</string>
     <string name="rank_top_gifters">Sự đóng góp</string>
     <string name="rank_top_star">Quyến rũ</string>
-    <string name="rank_top_game">🔥 Trò chơi</string>
+    <string name="rank_top_game">&#x1f525; Trò chơi</string>
     <string name="rank_top_1">Top 1</string>
     <string name="rank_top_2">Top 2</string>
     <string name="rank_top_3">Top 3</string>
@@ -598,4 +598,5 @@
     <string name="level_upgrades_to_level">Nâng cấp lên LV%s</string>
 
     <string name="common_view_details">Xem chi tiết</string>
+    <string name="common_no_records">Không có hồ sơ</string>
 </resources>

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

@@ -363,4 +363,5 @@
     <string name="commonui_no_message">沒有消息</string>
     <string name="level_upgrades_to_level">升級至LV%s</string>
     <string name="common_view_details">查看詳細</string>
+    <string name="common_no_records">沒有記錄</string>
 </resources>

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

@@ -680,4 +680,5 @@
    <string name="time_hours_ago">%1$s小时前在线</string>
    <string name="time_days_ago">%1$s天前在线</string>
    <string name="time_month_ago">2个月前在线</string>
+    <string name="common_no_records">没有记录</string>
 </resources>

+ 16 - 3
app/src/main/res/values/attrs.xml

@@ -403,15 +403,22 @@
     </attr>
 
     <attr name="strokeColor" format="color" />
-    <attr name="strokeWidth" format="dimension"/>
-    <attr name="strokeColorList" format="dimension"/>
-
+    <attr name="strokeWidth" format="dimension" />
+    <attr name="strokeColorList" format="dimension" />
+    <attr name="strokeColors" format="string" />
+    <attr name="strokePosition" format="string" />
+    <attr name="strokeOrientation" format="enum">
+        <enum name="vertical" value="0" />
+        <enum name="horizontal" value="1" />
+    </attr>
     <attr name="gradientColors" format="string" />
     <attr name="gradientPosition" format="string" />
     <attr name="gradientOrientation" format="enum">
         <enum name="vertical" value="0" />
         <enum name="horizontal" value="1" />
     </attr>
+    <attr name="diagonalColor" format="color" />
+    <attr name="diagonalWidth" format="dimension" />
 
     <declare-styleable name="GameTextView" tools:ignore="ResourceName">
 
@@ -419,11 +426,17 @@
 
         <attr name="strokeColor" />
         <attr name="strokeWidth" />
+        <attr name="strokeColors" />
+        <attr name="strokePosition" />
+        <attr name="strokeOrientation" />
 
         <attr name="gradientColors" />
         <attr name="gradientPosition" />
         <attr name="gradientOrientation" />
 
+        <attr name="diagonalColor" />
+        <attr name="diagonalWidth" />
+
         <attr name="shadowDx" />
         <attr name="shadowDy" />
         <attr name="shadowRadius" />

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

@@ -94,6 +94,27 @@
     <color name="black">#FF000000</color>
     <color name="white">#FFFFFFFF</color>
 
+    <color name="color_white_1">#1affffff</color>
+    <color name="color_white_2">#33ffffff</color>
+    <color name="color_white_3">#4dffffff</color>
+    <color name="color_white_4">#66ffffff</color>
+    <color name="color_white_5">#80ffffff</color>
+    <color name="color_white_6">#99ffffff</color>
+    <color name="color_white_7">#b3ffffff</color>
+    <color name="color_white_8">#ccffffff</color>
+    <color name="color_white_9">#e6ffffff</color>
+    <color name="color_white_10">#ffffffff</color>
+    <color name="color_red_1">#ffffb0b1</color>
+    <color name="color_red_2">#ffffa2a3</color>
+    <color name="color_red_3">#ffff8586</color>
+    <color name="color_red_4">#ffff686a</color>
+    <color name="color_red_5">#ffff4d4d</color>
+    <color name="color_red_6">#ffff2424</color>
+    <color name="color_red_7">#ffe51515</color>
+    <color name="color_red_8">#ffbc1819</color>
+    <color name="color_red_9">#ff9b0d0d</color>
+    <color name="color_red_10">#ff810908</color>
+
     <color name="color_0AFFFFFF">#0AFFFFFF</color>
     <color name="color_0DFFFFFF">#0DFFFFFF</color>
     <color name="color_80FFFFFF">#80FFFFFF</color>
@@ -962,7 +983,6 @@
     <color name="color_FFf47f09">#FFf47f09</color>
 
 
-
     <color name="color_FF55CCFF">#FF55CCFF</color>
     <color name="color_FFFF7C00">#FFFF7C00</color>
     <color name="color_FFF0F0F0">#FFF0F0F0</color>
@@ -1031,6 +1051,34 @@
     <color name="color_FFFA0202">#FFFA0202</color>
     <color name="color_FF96D8F4">#FF96D8F4</color>
     <color name="color_FFFF78F0">#FFFF78F0</color>
+    <color name="color_FF5B3B9C">#FF5B3B9C</color>
+    <color name="color_335B3B9C">#335B3B9C</color>
+    <color name="color_FFE9D1A2">#FFE9D1A2</color>
+    <color name="color_FFDAE5E9">#FFDAE5E9</color>
+    <color name="color_FFF9E3CE">#FFF9E3CE</color>
+    <color name="color_FFFFE8D2">#FFFFE8D2</color>
+    <color name="color_08FFFFFF">#08FFFFFF</color>
+    <color name="color_407245C5">#407245C5</color>
+    <color name="color_FF3041FF">#FF3041FF</color>
+    <color name="color_FFA19EFF">#FFA19EFF</color>
+    <color name="color_FFA42CFF">#FFA42CFF</color>
+    <color name="color_FFCDA9FF">#FFCDA9FF</color>
+    <color name="color_FFFF4830">#FFFF4830</color>
+    <color name="color_FFFFAD9E">#FFFFAD9E</color>
+    <color name="color_FFFF9730">#FFFF9730</color>
+    <color name="color_FFFFE59E">#FFFFE59E</color>
+    <color name="color_FF9EE5FF">#FF9EE5FF</color>
+    <color name="color_FFA56E00">#FFA56E00</color>
+    <color name="color_FFFFF66B">#FFFFF66B</color>
+    <color name="color_E00A1559">#E00A1559</color>
+    <color name="color_E0400A59">#E0400A59</color>
+    <color name="color_E0590A0A">#E0590A0A</color>
+    <color name="color_E059320A">#E059320A</color>
+    <color name="color_E00A2359">#E00A2359</color>
+    <color name="color_FFDE2AEF">#FFDE2AEF</color>
+    <color name="color_FFF683FF">#FFF683FF</color>
+    <color name="color_FFFAB4FF">#FFFAB4FF</color>
+    <color name="color_FFF9AEFF">#FFF9AEFF</color>
 
     <color name="color_FFFF6929">#FFFF6929</color>
     <color name="color_FFFFBE3F">#FFFFBE3F</color>
@@ -1074,4 +1122,19 @@
     <color name="color_FF541BFF">#FF541BFF</color>
     <color name="color_FF462E90">#FF462E90</color>
     <color name="color_FFA052E5">#FFA052E5</color>
+    <color name="color_FFFDEE">#FFFDEE</color>
+    <color name="color_FFB921">#FFB921</color>
+    <color name="color_FFA800">#FFA800</color>
+    <color name="color_FFDBC0">#FFDBC0</color>
+    <color name="color_FF7E21">#FF7E21</color>
+    <color name="color_DCC1FF">#DCC1FF</color>
+    <color name="color_9E51FF">#9E51FF</color>
+    <color name="color_AD6DFF">#AD6DFF</color>
+    <color name="color_F155FF">#F155FF</color>
+    <color name="color_F46CFF">#F46CFF</color>
+    <color name="color_AF00BD">#AF00BD</color>
+    <color name="color_BD0FCB">#BD0FCB</color>
+    <color name="color_FECFFF">#FECFFF</color>
+    <color name="color_0D0334">#0D0334</color>
+    <color name="color_5B2100">#5B2100</color>
 </resources>

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

@@ -796,4 +796,5 @@
    <string name="time_month_ago">1 month ago</string>
    <string name="common_lucky_coins">Lucky coins</string>
    <string name="common_coin_value">%1$s Coin Value</string>
+   <string name="common_no_records">No records</string>
 </resources>

+ 7 - 2
module/game/src/main/AndroidManifest.xml

@@ -8,7 +8,7 @@
         <dist:fusing dist:include="true" />
         <dist:delivery>
             <dist:install-time>
-                <dist:removable dist:value="true"/>
+                <dist:removable dist:value="true" />
             </dist:install-time>
         </dist:delivery>
     </dist:module>
@@ -22,7 +22,12 @@
             android:name=".redpacket.history.RedPacketHistoryActivity"
             android:screenOrientation="portrait"
             android:theme="@style/AppTheme" />
-        <activity android:name=".redpacket.RedPacketSelectCoverActivity"
+        <activity
+            android:name=".redpacket.RedPacketSelectCoverActivity"
+            android:screenOrientation="portrait"
+            android:theme="@style/AppTheme" />
+        <activity
+            android:name=".rocket.activity.RocketKingActivity"
             android:screenOrientation="portrait"
             android:theme="@style/AppTheme" />
     </application>

+ 41 - 12
module/game/src/main/java/com/adealink/weparty/game/GameServiceImpl.kt

@@ -8,12 +8,18 @@ import com.adealink.frame.util.PackageUtil
 import com.adealink.weparty.App
 import com.adealink.weparty.channel.getChannel
 import com.adealink.weparty.cocosgame.data.Game
+import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
+import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
 import com.adealink.weparty.game.datasource.remote.GameHttpService
 import com.adealink.weparty.game.manager.gameManager
 import com.adealink.weparty.game.miccharmpk.manager.micCharmPKManager
 import com.adealink.weparty.game.miccharmpk.viewmodel.MicCharmPKViewModel
 import com.adealink.weparty.game.redpacket.manager.redPacketManager
 import com.adealink.weparty.game.redpacket.viewmodel.RedPacketViewModel
+import com.adealink.weparty.game.rocket.floatview.RocketHeadlineFloatData
+import com.adealink.weparty.game.rocket.floatview.RocketHeadlineFloatView
+import com.adealink.weparty.game.rocket.manager.rocketManager
+import com.adealink.weparty.game.rocket.viewmodel.RocketViewModel
 import com.adealink.weparty.game.roulette.manager.rouletteManager
 import com.adealink.weparty.game.roulette.viewmodel.RouletteViewModel
 import com.adealink.weparty.game.viewmodel.GameViewModel
@@ -27,6 +33,7 @@ import com.adealink.weparty.module.game.data.GameType
 import com.adealink.weparty.module.game.data.GetUserLevelInfoReq
 import com.adealink.weparty.module.game.data.ReceiveRewardReq
 import com.adealink.weparty.module.game.data.UserGameLevelInfoResult
+import com.adealink.weparty.module.game.rocket.viewmodel.IRocketViewModel
 import com.adealink.weparty.module.game.viewmodel.IGameViewModel
 import com.adealink.weparty.module.game.viewmodel.IMicCharmPKViewModel
 import com.adealink.weparty.module.game.viewmodel.IRedPacketViewModel
@@ -59,6 +66,10 @@ class GameServiceImpl : IGameService {
         return ViewModelProvider(owner, GameViewModelFactory())[MicCharmPKViewModel::class.java]
     }
 
+    override fun getRocketViewModel(owner: ViewModelStoreOwner): IRocketViewModel {
+        return ViewModelProvider(owner, GameViewModelFactory())[RocketViewModel::class.java]
+    }
+
     override fun updateCurRedPacketInfo(redPacketInfo: RedPacketInfo?) {
         redPacketManager.updateCurRedPacketInfo(redPacketInfo)
     }
@@ -78,6 +89,7 @@ class GameServiceImpl : IGameService {
             is Rlt.Success -> {
                 result.data.data ?: hashMapOf()
             }
+
             is Rlt.Failed -> {
                 hashMapOf()
             }
@@ -85,33 +97,37 @@ class GameServiceImpl : IGameService {
     }
 
     override suspend fun getActivityGameRewardInfo(req: CommonActivityRewardInfoReq): GameActivityRewardInfo? {
-        return when(val result = gameHttpService.getReceiveRewardInfo(req)) {
-            is Rlt.Success ->{
+        return when (val result = gameHttpService.getReceiveRewardInfo(req)) {
+            is Rlt.Success -> {
                 return result.data.data
             }
-            is Rlt.Failed ->{
+
+            is Rlt.Failed -> {
                 return null
             }
         }
     }
 
     override suspend fun getUserGameLevelInfo(uids: List<Long>): UserGameLevelInfoResult? {
-        return when(val result = gameHttpService.getUserGameLevelInfo(GetUserLevelInfoReq(uids = uids))) {
-            is Rlt.Success ->{
+        return when (val result =
+            gameHttpService.getUserGameLevelInfo(GetUserLevelInfoReq(uids = uids))) {
+            is Rlt.Success -> {
                 return result.data.data
             }
-            is Rlt.Failed ->{
+
+            is Rlt.Failed -> {
                 return null
             }
         }
     }
 
     override suspend fun toReceiveGameReward(req: ReceiveRewardReq): Rlt<Boolean>? {
-        return when(val result = gameHttpService.toReceiveReward(req)) {
-            is Rlt.Success ->{
+        return when (val result = gameHttpService.toReceiveReward(req)) {
+            is Rlt.Success -> {
                 return Rlt.Success(true)
             }
-            is Rlt.Failed ->{
+
+            is Rlt.Failed -> {
                 return result
             }
         }
@@ -119,7 +135,7 @@ class GameServiceImpl : IGameService {
 
     override fun isShowSuperGift(): Boolean {
         val userInfo = ProfileModule.getMyUserInfo()
-        return userInfo != null && (userInfo.level?:0) >= 5
+        return userInfo != null && (userInfo.level ?: 0) >= 5
     }
 
     override fun getPlayingGame(): GameType? {
@@ -127,9 +143,11 @@ class GameServiceImpl : IGameService {
             micCharmPKManager.isMicCharmPKing() -> {
                 GameType.MIC_PK
             }
+
             PKModule.isJoinedRoomTeamPKing() -> {
                 GameType.TEAM_PK
             }
+
             else -> {
                 null
             }
@@ -137,11 +155,14 @@ class GameServiceImpl : IGameService {
     }
 
     override fun logout() {
-
+        rocketManager.onLogout()
     }
 
 
-    override suspend fun getGameBetCoinsConfig(game: Game, gameType: Int?): Map<Int, Map<Int, List<Int>>> {
+    override suspend fun getGameBetCoinsConfig(
+        game: Game,
+        gameType: Int?
+    ): Map<Int, Map<Int, List<Int>>> {
         return gameManager.getBetCoinConfig(game, gameType)
     }
 
@@ -149,6 +170,14 @@ class GameServiceImpl : IGameService {
         gameManager.checkPlayingGame()
     }
 
+    override fun initRocket() {
+        rocketManager.init()
+    }
+
+    override fun getRocketHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData> {
+        return RocketHeadlineFloatView(data as RocketHeadlineFloatData)
+    }
+
     override fun getService(): IGameService {
         return this
     }

+ 156 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/activity/RocketKingActivity.kt

@@ -0,0 +1,156 @@
+package com.adealink.weparty.game.rocket.activity
+
+import android.net.Uri
+import androidx.activity.viewModels
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.updateLayoutParams
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.aab.util.getCompatDrawable
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.fastLazy
+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.isActivityInValid
+import com.adealink.weparty.commonui.BaseActivity
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.commonui.ext.dp
+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.game.R
+import com.adealink.weparty.game.databinding.ActivityRocketKingBinding
+import com.adealink.weparty.game.databinding.LayoutRocketKingTopUserBinding
+import com.adealink.weparty.game.rocket.adapter.RocketKingItemViewBinder
+import com.adealink.weparty.game.rocket.listener.IRocketKingListener
+import com.adealink.weparty.game.rocket.viewmodel.RocketViewModel
+import com.adealink.weparty.game.viewmodel.GameViewModelFactory
+import com.adealink.weparty.module.game.Game
+import com.adealink.weparty.module.game.rocket.RocketKingUserInfo
+import com.adealink.weparty.module.profile.Profile
+import com.qmuiteam.qmui.widget.util.QMUIStatusBarHelper
+
+@RouterUri(path = [Game.Rocket.RocketKing.PATH], desc = "火箭发射排名")
+class RocketKingActivity : BaseActivity(), IRocketKingListener {
+
+    private val binding by viewBinding(ActivityRocketKingBinding::inflate)
+    private val listAdapter by fastLazy { MultiTypeListAdapter<RocketKingUserInfo>() }
+    private val rocketViewModel by viewModels<RocketViewModel> { GameViewModelFactory() }
+
+    override fun loadData() {
+        super.loadData()
+        rocketViewModel.getRocketKingList()
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        rocketViewModel.apply {
+            rocketKingLD.observe(this@RocketKingActivity) { topList ->
+                updateTop3User(topList.take(3))
+                updateTopList(topList.drop(3))
+            }
+
+            rocketKingMyLD.observe(this@RocketKingActivity) { myInfo ->
+                updateMyInfo(myInfo)
+            }
+        }
+
+    }
+
+    override fun initViews() {
+        QMUIStatusBarHelper.setStatusBarLightMode(this)
+        setContentView(binding.root)
+        binding.topBar.updateLayoutParams<ConstraintLayout.LayoutParams> {
+            this.topMargin = QMUIStatusBarHelper.getStatusbarHeight(this@RocketKingActivity)
+        }
+        initTopUserCard()
+        binding.ivRule.setOnClickListener {
+            Router.getRouterInstance<BaseDialogFragment>(Game.Rocket.RocketKingRule.PATH)
+                ?.show(supportFragmentManager)
+        }
+        binding.rvTopUser.apply {
+            listAdapter.register(RocketKingItemViewBinder(this@RocketKingActivity))
+            adapter = listAdapter
+            layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+            addItemDecoration(VerticalSpaceItemDecoration(0, 0, 30.dp()))
+        }
+    }
+
+    private fun initTopUserCard() {
+        binding.includeTop2User.root.updateLayoutParams<ConstraintLayout.LayoutParams> {
+            this.horizontalBias = 0.02f
+        }
+        binding.includeTop3User.root.updateLayoutParams<ConstraintLayout.LayoutParams> {
+            this.horizontalBias = 0.98f
+        }
+        binding.includeTop3User.root.background =
+            getCompatDrawable(R.drawable.game_rocket_king_top3_bg)
+        binding.includeTop3User.ivAvatarFrame.background =
+            getCompatDrawable(R.drawable.game_rocket_king_top3_avatar_frame)
+    }
+
+    private fun updateTop3User(top3UserInfo: List<RocketKingUserInfo>?) {
+        if (top3UserInfo.isNullOrEmpty()) {
+            return
+        }
+
+        val top1User = top3UserInfo.firstOrNull()
+        binding.ivTop1Avatar.setImageUrl(top1User?.userInfo?.url)
+        binding.tvTop1Name.text = top1User?.userInfo?.name
+        binding.tvTop1Value.text = top1User?.rankScore.toString()
+        binding.ivTop1Flag.setImageUrl(top1User?.countryFlag)
+        binding.ivTop1Avatar.setOnClickListener {
+            avtarClick(top1User?.uid ?: 0)
+        }
+        setTopUser(binding.includeTop2User, top3UserInfo.getOrNull(1))
+        setTopUser(binding.includeTop3User, top3UserInfo.getOrNull(2))
+    }
+
+    private fun setTopUser(
+        topUserBinding: LayoutRocketKingTopUserBinding,
+        topUserInfo: RocketKingUserInfo?
+    ) {
+        if (topUserInfo == null) {
+            return
+        }
+
+        topUserBinding.root.show()
+        topUserBinding.ivAvatar.setImageURI(Uri.parse(topUserInfo.userInfo.url), true)
+        topUserBinding.tvTopName.text = topUserInfo.userInfo.name
+        topUserBinding.ivTopFlag.setImageUrl(topUserInfo.countryFlag)
+        topUserBinding.tvTopValue.text = topUserInfo.rankScore.toString()
+        topUserBinding.ivAvatarFrame.setOnClickListener {
+            avtarClick(topUserInfo.uid)
+        }
+    }
+
+    private fun updateMyInfo(myInfo: RocketKingUserInfo?) {
+        val myInfoBinding = binding.includeBottomBar
+        myInfoBinding.ivMyAvatar.setImageUrl(myInfo?.userInfo?.url)
+        myInfoBinding.tvMyName.text = myInfo?.userInfo?.name
+        myInfoBinding.ivMyFlag.setImageUrl(myInfo?.countryFlag)
+        myInfoBinding.tvMyRank.text = if (myInfo?.userRank in 1..100) {
+            myInfo?.userRank.toString()
+        } else {
+            getCompatString(R.string.game_rocket_no_ranking)
+        }
+        myInfoBinding.tvTopValue.text = myInfo?.rankScore.toString()
+    }
+
+    private fun updateTopList(topList: List<RocketKingUserInfo>) {
+        listAdapter.submitList(topList)
+        binding.rvTopUser.updateLayoutParams {
+            this.height = topList.size * 66.dp() + 150.dp()
+        }
+    }
+
+    override fun avtarClick(uid: Long) {
+        if (isActivityInValid(this)) {
+            return
+        }
+        Router.build(this, Profile.UserProfile.PATH)
+            .putExtra(Profile.Common.EXTRA_UID, uid)
+            .start()
+    }
+}

+ 60 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/LevelRocketTabItemViewBinder.kt

@@ -0,0 +1,60 @@
+package com.adealink.weparty.game.rocket.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.LayoutLevelRocketTabBinding
+import com.adealink.weparty.game.rocket.data.LevelRocketTabData
+import com.adealink.weparty.game.rocket.listener.IRocketTabListener
+import com.adealink.weparty.module.game.rocket.RocketLevel
+import com.adealink.weparty.module.game.rocket.util.getRocketSmallResId
+
+class LevelRocketTabItemViewBinder(private val listener: IRocketTabListener) :
+    ItemViewBinder<LevelRocketTabData, LevelRocketTabItemViewBinder.ViewHolder>() {
+
+    inner class ViewHolder(binding: LayoutLevelRocketTabBinding) :
+        BindingViewHolder<LayoutLevelRocketTabBinding>(binding) {
+        fun update(data: LevelRocketTabData) {
+            binding.ivRocket.setImageResource(getRocketSmallResId(data.level.level))
+            binding.tvRocketLevel.text = when (data.level) {
+                RocketLevel.LEVE1 -> getCompatString(R.string.game_rocket_level_1)
+                RocketLevel.LEVE2 -> getCompatString(R.string.game_rocket_level_2)
+                RocketLevel.LEVE3 -> getCompatString(R.string.game_rocket_level_3)
+                RocketLevel.LEVE4 -> getCompatString(R.string.game_rocket_level_4)
+//                RocketLevel.LEVE5 -> getCompatString(R.string.game_rocket_level_5)
+                else -> ""
+            }
+            if (data.selected) {
+                binding.vRocketSelected.show()
+                binding.ivRocket.alpha = 1f
+                binding.tvRocketLevel.isSelected = true
+            } else {
+                binding.vRocketSelected.gone()
+                binding.ivRocket.alpha = 0.6f
+                binding.tvRocketLevel.isSelected = false
+            }
+        }
+    }
+
+    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
+        return ViewHolder(
+            LayoutLevelRocketTabBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        )
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, item: LevelRocketTabData) {
+        holder.update(item)
+        holder.binding.root.setOnClickListener {
+            listener.onLevelRocketSelected(item.level)
+        }
+    }
+}

+ 45 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/RocketKingItemViewBinder.kt

@@ -0,0 +1,45 @@
+package com.adealink.weparty.game.rocket.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.game.databinding.LayoutRocketKingItemBinding
+import com.adealink.weparty.game.rocket.listener.IRocketKingListener
+import com.adealink.weparty.module.game.rocket.RocketKingUserInfo
+
+class RocketKingItemViewBinder(private val listener: IRocketKingListener) :
+    ItemViewBinder<RocketKingUserInfo, RocketKingItemViewBinder.ViewHolder>() {
+
+    inner class ViewHolder(binding: LayoutRocketKingItemBinding) :
+        BindingViewHolder<LayoutRocketKingItemBinding>(binding) {
+        fun update(data: RocketKingUserInfo) {
+            binding.ivAvatar.setImageUrl(data.userInfo.url)
+            binding.tvName.text = data.userInfo.name
+            binding.tvTopValue.text = data.rankScore.toString()
+            binding.ivFlag.setImageUrl(data.countryFlag)
+            binding.tvRank.text = if (data.userRank < 10) {
+                "0" + data.userRank
+            } else {
+                data.userRank.toString()
+            }
+            binding.ivAvatar.setOnClickListener {
+                listener.avtarClick(data.uid)
+            }
+        }
+    }
+
+    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
+        return ViewHolder(
+            LayoutRocketKingItemBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        )
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, item: RocketKingUserInfo) {
+        holder.update(item)
+    }
+}

+ 54 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/RocketLevelRewardItemViewBinder.kt

@@ -0,0 +1,54 @@
+package com.adealink.weparty.game.rocket.adapter
+
+import android.net.Uri
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.updateLayoutParams
+import com.adealink.frame.util.DisplayUtil
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.game.databinding.LayoutRocketLevelRewardItemBinding
+import com.adealink.weparty.game.rocket.data.TopRewardTabIndex
+import com.adealink.weparty.module.operation.data.RewardDetailData
+
+class RocketLevelRewardItemViewBinder(
+    private val tabType: TopRewardTabIndex? = TopRewardTabIndex.Room,
+    private val parentWidth: Int = DisplayUtil.getScreenWidth()
+) : ItemViewBinder<RewardDetailData, RocketLevelRewardItemViewBinder.ViewHolder>() {
+
+    inner class ViewHolder(binding: LayoutRocketLevelRewardItemBinding) :
+        BindingViewHolder<LayoutRocketLevelRewardItemBinding>(binding) {
+
+        init {
+            binding.root.updateLayoutParams<ViewGroup.LayoutParams> {
+                width = (parentWidth * 0.3f).toInt()
+            }
+        }
+
+        fun update(data: RewardDetailData) {
+            when (tabType) {
+                TopRewardTabIndex.RoomOnlineUser -> {
+                    binding.tvRewardType.show()
+                    binding.tvRewardType.text = data.getResourceTypeName()
+                }
+
+                else -> {
+                    binding.tvRewardType.gone()
+                }
+            }
+            binding.vReward.ivReward.setImageURI(Uri.parse(data.rewardResourceUrl), true)
+            binding.vReward.tvReward.text = data.getCountDesc()
+        }
+    }
+
+    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
+        val binding = LayoutRocketLevelRewardItemBinding.inflate(inflater, parent, false)
+        return ViewHolder(binding)
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, item: RewardDetailData) {
+        holder.update(item)
+    }
+}

+ 29 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/RocketRecordGiftItemViewBinder.kt

@@ -0,0 +1,29 @@
+package com.adealink.weparty.game.rocket.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.game.databinding.LayoutRocketRewardGiftItemBinding
+import com.adealink.weparty.module.operation.data.RewardDetailData
+
+
+class RocketRecordGiftItemViewBinder :
+    ItemViewBinder<RewardDetailData, BindingViewHolder<LayoutRocketRewardGiftItemBinding>>() {
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup
+    ): BindingViewHolder<LayoutRocketRewardGiftItemBinding> {
+        return BindingViewHolder(LayoutRocketRewardGiftItemBinding.inflate(inflater, parent, false))
+    }
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<LayoutRocketRewardGiftItemBinding>,
+        item: RewardDetailData
+    ) {
+        holder.binding.ivReward.setImageUrl(item.rewardResourceUrl)
+        holder.binding.tvReward.text = item.getCountDesc()
+
+    }
+}

+ 64 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/RocketRecordItemViewBinder.kt

@@ -0,0 +1,64 @@
+package com.adealink.weparty.game.rocket.adapter
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.fastLazy
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.MultiTypeAdapter
+import com.adealink.weparty.commonui.recycleview.itemdecoration.HorizontalItemDecoration
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.LayoutRocketRewardItemBinding
+import com.adealink.weparty.module.game.rocket.RocketRewardRecord
+import com.adealink.weparty.module.game.rocket.util.getRankTopName
+import java.text.SimpleDateFormat
+import java.util.Date
+import com.adealink.weparty.R as APP_R
+
+
+class RocketRecordItemViewBinder :
+    ItemViewBinder<RocketRewardRecord, RocketRecordItemViewBinder.ViewHolder>() {
+
+    inner class ViewHolder(binding: LayoutRocketRewardItemBinding) :
+        BindingViewHolder<LayoutRocketRewardItemBinding>(binding) {
+
+        private val rewardAdapter by fastLazy { MultiTypeAdapter() }
+
+        fun initView() {
+            rewardAdapter.register(RocketRecordGiftItemViewBinder())
+            binding.rvContent.addItemDecoration(HorizontalItemDecoration(4f, 8f, 8f))
+            binding.rvContent.adapter = rewardAdapter
+            binding.rvContent.layoutManager =
+                LinearLayoutManager(binding.root.context, LinearLayoutManager.HORIZONTAL, false)
+        }
+
+        @SuppressLint("SimpleDateFormat", "NotifyDataSetChanged")
+        fun update(data: RocketRewardRecord) {
+            binding.tvData.text = SimpleDateFormat("MM/dd/yyyy").format(Date(data.createTime))
+            binding.tvRoomId.text =
+                getCompatString(APP_R.string.game_rocket_headline_title, data.roomId)
+            binding.tvRank.text =
+                getCompatString(R.string.game_rocket_reward_ranking, getRankTopName(data.rank))
+            //奖励列表
+            rewardAdapter.items = data.rewards
+            rewardAdapter.notifyDataSetChanged()
+        }
+    }
+
+    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
+        return ViewHolder(
+            LayoutRocketRewardItemBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        ).apply { initView() }
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, item: RocketRewardRecord) {
+        holder.update(item)
+    }
+}

+ 30 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/MyRewardEmptyItemViewBinder.kt

@@ -0,0 +1,30 @@
+package com.adealink.weparty.game.rocket.adapter.reward
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.game.databinding.LayoutRocketMyEmtpyRewardBinding
+import com.adealink.weparty.game.rocket.viewmodel.MyRewardEmptyItem
+
+/**
+ * 未中奖
+ */
+class MyRewardEmptyItemViewBinder :
+    ItemViewBinder<MyRewardEmptyItem, BindingViewHolder<LayoutRocketMyEmtpyRewardBinding>>() {
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup
+    ): BindingViewHolder<LayoutRocketMyEmtpyRewardBinding> {
+        val viewBinding = LayoutRocketMyEmtpyRewardBinding.inflate(inflater, parent, false)
+        return BindingViewHolder(viewBinding)
+    }
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<LayoutRocketMyEmtpyRewardBinding>,
+        item: MyRewardEmptyItem
+    ) {
+    }
+
+}

+ 43 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/MyRewardGotItemViewBinder.kt

@@ -0,0 +1,43 @@
+package com.adealink.weparty.game.rocket.adapter.reward
+
+import android.net.Uri
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.updateLayoutParams
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.game.databinding.LayoutRocketMyRewardItemBinding
+import com.adealink.weparty.module.operation.data.RewardDetailData
+
+/**
+ * 火箭发射后,自己得到的奖励列表项
+ */
+class MyRewardGotItemViewBinder(private val itemWidth: Int) :
+    ItemViewBinder<RewardDetailData, MyRewardGotItemViewBinder.ViewHolder>() {
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup
+    ): ViewHolder {
+        val viewBinding = LayoutRocketMyRewardItemBinding.inflate(inflater, parent, false)
+        return ViewHolder(viewBinding)
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, item: RewardDetailData) {
+        holder.binding.ivReward.setImageURI(Uri.parse(item.rewardResourceUrl), true)
+        holder.binding.tvReward.text = item.getCountDesc()
+    }
+
+    inner class ViewHolder(binding: LayoutRocketMyRewardItemBinding) :
+        BindingViewHolder<LayoutRocketMyRewardItemBinding>(binding) {
+
+        init {
+            // 调整item大小
+            if (itemWidth > 0) {
+                binding.root.updateLayoutParams<ViewGroup.LayoutParams> {
+                    width = itemWidth
+                }
+            }
+        }
+    }
+}

+ 52 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/MyRewardInfoViewBinder.kt

@@ -0,0 +1,52 @@
+package com.adealink.weparty.game.rocket.adapter.reward
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.util.DisplayUtil
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.commonui.recycleview.itemdecoration.HorizontalItemDecoration
+import com.adealink.weparty.game.databinding.LayoutRocketMyRewardViewBinding
+import com.adealink.weparty.game.rocket.viewmodel.MyRewardItem
+import com.adealink.weparty.module.operation.data.RewardDetailData
+
+/**
+ * 火箭发射后,自己得到的奖励;
+ */
+class MyRewardInfoViewBinder :
+    ItemViewBinder<MyRewardItem, MyRewardInfoViewBinder.ViewHolder>() {
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup
+    ): ViewHolder {
+        val viewBinding = LayoutRocketMyRewardViewBinding.inflate(inflater, parent, false)
+        return ViewHolder(viewBinding)
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, item: MyRewardItem) {
+        holder.rewardAdapter.submitList(item.rewardList)
+    }
+
+    inner class ViewHolder(binding: LayoutRocketMyRewardViewBinding) :
+        BindingViewHolder<LayoutRocketMyRewardViewBinding>(binding) {
+
+        val rewardAdapter by fastLazy { MultiTypeListAdapter<RewardDetailData>() }
+
+        init {
+            // 至少可见5项奖励,>5项可滚动
+            val totalWidth = DisplayUtil.getScreenWidth() - (2 * 33 + 2 * 14 + 4 * 4).dp()
+            rewardAdapter.register(MyRewardGotItemViewBinder(totalWidth / 5))
+            binding.rvRewards.apply {
+                layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
+                adapter = rewardAdapter
+                addItemDecoration(HorizontalItemDecoration(4f, 0f, 0f))
+            }
+        }
+    }
+}

+ 49 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/RewardEmptyItemViewBinder.kt

@@ -0,0 +1,49 @@
+package com.adealink.weparty.game.rocket.adapter.reward
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.view.updateLayoutParams
+import com.adealink.weparty.R
+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.game.databinding.LayoutRocketRewardEmtpyDataBinding
+import com.adealink.weparty.game.rocket.viewmodel.RewardEmptyItem
+
+/**
+ * 没有任何数据
+ */
+class RewardEmptyItemViewBinder :
+    ItemViewBinder<RewardEmptyItem, BindingViewHolder<LayoutRocketRewardEmtpyDataBinding>>() {
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup
+    ): BindingViewHolder<LayoutRocketRewardEmtpyDataBinding> {
+        return BindingViewHolder(
+            LayoutRocketRewardEmtpyDataBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        ).apply {
+            binding.root.updateLayoutParams<ViewGroup.LayoutParams> {
+                height = 350.dp()
+            }
+        }
+    }
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<LayoutRocketRewardEmtpyDataBinding>,
+        item: RewardEmptyItem
+    ) {
+        holder.binding.emptyView.apply {
+            show(
+                R.drawable.common_content_empty_ic_1,
+                R.string.common_none
+            )
+            setImageSize(140.dp())
+        }
+    }
+
+}

+ 40 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/RewardTitleItemViewBinder.kt

@@ -0,0 +1,40 @@
+package com.adealink.weparty.game.rocket.adapter.reward
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.game.databinding.LayoutRocketRewardTitleItemBinding
+import com.adealink.weparty.game.rocket.viewmodel.RewardTitleItem
+
+class RewardTitleItemViewBinder :
+    ItemViewBinder<RewardTitleItem, RewardTitleItemViewBinder.ViewHolder>() {
+
+    inner class ViewHolder(binding: LayoutRocketRewardTitleItemBinding) :
+        BindingViewHolder<LayoutRocketRewardTitleItemBinding>(binding) {
+        fun update(data: RewardTitleItem) {
+            if (data.title.isNullOrEmpty()) {
+                binding.tvTitle.gone()
+            } else {
+                binding.tvTitle.show()
+                binding.tvTitle.text = data.title
+            }
+        }
+    }
+
+    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
+        return ViewHolder(
+            LayoutRocketRewardTitleItemBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        )
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, item: RewardTitleItem) {
+        holder.update(item)
+    }
+}

+ 99 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/RewardUserDetailItemViewBinder.kt

@@ -0,0 +1,99 @@
+package com.adealink.weparty.game.rocket.adapter.reward
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.base.fastLazy
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.hide
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.commonui.recycleview.itemdecoration.HorizontalItemDecoration
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.LayoutRocketRewardUserDetailBinding
+import com.adealink.weparty.game.rocket.listener.IRocketRewardUserItemListener
+import com.adealink.weparty.game.rocket.viewmodel.RewardDetailItem
+import com.adealink.weparty.module.operation.data.RewardDetailData
+
+/**
+ * 用户得到奖励的排行列表项;
+ *
+ * 1.显示:用户、得到的奖励;
+ */
+class RewardUserDetailItemViewBinder(
+    val listener: IRocketRewardUserItemListener
+) : ItemViewBinder<RewardDetailItem, RewardUserDetailItemViewBinder.ViewHolder>() {
+
+    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
+        return ViewHolder(
+            LayoutRocketRewardUserDetailBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        )
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, item: RewardDetailItem) {
+        holder.update(item)
+    }
+
+    inner class ViewHolder(binding: LayoutRocketRewardUserDetailBinding) :
+        BindingViewHolder<LayoutRocketRewardUserDetailBinding>(binding) {
+
+        private var mData: RewardDetailItem? = null
+        private val rewardAdapter by fastLazy { MultiTypeListAdapter<RewardDetailData>() }
+
+        init {
+            binding.ivAvatar.setOnClickListener {
+                val uid = mData?.data?.uid ?: return@setOnClickListener
+                listener.clickUser(uid)
+            }
+            rewardAdapter.register(UserGotRewardItemViewBinder())
+            binding.rvRewards.apply {
+                layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
+                adapter = rewardAdapter
+                addItemDecoration(HorizontalItemDecoration(2f, 0f, 0f))
+            }
+        }
+
+        fun update(data: RewardDetailItem) {
+            mData = data
+            binding.tvName.text = data.data.name
+            when (data.data.rank) {
+                1 -> {
+                    binding.ivRank.show()
+                    binding.ivAvatarFrame.show()
+                    binding.ivAvatarFrame.setImageResource(R.drawable.game_rocket_rank_top_frame_1)
+                    binding.ivRank.setImageResource(R.drawable.game_rocket_reward_user_rank_1)
+                }
+
+                2 -> {
+                    binding.ivRank.show()
+                    binding.ivAvatarFrame.show()
+                    binding.ivAvatarFrame.setImageResource(R.drawable.game_rocket_rank_top_frame_2)
+                    binding.ivRank.setImageResource(R.drawable.game_rocket_reward_user_rank_2)
+                }
+
+                3 -> {
+                    binding.ivRank.show()
+                    binding.ivAvatarFrame.show()
+                    binding.ivAvatarFrame.setImageResource(R.drawable.game_rocket_rank_top_frame_3)
+                    binding.ivRank.setImageResource(R.drawable.game_rocket_reward_user_rank_3)
+                }
+
+                else -> {
+                    binding.ivRank.hide()
+                    binding.ivAvatarFrame.gone()
+                }
+            }
+
+            binding.ivAvatar.setImageUrl(data.data.avatar)
+            rewardAdapter.submitList(data.data.rewards, forceUpdate = true)
+        }
+    }
+
+}

+ 32 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/adapter/reward/UserGotRewardItemViewBinder.kt

@@ -0,0 +1,32 @@
+package com.adealink.weparty.game.rocket.adapter.reward
+
+import android.net.Uri
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.game.databinding.LayoutRocketUserRewardItemBinding
+import com.adealink.weparty.module.operation.data.RewardDetailData
+
+/**
+ * 每个用户对应获得的奖励列表项
+ */
+class UserGotRewardItemViewBinder :
+    ItemViewBinder<RewardDetailData, BindingViewHolder<LayoutRocketUserRewardItemBinding>>() {
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup
+    ): BindingViewHolder<LayoutRocketUserRewardItemBinding> {
+        val viewBinding = LayoutRocketUserRewardItemBinding.inflate(inflater, parent, false)
+        return BindingViewHolder(viewBinding)
+    }
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<LayoutRocketUserRewardItemBinding>,
+        item: RewardDetailData
+    ) {
+        holder.binding.ivReward.setImageURI(Uri.parse(item.rewardResourceUrl), true)
+        holder.binding.tvReward.text = item.getCountDesc()
+    }
+}

+ 213 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/comp/RocketLevelRewardComp.kt

@@ -0,0 +1,213 @@
+package com.adealink.weparty.game.rocket.comp
+
+import android.annotation.SuppressLint
+import android.graphics.Typeface
+import android.os.Bundle
+import android.util.TypedValue
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.LifecycleOwner
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import com.adealink.frame.aab.util.getCompatColor
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.router.Router
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.ext.setOverScrollModeToNever
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.widget.EmptyFragment
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.LayoutRocketAwardPanelBinding
+import com.adealink.weparty.game.databinding.LayoutRocketLevelRewardTabBinding
+import com.adealink.weparty.game.rocket.data.TopRewardTabIndex
+import com.adealink.weparty.module.game.Game
+import com.adealink.weparty.module.game.rocket.RocketLevel
+import com.google.android.material.tabs.TabLayout
+import com.google.android.material.tabs.TabLayoutMediator
+import com.adealink.weparty.R as APP_R
+
+/**
+ * 火箭等级 对应的 奖励信息;
+ * 1.分Tab:Top1、Top2、Top3、InRoom...
+ */
+class RocketLevelRewardComp(
+    lifecycleOwner: LifecycleOwner,
+    val binding: LayoutRocketAwardPanelBinding
+) : ViewComponent(lifecycleOwner) {
+
+    companion object {
+        private var REWARD_TABS = mapOf(
+//            RocketLevel.LEVE5 to listOf(
+//                TopRewardTabIndex.Room,
+//                TopRewardTabIndex.Top1,
+//                TopRewardTabIndex.Top2,
+//                TopRewardTabIndex.Top3,
+//                TopRewardTabIndex.RoomOnlineUser
+//            ),
+            RocketLevel.LEVE4 to listOf(
+                // 暂无需限时礼物
+//                TopRewardTabIndex.Room,
+                TopRewardTabIndex.Top1,
+                TopRewardTabIndex.Top2,
+                TopRewardTabIndex.Top3,
+                TopRewardTabIndex.RoomOnlineUser
+            ),
+            RocketLevel.LEVE3 to listOf(
+                TopRewardTabIndex.Top1,
+                TopRewardTabIndex.Top2,
+                TopRewardTabIndex.Top3,
+                TopRewardTabIndex.RoomOnlineUser
+            ),
+            RocketLevel.LEVE2 to listOf(
+                TopRewardTabIndex.Top1,
+                TopRewardTabIndex.Top2,
+                TopRewardTabIndex.Top3,
+                TopRewardTabIndex.RoomOnlineUser
+            ),
+            RocketLevel.LEVE1 to listOf(
+                TopRewardTabIndex.Top1,
+                TopRewardTabIndex.Top2,
+                TopRewardTabIndex.Top3,
+                TopRewardTabIndex.RoomOnlineUser
+            )
+        )
+    }
+
+    private var currentLevel = RocketLevel.LEVE1
+    private var currentIndex = 0
+    private lateinit var pageAdapter: FragmentStateAdapter
+
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+    }
+
+    private fun initView() {
+        val frg = fragment ?: return
+        binding.layoutTitle.tvTitle.text = getCompatString(R.string.game_rocket_award_title)
+        pageAdapter = object : FragmentStateAdapter(frg) {
+            override fun getItemCount(): Int {
+                return REWARD_TABS[currentLevel]?.size ?: 0
+            }
+
+            /**
+             * ID的组成,
+             * level * 100 + rewardIndex
+             */
+            override fun getItemId(position: Int): Long {
+                val rewardIndex =
+                    REWARD_TABS[currentLevel]?.getOrNull(position) ?: return RecyclerView.NO_ID
+                return (currentLevel.level * 100 + rewardIndex.index).toLong()
+            }
+
+            override fun containsItem(itemId: Long): Boolean {
+                val level = (itemId / 100).toInt()
+                val rewardIndex = (itemId % 100).toInt()
+                if (level != currentLevel.level) {
+                    return false
+                }
+                return REWARD_TABS[currentLevel]?.find { it.index == rewardIndex } != null
+            }
+
+
+            override fun createFragment(position: Int): Fragment {
+                return when (val rewardIndex = REWARD_TABS[currentLevel]?.get(position)) {
+                    TopRewardTabIndex.Room -> {
+                        Router.getRouterInstance<BaseFragment>(Game.Rocket.RocketLevelRewardFragment.LIMIT_GIFT_PATH)
+                            ?: EmptyFragment()
+                    }
+
+                    TopRewardTabIndex.Top1,
+                    TopRewardTabIndex.Top2,
+                    TopRewardTabIndex.Top3,
+                    TopRewardTabIndex.RoomOnlineUser -> {
+                        Router.getRouterInstance<BaseFragment>(Game.Rocket.RocketLevelRewardFragment.PATH)
+                            ?.apply {
+                                arguments = Bundle().apply {
+                                    putSerializable(
+                                        Game.Rocket.RocketLevelRewardFragment.EXTRA_REWARD_TAB_INDEX,
+                                        rewardIndex
+                                    )
+                                }
+                            } ?: EmptyFragment()
+                    }
+
+                    null -> {
+                        EmptyFragment()
+                    }
+                }
+            }
+        }
+        binding.rewardVp.adapter = pageAdapter
+        (binding.rewardVp.getChildAt(0) as? RecyclerView)?.setOverScrollModeToNever()
+        TabLayoutMediator(
+            binding.rewardTl, binding.rewardVp, true, true
+        ) { tab: TabLayout.Tab, position: Int ->
+            run {
+                tab.setCustomView(R.layout.layout_rocket_level_reward_tab)
+                updateTabView(
+                    tab,
+                    currentIndex == position,
+                    REWARD_TABS[currentLevel]?.get(position)
+                )
+            }
+        }.attach()
+        binding.rewardTl.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
+            override fun onTabSelected(tab: TabLayout.Tab?) {
+                currentIndex = tab?.position ?: 0
+                updateTabView(tab, true, null)
+            }
+
+            override fun onTabUnselected(tab: TabLayout.Tab?) {
+                updateTabView(tab, false, null)
+            }
+
+            override fun onTabReselected(tab: TabLayout.Tab?) {
+            }
+        })
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    fun changeToLevel(level: RocketLevel) {
+        currentLevel = level
+        pageAdapter.notifyDataSetChanged()
+        currentIndex = 0
+        binding.rewardVp.currentItem = currentIndex
+    }
+
+    private fun updateTabView(
+        tab: TabLayout.Tab?,
+        selected: Boolean,
+        tabIndex: TopRewardTabIndex?
+    ) {
+        val tabName = getTabName(tabIndex)
+        tab?.customView?.let {
+            val customViewBinding = LayoutRocketLevelRewardTabBinding.bind(it)
+            tabName?.let { name ->
+                customViewBinding.tvTab.text = name
+            }
+            customViewBinding.ivSelect.show(selected)
+            if (selected) {
+                customViewBinding.tvTab.typeface = Typeface.DEFAULT_BOLD
+                customViewBinding.tvTab.setTextColor(getCompatColor(APP_R.color.white))
+                customViewBinding.tvTab.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
+            } else {
+                customViewBinding.tvTab.typeface = Typeface.DEFAULT
+                customViewBinding.tvTab.setTextColor(getCompatColor(APP_R.color.color_white_6))
+                customViewBinding.tvTab.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
+            }
+        }
+    }
+
+    private fun getTabName(tabIndex: TopRewardTabIndex?): String? {
+        tabIndex ?: return null
+        val strId = when (tabIndex.index) {
+            TopRewardTabIndex.Room.index -> APP_R.string.game_rocket_room
+            TopRewardTabIndex.Top1.index -> APP_R.string.game_rocket_top1
+            TopRewardTabIndex.Top2.index -> APP_R.string.game_rocket_top2
+            TopRewardTabIndex.Top3.index -> APP_R.string.game_rocket_top3
+            else -> APP_R.string.game_rocket_online_user
+        }
+        return getCompatString(strId)
+    }
+}

+ 69 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/comp/RocketRandListComp.kt

@@ -0,0 +1,69 @@
+package com.adealink.weparty.game.rocket.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.frame.router.Router
+import com.adealink.weparty.commonui.ext.getActivity
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.LayoutRocketRankTop3PanelBinding
+import com.adealink.weparty.game.databinding.LayoutRocketRankTopUserBinding
+import com.adealink.weparty.game.rocket.viewmodel.RocketViewModel
+import com.adealink.weparty.game.viewmodel.GameViewModelFactory
+import com.adealink.weparty.module.game.rocket.RocketRankUserInfo
+import com.adealink.weparty.module.profile.Profile
+
+class RocketRandListComp(
+    lifecycleOwner: LifecycleOwner,
+    val binding: LayoutRocketRankTop3PanelBinding,
+) : ViewComponent(lifecycleOwner) {
+    private val rocketViewModel by viewModels<RocketViewModel>({ viewModelStoreOwner }) { GameViewModelFactory() }
+
+    override fun onCreate() {
+        super.onCreate()
+        observeViewModel()
+        initView()
+    }
+
+    private fun initView() {
+        binding.layoutTitle.tvTitle.text = getCompatString(R.string.game_rocket_rank_title)
+        // TOP1
+        binding.vTop1.ivRankBg.setImageResource(R.drawable.game_rocket_rank_top1_bg)
+        binding.vTop1.ivRankNum.setImageResource(R.drawable.game_rocket_rank_1)
+        // TOP2
+        binding.vTop2.ivRankBg.setImageResource(R.drawable.game_rocket_rank_top2_bg)
+        binding.vTop2.ivRankNum.setImageResource(R.drawable.game_rocket_rank_2)
+        // TOP3
+        binding.vTop3.ivRankBg.setImageResource(R.drawable.game_rocket_rank_top3_bg)
+        binding.vTop3.ivRankNum.setImageResource(R.drawable.game_rocket_rank_3)
+    }
+
+    private fun observeViewModel() {
+        rocketViewModel.rankUsersLD.observe(viewLifecycleOwner) {
+            val rank1 = it[1] ?: RocketRankUserInfo(1, 0, 0, "", "")
+            updateTopInfo(binding.vTop1, rank1)
+            val rank2 = it[2] ?: RocketRankUserInfo(2, 0, 0, "", "")
+            updateTopInfo(binding.vTop2, rank2)
+            val rank3 = it[3] ?: RocketRankUserInfo(3, 0, 0, "", "")
+            updateTopInfo(binding.vTop3, rank3)
+        }
+    }
+
+    private fun updateTopInfo(
+        binding: LayoutRocketRankTopUserBinding,
+        rankInfo: RocketRankUserInfo
+    ) {
+        binding.ivAvatar.setImageUrl(rankInfo.avatar)
+        binding.nameTv.text = rankInfo.name
+        binding.tvRankNum.text = rankInfo.coins.toString()
+        binding.root.setOnClickListener {
+            val activity = it.getActivity() ?: return@setOnClickListener
+            if (rankInfo.uid == 0L) {
+                return@setOnClickListener
+            }
+            Router.build(activity, Profile.UserProfile.PATH)
+                .putExtra(Profile.Common.EXTRA_UID, rankInfo.uid).start()
+        }
+    }
+}

+ 75 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/data/Data.kt

@@ -0,0 +1,75 @@
+package com.adealink.weparty.game.rocket.data
+
+import com.adealink.weparty.module.game.rocket.RocketKingUserInfo
+import com.adealink.weparty.module.game.rocket.RocketLevel
+import com.adealink.weparty.module.game.rocket.RocketRewardRecord
+import com.adealink.weparty.module.operation.data.RewardDetailData
+import com.adealink.weparty.module.room.data.RoomLimitTimeGiftData
+import com.google.gson.annotations.GsonNullable
+import com.google.gson.annotations.SerializedName
+
+/**
+ * Created by sunxiaodong on 2023/1/12.
+ */
+
+data class RocketInfo(
+    @SerializedName("currLevel")
+    val currLevel: Int,
+    @SerializedName("currValue")
+    val currValue: Long,
+    @SerializedName("isShow")
+    val isShow: Boolean,
+    @SerializedName("levelInfo")
+    val levelInfo: List<RocketLevelInfo>,
+    @SerializedName("rewardInfo")
+    val rewardInfo: List<RocketRewardInfo>,
+    @SerializedName("weekRankInfo")
+    val weekRankInfo: List<RocketKingUserInfo>,
+)
+
+data class RocketLevelInfo(
+    @SerializedName("level")
+    val level: Int,
+    @SerializedName("coins")
+    val coins: Long
+)
+
+data class RocketRewardInfo(
+    @SerializedName("level")
+    val level: Int,
+    @SerializedName("rankInfo")
+    val rankInfo: List<RocketRankRewardInfo>,
+    @GsonNullable
+    @SerializedName("activityGiftInfos")
+    val limitGifts: List<RoomLimitTimeGiftData>? = null
+)
+
+data class RocketRankRewardInfo(
+    @SerializedName("rank")
+    val rank: Int,
+    @SerializedName("rewards")
+    val rewards: List<RewardDetailData>
+)
+
+data class RocketRewardRecordRes(
+    @SerializedName("records")
+    val records: List<RocketRewardRecord>,
+    @SerializedName("current")
+    val current: Int
+)
+
+data class LevelRocketTabData(
+    val level: RocketLevel,
+    var selected: Boolean = false,
+)
+
+// index对齐Server协定
+enum class TopRewardTabIndex(val index: Int) {
+    Room(-1),
+    Top1(1),
+    Top2(2),
+    Top3(3),
+    RoomOnlineUser(998),
+}
+
+data class RocketTab(val rocketLevel: RocketLevel, val tabs: List<TopRewardTabIndex>)

+ 5 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/data/Tags.kt

@@ -0,0 +1,5 @@
+package com.adealink.weparty.game.rocket.data
+
+const val TAG_ROCKET = "tag_rocket"
+const val TAG_PYRAMID_SLOTS = "tag_pyramid_slots"
+const val TAG_ROCKET_EFFECT = "tag_rocket_effect"

+ 49 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/datasource/remote/RocketHttpService.kt

@@ -0,0 +1,49 @@
+package com.adealink.weparty.game.rocket.datasource.remote
+
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.network.data.Res
+import com.adealink.weparty.game.rocket.data.RocketInfo
+import com.adealink.weparty.game.rocket.data.RocketRewardRecordRes
+import com.adealink.weparty.module.game.rocket.GetRocketKingReq
+import com.adealink.weparty.module.game.rocket.RocketKingRes
+import com.adealink.weparty.module.game.rocket.RocketRankUserInfo
+import com.adealink.weparty.module.game.rocket.RocketUserRewardInfo
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Query
+
+/**
+ * Created by sunxiaodong on 2023/1/12.
+ */
+interface RocketHttpService {
+
+    @GET("activity/getRoomRocketInfo")
+    suspend fun getRocketInfo(
+        @Query("roomid") roomId: Long,
+        @Query("showRewardInfo") showRewardInfo: Int,
+        @Query("showWeekRankInfo") showWeekRankInfo: Int
+    ): Rlt<Res<RocketInfo>>
+
+    @GET("activity/getRoomRocketTopUser")
+    suspend fun getRocketTopUser(
+        @Query("roomid") roomId: Long,
+        @Query("level") level: Int
+    ): Rlt<Res<List<RocketRankUserInfo>>>
+
+    @GET("activity/getRoomRocketRewardInfoByLevel")
+    suspend fun getRocketRewardInfoByLevel(
+        @Query("roomid") roomId: Long,
+        @Query("level") level: Int
+    ): Rlt<Res<RocketUserRewardInfo>>
+
+    @GET("activity/getRoomRocketRewardInfo")
+    suspend fun getRocketRewardRecord(
+        @Query("currentPage") page: Int,
+        @Query("size") size: Int
+    ): Rlt<Res<RocketRewardRecordRes>>
+
+    @POST("activity/getRoomRocketWeekRankInfo")
+    suspend fun getRoomRocketWeekRankInfo(@Body req: GetRocketKingReq): Rlt<Res<RocketKingRes>>
+
+}

+ 36 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketKingRuleDialog.kt

@@ -0,0 +1,36 @@
+package com.adealink.weparty.game.rocket.dialog
+
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.frame.util.DisplayUtil
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.DialogRocketKingRuleBinding
+import com.adealink.weparty.module.game.Game
+
+@RouterUri(path = [Game.Rocket.RocketKingRule.PATH], desc = "火箭王规则弹窗")
+class RocketKingRuleDialog : BaseDialogFragment(R.layout.dialog_rocket_king_rule) {
+    private val binding by viewBinding(DialogRocketKingRuleBinding::bind)
+
+    override fun initViews() {
+        super.initViews()
+        binding.closeBtn.setOnClickListener {
+            dismiss()
+        }
+    }
+
+    override fun resetWindowAttributes(window: Window) {
+        super.resetWindowAttributes(window)
+        window.setLayout(DisplayUtil.dp2px(344f), WindowManager.LayoutParams.WRAP_CONTENT)
+        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        val attr = window.attributes
+        attr.gravity = Gravity.CENTER
+        attr.dimAmount = 0.7f
+        window.attributes = attr
+    }
+}

+ 77 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketLimitTimeGiftDialog.kt

@@ -0,0 +1,77 @@
+package com.adealink.weparty.game.rocket.dialog
+
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.Router
+import com.adealink.frame.router.annotation.BindExtra
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.DialogRocketLimitTimeGifitBinding
+import com.adealink.weparty.module.anchor.data.FromScene
+import com.adealink.weparty.module.game.Game
+import com.adealink.weparty.module.gift.Gift
+import com.adealink.weparty.module.room.Room
+import com.adealink.weparty.module.room.data.RoomLimitTimeGiftData
+
+@RouterUri(path = [Game.Rocket.RocketLimitTimeGift.PATH], desc = "限时礼物弹窗")
+class RocketLimitTimeGiftDialog : BaseDialogFragment(R.layout.dialog_rocket_limit_time_gifit) {
+
+    private val binding by viewBinding(DialogRocketLimitTimeGifitBinding::bind)
+
+    @BindExtra(Game.Rocket.RocketLimitTimeGift.EXTRA_REWARD_INFO)
+    var giftInfo: RoomLimitTimeGiftData? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        Router.bind(this)
+        setStyle(STYLE_NO_TITLE, com.adealink.weparty.R.style.FullScreenDialogTheme)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        resetDialogAttributes()
+    }
+
+    private fun resetDialogAttributes() {
+        val dialog = dialog ?: return
+        val window = dialog.window ?: return
+        val layoutParams = window.attributes
+        layoutParams.gravity = Gravity.TOP
+        layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
+        layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT
+        layoutParams.dimAmount = 0.6f
+        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        window.attributes = layoutParams
+        dialog.setCanceledOnTouchOutside(false)
+    }
+
+    override fun initViews() {
+        super.initViews()
+        val expAddMultiple = giftInfo?.expAddMultiple ?: 0
+        if (expAddMultiple <= 0) {
+            binding.tvSubTitle.text = null
+        } else {
+            binding.tvSubTitle.text =
+                getCompatString(R.string.game_rocket_limit_gift_desc, expAddMultiple.toString())
+        }
+        binding.ivLimitTimeGift.setImageUrl(giftInfo?.previewUrl)
+
+        binding.sendBtn.setOnClickListener {
+            Router.getRouterInstance<BaseDialogFragment>(Room.SendGiftPanel.PATH)?.apply {
+                arguments = Bundle().apply {
+                    putInt(
+                        Gift.SendPanel.EXTRA_FROM_SCENE, FromScene.LIMIT_TIME.scene
+                    )
+                }
+            }?.show(parentFragmentManager, Room.SendGiftPanel.TAG)
+            dismiss()
+        }
+    }
+
+}

+ 353 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketPanelDialog.kt

@@ -0,0 +1,353 @@
+package com.adealink.weparty.game.rocket.dialog
+
+import android.annotation.SuppressLint
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.view.Gravity
+import android.view.View
+import android.view.WindowManager
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.ext.isViewBindingValid
+import com.adealink.frame.log.Log
+import com.adealink.frame.mvvm.lifecycle.observeOnDestroy
+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.ONE_HOUR
+import com.adealink.frame.util.ONE_MINUTE
+import com.adealink.frame.util.ONE_SECOND
+import com.adealink.frame.util.getDayEndTime
+import com.adealink.frame.util.runOnUiThread
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.hide
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.MultiTypeAdapter
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.DialogRocketMainPanelBinding
+import com.adealink.weparty.game.rocket.adapter.LevelRocketTabItemViewBinder
+import com.adealink.weparty.game.rocket.comp.RocketLevelRewardComp
+import com.adealink.weparty.game.rocket.comp.RocketRandListComp
+import com.adealink.weparty.game.rocket.data.LevelRocketTabData
+import com.adealink.weparty.game.rocket.data.TAG_ROCKET_EFFECT
+import com.adealink.weparty.game.rocket.listener.IRocketTabListener
+import com.adealink.weparty.game.rocket.viewmodel.RocketViewModel
+import com.adealink.weparty.game.viewmodel.GameViewModelFactory
+import com.adealink.weparty.module.game.Game
+import com.adealink.weparty.module.game.rocket.RocketLevel
+import com.adealink.weparty.module.room.RoomModule
+import com.tencent.qgame.animplayer.AnimConfig
+import com.tencent.qgame.animplayer.inter.IAnimListener
+import com.tencent.qgame.animplayer.util.ScaleType
+import java.io.File
+import java.util.Date
+import java.util.TimeZone
+import com.adealink.weparty.R as APP_R
+
+@RouterUri(path = [Game.Rocket.RocketPanel.PATH], desc = "火箭主面板")
+class RocketPanelDialog : BaseDialogFragment(R.layout.dialog_rocket_main_panel),
+    IRocketTabListener {
+
+    private val binding by viewBinding(DialogRocketMainPanelBinding::bind)
+    private val rocketViewModel by viewModels<RocketViewModel>({ this }) { GameViewModelFactory() }
+    private var countDownTimer: CountDownTimer? = null
+    private val rocketTabsAdapter = MultiTypeAdapter()
+    private val rocketTabsList by fastLazy {
+        arrayListOf(
+            // 目前仅有4级
+//            LevelRocketTabData(RocketLevel.LEVE5),
+            LevelRocketTabData(RocketLevel.LEVE4),
+            LevelRocketTabData(RocketLevel.LEVE3),
+            LevelRocketTabData(RocketLevel.LEVE2),
+            LevelRocketTabData(RocketLevel.LEVE1)
+        )
+    }
+    private var selectedRocketLevel: RocketLevel? = null
+    private var rewardComp: RocketLevelRewardComp? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setStyle(STYLE_NO_TITLE, com.adealink.weparty.R.style.BottomDialog)
+        Router.bind(this)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        resetDialogAttributes()
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        viewLifecycleOwner.observeOnDestroy {
+            countDownTimer?.cancel()
+            binding.bgEffectView.setAnimListener(null)
+            binding.bgEffectView.stopPlay()
+            binding.rocketEffectView.setAnimListener(null)
+            binding.rocketEffectView.stopPlay()
+        }
+        showLaunchPadAnimation()
+    }
+
+    override fun initViews() {
+        super.initViews()
+        rocketTabsAdapter.register(LevelRocketTabItemViewBinder(this))
+        binding.rvRocketLevel.layoutManager =
+            LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+        binding.rvRocketLevel.adapter = rocketTabsAdapter
+        rocketTabsAdapter.items = rocketTabsList
+        rocketTabsAdapter.notifyDataSetChanged()
+
+        binding.ivClose.setOnClickListener {
+            dismiss()
+        }
+        val kingClickListener = View.OnClickListener {
+            val act = activity ?: return@OnClickListener
+            Router.build(act, Game.Rocket.RocketKing.PATH).start()
+        }
+        binding.ivKing.setOnClickListener(kingClickListener)
+        binding.ivRecord.setOnClickListener {
+            Router.getRouterInstance<BaseDialogFragment>(Game.Rocket.RocketRecord.PATH)
+                ?.show(childFragmentManager)
+        }
+        binding.ivHelp.setOnClickListener {
+            Router.getRouterInstance<BaseDialogFragment>(Game.Rocket.RocketRule.PATH)
+                ?.show(childFragmentManager)
+        }
+        binding.bgEffectView.setScaleType(ScaleType.FIT_CENTER)
+        binding.bgEffectView.setLoop(Int.MAX_VALUE)
+        binding.rocketEffectView.setScaleType(ScaleType.CENTER_CROP)
+        binding.rocketEffectView.setLoop(Int.MAX_VALUE)
+        binding.vTopSpace.setOnClickListener { dismiss() }
+        binding.vTopSpace1.setOnClickListener { dismiss() }
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    override fun observeViewModel() {
+        super.observeViewModel()
+        rocketViewModel.maxRocketLevelLD.observe(viewLifecycleOwner) {
+            rocketTabsList.clear()
+            for (i in it downTo 1) {
+                rocketTabsList.add(LevelRocketTabData(RocketLevel.map(i)))
+            }
+            rocketTabsAdapter.items = rocketTabsList
+            rocketTabsAdapter.notifyDataSetChanged()
+        }
+        rocketViewModel.currRocketInfoLD.observe(viewLifecycleOwner) {
+            if (this.selectedRocketLevel != it.first) {
+                this.selectedRocketLevel = it.first
+                updateLaunchPadRocket()
+            }
+
+            rocketTabsList.onEach { rocketTab ->
+                rocketTab.selected = rocketTab.level == it.first
+            }
+            rocketTabsAdapter.items = rocketTabsList
+            rocketTabsAdapter.notifyDataSetChanged()
+            rewardComp?.changeToLevel(it.first)
+            //更新进度
+            updateEnergyProgress(it.second)
+        }
+        rocketViewModel.rocketRewardLD.observeWithoutCache(viewLifecycleOwner) {
+            RoomModule.getJoinedRoomId()?.let { roomId ->
+                rocketViewModel.getRocketInfo(roomId, true)
+            }
+        }
+    }
+
+    override fun initComponents() {
+        super.initComponents()
+        rewardComp = RocketLevelRewardComp(this, binding.layoutAward).also {
+            it.attach()
+        }
+        RocketRandListComp(this, binding.layoutTop3).attach()
+    }
+
+    override fun loadData() {
+        super.loadData()
+        startCountDown()
+        RoomModule.getJoinedRoomId()?.let { roomId ->
+            rocketViewModel.getRocketInfo(roomId, true)
+        }
+    }
+
+    private fun startCountDown() {
+        // PM Mark:每日重置火箭的时间用utc+2.5的24点
+        val dayEndTime = getDayEndTime(Date(), TimeZone.getTimeZone("UTC")).time
+        val offsetMillis = (2.5 * 60 * 60 * 1000).toLong()
+        val millisInFuture = dayEndTime - offsetMillis - System.currentTimeMillis()
+        countDownTimer = object : CountDownTimer(millisInFuture, 1000L) {
+
+            override fun onTick(millisUntilFinished: Long) {
+                updateLeftTime(millisUntilFinished)
+            }
+
+            override fun onFinish() {
+                runOnUiThread(
+                    {
+                        loadData()
+                    }, 2000L
+                )
+            }
+        }
+        countDownTimer?.start()
+    }
+
+    private fun updateLeftTime(millisUntilFinished: Long) {
+        if (!isViewBindingValid()) {
+            return
+        }
+        var leftTimeMS: Long = millisUntilFinished
+        val leftTimeHour = leftTimeMS / ONE_HOUR
+        leftTimeMS = leftTimeMS.rem(ONE_HOUR)
+        val leftTimeMinute = leftTimeMS / ONE_MINUTE
+        leftTimeMS = leftTimeMS.rem(ONE_MINUTE)
+        val leftTimeSeconds = leftTimeMS / ONE_SECOND
+        binding.layoutProgressTime.countDownHourTv.text = getTimeNumStr(leftTimeHour)
+        binding.layoutProgressTime.countDownMinTv.text = getTimeNumStr(leftTimeMinute)
+        binding.layoutProgressTime.countDownSecondTv.text = getTimeNumStr(leftTimeSeconds)
+    }
+
+    private fun getTimeNumStr(value: Long): String {
+        return when {
+            value >= 10 -> "$value"
+            else -> "0${value}"
+        }
+    }
+
+    private fun resetDialogAttributes() {
+        val dialog = dialog ?: return
+        val window = dialog.window ?: return
+        val layoutParams = window.attributes
+        layoutParams.gravity = Gravity.BOTTOM
+        layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
+        layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT
+        layoutParams.dimAmount = 0.7f
+        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        window.attributes = layoutParams
+        dialog.setCanceledOnTouchOutside(true)
+    }
+
+    override fun onLevelRocketSelected(level: RocketLevel) {
+        if (selectedRocketLevel?.level == level.level) {
+            return
+        }
+        this.selectedRocketLevel = level
+        rocketViewModel.changeToLevel(level, true)
+        updateLaunchPadRocket()
+    }
+
+    /**
+     * 展示发射台动效
+     */
+    private fun showLaunchPadAnimation() {
+        rocketViewModel.getLaunchPadEffectPath().observe(viewLifecycleOwner) {
+            when (it) {
+                is Rlt.Success -> {
+                    binding.ivBg.gone()
+                    binding.bgEffectView.show()
+                    binding.bgEffectView.startPlay(File(it.data))
+                }
+
+                is Rlt.Failed -> {
+                    binding.ivBg.show()
+                    binding.bgEffectView.gone()
+                }
+            }
+        }
+    }
+
+    /**
+     * 更新发射台火箭动效
+     */
+    private fun updateLaunchPadRocket() {
+        val level = selectedRocketLevel ?: return
+
+        val effectPath = rocketViewModel.getDownloadedLaunchPadRocketEffectPath(level)
+        if (effectPath != null) {
+            playRocketVapVideo(effectPath)
+            return
+        }
+
+        showStaticLaunchPadRocket()
+        rocketViewModel.getLaunchPadRocketEffectPath(level).observe(viewLifecycleOwner) {
+            when (it) {
+                is Rlt.Success -> {
+                    playRocketVapVideo(it.data)
+                }
+
+                is Rlt.Failed -> {
+                    showStaticLaunchPadRocket()
+                }
+            }
+        }
+    }
+
+    private fun playRocketVapVideo(effectPath: String) {
+        if (!isViewBindingValid()) {
+            return
+        }
+        binding.rocketIv.hide()
+        binding.rocketEffectView.show()
+        if (binding.rocketEffectView.isRunning()) {
+            binding.rocketEffectView.setAnimListener(object : IAnimListener {
+
+                override fun onFailed(errorType: Int, errorMsg: String?) {
+                    Log.e(TAG_ROCKET_EFFECT, "play error, errorType:$errorType, errorMsg:$errorMsg")
+                }
+
+                override fun onVideoStart() {
+                }
+
+                override fun onVideoRender(frameIndex: Int, config: AnimConfig?) {
+                }
+
+                override fun onVideoComplete() {
+                }
+
+                override fun onVideoDestroy() {
+                    Log.d(TAG_ROCKET_EFFECT, "playRocketVapVideo, onVideoDestroy")
+                    if (!isViewBindingValid()) {
+                        return
+                    }
+                    binding.rocketEffectView.startPlay(File(effectPath))
+                }
+            })
+            binding.rocketEffectView.stopPlay()
+        } else {
+            binding.rocketEffectView.startPlay(File(effectPath))
+        }
+    }
+
+    /**
+     * 展示静态发射台
+     */
+    private fun showStaticLaunchPadRocket() {
+        binding.rocketEffectView.hide()
+        val resId = when (selectedRocketLevel?.level ?: 1) {
+            2 -> APP_R.drawable.game_rocket_level_2_large_ic
+            3 -> APP_R.drawable.game_rocket_level_3_large_ic
+            4 -> APP_R.drawable.game_rocket_level_4_large_ic
+            in 5..Int.MAX_VALUE -> APP_R.drawable.game_rocket_level_5_large_ic
+            else -> APP_R.drawable.game_rocket_level_1_large_ic
+        }
+        binding.rocketIv.show()
+        binding.rocketIv.setImageResource(resId)
+    }
+
+    /**
+     * 更新能量条
+     */
+    private fun updateEnergyProgress(progress: Int) {
+        val ratio = progress / 100F
+        binding.layoutProgressTime.sinProgressBehind.progress = ratio
+        binding.layoutProgressTime.tvProgress.text =
+            getCompatString(APP_R.string.common_percent, progress)
+    }
+}

+ 119 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketRecordDialog.kt

@@ -0,0 +1,119 @@
+package com.adealink.weparty.game.rocket.dialog
+
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import androidx.core.view.doOnAttach
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.itemdecoration.VerticalSpaceItemDecoration
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.DialogRocketRecordBinding
+import com.adealink.weparty.game.rocket.adapter.RocketRecordItemViewBinder
+import com.adealink.weparty.game.rocket.viewmodel.RocketRecordsViewModel
+import com.adealink.weparty.game.viewmodel.GameViewModelFactory
+import com.adealink.weparty.module.game.Game
+import com.adealink.weparty.module.game.rocket.RocketRewardRecord
+import com.scwang.smart.refresh.footer.ClassicsFooter
+import com.scwang.smart.refresh.layout.api.RefreshLayout
+import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener
+import com.adealink.weparty.R as APP_R
+
+@RouterUri(path = [Game.Rocket.RocketRecord.PATH], desc = "火箭历史记录")
+class RocketRecordDialog : BaseDialogFragment(R.layout.dialog_rocket_record) {
+    private val binding by viewBinding(DialogRocketRecordBinding::bind)
+    private val listAdapter by fastLazy { MultiTypeListAdapter<RocketRewardRecord>() }
+    private val recordsViewModel by viewModels<RocketRecordsViewModel>({ this }) { GameViewModelFactory() }
+
+    override fun initViews() {
+        super.initViews()
+        binding.layoutTitle.tvTitle.setCompoundDrawables(null, null, null, null)
+        binding.layoutTitle.tvTitle.text = getCompatString(R.string.game_rocket_reward_record_title)
+        updateEmptyVIew(true)
+        binding.closeBtn.setOnClickListener {
+            dismiss()
+        }
+        listAdapter.register(RocketRecordItemViewBinder())
+        binding.rvContent.apply {
+            addItemDecoration(VerticalSpaceItemDecoration(20.dp()))
+            adapter = listAdapter
+            layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+        }
+        binding.refreshLayout.doOnAttach {
+            binding.refreshLayout.refreshFooter?.let {
+                if (it is ClassicsFooter) {
+                    it.setAccentColor(Color.WHITE)
+                }
+            }
+        }
+        binding.refreshLayout.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
+            override fun onRefresh(refreshLayout: RefreshLayout) {
+                refreshData()
+            }
+
+            override fun onLoadMore(refreshLayout: RefreshLayout) {
+                loadMoreData()
+            }
+        })
+        binding.refreshLayout.setEnableRefresh(true)
+    }
+
+    override fun loadData() {
+        super.loadData()
+        refreshData()
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        recordsViewModel.recordListLD.observe(this) {
+            binding.refreshLayout.finishRefresh()
+            binding.refreshLayout.finishLoadMore()
+            listAdapter.submitList(it)
+            updateEmptyVIew(it.isEmpty())
+        }
+    }
+
+    private fun refreshData() {
+        recordsViewModel.refreshRocketRewardRecord()
+    }
+
+    private fun loadMoreData() {
+        recordsViewModel.loadMoreRocketRewardRecord().observe(this) {
+            if (it) {
+                binding.refreshLayout.setNoMoreData(true)
+            }
+        }
+    }
+
+    private fun updateEmptyVIew(visible: Boolean) {
+        if (!visible) {
+            binding.emptyView.hide()
+            return
+        }
+        binding.emptyView.apply {
+            show(APP_R.drawable.common_content_empty_ic_1, APP_R.string.common_no_records)
+            setImageSize(140.dp())
+        }
+    }
+
+    override fun resetWindowAttributes(window: Window) {
+        window.setLayout(
+            WindowManager.LayoutParams.MATCH_PARENT,
+            WindowManager.LayoutParams.WRAP_CONTENT
+        )
+        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        val attr = window.attributes
+        attr.gravity = Gravity.CENTER
+        window.attributes = attr
+    }
+}

+ 39 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketRuleDialog.kt

@@ -0,0 +1,39 @@
+package com.adealink.weparty.game.rocket.dialog
+
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.DialogRocketRuleBinding
+import com.adealink.weparty.module.game.Game
+
+@RouterUri(path = [Game.Rocket.RocketRule.PATH], desc = "火箭规则弹窗")
+class RocketRuleDialog : BaseDialogFragment(R.layout.dialog_rocket_rule) {
+    private val binding by viewBinding(DialogRocketRuleBinding::bind)
+
+    override fun initViews() {
+        super.initViews()
+        binding.layoutTitle.tvTitle.setCompoundDrawables(null, null, null, null)
+        binding.layoutTitle.tvTitle.text = getCompatString(R.string.game_rocket)
+        binding.closeBtn.setOnClickListener {
+            dismiss()
+        }
+    }
+
+    override fun resetWindowAttributes(window: Window) {
+        window.setLayout(
+            WindowManager.LayoutParams.MATCH_PARENT,
+            WindowManager.LayoutParams.WRAP_CONTENT
+        )
+        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        val attr = window.attributes
+        attr.gravity = Gravity.CENTER
+        window.attributes = attr
+    }
+}

+ 134 - 0
module/game/src/main/java/com/adealink/weparty/game/rocket/dialog/RocketUserRewardDialog.kt

@@ -0,0 +1,134 @@
+package com.adealink.weparty.game.rocket.dialog
+
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.ext.isViewBindingValid
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.Router
+import com.adealink.frame.router.annotation.BindExtra
+import com.adealink.frame.router.annotation.RouterUri
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.itemdecoration.VerticalSpaceItemDecoration
+import com.adealink.weparty.game.R
+import com.adealink.weparty.game.databinding.DialogRocketUserRewardBinding
+import com.adealink.weparty.game.rocket.adapter.reward.MyRewardEmptyItemViewBinder
+import com.adealink.weparty.game.rocket.adapter.reward.MyRewardInfoViewBinder
+import com.adealink.weparty.game.rocket.adapter.reward.RewardEmptyItemViewBinder
+import com.adealink.weparty.game.rocket.adapter.reward.RewardTitleItemViewBinder
+import com.adealink.weparty.game.rocket.adapter.reward.RewardUserDetailItemViewBinder
+import com.adealink.weparty.game.rocket.listener.IRocketRewardUserItemListener
+import com.adealink.weparty.game.rocket.viewmodel.RewardEmptyItem
+import com.adealink.weparty.game.rocket.viewmodel.RocketRewardItemData
+import com.adealink.weparty.game.rocket.viewmodel.RocketRewardViewModel
+import com.adealink.weparty.game.viewmodel.GameViewModelFactory
+import com.adealink.weparty.module.game.Game
+import com.adealink.weparty.module.game.rocket.RocketLevel
+import com.adealink.weparty.module.profile.Profile
+
+@RouterUri(path = [Game.Rocket.RocketUserReward.PATH], desc = "火箭用户奖励弹窗")
+class RocketUserRewardDialog : BaseDialogFragment(R.layout.dialog_rocket_user_reward),
+    IRocketRewardUserItemListener {
+    private val binding by viewBinding(DialogRocketUserRewardBinding::bind)
+
+    @BindExtra(Game.Rocket.RocketUserReward.EXTRA_REWARD_LEVEL)
+    var level: Int? = null
+
+    private val rocketViewModel by viewModels<RocketRewardViewModel>({ this }) { GameViewModelFactory() }
+
+    private val listAdapter by fastLazy { MultiTypeListAdapter<RocketRewardItemData>() }
+
+    override val canceledOnTouchOutside: Boolean = false
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        Router.bind(this)
+    }
+
+    override fun initViews() {
+        super.initViews()
+        binding.layoutTitle.tvTitle.setCompoundDrawables(null, null, null, null)
+        binding.layoutTitle.tvTitle.text = getCompatString(R.string.game_rocket_reward_title)
+        binding.closeBtn.setOnClickListener {
+            dismiss()
+        }
+
+        listAdapter.register(RewardTitleItemViewBinder())
+        listAdapter.register(MyRewardInfoViewBinder())
+        listAdapter.register(MyRewardEmptyItemViewBinder())
+        listAdapter.register(RewardEmptyItemViewBinder())
+        listAdapter.register(RewardUserDetailItemViewBinder(this))
+        binding.rvContent.apply {
+            layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+            adapter = listAdapter
+            addItemDecoration(VerticalSpaceItemDecoration(12.dp()))
+        }
+
+    }
+
+    override fun loadData() {
+        super.loadData()
+        val level = level ?: return
+        val rocketLevel = RocketLevel.map(level)
+        rocketViewModel.getRewardUserList(rocketLevel).observe(viewLifecycleOwner) { rlt ->
+            when (rlt) {
+                is Rlt.Success -> listAdapter.submitList(rlt.data)
+                is Rlt.Failed -> listAdapter.submitList(listOf(RewardEmptyItem))
+            }
+        }
+    }
+
+    override fun clickUser(uid: Long) {
+        super.clickUser(uid)
+        if (!isViewBindingValid()) {
+            return
+        }
+        val act = activity ?: return
+        Router.build(act, Profile.UserProfile.PATH)
+            .putExtra(Profile.Common.EXTRA_UID, uid)
+            .start()
+    }
+
+    override fun resetWindowAttributes(window: Window) {
+        window.setLayout(
+            WindowManager.LayoutParams.MATCH_PARENT,
+            WindowManager.LayoutParams.WRAP_CONTENT
+        )
+        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+        val attr = window.attributes
+        attr.gravity = Gravity.CENTER
+        window.attributes = attr
+    }
+
+    // PM Mark:【限时礼物】:火箭升级到4级后,会解锁一个限时礼物,该礼物送出可以获得10倍的exp —— 这个不要
+//    override fun onDismiss(dialog: DialogInterface) {
+//        super.onDismiss(dialog)
+//        val roomId = RoomModule.getJoinedRoomId()
+//        if (roomId != null && roomId != 0L) {
+//            val timeLimitGift = GiftModule.getTimeLimitGifts(roomId)?.firstOrNull()
+//            if (timeLimitGift?.isExpired() == false && rocketIndex != null && rocketIndex == 0) {
+//                Router.getRouterInstance<BaseDialogFragment>(Game.Rocket.RocketLimitTimeGift.PATH)
+//                    ?.apply {
+//                        arguments = Bundle().apply {
+//                            putParcelable(
+//                                Game.Rocket.RocketLimitTimeGift.EXTRA_REWARD_INFO,
+//                                timeLimitGift
+//                            )
+//                        }
+//                    }?.show(parentFragmentManager)
+//            }
+//        }
+//    }
+
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов